文章目录

  • 3.3 作为参数的数组声明
  • 3.4 避免举隅法
  • 3.5 空指针并非空字符串
  • 3.6 边界计算与不对称边界
  • 3.7 求值顺序
  • 3.8 运算符&&,|| 和 !
  • 3.9 整数溢出
  • 声明与定义陷阱

scanf

scanf("%d,%d,&a,&b") /输入格式必须和sacnf保持一致
//输入1,2

3.3 作为参数的数组声明

数组名是首元素地址

在C语言中,我们没有办法可以将一个数组作为函数参数直接传递。如果我们使用数组名作为参数,那么数组名会立刻被转换为指向该数组第1个元素的指针。

char hello[] = "hello";
//声明了hello是一个字符数组。如果将该数组作为参数传递给一个函数:
printf("%s\n", hello);
//实际上与将该数组第1个元素的地址作为参数传递给函数的作用完全等效,即:
printf("%s\n", &hello[1]);

因此,将数组作为函数参数毫无意义。所以,C语言中会自动地将作为参数的数组声明转换为相应的指针声明。

int strlen(char s[])
{/* 具体内容 */
}
//两者写法等效
int strlen(char *s)
{/* 具体内容 */
}

C 程序员经常错误地假设,在其他情形下也会有这种自动地转换。本书 4.5节详细地讨论了一个具体的例子

extern char *hello;
//这个语句与下面的语句有着天渊之别:
extern char hello[];

如果一个指针参数并不实际代表一个数组,即使从技术上而言是正确的,采用数组形式的记法经常会起到误导作用。如果一个指针参数代表一个数组,情况又是如何呢?一个常见的例子就是函数main的第二个参数:

main(int argc,char* argv[])  //数组里存放的是一个个指针地址
{/* 具体内容 */
}
//两者写法等价
main(int argc,char** argv)
{/* 具体内容 */
}

前一种写法在于argv是一个指向某数组起始元素的指针,该数组里的元素为字符指针类型

3.4 避免举隅法

举隅法:以偏概全

指针就是地址

char *p,*q;  //char 开辟4个字节空间
p="xyz"//赋值
char *q=p;   // or q=p;q[1]='Y'  //禁止修改内容


实际上,p的值是一个指向由’x’、‘y’、‘z"和’\0’这4 个字符组成的数组的起始元素的指针

复制指针是复制地址而不是数据

//以数组的形式存放
char p[]="xyz"

这种数组形式是真实开辟空间而非放在常量池里

注意:指针定义与数组定义的区别

3.5 空指针并非空字符串

除了一个重要的例外情况,在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,编译器保证由 0 转换而来的指针不等于任何有效的指针。出于代码文档化的考虑,常数0这个值经常用一个符号来代替:

#define NULL 0

当然无论是直接用常数 0,还是用符号 NULL,效果都是相同的。需要记住的重要一点是,当常数 0 被转换为指针使用时,这个指针绝对不能被解除引用(dereference)。换句话说,当我们将 0 赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。下面的写法是完全合法的:

if( p == (char *) 0)   //合法if( strcmp(p,(char *) 0) == 0)   //非法

就是非法的了,原因在于库函数 strcmp的实现中会包括查看它的指针参数所指向内存中的内容的操作。

void main(){int *p=0; //避免被误认为赋值,一般写下面的//两者写法等价int *p=NULL; //不指向任何地址,未定义,空指针
}

空指针与空字符串

void main(){char *p=NULL;  //p//两者写法不等价char *q=" "; // \0printf("%d\n",strlen(q)); //0
}

如果p是一个空指针

printf(p);
printf("%s",p);

行为是未定义的,而且,与此类似的语句在不同的计算机上会有不同的效果

3.6 边界计算与不对称边界

在C语言中,这个数组的下标范围是从0到9。一个拥有10个元素的数组中,存在下标为0的元素,却不存在下标为10的元素。C语言中一个拥有n个元素的数组,却不存在下标为 n的元素,它的元素的下标范围是从0到n-1

例如,让我们仔细地来看看本书导读中的一段代码:

