《C语言深度剖析》学习笔记二

 

 

1.5,最冤枉的关键字----sizeof

1.5.1,常年被人误认为函数

sizeof是关键字不是函数,其实就算不知道它是否为 32个关键字之一时,我们也可以借助编译器确定它的身份。看下面的例子:

int i=0;

A),sizeof(int); B),sizeof(i); C),sizeof int; D),sizeof i;

毫无疑问,32位系统下 A),B)的值为 4。那C)的呢?D)的呢?

      在 32位系统下,通过 Visual C++6.0或任意一编译器调试,我们发现 D)的结果也为 4。sizeof后面没有括号居然也行,那想想,函数名后面没有括号绝对不行。由此轻易得出 sizeof绝非函数。

好,再看 C)。编译器怎么怎么提示出错呢?我们可以在 int前加 unsigned,const等关键字但不能加 sizeof。

好,记住:sizeof在计算变量所占空间大小时,括号可以省略,而计算类型(模子)大小时不能省略。一般情况下,尽量写上括号。

 

1.5.2,sizeof(int)*p表示什么意思?

sizeof(int)*p表示什么意思?

留几个问题(讲解指针与数组时会详细讲解),32位系统下:

int *p = NULL;

sizeof(p)的值是多少?

sizeof(*p)呢?

 

 

int a[100];

sizeof (a) 的值是多少?

sizeof(a[100])呢?//请尤其注意本例。

sizeof(&a)呢?

sizeof(&a[0])呢?

 

int b[100];

void fun(int b[100])

{

     sizeof(b);// sizeof (b) 的值是多少?

}

结果:

sizeof(int)*p 表示 sizeof(int) 乘以 p

 

sizeof(a)= 400  //

sizeof(a[100])= 4  

sizeof(&a)= 4     //VC6 中会输出400,这是个bug,在VS2005 sp1之前的编译器都有这个bug

sizeof(&a[0])= 4

 

sizeof(b) = 4;

1.4,signed、unsigned 关键字

      我们知道计算机底层只认识 0、1,任何数据到了底层都会变计算转换成 0、1。那负数怎么存储呢?把基本数据类型的最高位腾出来,用来存符号,同时约定如下:最高位如果是 1,表明这个数是负数,其值为除最高位以外的剩余位的值添上这个“-”号;如果最高位是 0,表明这个数是正数,其值为除最高位以外的剩余位的值。

 

      这样的话,一个32位的 signed int类型整数其值表示法范围为:- 2^31~ 2^31 - 1;8 位的 char类型数其值表示的范围为- 2^7~ 2^7-1。一个 32位的 unsigned int类型整数其值表示范围为:0~ 2^32-1;8位的 unsigned char类型数其值表示的范围为 0~ 2^8-1。编译器缺省默认情况下数据为 signed 类型的。

 

int main()

{

char a[1000];

int i;

for(i=0; i<1000; i++)

{

a[i] = -1- i;

}

printf("%d",strlen(a));

return 0;

}

 

此题看上去真的很简单,但是却鲜有人答对。答案是 255。

 

      for循环内,当 i 的值为 0时,a[0]的值为-1。关键就是-1在内存里面如何存储。我们知道在计算机系统中,数值一律用补码来表示(存储) 。主要原因是使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时如果最高位(符号位)有进位,则进位被舍弃。正数的补码与其原码一致;负数的补码:符号位为 1,其余位为该数绝对值的原码按位取反,然后整个数加 1。按照负数补码的规则,可以知道-1 的补码为0xff,-2 的补码为0xfe……当 i 的值为127时,a[127]的值为-128,而-128是char类型数据能表示的最小的负数。当 i继续增加,a[128]的值肯定不能是-129。因为这时候发生了溢出,-129 需要 9 位才能存储下来,而 char类型数据只有 8 位,所以最高位被丢弃。剩下的 8 位是原来 9 位补码的低 8 位的值,即 0x7f。当 i 继续增加到255的时候,-256的补码的低 8位为 0。然后当i增加到 256时,-257的补码的低 8 位全为1,即低八位的补码为 0xff,如此又开始一轮新的循环……按照上面的分析,a[0]到 a[254]里面的值都不为 0,而 a[255]的值为 0。strlen函数是计算字符串长度的,并不包含字符串最后的‘/0’ 。而判断一个字符串是否结束的标志就是看是否遇到‘/0’ 。如果遇到‘/0’ ,则认为本字符串结束。

      分析到这里,strlen(a)的值为 255应该完全能理解了。这个问题的关键就是要明白 char类型默认情况下是有符号的,其表示的值的范围为[-128,127],超出这个范围的值会产生溢出。 另外还要清楚的就是负数的补码怎么表示。 弄明白了这两点, 这个问题其实就很简单了。

