《C陷阱与缺陷》学习笔记(上)

一、词法中的“贪心法”
   C语言中有的符号为单字符,如+、-、*和/等;而有的为多字符,如/**/、++、==;当输入一个字符后又输入一个字符,编译器必须判
断是按两个单字符处理还是将其合并为一个符号对待?
   C语言对于这类问题可以归纳我为一个简单的原则:每一个符号应该包含尽可能多的字符。即从左至右一个字符一个字符的读入,如果
该字符可以组成一个符号,那么在读入下一个字符,在判断两个字符是否可能是一个符号的组成;如果可以重复上述过程,直到读入的字
符不再能组成一个有意义的符号为止。需要注意的是除了字符串与字符常量,符号的中间不能有空白(空格符、制表符、换行符)。
如a---b会被编译器认为是(a--)-b;而a- --b会被编译器认为是a-(--b)(--运算的优先级高);
用单引号引起的字符实际上代表的就是一个整数,有对应的ASCII值。而用双引号引起来的字符串,却代表的一个指向字符数组的无名指

针。


二、语法陷阱
1、如何理解表达式(*(void(*)())0)()?
首先void(*)()是一个指向返回值为void,无参数的函数指针,用类型T代替,然后再来看表达式就可以表示为(*(T)0)();而(T)0就是将

地址0强制类型转换(类似于(float)3)为T类型的指针(即返回值为void,无参数的函数),将这个指针用p表示,则表达式为

(*p)(),那么再来看这个表达式是不是就很清楚了呢?其实就是调用地址0处的返回值为void的无参函数。类似于这种类型的构造在Linux内核

中出现过。