void main(){int i,a[10];for (i=1;i<=10;i++){a[i]=0;}
}

这段代码本意是要设置数组a中所有元素为0,却产生了一个出人意料的”副效果“——无限循环

3.7 求值顺序

a < b && c < d

C语言的定义中说明a<b应当首先被求值。如果a确实小于b,此时必须进一步对 c<d 求值,以确定整个表达式的值。但是,如果 a 大于或等于 b,则无需对c<d求值,表达式肯定为假

void main(){int a = 0;int b = 1;int c = a && b++  //a=0,表达式为假,b++不会参与执行int d = a || b++  //a=0,表达式为真,b++不会参与执行printf("b=%d\n",b);
}

另外,要对 a< b 求值,编译器可能先对 a 求值,也可能先对 b 求值,在某些机器上甚至有可能对它们问时并行求值。

C 语言中其他所有运算符对其操作数求值的顺序是未定义的。特别地,赋值运算符并不保证任何求值顺序。

下面这种从数组ar中复制前n个元素到数组br中的做法是不正确的,因为它对求值顺序作了太多的假设:

void main(){int ar[10] = {1,2,3,4,5,6,7,8,9,10};int br[10];int i = 0;while(i < 10)br[i]=ar[i++]
}

不同编译器执行顺序不同,可能自增之后再赋值,也可能相反

3.8 运算符&&,|| 和 !

按位运算符&,|和 ~ 对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作。例如,10&12 的结果是8(二进制表示为1000)

逻辑运算符&&,|| 和 !,结果只有0或者1

