C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析

马肤

温馨提示:这篇文章已超过467天没有更新,请注意相关的内容是否还可用!

摘要:本书是关于C语言编程的书籍,其中特别探讨了C语言中的陷阱之处。通过阅读本书,读者可以深入了解C语言的语法和特性,并学习如何避免常见的编程陷阱和错误。本书对于初学者和经验丰富的程序员来说都很有价值,因为它提供了关于如何更好地理解和使用C语言的实用建议和技巧。

文章参考于文献:《C陷阱与缺陷》[美]Andrew Koening

 C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析 第1张

🌈个人主页:慢了半拍

🔥 创作专栏:《史上最强算法分析》 | 《无味生》 |《史上最强C语言讲解》 | 《史上最强C练习解析》

🏆我的格言:一切只是时间问题。 

C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析 第2张

C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析 第3张

目录

词法陷阱 

一、= 不同于 ==

二、& 和 | 不同于 && 和 ||

三、词法分析中的“ 贪心法 ”

四、整型常量

五、字符与字符串

语法陷阱

一、理解函数声明

二、运算符的优先级问题

三、注意作为语句结束标志的分号

四、swith语句

五、函数调用

六、“悬挂”else引发的问题

语义陷阱

一、指针与数组

二、非数组的指针

三、作为参数的数组声明

四、避免“ 举隅法 ”

五、空指针并非空字符串

六、边界计算与不对称计算

七、求职顺序

八、逻辑运算符&&、| 和 !

九、整数溢出

十、为函数main提供返回值


词法陷阱 

一、= 不同于 ==

在 if 判断时容易出错。

=:赋值运算,a=3;表示的是将3赋值给a变量。

==:比较运算,a==3;表示判断a是否等于3,若等于则返回1,否则返回0。

二、& 和 | 不同于 && 和 ||

这是两个 逻辑操作符与位操作符的区别。

详见《逻辑操作符》与《位操作符》

三、词法分析中的“ 贪心法 ”

a---b;等于a-- -b;  //先a-b,再a--
不等于a- --b;  //--b先做自减运算,再a-b

C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析 第4张

四、整型常量

如果一个整型常量的第一个数字是0,则该常量会被当做是八进制数,因此10和010代表是分别是十进制的10和十进制的8。

五、字符与字符串

单引号引起来的字符代表的是该字符的ASCII码值;

双引号引起来的字符串代表的是一个指向无名数组的起始字符的指针,该数组被双引号之间的字符以及一个额外’\0’(字符串标志)初始化。

