




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、1.1 &,| :按位运算 &&,| :逻辑运算 = 不同于 = 来看这样一个例子,while (c=' ' | c= 't' | c='n') c = getc(f); 由于程序员在比较字符 和变量c时,误将 = 写成 = ,那么while后的表达式恒为 1,因为' '不等于零,它的ASCII码值为32。 为了避免这种情况,我们可以这样写 while(''=c | 't'= c | 'n'=c) ,这样即使不小心将 = 写成 = ,编译器也会报错。 在C中,
2、单引号括起来表示一个整数,双引号括起来表示指针。例如,字符和字符串。1.3 词法分析中的“贪心法” C编译器读入一个符号的规则:每一个符号应该包含尽可能多的字符,方向为自左向右。.大嘴法 编译器在解析字符的时候会尽可能的解析成可能组成一个符号的最常的字符串。 例如,a-b <=> (a-) -b 而 y = x/*p; 中 /* 被编译器理解为一段注释的开始,如果本意是用x除以p所指向的值,应该重写如下y = x/ *p; 或更加清楚一点,写作 y = x/(*p); 在用双引号括起来的字符串中,注释符 /* 属于字符串的一部分,而在注释中出现的双引号""属于注
3、释的一部分。整数常量的第一个字符是数字0,那么该常量将被视为八进制数。 单引号所括起的字符代表一个整数,双引号括起的则代表一个指针(字符数组,自动加0) 1. 理解函数声明 通过认识这个让人“不寒而栗”的式子: (*(void(*)()0)() 1)把0强制转换成了类型 void(*)(),这是一个指向函数的指针,所指向的这个函数返回值为void ps:a)float *g():这是一个函数,返回指针float*,因为()的优先级比*高。 b)如果fp是一个函数指针,怎么调用该指针所指向的函数呢? (*fp)(); c)为了使表述更加清晰: typedef void (*funptr)();
4、则那个不寒而栗的式子可以表达为: (*(funptr)0)() 2)由1)-b我们得知,这个式子的意思就是调用了上面所说的函数指针所指向的函数。 2.运算符优先级 1)任何一个逻辑运算符的优先级低于任何一个关系运算符 2)移位运算符的优先级低于算术运算符,但是高于关系运算符 算术>移位>关系>逻辑 3.不要忘了switch中的break,否则会把满足的case后面的所有case都执行。 小心指针和除法的一起使用: y = x/*p; 这里的/*理解为注释!解决方法是: (1) y = x / *p (2) y = x /(*p) 以第二个更为清晰.6./*看上去是注释的开始如
5、: a=/*b; 老版本的编译器会当作是:a =/ *b 7.char *slash = '/' /* 编译错误,'/'并不是字符指针 */ 同理,而且有些编译器不检查函数参数类型,所以: printf('n'); 在程序运行时会产生难以预料的结果,而不给出编译器警告或者错误. 8.整型数(一般为16或者32位)的存储空间可以容纳多个字符(一般为8位),因此有些C编译器允许在一个字符常量(以及字符串常量)中包含多个字符.也就是说,用'yes'代替"yes"并不被该编译器检测到.后者的含义是依次包含'y&
6、#39;,'e','s'以及空字符'0'的4个连续内存单元的首地址.前者(即是'yes')的含义并没有准确地进行定义:大多数C编译器理解为:"一个整数值,由'y','e','s'所代表的整数值按照特定编译器实现中定义的方式组合得到". (1)在Borland C+ v5.5和LCC v3.6中采取的做法是,忽略多余的字符,最后的整数值就是第一个字符的整数值; (2)在Visual C+ 6.0和GCC v2.95中采取的做法是,依次用后一个字符覆盖前一个字符,最后
7、得到的整数值就是最后一个字符的整数值. 二、语法陷阱2.1 理解函数声明 来看一个表达式, (*(void(*)()0)(); 你能看出来这个表达式的含义吗? 看不出来不要紧,慢慢来. 首先,回顾一下C变量的声明,它由两部分组成:类型以及一组类似表达式的声明符,最简单的声明符就是单个变量。如,float f,g; 这个声明的含义:当对其求值,表达式f和g的类型为浮点型。 因为声明符与表达式类似,我们可以在声明符中任意使用括号: float (f); 含义:当对其求值,(f)的类型为浮点型,由此可知,f也是浮点型。 我们将这个逻辑推广到函数和指针的声明,如: float ff(); float
8、*pt; 这些形式组合使用,如 float *g(),(*h)(); 由于()的结合优先级高于 * ,所以g是一个函数,它的返回类型为指向浮点数的指针;h是一个函数指针,h所指向函数的返回值为浮点类型。 由声明到类型转换符:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余部分用一个括号整个“封装”。如,声明 float (*h)(); 为一个指向返回值为float的函数的指针 而 (float (*)() 表示一个“指向返回值为float的函数的指针”的类型转换符。 函数指针的调用: (*fp)();如果fp的声明如下:void (*fp)();fp是一个指向返回值为void类型的函数的指
9、针,那么(*fp)()的值为void。、 因此 (void (*)()0 表示将常数 0 转型为“指向返回值为 void 的函数的指针”类型。请认真理解这一点。 我们可以用 (void (*)()0 代替 fp ,从而得到如下调用(*(void(*)()0)(); 2.3 注意作为语句结束标志的分号 来看一个代码段if (a>b); a=0;else b=0; 注意if表达式后的分号,上面的代码相当于if (a>b) a=0;else b=0; 由于没有if与 else 匹配,编译器将产生警告。再来看一个例子,struct logrec int date; int time;mai
10、n(). 注意到第一个与main定义之间是没有分号的。因此这段代码的效果是声明函数main的返回值是结构 logrec 类型。三、语义陷阱3.1 指针与数组C中的数组值得注意的两点: 一是C中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然而,数组的元素可以使任何类型的对象,当然也可以是另外一个数组,这样便可以“仿真”多维数组。 二是对于一个数组,我们只能够做两件事:确定该数组大小,一级获得指向该数组下标为0的元素的指针(下标运算其实都是通过指针进行的)。 来看一个例子,若定义int s1231;int *p;int i;则 p = s4;是无误的而 p = s; 则是不行的
11、。因为s会被转换为指向二维数组的指针,与p类型不匹配。然而,像下面这样做是可以的int (*ap)31;int s1231;那么 ap = s; 是可以通过的。 假如定义了 int a22; 则 *(a+i) 即数组a中下标为i的元素的引用,简记为 ai。实际上, a+i 与 i+a 的含义一样,因此 ai 与 ia 也具有同样的含义。 3.2 非数组的指针 假如我们有两个这样的字符串 s 和 t,并且希望将他们连成单个字符串 r。如char *r;strcpy(r,s);strcat(r,t);不幸的是,这样做事不行的。因为不能确定r 指向何处。不仅要让r 指向一个地址,而且r所指向的地址还
12、应该有内存空间可供容纳字符串。于是我们可以这样做char *r;r = malloc(strlen(s) + strlen(t) + 1);现在可以 strcpy(r,s); strcat(r,t); 了,不过别忘了最后 free(r); 这里注意到给 r 分配内存的时候 +1 了,这是因为 strlen() 返回的值不包括 '0',所以要多申请存放 '0' 的空间! 3.6 边界计算与不对称边界 先来看一个例子,我们常常对类似的代码这样处理int i,a10;for (i=0;i<10;i+)ai = 0;而不是像下面这样:int i,a10;for (
13、i=1;i<=10;i+)ai-1 = 0;或者int i,a10;for (i=0;i<=9;i+)ai = 0; 原因是前一种方式更适合像C这样的数组下标从0开始的语言。这里包含在取值范围中的 0 为“入界点”,而不包含在取值范围之中的 10 为“出界点”。由于是形如 >=0 且 <10 这样不对称的形式,不妨把这种方式称为“不对称边界”。 在处理数组指针的时候,我们将“上界”指向缓冲区第一个未占用字符(假设指针为k),而不是最后一个已占用的字符,则数组的元素个数为该指针减去数组的首地址(假设为p): k - p。 这里细心的读者可能会担心越界的问题。请看: C标准
14、中:数组中 实际不存在的“溢界”元素 的地址位于数组所占内存之后,对该元素的引用是非法的,然而这个地址可以用于进行赋值和比较。也就是数组最后一个元素所在内存的下一个内存地址可以用于比较,但该内存中的内容却是不可读写的。 这样,结合特例外推法可以准确、直观的处理边界问题。这部分内容值得反复体会,请参阅原书P45。 3.7 求值顺序 C语言中只有四个运算符(&& 、|、?:和 ,)存在规定的求值顺序。其中:&&和 | 是先对左侧操作数求值,只在需要时对右操作数求值。A?B:C 是先对A求值,根据A的值再求B或C的值。而逗号运算符是先对做操作数求值,然后该值被“丢弃”
15、,再求右值。 在函数参数中的逗号并非逗号运算符,其求值顺序未定义。但在g(x,y)中却是先 x后y求值,该函数只有一个参数,即求值结果。 来看个小技巧,if(y!=0 && x/y > 5) complain(); 这里利用 && 的求职顺序先检测y是否为 0 ,避免 0 作为除数。 3.10 为函数 main提供返回值 来看这个函数main()若无显式声明返回类型,则默认返回类型为 int 。一个返回值为整型的函数如果返回失败,实际上是隐含地返回了某个“垃圾”整数。但是由这个返回值是无法判断该函数是否执行成功的! 六 预处理器需求:需要将特定数量(例如,
16、某个数据表的大小)在程序里出现的所有实例统统加以修改。我们希望只改动一处数值,然后重新编译即可实现。大多数C语言实现在函数调用时都会带来重大的开销。我们希望有这样一种程序块,它看上去像函数,但却没有函数调用的开销。 6.1 宏中的空格对于 #define f (x) (x)-1)它展开后为 (x) (x)-1) 而 #define f(x) (x)-1) 展开后为 (x)-1) ,并且对这个宏定义,f(3) 和 f (3)这两种调用方式求值后都为2。 6.2 宏并不是函数 从表面上看,宏玉函数的行为非常相似,但它们并不完全等同。假如:#define abs(x) x>0?x:-x则对 a
17、bs(a-b)展开后得到 a-b>0?a-b:-a-b ,其中-a-b即(-a)-b 与我们期望的 -(a-b) 不一致;又,对abs(a)+1 展开后得到 a>0?a:-a+1 (与上类似,与期望不符)。为此,应该将宏定义中每一个参数用括号括起来,并且将整个表达式也括起来,以预防引起优先级有关的问题。正确的定义应该是这样的#define abs(x) (x)>0)?(x):(-x) 再来看一个例子,假如有宏定义#define max(a,b) (a)>(b)?(a):(b)程序中有这样的代码:biggest = x0;i=1;while(i<n) biggest
18、 = max(biggest,xi+);如果max为一个真正的函数,则上述代码无误,而如果max为一个宏,那就不能正常工作。原因是对宏扩展后biggest = (biggest)>(xi+)?(biggest):(xi+);其中这部分 xi+ 有可能被执行两次,与我们的期望不符。解决办法是确保宏max中的参数没有副作用,像这样:biggest = x0;for(i = 1;i<n;i+) biggest = max(biggest,xi); 由此,我们看到宏和函数的一些区别。 6.3 宏并不是语句 考虑这样一个例子#define assert(e) if(!(e) assert_error(_FILE_,_LINE_)则对以下代码if
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- JDG管材供应合同范例
- 休闲吧合同范例
- 农庄承包安全合同范例
- 农业代育苗合同范例
- ktv服务合同范例
- 信息设备 维修 合同范例
- 供货合同范例 酒水
- 公务印刷服务合同范例
- 代理经纪公司合同范例
- 不锈钢设计合同范例
- (一模)东北三省三校2025年高三第一次联合模拟考试 生物试卷(含答案)
- 红金大气商务风领导欢迎会PPT通用模板
- 学前教育学00383-历年真题-试卷
- 淡马锡模式解读汇总课件
- 2022年郑州卫生健康职业学院单招职业适应性测试笔试试题及答案解析
- 穴位贴敷技术操作流程图及评分标准
- 湖北省黄冈市基层诊所医疗机构卫生院社区卫生服务中心村卫生室地址信息
- 个人有关事项报告表(全)
- 角膜上皮损伤-临床诊治专家共识-课件
- 电力排管检验批
- 毕业论文-楼道节能灯的设计与实现
评论
0/150
提交评论