3.1指针与数组
C语言中的数组值得注意的地方主要有以下两点:
1. C语言中其实只有一维数组,并且数组的大小在编译的时候就作为一个常数确定下来了。所
谓的多维数组,只是因为数组的元素也是数组。
2. 数组的下标运算,实际上都是通过指针进行操作的。
下面对上述两点展开,进行详细的讨论。
考虑下面的例子:
int calendar[12][31];
这个语句声明了calendar是一个数组。该数组拥有12个元素,每个元素为一个具有31个元素的数组。 //而不是拥有31个元素,每个元素为具有12个元素的数组
考虑下面的例子:
int *p;
int a[10];
p=a; //正确,类似于p=&a[0]
p=&a; //错误,因为&a是一个指向数组的指针,而p是一个指向整数的指针,他们的类型不匹
//配。但大部分编译器会给出一个警告信息,说是指针赋值的不匹配。
考虑下面的例子:
int calendar[12][31]; //思考calendar[4]的含义是什么?
因为calendar是一个有着12个数组类型元素的数组,所以calendar[4]是calendar的第五个元素,它表现出来的行为就是一个具有31个整形元素的数组的行为。 例如:sizeof(calendar[4])的结果为31*sizeof(int)的乘积。
又如:p = calendar[4]是的指针指向了数组calendar[4]中下标为0的元素。
正是因为calendar[4]是一个数组,所以我们可以通过下标的形式来指定这个数组中的元素,如下所示:
i = calendar[4][7]可以写成 i = *(calendar[4] + 7),或者是
i = *(*(calendar + 4) + 7),但是我们很容易的看出,使用下标是一种更简便的记事方式。
思考:
如果我们写出这样的语句:p = calendar; 编译器会报出什么信息?应该如何声明p, 以实现p = calendar;
答:会出现指针赋值类型不匹配的警告。应该如此声明p:
int (*p)[31];
p = calendar;
3.2 非数组的指针
写一个程序,将字符串s,t连接成单个字符串r,程序如下:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t));
strcpy(r, s);
strcat(r, t);
这段代码是错误的,原因有三:
一: malloc有可能无法提供请求的内存,这种情况下malloc会返回一个NULL来作为"内存分配失败"时间的信号。
二: 给r分配的内存必须在使用完之后立即及时释放,这点必须牢牢记住。因为malloc是显示的分配内存,所以必须显示的进行释放。
三: 这也是最重要的原因,必须分配strlen(s)+strlen(t)+1个空间.
正确的程序如下:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);
if(!=r){
complain();
exit(1)
}
strcpy(r, s);
strcat(r, t);
/* 一段时间之后再使用*/
free(r);
3.6 边界计算与不对称边界
对于像C语言这样的数组下标从0开始的语言,不对称边界设计带来的便利尤为明显.
考虑下面的例子:
定义一个10个元素的数组,那么“0”就是数组下标的“入界点”,而10就是数组下标的“出界点”,所以我们最好这样写程序:
int a[10], i;
for (i=0; i < 10; i ++){ /* do something */}
而不是写成这样:
int a[10], i;
for(i = 0; i <= 9; i ++){ /* do something */ }
这样带来的好处是显而易见的,“出界点”-“入界点”刚好就是这个数组的元素个数。
另外当我们对一块缓冲区进行读写操作的时候,一般我们都是把*bufptr指向缓冲区中第一个未占用的字符,而不是指向缓冲区中最后一个已占用的字符。
3.7 求值顺序
求值顺序不是值运算符的优先级,也不是结合性。
C语言中的某些运算总是以一种已知的、规定的顺序来对其操作数进行求值,而另外一些则不是这样。
C语言中只有四个运算符(&&, ||, ?:和,)具有规定的求值顺序。 && 和 ||首先对左侧的操作数进行求值,只在需要的时候才对右侧操作数求值。 在a?b:c中,首先对a求值,然后根据a的值,再求b或者c的值。而逗号运算符,首先对左侧操作数求值,然后该值被丢弃,再对右侧的数求值。
C语言中对于其他的运算符的操作数求值顺序是未定义的。特别地,赋值运算符不保证任何求值顺序。
[注:]g((x, y))与g(x, y)的不同
举例:if(y!=0 && x/y > tolerance)
Complain();
这里必须保证仅当y非0的时候,才对x/y求值。
下面的语句是错误的:
i = 0;
while(i < n)
y[i] = x[i++];
或者
i = 0;
while(i < n)
y[i++] = x[i];
由于ANSI C没有规定赋值表达式的求值顺序,所以具体会出现什么运算结果,和编译器相关。
3.9 整数溢出
假设a, b两个非负整数变量,我们需要检查a+b是否会“溢出”。
下面的检查方式是错误的:
if (a + b<0)
complain();
这并不能正常运行。当a+b的确发生溢出的时候,任何有关结果的假设都不可靠。举例来说:很多机器,加法运算将设置一个内部寄存器来表示运算结果:正、负、零、溢出。这种机器上,C编译器完全有理由这样来实现上面的例子,即a+b相加,为检查是否发生溢出,判断这个寄存器的状态是否为“溢出”而不是“负”。如果这样涉及的话,上面的if语句检查就会失败。
可以这样进行判断:
if ((unsigned)a + (unsigned)b > INT_MAX)
complain();// 为什么啊,我都没理解
3.10 为函数main提供返回值
经典的”hello world”程序看上去应该如此:
int main(int argc, char *argv[])
{
printf(“Hello world!/n”);
return 0;
}