1. 指针和数组
C语言中只有一维数组,而且数组的大小必须在编译器就作为一个常数确定下来,然而在C语言中数组的元素可以是任何类型的对象,当然也可以是另外的一个数组,这样,要仿真出一个多维数组就不是难事。
对于一个数组,我们只能够做两件事:确定数组大小;获得指向该数组下标为0的元素的指针。
int calendar[12][31];
以上语句声明了calendar是一个数组,该数组拥有12个数组类型的元素,其中每个元素都是一个拥有31个整型元素的数组。
如果calendar不是用于sizeof的操作数,那么calendar总是被转换成一个指向calnedar数组的起始元素的指针。
任何指针都是指向某种类型的变量。
给一个指针加上一个整数,与给该指针的二进制表示加上同样的整数,两者的含义截然不同。
把两个指针相减也是有意义的,但是这两个指针必须指向同类型的变量,否则结果未定义。
当p是int指针,a是int一维数组名时,以下写法正确
p = a;
以下写法错误:
p = &a;
因为&a是一个指向数组的指针,而p是一个指向整型变量的指针。*a是数组a中下标为0的元素的引用。*(a+i)即数组a中下标为i的元素的引用,简记为a[i]。多数情况下i[a]和a[i]的意义相同,但是不推荐使用前面的写法。
calendar[4]是calendar数组的第5个元素,calendar[4]的行为表现为一个有着31个整型元素的数组的行为。
声明指向数组的指针的方法,举例如下:
int calendar[12][31];
int (*monthp)[31];
monthp = calendar;
2. 非数组的指针
假设我们用两个字符串s和t,我们希望将这两个字符串连接成单个字符串r,借助库函数strcpy和strcat正确写法如下:
char *r, *malloc();//声明malloc原型,这样后面就不用再写转换成char*类型了
r = malloc(strlen(s) + strlen(t) + 1); //字符串结尾为'\0',strlen不用加上这个计数1
if (!r)//当malloc无法完成内存分配时,会返回NULL
{
complain();//报错
exit(1);//退出
}
strcpy(r, s);
strcpy(r, t);
//使用一段时间后
free(r); //对动态分配内存程序员负责回收
主要注意到点是:
- 字符串以空字符'\0'作为结束标志,库函数strlen返回参数中字符串所包含的字符数组,而结尾标志的空字符并未计算在内;
- malloc函数有可能无法提供请求的内存,这种情况malloc函数通过返回一个空指针来作为“内存分配失败”事件的信号
- malloc分配的内存使用完后应该及时释放,否则会导致内存泄露
3. 作为参数的数组声明
C语言中会自动地将作为参数的数组声明转换为相应的指针声明,也就是说下面两种写法完全等价
int strlen(char s[])
{}
int strlen(char* s)
{}
但是需要注意的是,在其他情况下这两者并不会等价,如
extern char* hello;
extern char hello[];
4. 注意整体代替部分的错误
指针的赋值并不会复制它们指向的内容,因此如下语句
char *p, *q;
p = "xyz";
q = p;
的结果如下:
5. 空指针并非空字符串
当常数0被转换成指针使用时,这个指针绝对不能不能被解除引用dereference
if (p == (char *) 0)...
以上写法正确,因为没有解除引用
if (strcmp(p, (char *) 0) == 0)...
以上写法错误,因为strcmp会查看指针所指向内存的内容
同样以下写法也是错误的:
int *p = NULL;
printf(p);
printf("%s", p);
6. 边界计算与不对称边界
如果一个数组有10个元素,那么这个数组下标的允许范围是0-9
在多数C语言实现中,--n >= 0至少和n-->0一样快
可以用
if (bufptr == &buffer[N])
代替
if (bufptr > &buffer[N - 1])
数组中实际不存在的溢界元素的地址位于数组所占内存之后,这个地址可以用于进行赋值和比较。但不允许进行解引用。
7. 求值顺序
C语言中只有4个运算符(&&、||、?:和,)存在规定的求值顺序:
- &&和||首先对左侧操作数求值,只有在需要时才对右侧操作数求值
- a?b:c有三个操作数,操作数a首先被求值,根据a的值再求b或者c
- 逗号运算符,首先对左侧操作数求值,然后丢弃该值,再对右侧操作数求值
注意:分隔函数参数的逗号并非逗号运算符,例如f(x,y)中求值顺序顺序是未定义的,而在g((x,y))中先求x的值,然后求y的值。
C语言中其他所有运算符对其操作数求值的顺序是未定义的,特别地,赋值运算符并不保证任何求值顺序。
比如,从数组x中复制前n个元素到数组y中,以下做法是不对的:
i = 0;
while(i < n)
y[i] = x[i++];
因为这里假设y[i]的地址i在自增操作前执行前被求值
正确的做法是:
i = 0;
while(i < n)
{
y[i] = x[i];
i++;
}
8. 运算符&&、||和!和按位运算符&、|、~
按位运算符&、|、~对操作数的处理方式是将其视作一个二进制位序列,分别对其每个位进行操作。
逻辑运算符&&、||和!对操作数的处理方式是将其视作要么是真,要么是假,通常将0视为假,而非0视作真。
9. 整数溢出
当两个操作数都是有符号整数时,溢出有可能发生,而且溢出的结果是未定义的。
例如a和b是两个非负整型变量,我们需要检查a+b是否会溢出,以下的写法是错误的:
if (a + b < 0)
complain();
正确的写法是将a和b都强制转换成无符号整数:
if ((unsigned)a + (unsigned)b > INT_MAX)
complain();
也可以使用以下写法:
if (a > INT_MAX -b)
complain();
10. 为函数main提供返回值
大多数C语言实现都通过main的返回值来告知操作系统该函数执行是成功还是失败,典型的处理方式是返回值0表示程序执行成功,返回值非0表示程序执行失败。