2、运算符优先级问题
总结:
(1)优先级最高的并不是真正意义上的运算符,包括:数组下标、函数调用操作符和结构体成员变量选择操作符。他们都是自左而右的
结合方式。
(2)单目运算符的优先级仅次于上述运算符,单目运算符是自右向左的方式结合。如*p++,*和++都是单目运算符,是自由向左的结合方
式,故此表达式为*(p++)。
(3)接下来就是双目运算符,双目运算符中优先级:算数运算符(/*+-和%) > 移位运算符(<<和>>) > 关系运算符(>、>=、<、<=) > 
逻辑运算符(==、!=、&、^、|、&&、||) >  三目运算符 > 赋值运算符 。
注意:关系运算符中==和!=的优先级低于其他关系运算符。逻辑运算符中,所以的按位运算符比顺序运算符优先级高,与运算符比或运算

符高,而按位异或介于按位与和按位或之间。


3、注意作为语句结束的分号
多一个分号有时并不会造成太大的影响,只是多了一条空语句如int a = 3;;而有时会造成致命影响如下例:
if(x[i]>big);
 big = x[i];
第一行的分号要与不要差别巨大。
少一个分号一般编译器会报错,但是在某些情况下不会报错而产生致命影响,如下例:
if(n<3)
return
log.date = x[0];
log.time = x[1];
log.code = x[3];
本意是想在n<3时候返回,而实际却把log.date = x[0]作为了返回值。
如果在如果一个声明结束后紧跟一个函数定义,若声明结尾的分号被省略,则编译器会将声明的类型作为函数的返回值;
例:struct log{
int data;
int time;
int code;
}
main()
{
...
}
main函数返回值为struct log结构体。若分号没有被省略则会是默认类型的int。这种错误编译器并不会报错,也许有时候并不会对结果


产生影响,但有时候会导致程序运行出错。ANSI标准main为
int main()
{
...
return 0;
}
返回值为0是告诉操作系统程序成功执行,不成功则会返回非0;很显然这类错误有时候会导致操作系统误以为程序未正确执行。避免这类

错误只需将main写成标准形式。


三、语义陷阱
1、数组与指针
(1)C语言中只有一维数组,而且数组的大小必须在编译期就被确定,数组的元素可以是任何类型的对象,同时也可以是另外一个数组。
所以只要数组的元素是数组就可以仿真出多维数组。
(2)对于数组我们只能做两件事:一是确定数组的大小,二是确定数组下标为0元素的地址。其他关于数组的操作看上去是以下标进行运
算的,而实际上都是都是通过指针进行的。
例如:int calendar[12][31],该数组看似二维数组,其实是任然是一个一维数组,只是该数组拥有12个元素,每个元素是又是拥有31个
int类型的数组。
如int calendar[12][31]就表示该数组拥有十二个数组,每个数组有31个int型元素。
在VC中很容易验证
i = *calendar[4]与i = calendar[4][0]结果相同,故可说明calendar[4][0]实际上是对这个元素地址的解引用。
与上述结果类似的
i=calendar[4][7]与i=*(calendar[4]+7)、i=*(*(calendar+4)+7)这三者的结果完全相同。
因为calendar是calendar[0][0]的地址,实际上就是指向地址的地址(i = calendar[0][0]与i = **calendar完全相同),而calendar
[4]是指向int类型的地址,可以理解为calendar是一个二级指针(指向地址的指针),而calendar[4]是一级指针(实际上数组名并不能
等同于指针,这么说便于理解),int *p=calendar是不合法的,int **p=calendar和int *p=calendar[4]是合法的。现实中我们并不这
样访问数组,这会很繁琐,这很好的验证了(1)(2)。
进一步讨论数组名与指针的区别:我们发现数组名可以像指针一样去访问它所指向的内容,然而数组名并不等同与指针。他们最大的区别
就是数组名是一个地址常量,编译器没有为其分配内存空间,而且值不能修改,运算中不能作为左值;而指针是变量,变量的值是一个地
址,编译器会为其分配内存空间,值可以修改,运算中可以作为左值。
需要注意的是:
int strlen(char s[]){ ...}与int strlen(char *s){ ... }完全相同
而extern char s[] 与extern char *s却截然不同。(个人理解:s是一个地址常量,该地址指向s数组的第一个元素,而后者声明后会把
s数组中的值强制类型转化为地址)。

对于指针要分清地址与地址锁指向的内容。


2、数组边界问题
(1)我们知道数组下标从0开始,声明数组int s[10],能够访问的元素是a[0]...a[9],若我们访问元素a[10]编译器并不会报错,而且程
序可以运行,但有时候正常,有时候会带来灾难性的问题;如下例:
int i,a[10];
for(i=0;i<=10;i++)
{
a[i] = 0;
}
这段代码访问了并不存在的数组元素a[10],编译器不会报错也没有警告,但是这段代码在VC中会进入死循环,但是将第一句改为
int a[10],i;则这段代码却能够正常执行;对于第一种情况一般编译器是按照内存地址递减的方式来给变量分配内存,那么先定义的i分
配的高地址,后定义的数组a[10]分配低地址,而数组中的元素地址是递增的故a[10]实际就是i,实际上将计数器i的值设置为0了,故会
进入死循环。在某些不是内存地址递减方式分配内存的编译器上就不会出现这样的问题,但同样会出现给其他变量赋值的可能;所以数组
越界会带来灾难性的问题。需要仔细计算数组边界。解决这类问题有一个简单的方法即采用不对称边界的方法改为for(i=0;i<10;i++)即

可,因为0=<i<=9,即0=<i<10;这样10-0恰好是数组元素的个数。


3、求值顺序的问题
求值顺序与运算符优先级并不是同一类问题
如下例:
if(count!=0 && sum/count < smallaverage)
对于这个表达式当count=0时也不会出现“除数为0”的错误。我们知道这个表达式中运算优先级是依次是/ 、<、!=、&&,即将count!=0 
的结果与sum/count < smallaverage结果进行逻辑与。然而这两个运算并不是同时发生,而是先计算左边,如果左边为假,则无论右边为
何值,最终结果认为假,所以此时并不会求右边表达式的值,故不会出现上述错误。
C语言中只有四个运算符(&&、||、?:和,)会发生求值顺序的问题。运算符&&、||首先求左边表达式,只有需要右边表达式值时才会再
求右边表达式的值;运算符?:有三个操作数在a?b:c中首先求a的值,再根据需要求b或者c的值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
08-10
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值