第三章 语义“陷阱”

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;

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值