在双引号引起来的字符串中,注释符号/*属于字符串的一部分;在注释中出现的双引号“”又属于注释的一部分。

因此,语句:printf("The world") 和以下语句是等价的:

char str[]= {'T','h','e',' ','w', 'o','r','l','d','\n'};
printf(str);

语法陷阱

一、理解函数声明

指针例题

二、运算符的优先级问题

详见 优先级

三、注意作为语句结束标志的分号

//代码1
if(x[i] > b);
	b = x[i];
//代码2
if(x[i] > b){}
	b = x[i];
//代码3
if(x[i] > b)
	b = x[i];

一个分号就代表一个语句的结束。代码1与代码2是等价,if和赋值语句是两个独立的语句;而第三句中赋值语句在if中。

四、swith语句

语句详解

五、函数调用

C语言要求:在函数调用时即使函数不带参数也应该包括参数列表。因此,如果f是一个函数,

f();//是一个函数调用语句。

f;//却是一个什么也不做的语句

更精确地说,这个语句计算函数f的地址,却并不调用该函数。

六、“悬挂”else引发的问题

if (x == 0)
		if (y == 0) error();
	else {
			Z = X + Y;
			f(&z);
		 }

 两种解读:

解读一:

if (x == 0) 
{
	if (y == 0) error();
	else 
	{
		Z = X + Y;
		f(&z);
	}
}

 解读二:

if (x == 0)
{
	if (y == 0) error();
}
	else
	{
		Z = X + Y;
		f(&z);
	}

语义陷阱

一、指针与数组

深入理解指针系列文章

二、非数组的指针

1、假设我们要将两个字符串s和t连接组成一个字符串r,我们可以借助库函数strcpy()和strcat():

char *r;
strcpy(r,s);
strcat(r,t);

这样子其实无法实现,因为r所指向的地址还应该有内存空间可供容纳字符串,这个空间应该是以某种方式被分配了的。

2、所以我们此次给r分配一定的存储空间:

char r[100];
strcpy(r,s);
strcat(r,t);

可以看到,我们分配的大小为100,但是我们无法确保r足够大可以容纳s和t连接之后的字符串。

3、因此我们可以利用malloc()函数来分配内存:

char *r, *malloc();
r = malloc(strlen(s) + strlen(t));
strcpy(r,s);
strcat(r,t);

这样我们就手动给r开发了一块内存,大小为s和t字符数的和。

4、不过这样还是不够好,malloc()函数有可能无法提供请求的内存,这样的情况下malloc函数会通过返回一个空指针来作为“内存分配失败”的事件信号。下面是较为完善的代码:

char *r, *malloc();
r = malloc(strlen(s) + strlen(t) + 1);  //需要多分配一个内存空间,以装一个空字符作为字符串结束标志
if(!r)  // 判空语句
{
	complain();
	exit(1);
}
strcpy(r,s);
strcat(r,t);
free(r);  //释放暂时申请的内存空间r

三、作为参数的数组声明

在函数中传递参数时,C语言会自动地将作为参数的数组相地转换为指针声明,也就是说:

int strlen(char s[]){		;		}
int strlen(char *s){		;		}
//是等价的

但是这两种是不等价的:

extern char *hello;
extern char hello[];

一个指针参数代表一个参数:

main(int argc, char *argv[]){	;	}
main(int argc, char **argv){	;	}

在main函数中的第二个参数就是一个指针参数代表一个数组的情况。

四、避免“ 举隅法 ”

常见错误解释:避免以整体代表部分,或者以部分代表整体。

常见错误:混淆指针与指针所指向的数据。

char *p,*q;
p ="xyz";

上面的赋值语句使得p的值就是字符串"xyz",然而实际情况并不是这样,实际上,p的值是一个指向由'x'、'y'、’z"和\0 4个字符组成的数组的起始元素的指针。

因此,如果我们执行下面的语句:q=p;

p和q现在是两个指向内存中同一地址的指针,但这个赋值语句并没有同时复制内存中的字符。

C语言书籍——A/陷阱之处,C语言书籍中的陷阱解析 第5张

复制指针并不同时复制指针所指向的数据,因此,当我们执行完下面的语句之后:q[1]='y';q所指向的内存现在存储的是字符串'xyz’。因为p和q所指向的是同一块内存,所以p指向的内存中存储的当然也是字符串’xyz'。

五、空指针并非空字符串

在C语言中将一个整数转换为一个指针,最后得到的结果都取决于具体的C编译器实现。这个特殊情况就是常数0,编译器保证由0转换而来的指针不等于任何有效的指针。

出于代码文档化的考虑,常数0这个值经常用一个符号来代替:#define NULL 0

当然无论是直接用常数0,还是用符号ULL,效果都是相同的。

需要记住的重要一点是,当常数0被转换为指针使用时,这个指针绝对不能被解除引用(dereference)。换句话说,当我们将0赋值给一个指针变量时,绝对不能企图使用该指针所指向的内存中存储的内容。

合法格式:

if(p == (char *) 0)

非法格式:

if(strcmp(p,(char*)0) == 0)

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

六、边界计算与不对称计算

        在C语言中,一个拥有10个元素的数组,它的下标范围是从0到9的。那么0就是数组下标的第一个“入界点”(指的是处于数组下标范围以内的点,包括边界点),而10就是数组下标中的第一个“出界点”(指的是数组下标范围以内的点,不包括边界点)。

正因为如此,我们可以这样写:

int a[10], i;
for (i=0; i

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码