C陷阱与缺陷
1. typedef用法:
① 定义一种类型别名,而不是简单的宏替换:
char *pa,pb;(注意:pb并没有定义为指针,虽然你可能想这么定义它)
typedef char* PCHAR
PCHAR pa, pb;
② 用在旧的C代码中,帮助struct。以前的代码中,声明struct新对象时,必须带上struct,即形式为:struc结构名对象名,如:
struct tagPOINT1
{
Int x;
Int y;
};
struct tagPOINT1 p1;
typedef struct tagPOINT
{
Int x;
Int y;
}POINT;
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事。
③ 用其定义与平台无关的类型:
例如定义一个叫REAL的浮点类型,在目标平台上,让它表示最高精度的类型为:
Typedef long double REAL;
在不支持long double的平台二上,改为:
Typedef double REAL;
即,跨平台时,只要改下typedef本身即可,不需要对其他源代码进行修改
④ 为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂的声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。例如:
原声明:void(*b[10])(void(*)());
改为:
Typedef void (*pFunParam)();
Typedef void (*pFunx)(pFunParam);
则原声明最简化版:
pFunx b[10];
举例2:
举例3:
(*(void (*)() ) 0) ();
Typedef void(* funcptr) ();
(*(funcptr) 0) ();
2. 注意float *g(); float (*g)(); 不一样,前者表示函数g的返回值为指向浮点数的指针;后者表示g所指向的函数的返回值为浮点类型;
进一步的,对于 float (*h)();函数,表示:
H是一个指向返回值为浮点类型的函数的指针,
因此,对应( float (*)() )表示:
一个“指向返回值为浮点类型的函数的指针”的类型转换符。
3. 调用首地址为0位置的子例程:
(*0)(); ? no!因为运算符*必须要一个指针来做操作数,而且这个指针还应该是一个函数指针,所以需要加一个类型转换符来“强制类型转换”,(void(*)()),该类型可以描述为:一个指向返回值类型为void的函数的指针,最后结果:
(*(void(*)())0)(); OMG!
4. 运算符优先级:
容易出错点:
R = hi<< 4 +low;
+的优先级大于<< !!!!!
算术 > 移位 > 关系 > 逻辑 > 赋值 > 条件
5. Int (*ap)[31]; 表示声明了*ap是一个拥有31个整型元素的数组,因此ap就是一个指向这样的数组的指针;因此,可以:
Int calendar[12][31];
Int (*monthp)[31];
Monthp = calendar;
假设:新年开始,我们需要对calendar清零,如下代码1:
int month;
for(month = 0; month < 12; month++)
{
int day;
for( day = 0; day < 31; day++)
{
calendar[month][day] = 0;
}
}
或者代码2:
int (*monthp)[31];
for(monthp = calendar; monthp < &calendar[12]; monthp++)
{
Int dayp;
for(dayp = *monthp; dayp < &*monthp[31]; dayp++)
{
*dayp = 0;
}
}
6. 字符串操作问题:
如下代码1 :
char *r;
strcpy(r, s);
strcat(r, t);
错误:
① r没有指向地址;
② r所指向地址是否还有足够的内存空间存放s和t?
如下代码2:
char r[100];
strcpy(r, s);
strcat(r, t);
错误:
① r空间足够大容纳s和t吗?
如下代码3:
char *r, *malloc();
r = malloc(strlen(s) + strlen(t));
strcpy(r, s);
strcat(r, t);
错误:
① malloc可能无法请求内存,此时返回空;
② r静态分配内存,应该手动释放;
③ 记住字符串最后需要一个’/0’;
综上,代码4:
char *r, *malloc();
r = malloc( strlen(s) + strlen(t) + 1 );
if(!r)
{
complain();
exit(1);
}
strcpy(r, s);
strcat(r, t);
/* 一段时间之后再使用*/
free(r);
7. 指针和数组区别之一:
char *hello = “hello”;
char hell0[] = “hello”;
二者在内存分配上,指针变量会需要一个额外的存储空间,而数组名只能算一个标示字符,不占用内存空间;
8. 对于边界问题良好的编程风格:
良好的:
for(i = 0; i < 10; i++) // 明显的显示了边界
{}
不良的:
for(i = 0; i <= 9; i++ )
9. 溢出问题:
c语言中,无符号数没有溢出一说,因为所有的无符号运算都是以2的n次方为模。
如果是符号与无符号数运算,也不存在溢出,因为会自动强制类型转为无符号;
但是,如果是两个有符号数运算则有可能溢出,且结果不确定;
一种检测方法:
if( (unsigned)a + (unsigned)b > INT_MAX)
{
complain();
}
其中INT_MAX是一个已定义的常量,代表可能的最大整数值。ANSI C中在<limits.h>中定义了INT_MAX,其他平台需要自己定义;
10. 定义与声明
int a; 定义了a变量,并且c中默认a为0;
extern int a;声明了a变量,显示的说明了a的存储空间是在程序的其他地方分配的;
11. static可以避免多个文件的命名冲突;
12. scanf的问题:
char c;
scanf(“%d”, &c);
问题在于c被声明为char,而不是int,当程序要求scanf读入一个整数时,应该传递给他一个指向整数的指针。而scanf函数却得到了一个指向字符的指针,因为整数所占存储空间大于字符,所以字符c附近的内存将被覆盖;
13. 良好的编程习惯:每个外部对象只在一个地方声明,这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。定义这个外部对象的模块也应该包括这个头文件。
14. --n >= 0 与 n-- >0 不同!
前者先从n中减去1,然后与0比较,后者先保存n,从n中减去1,然后与0比较;
15. &buffer[N] 与 buffer[N]的区别,对于溢出的数组元素地址可以引用,后者为溢出的数组元素值不可以,为非法;
16. 关于不对称边界问题,参考书3.6小节;
17. 数组名起始就是指针,但非指针变量,但也不是常量。常量与变量都是需要内存存储的。数组名是一个指针立即数,在程序运行时直接计算出来就发送给CPU。
18. 宏定义使用:
① 参数不可有自增或自减:
#define max(a,b) ((a)>(b)?(a):(b))
biggest = x[0];
I = 1;
while (I < n)
biggest = max(biggest, x[i++]);
原因:变量i自增,导致biggest取值不正确;
② 另一个危险:宏展开可能产生非常庞大的表达式,占用的空间远远超过了期望:
max(a, max(b, max(c,d))) 等效于:
不写了。。。
③ 宏定义不要定义if判断语句
#define assert(e) if(!e) assert_error(__FILE__, __LINE__)
if(x > 0&& y >0)
assert(x > y);
else
assert(y > x);
④ 宏并不是类型定义,与typedef 不同,主要区别于带指针的定义:
typedef struct foo FOOTYPE;
#define T1 struct foo *
typedef struct foo * T2;
T1 a, b; // b属于struct类型
T2 a, b;
19. 除2与左移:
A / 2 与 A>>2区别:
首先清楚如下几点:
计算机存储负数,在机器上是以补码的形式存储;
负数的反码为负数除符号位外取反;负数的补码为负数的反码加1;
A / 2的汇编代码如下:
mov eax, dword ptr [ebp-4]
cdq //如果eax为正,edx 各位为0,如果eax为负数,edx为全FF(各位为1)
sub eax, edx // eax = eax - edx
sar eax, 1 // 算术右移一位,算术右移表示符号位不变,高位补符号位,低位舍弃
mov dword ptr [ebp-4],eax
A >> 1的汇编代码如下:
move eax, dword ptr [ebp-4]
sar eax,1
mov dword ptr [ebp-4], eax
因此: -1 / 2 = 0 -1 >> 1 = -1 !!!
20. 如何成为好的C++程序员? Koenig&Moo:
① 避免使用指针;
② 提倡使用程序库;
③ 使用类来表示概念;
④ 一开始不要太研究语法。。就其本身而言,C++是一种非常低级的语言,唯有利用库,才能写出高层次的程序。确实有不少程序应该用低层次技术来构造,但是对于初学者不合适。库优先于语言细节。根据我们的经验,学生们首先掌握如何使用程序库之后,就会很容易理解类的概念,学会如何构造类的技术,而且首先学习程序库,能够使学生培养复用库代码的良好习惯,而不是凡事自己动手。首先学习语言细节的学生,最后的编程风格往往是C类型的。
21. C语言中int表示范围-32768~32767 :所以需要注意!不要轻易对负数改变符号,如果是-32768改变符号后就会溢出!