void main()
{int a=10; //1010int b=12; //1100int c;//逻辑与c=a && b; //1 0printf("c=%d\n",c) //1//二进制与c=a & b; //1000printf("c=%d\n",c) //8}

3.9 整数溢出

C 语言中存在两类整数算术运算,有符号运算与无符号运算。在无符号算术运算中,没有所谓的“溢出”一说:所有的无符号运算都是以2的n次方为模,这里 n 是结果中的位数。如果算术运算符的一个操作数是有符号整数,另一个是无符号整数,那么有符号整数会被转换为无符号整数,“溢出”也不可能发生。但是,当两个操作数都是有符号整数时,“溢出”就有可能发生,而且“溢出”的结
果是未定义的。当一个运算的结果发生“溢出”时,作出任何假设都是不安全的。

void main(){int a = 2147483647;int b = 1;a += b;printf("a = %d\n",a); // 输出 -2147483648
}

例如,假定a和b是两个非负整型变量,我们需要检查a+b是否会“溢出”。一种想当然的方式是这样:

void main(){int a = 2147483647;int b = 1;if (a+b < 0){printf("OK!\n"); }
}

这并不能正常运行。当 a+b 确实发生“溢出”时,所有关于结果如何的假设都不再可靠。例如,在某些机器上,加法运算将设置一个内部寄存器为四种状态之一:正、负、零和溢出。在这种机器上,C编译器完全有理由这样来实现上面的例子,即 a与b相加,然后检查该内部寄存器的标志是否为“负”。当加法操作发生“溢出”时,这个内部寄存器的状态是溢出而不是负,那么 if 的语句的检查就会失败。

一种正确的方式是将a和b都强制转换为无符号整数

#define IN_MAX 2147483647
void main(){int a = 2147483647;int b = 1;if ((unsigned int)a + (unsigned int)b > IN_MAX){printf("OK!\n"); }
}
//此处的 INT_MAX 是一个已定义常量,代表可能的最大整数值

声明与定义陷阱

int a;  // 定义 aextern int a;  // 声明 a  没有定义 引入外部变量会报错//区别定义与声明在于有没有开辟空间

C陷阱与缺陷(C Traps and Pitfalls)学习笔记相关推荐

  1. 57、读C陷阱和缺陷(C Traps and Pitfalls)(三)

    5.预处理器 使用预处理器大致有两个方向的原因: (1)需要将某个变量在程序中所有实现的实例全部修改. 这一条在C++中已用const定义变量替代了. (2)宏处理,来替代一个简单的函数,如putch ...

  2. C Traps and Pitfalls学习摘要

    <C陷阱与缺陷>是人民邮电出版社2008年出版的书籍,作者是(美)凯尼格.本书主要内容是从多个方面分析了C编程中可能遇到的问题. =不同于== = 赋值运算符 == 条件运算符if (x ...

  3. 《C Traps and Pitfalls》 笔记

    这本书短短的100多页,很象是一篇文章.但是指出的很多问题的确容易出现在笔试的改错题中 -------------------------------------------------------- ...

  4. 《C陷阱与缺陷》第三章阅读笔记

    语义"陷阱" 3.1 指针与数组 C语言中数组值得注意的地方有以下两点: 1.C语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来.然而,C语言中数组的元素可以是 ...

  5. C陷阱和缺陷(C Traps and Pitfalls)-读书笔记

    C Traps and Pitfalls Chap 01 词法陷阱 1.=是赋值运算符,==表示相等判断. 2.&.|是位运算符号,&&.||是逻辑运算符. 3.词法分析的贪心 ...

  6. c陷阱与缺陷第三章——Semantic Pitfalls

    c陷阱与缺陷第三章--Semantic Pitfalls 3.1 Pointers and array Array a[i] == i[a] Array subscripting Constrains ...

  7. 《C语言陷阱和缺陷》笔记

    原著:Andrew Koenig - AT&T Bell Laboratories Murray Hill, New Jersey 07094 翻译:lover_P 修订:CQBOY 来自:h ...

  8. 《C陷阱与缺陷》一导读

    前 言 C陷阱与缺陷 对于经验丰富的行家而言,得心应手的工具在初学时的困难程度往往要超过那些容易上手的工具.刚刚接触飞机驾驶的学员,初航时总是谨小慎微,只敢沿着海岸线来回飞行,等他们稍有经验就会明白这 ...

  9. 《Java解惑》陷阱和缺陷的目录

    陷阱和缺陷的目录 一.词汇问题 1.字母l在许多字体中都与数字1相像. 2.负的十六进制字面常量看起来像是正的. 3.八进制字面常量与十进制字面常量相像. 4.ASCII字符的Unicode转义字符容 ...

最新文章

  1. Ajax异步调用Web服务的例子
  2. NXP(I.MX6uLL) UART串口通信原理————这个未复习
  3. 为什么中国这么多高薪程序员,开发不出Java, Typescript, Python, Rust, Node.js这些基础设施?...
  4. linux :Docker 方式 安装 zookeeper、阿里服务器上 Docker 运行 zookeeper
  5. 【Elasticsearch】使用Elasticsearch中的copy_to来提高搜索效率
  6. python写web自动化_jenkins+selenium+python实现web自动化测试
  7. winform定义数据源名称_WinForm中使用CrystalReport水晶报表——基础,分组统计,自定义数据源...
  8. 带你了解Java Agent
  9. Java自学视频整理
  10. 苹果邮件怎么添加qq邮箱_QQ邮箱为何能收件,不能发邮件啊!??
  11. 测试开发工作者日记:2020.67-6.9
  12. 动态绑定style写法
  13. 7个实用的Python自动化测试框架
  14. 5.NDK Android jni开发 异常处理 native奔溃解决(相机图片美化)
  15. nginx+rtmp+OBS搭建音视频直播服务
  16. UEStudio/UltraEdit 的语法高亮文件 (*.uew)
  17. opencv检测相交点_在网络摄像头feed opencv中检测2条线之间的交点
  18. 微信小程序 用户协议和隐私协议
  19. STM32CubeIDE体验
  20. stata学习笔记|内生性

热门文章

  1. 努比亚z17s刷魔趣90
  2. linux netfilter 忽略网址,Linux 跟踪连接netfilter 调优
  3. mysql字段的相似度_SQL字段的相似度
  4. dpdk pci设备初始化
  5. CyclicBarrier分析
  6. 甘肃金昌市“公交一卡通”正式启用
  7. 无Mac机IOS开发环境搭建手记
  8. 各种依赖库(转载地址:https://blog.csdn.net/as89751)
  9. css3实现三级树形,css3树形导航
  10. 007-企业网站纽曼官网实现