第03章 语义"陷阱"

《C陷阱与缺陷》 Andrew Koenig) 阅读笔记 部分资料来自网上(内附链接)


3.1 指针与数组

注意:数组和指针并不是一样的,只是在数组的操作上可以和指针一样操作。

编译的时候,程序中的所有的变量都会分配一个固定的地址。

  • 数组a[ ]a分配了地址,那个地址处存的是a[0]的值。
  • 指针*p,p分配了地址,那个地址处存的是另外一个地址。

也就是指针的操作比数组会多一个步骤。

关于数组的操作,实际上都是通过指针进行的。(任何一个数组下标运算都等同于一个对应的指针运算),
*(a+1)是数组a中下标为1的元素的引用
*(a+i)是数组a中下标为i的元素的引用,由于常用,被简记a[i]

重点来了:
由于a+i和i+a的含义是一样的,所以,a[i],和i[a]也是一样的。(但是不推荐这样写)

#include <stdio.h>
int main(){
    int a[10]={0,1,2,3,4,};
    printf("%d\n",a[3]);
    printf("%d\n",3[a]);
    printf("%d\n",a[4]);//顺便看一下数组默认的初始化值
    printf("%d\n",a[5]);//所以多加了两行
}

运行结果如下图:

df215898-5f30-46cd-91a1-c6966179ce3d.png

指针指向的类型,实际上只是改变了在指针+1时的步长。
比如char *p; p+1实际上加了一个字节的偏移量
int *p; p+1加了4个字节的偏移量,因为int是4个字节。

3.2 非数组的指针

处理字符串时,注意字符串最后有一个'\0'
也就是我们在使用malloc( )的时候,如果是给一个字符串分配空间,需要考虑最后一个'\0'
特别是在malloc( )配合strlen( )使用的时候,因为strlen( )出来的长度是没有算上'\0'

3.3 作为参数的数组声明

在函数传参时,作为参数的数组会自动转换为相应的指针声明。
即:

int strlen(char s[]){

}

会转换为:

int strlen(char *s){

}

3.4 避免“举隅法”

指针保存的是地址,不能直接赋其他值。

3.5 空指针不是空字符串

  • 当常数0被转换为指针使用时,这个指针绝对不能被解除引用(操作其地址的值)。
    因为转换后的指针指向的是地址0,而其存储内容是未知的。

3.6 边界计算与不对称边界

  • 数组下边是0开始的
  • 栏杆错误(差一错误)
    • 比如计算数组下标2-5的长度,应该是5-2+1

避免栏杆错误的两个原则:

  • 首先考虑最简单的特例,然后将结果外推
  • 仔细计算边界

编程上的技巧:

  1. 用一个入界点和一个出界点表示一个数值范围。(不对称边界表示)
    例如:
    for( ;x>=6&&x<=37;x++)
    改写为:(左闭右开)
    for( ;x>=6&&x<38;x++)
  • 下界是入界点
  • 上界是出界点
  1. buff数组中的技巧
    有一个buff缓冲数组buffer[ ],我们再定义一个指针*bufptr指向buffer数据的末尾。
    也就有两个选择:
    指向数据的最后一个
    指向缓冲区未占用的第一个字符。
    考虑到计算缓冲区长度方便,可以如下图采用第二种方式。好处是显而易见,比如我在写入缓冲区用*bufptr++=c每次添加后,bufptr都是很指向第一个未占用的字符,那么我计算缓冲区已存放字符的长度的时候就可以直接bufptr-buffer就可以了
buffer 1 2 3 4 5 *bufptr 7 8
数据1 数据2 数据3 数据4 数据5 数据6 NULL NULL NULL

书中P52最后,有关于特殊print的思路

3.7 求值顺序

求值顺序不同于运算优先级,求值顺序是一种规则

c语言中只有四个运算符(&&||? :)存在规定的求值顺序。

  • &&||首先对左侧操作数求值,只有在需要时才对右侧求值
    • a<b && c<d 只有当a<b 成立时才对右侧求值
  • ? :选择求值
    • a?b:c求值a,然后根据a的值再求b或者c
  • ,对左侧求值,然后该值“丢弃”,再对右侧求值
    • 分割函数参数的逗号不是逗号运算符。
    • f(x,y)中求值顺序是未定的。