1.6.2, float变量与“零值”进行比较

float变量与“零值”进行比较的 if语句怎么写?

float fTestVal = 0.0;

A), if(fTestVal == 0.0); if(fTestVal != 0.0);

B), if((fTestVal >= -EPSINON) && (fTestVal <= EPSINON)); //EPSINON 为定义好的精度。

哪一组或是那些组正确呢?我们来分析分析:

      float和 double类型的数据都是有精度限制的,这样直接拿来与 0.0比,能正确吗?明显不能,看例子: 的值四舍五入精确到小数点后 10位为:3.1415926536,你拿它减去 0.00000000001然后再四舍五入得到的结果是多少?你能说前后两个值一样吗?

EPSINON 为定义好的精度,如果一个数落在[0.0-EPSINON,0.0+EPSINON] 这个闭区间内,我们认为在某个精度内它的值与零值相等;否则不相等。扩展一下,把0.0替换为你想比较的任何一个浮点数,那我们就可以比较任意两个浮点数的大小了,当然是在某个精度内。

      同样的也不要在很大的浮点数和很小的浮点数之间进行运算,比如:10000000000.00 + 0.00000000001这样计算后的结果可能会让你大吃一惊。

1.6.3,指针变量与“零值”进行比较

int*p=NULL;//定义指针一定要同时初始化,

 if(NULL == p); if(NULL != p);

 

这个写法才是正确的,但样子比较古怪。为什么要这么写呢?是怕漏写一个“=”号:if(p = NULL),这个表达式编译器当然会认为是正确的,但却不是你要表达的意思。所以,非常推荐这种写法。

1.6.4,else到底与哪个 if配对呢?

else常常与 if语句配对,但要注意书写规范,看下面例子:
if(0==x)
if(0==y) error() ;
else{
//program code
}

这个 else到底与谁匹配呢?让人迷糊,尤其是初学者。还好,C 语言有这样的规定:else始终与同一括号内最近的未匹配的 if语句结合。虽然老手可以区分出来,但这样的代码谁都会头疼的,任何时候都别偷这种懒。关于程序中的分界符‘{’和‘}’ ,建议如下:

【建议 1-16】程序中的分界符‘{’和‘}’对齐风格如下:

注意下表中代码的缩进一般为 4 个字符,但不要使用 Tab键,因为不同的编辑器 Tab键定义的空格数量不一样,别的编辑器打开 Tab键缩进的代码可能会一片混乱。

不提倡的风格
void Function(int x){
//program code
}

