分析编程语言缺陷的一种方法就是把所有的缺陷归于三类:
“多做之过”--不该做的做了;
“少做之过”--该做的没有做;
“误做之过”--该做了做了但是做的不对;
一、无论何时,如果你遇见这样一条语句,你可以断定它是错误的:
malloc(strlen(str)); //error
这是因为其他的字符串处理库函数几乎都包含一个额外空间,用于容纳字符串结尾‘\0’字符。所以malloc(strlen(str)+1);才是正确的!人们很容易忽略strlen这个特殊的情况。
sizeof 是一个运算符:计算大小时,如果运算对象是类型的名字就必须加括号,如果是一个具体的数据或者变量加不加都可以!
例如 sizeof int ; int *p ; sizeof *p ; sizeof (int );//此括号必须加;
二、“L”的故事
一个L的NUL和两个L的NULL
一个L的NUL用于结束一个ASCII字符串。 ASCII中字符0的位模式被称为‘NUL’;两个L的 NULL用于表示什么也不指向,表示一个(空指针);
如果发现了三个L的NULLL,就要检查看是否出现了错误!
三、switch语句中default的作用与位置对程序的影响。
(1)default对switch语句的影响:
例一:
int num=4;
switch(num)
{
default:cout<<"default"<<endl;
case 1:cout<<"1"<<endl;break;
case 4:cout<<"4"<<endl;break;
}
结果:4;//如果有对应的case,则与default无关;
例二:
int num=4;
switch(num)
{
case 7:cout<<"7"<<endl;
default:cout<<"default"<<endl;
case 1:cout<<"1"<<endl;break;
case 2:cout<<"2"<<endl;break;
}
结果:default
1
//从default后面开始执行,遇到break退出;
例三:
int num=4;
switch(num)
{
default:cout<<"default"<<endl;
case 1:cout<<"1"<<endl;
case 2:cout<<"2"<<endl;
}
结果:default;
1
2
//如果没有case语句与之对应,并且case后面没有break,则从default语句的位置开始向后执行,直到遇到某些特殊的要求停止。我们称这种情况为 "fall through"。缺省采用“fall through”,在97%情况下都是错误的。
(2)switch的另一个问题,break到底中断了什么?
由于C语言中的switch一条语句,曾经导致美国AT&T电话服务的停顿大约9个小时,使AT&T电话网络大部分处于瘫痪状态。当时的电话交换(switch system)系统,采用了switch语句实现交换功能。
代码大意如下:
netword code()
{
switch(line1)
{
case thing1:do1();break;
case thing2:do2();
if(true)
{
dosother();
break;//愿意是跳出本次的if语句;
}
initialization_machine();
break;
case thing3:do3();break;
default:processing();
} //但是break跳出了这里;
using_initialization_machine();//导致没有执行inittialization_machine()函数;
}
就是因为这段简略的代码导致了AT&T历史上重大的网络故障。这里可以看出,break是跳出最近的那层循环语句或者switch语句。由于它跳出了switch语句致使initialization_machine()工作没有完成,为后面的失败埋下了伏笔。
四、缺省的可见性
对于一个声明的函数,如果不加任何的修饰符,则是全局可见的。
function work(){//全局可见}
extern function work1(){//在任何地方可见}
static function work2(){//这个文件之外不可见,限制了其作用范围}
extern:用于函数定义,表示全局可见(属于冗余的)。用于变量,表示它在其他地方定义。
static:在函数内部,表示该变量的值在各个调用间一直保持延续性;在函数这一级,表示该函数只对本文件可见。
根据经验,这种缺省的可见性被多次证明是个错误。对象在大多数情况下应该采用缺省的可见性。如果要让它全局可见,应该采用显示的手段来进行注明。
五、“,”号赋值
i=1,2,3;
i的结果最终是什么呢?我们知道“,”运算符的值就是最右边操作数的值。但是这里,赋值符的优先级更高,,所以实际的情况应该是:
(i=1),2,3;
i赋值为1,接着执行常量2,3的运算,计算结果丢弃,最终结果为1。
在看下面的情况:
i=(1,2,3);
此时i的结果就是3,由于()改变了赋值语句的优先级,“,”运算符的值就是最右边操作数的值,所以最终是3.
六、“结合性”是什么意思?
C语言中的优先级是一个十分重要的知识点儿。需要牢记。那么结合性又是什么呢?我们知道在一个表示式中,如果运算符的优先级都不一样,那么我们就按照运算符的优先级进行计算。但是如果所有的优先级都一样呢?
这时候就用到了结合性。 结合性就是仲裁者,它决定在几个优先级相同的操作符中先执行哪一个。
例如:a*b+c //很easy,我们知道先算*,在算+,因为乘除优先于加减。
下面一个例子:
int a,b=1,c=2;
a=b=c;
那么上面的例子如何进行计算呢?
这里就用到了结合性。所有的赋值符都具有右结合性,就是从右至左依次进行;类似的(&和|)位运算符都具有左结合性。结合性只用于表达式出现两个以上相同优先级的操作符情况。
所以上面的结果毫无疑问:a=b=c=2.而不是1或者其他的情况。
七、最大一口策略 --(maximal munch strategy)
如下表达式:
z=y+++x;
此句话编译器该怎么翻译呢?
z=y++ + x; 或者 z=y+ ++x;呢?
为了避免歧义:ANSI C规定了一种逐渐为人所知的“maximal munch strategy”,最大一口策略。这种策略表示如果下一个标记有超过一种的解释方案,编译器将选取能组成最长字符序列的方案。
所以翻译的结果为:z=y++ + x;
八、返回局部变量的问题.
我们知道,当控制流离开声明自动变量的(即局部变量)的范围时,自动变量便会自动失效,其栈内存空间将被回收。
那么如何解决这个问题呢?
(1)返回一个指向字符串常量的指针;
char * func()
{
return "Hello"; //只适用于简单的字符;
}
(2)使用全局声明的数组;
int num[3]
int *func()
{
num[3]=...
return num;
}
(3)使用静态变量或数组,改变其生命周期;
int func()
{
static int number=10;
return number;
}
(4)显示的分配内存 ,当然要记得释放;
int *func()
{
int *p=malloc(SIZE);
return p;
}
一般的来说,函数是可以返回局部变量的。 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了。因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错。但是如果返回的是局部变量的地址(指针)的话,程序运行后会出错。因为函数只是把指针复制后返回了,但是指针指向的内容已经被释放了,这样指针指向的内容就是不可预料的内容,调用就会出错。
准确的来说,函数不能通过返回指向栈内存的指针(注意这里指的是栈,返回指向堆内存的指针是可以的)。