c语言对其他运算符的求值顺序是未定的。

示例1

if(y!=0 &&x/y > tolerance){
    complain();
}

上面的代码保证了运行时不出现“用0作为除数”的错误。因为当y==0时后面的一句话是不会执行的。

示例2

从数组x中复制前n个元素都数组y中。

i=0;
while(i<n){
    y[i]=x[i++];
}

上面的代码不能保证正确性,因为如果是先运算的左边没有问题,如果是先求值的右边,就会取完x[i],然后i++,然后再y[i],此时y[i]就不是以前的i了。

3.8 运算符&&、||和!

注意两点:

  • &,|和&&,||的区别
  • 他们运算的顺序

举例:

i=0;
while (i<tabsize && tab[i]!=x){
    i++;
}

如果错写成了while(i<tasize& tab[i]!=x)
虽然可以正确运行,因为:

  • &两边的结果都只有真或假(0 or 1),&&与&的结果是一样的
  • 改变后,虽然数组超界,但是只是读值,而没有对该值操作,不然会出现段错误。(gcc 4.4.8 读值越界无报错,对此网上给的答案是)

QQ截图20151007162445.png

(图片来自www.shiyanlou.com问答)

但是:

  • 运算顺序改变了,前一个只要不满足第一个条件就不会运算tab[i]!=x,而改变后,就会出现数组超界。
  • 运算改变了,逻辑与变成了按位与

3.9 整数溢出

参考文章
C语言中存在两类整数算数运算

  1. 有符号运算
  2. 无符号运算

只有在有符号有符号运算时候才会有“溢出”发生

  • 无符号与无符号运算时
    所有无符号的运算都是以2的n次方为模运算的(n指结果的位数)
    有的文章也有说是:“溢出后的数会以2^(8*sizeof(type))作模运算”,也就是说,如果一个unsigned char(1字符,8bits)溢出了,会把溢出的值与256求模。
    其实效果都一样 ,可以在理解上认为是进位“舍弃”了。例如8位的0xff+0x1,相加后的进位“舍弃”掉后,就是0了。
  • 有符号与无符号运算时
    有符号会转换为无符号整数计算

当两个都是有符号整数时,“溢出”就有可能会发生,而且“溢出“的结果是未定义的。
也就是编译器爱怎么实现就怎么实现。对于大多数编译器来说,算得啥就是啥。

signed char x =0x7f; //注:0xff就是-1了,因为最高位是1也就是负数了

printf("%dn", ++x);

上面的代码会输出:-128,因为0x7f + 0×01得到0×80,也就是二进制的1000 0000,符号位为1,负数,后面为全0,就是负的最小数,即-128。

检测有符号溢出方法:

if((unsigned)a + (unsigned)b > INT_MAX){
    //......
}

if(a > INT_MAX -b){
    //......
}

INT_MAX 表示可能的最大整数值(有符号),在<limits.h>有定义。

3. 10 为函数main提供返回值

main的返回值告知操作系统该函数的执行时成功(返回0)还是失败(返回非0)

而:

main(){

}

默认的返回类型为整形,而一个返回值为整形的函数如果失败,实际上是隐含地返回了某个”垃圾“整数(即返回的非0数)。
在不用到该数的时候无关紧要,然而当需要使用到该数的时候,结果可能让人惊讶。

如果一个程序的main函数不返回任何值,那么有可能看上去执行失败。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • 第一部分Common Lisp介绍第1章 介绍一下Lisp你在学的时候觉得已经明白了,写的时候更加确信了解了,教别...
    geoeee阅读 2,910评论 5 8
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,504评论 1 51
  • 你好
    阿宽1阅读 112评论 0 0
  • 1:运动轨迹兑换流量。有效里程越多,可兑换更多流量; 2:生命卡:类似运动手环,可以远程监测心率、血压等生命指标并...
    db22e342f2b9阅读 155评论 0 0