C陷阱与缺陷 读书笔记

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所指向地址是否还有足够的内存空间存放st

  

   如下代码2

     char r[100];

  strcpy(r, s);

  strcat(r, t);

错误:

    r空间足够大容纳st吗?

  

   如下代码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语言中,无符号数没有溢出一说,因为所有的无符号运算都是以2n次方为模。

如果是符号与无符号数运算,也不存在溢出,因为会自动强制类型转为无符号;

但是,如果是两个有符号数运算则有可能溢出,且结果不确定;

 

一种检测方法:

if( (unsigned)a + (unsigned)b > INT_MAX)

{

  complain();

}

其中INT_MAX是一个已定义的常量,代表可能的最大整数值。ANSI C中在<limits.h>中定义了INT_MAX,其他平台需要自己定义;

10. 定义与声明

int a; 定义了a变量,并且c中默认a0

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改变符号后就会溢出!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值