if (condition){
//program code
}else{
//program code

1.6.5,if语句后面的分号

if(NULL != p) ;
fun();

在 C 语言中,分号预示着一条语句的结尾,使用时要注意。建议在真正需要用空语句时写成这样:

NULL;

而不是单用一个分号。这就好比汇编语言里面的空指令,比如 ARM 指令中的 NOP 指令。这样做可以明显的区分真正必须的空语句和不小心多写的分号。

1.6.6,使用if语句的其他注意事项

【规则 1-17】先处理正常情况,再处理异常情况。
      在编写代码时,要使得正常情况的执行代码清晰,确认那些不常发生的异常情况处理代码不会遮掩正常的执行路径。这样对于代码的可读性和性能都很重要。因为,if语句总是需要做判断,而正常情况一般比异常情况发生的概率更大(否则就应该把异常正常调过来了) ,如果把执行概率更大的代码放到后面,也就意味着 if语句将进行多次无谓的比较。另外,非常重要的一点是,把正常情况的处理放在 if 后面,而不要放在 else 后面。当然这也符合把正常情况的处理放在前面的要求。
【规则 1-18】确保if和 else子句没有弄反。
这一点初学者也容易弄错,往往把本应该放在 if语句后面的代码和本应该放在 else 语句后面的代码弄反了。

1.7,switch、case 组合

1.7.2,case关键字后面的值有什么要求吗?
好,再问问:真的就这么简单吗?看看下面的问题:
Va lue1 的值为 0.1行吗?-0.1呢?-1呢?0.1+0.9呢? 1+2呢?3/2呢?‘A’ 呢? “A”
呢?变量 i(假设 i已经被初始化)呢?NULL 呢?等等。这些情形希望你亲自上机调试一
下,看看到底哪些行,哪些不行。
记住:case 后面只能是整型或字符型的常量或常量表达式(想想字符型数据在内存里是怎么存的) 。

1.7.3,case语句的排列顺序

似乎从来没有人考虑过这个问题,也有很多人认为 case 语句的顺序无所谓。但事实却不是如此。如果 case语句很少,你也许可以忽略这点,但是如果 case语句非常多,那就不得不好好考虑这个问题了。比如你写的是某个驱动程序,也许会经常遇到几十个 case语句
的情况。
一般来说,我们可以遵循下面的规则:

【规则 1-22】把正常情况放在前面,而把异常情况放在后面。
如果有多个正常情况和异常情况,把正常情况放在前面,并做好注释;把异常情况放在后面,同样要做注释。比如:

switch(variable)
{
///
//正常情况开始
case A:
//program code
break;
case B:
//program code
break;
//正常情况结束
//

//异常情况开始
case -1:
//program code
break;
//异常情况结束
//

default:
break;
}
【规则 1-23】按执行频率排列case语句
把最常执行的情况放在前面,而把最不常执行的情况放在后面。最常执行的代码可能也是调试的时候要单步执行的最多的代码。如果放在后面的话,找起来可能会比较困难,而放在前面的话,可以很快的找到。

【规则 1-24】简化case 每种情况对应的代码

【规则 1-26】把default子句只用于检查真正的默认情况。

      有时候,你只剩下了最后一种情况需要处理,于是就决定把这种情况用 default子句来处理。这样也许会让你偷懒少敲几个字符,但是这却很不明智。这样将失去 case 语句的标号所提供的自说明功能,而且也丧失了使用 default子句处理错误情况的能力。所以,奉劝
你不要偷懒,老老实实的把每一种情况都用 case 语句来完成,而把真正的默认情况的处理交给 default子句。

1.8,do、while、for关键字

C 语言中循环语句有三种:while循环、do-while循环、for循环。

1.8.1,break与 continue的区别
      break关键字很重要,表示终止本层循环。现在这个例子只有一层循环,当代码执行到break时,循环便终止。如果把 break换成continue会是什么样子呢?continue表示终止本次(本轮)循环。当代码执行到 continue时,本轮循环终止,进入下一轮循环。
      while(1)也有写成 while(true) 或者 while(1==1) 或者 while((bool) 1)等形式的,效果一样。
留一个问题:在 switch case 语句中能否使用 continue关键字?为什么?

不能,在VC6 SP6 中提示错误: illegal continue,因为switch不是循环。

【建议 1-27】在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少 CPU 跨切循环层的次数。

【规则 1-31】把循环嵌套控制在3 层以内。
      国外有研究数据表明,当循环嵌套超过 3 层,程序员对循环的理解能力会极大的降低。如果你的循环嵌套超过 3 层,建议你重新设计循环或是将循环内的代码改写成一个字函数。

1.10,void 关键字

1.10.1,void a?
void的字面意思是“空类型”, void *则为“空类型指针”, void *可以指向任何类型的数据。
void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个 void变量,看看下面的例子:
void a;
Visual C++6.0上,这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a 的编译不会出错,它也没有任何实际意义。
void真正发挥的作用在于:
(1) 对函数返回的限定;
(2) 对函数参数的限定。

1.10.2,void修饰函数返回值和参数
【规则 1-33】如果函数没有返回值,那么应声明为 void类型
在 C 语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。

【规则 1-34】如果函数无参数,那么应声明其参数为void

在 C++语言中声明一个这样的函数:
int function(void)
{
return 1;
}
则进行下面的调用是不合法的:function(2);

      因为在 C++中,函数参数为 void的意思是这个函数不接受任何参数。而在 C语言中,可以给无参数的函数传送任意类型的参数,但是在 C++编译器中编译同样的代码则会出错。在 C++中,不能向无参数的函数传送任何参数,出错提示“'fun' : function does not take 1 parameters”。

所以,无论在 C 还是C++中,若函数不接受任何参数,一定要指明参数为 void。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值