精华:C++编程新手错误语录(续一)
2008-03-08 21:35:53 来源:WEB开发网核心提示:废话不说,直接进入正题,精华:C++编程新手错误语录(续一),本文承接先前发布的《C/C++编程新手错误语录》(http://www.pconline.com.cn/pcedu/empolder/gj/c/0508/691597.Html),继续归纳错误语录,更多错误语录,当然是待续,
废话不说,直接进入正题,本文承接先前发布的《C/C++编程新手错误语录》(http://www.pconline.com.cn/pcedu/empolder/gj/c/0508/691597.Html),继续归纳错误语录。
(8)“我想用malloc”、“我用不好malloc”
来看看一个变态程序:
/* xx.c:xx模块实现文件 */
int *pInt;
/* xx模块的初始化函数 */
xx_intial()
{
pInt = ( int * ) malloc ( sizeof( int ) );
...
}
/* xx模块的其他函数(仅为举例)*/
xx_otherFunction()
{
*Int = 10;
...
}
这个程序定义了一个全局整型变量指针,在xx模块的初始化函数中对此指针动态申请内存,并将pInt指向该内存首地址,并在xx模块的其他函数中都使用pInt指针对其指向的整数进行读取和赋值。
这个程序让我痛不欲生了好多天,扼腕叹息!这是我母校计算机系一位硕士的作品!作者为了用上malloc,拼命地把本来应该用一个全局整型变量摆平的程序活活弄成一个全局整型指针并在初始化函数中“动态”申请内存,自作聪明而正好暴露自己的无知!我再也不要见到这样的程序。
那么malloc究竟应该怎么用?笔者给出如下规则:
规则1 不要为了用malloc而用malloc,malloc不是目的,而是手段;
规则2 malloc的真正内涵体现在“动态”申请,假如程序的特性不需动态申请,请不要用malloc;
上面列举的变态程序完全不具备需要动态申请的特质,应该改为:
/* xx.c:xx模块实现文件 */
int example;
/* xx模块的初始化函数 */
xx_intial()
{
...
}
/* xx模块的其他函数(仅为举例) */
xx_otherFunction()
{
example = 10;
...
}
更多内容请看C/C++技术专题 java编程开发手册专题,或
规则3 什么样的程序具备需要动态申请内存的特质呢?包含两种情况:
(1)不知道有多少要来,来了的又走了
不明白?这么说吧,譬如你正在处理一个报文队列,收到的报文你都存入该队列,处理完队列头的报文后你需要取出队列头的元素。
你不知道有多少报文来(因而你不知道应该用多大的报文数组),这些来的报文处理完后都要走(释放),这种情况适合用malloc和free。
(2)慢慢地长大
譬如你在资源受限的系统中编写一文本编辑器程序,你怎么做,你需要这样定义数组吗?
char str[10000]; 不,你完全不应该这么做。即使你定义了一个10000字节大的字符串,用户假如输入10001个字符你的程序就完完了。
这个时候适合用malloc,因为你根本就不知道用户会输入多少字符,文本在慢慢长大,因而你也应慢慢地申请内存,用一个队列把字符串存放起来。
那么是不是应该这样定义数据结构并在用户每输入一个字符的情况下malloc一个CharQueue空间呢?
typedef strUCt tagCharQueue
{
char ch;
struct tagCharQueue *next;
}CharQueue;
不,这样做也不对!这将使每个字符占据“1+指针长度”的开销。
正确的做法是:
typedef struct tagCharQueue
{
char str[100];
struct tagCharQueue *next;
}CharQueue;
让字符以100为单位慢慢地走,当输入字符数达到100的整数倍时,申请一片CharQueue空间。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
规则4 malloc与free要成对出现
它们是一对恩爱夫妻,malloc少了free就必然会慢慢地死掉。成对出现不仅体现在有多少个malloc就应该有多少个free,还体现在它们应尽量出现在同一函数里,“谁申请,就由谁释放”,看下面的程序:
char * func(void)
{
char *p;
p = (char *)malloc(…);
if(p!=NULL)
…; /* 一系列针对p的操作 */
return p;
}
/*在某处调用func(),用完func中动态申请的内存后将其free*/
char *q = func();
…
free(q);
上述代码违反了malloc和free的“谁申请,就由谁释放”原则,代码的耦合度大,用户在调用func函数时需确切知道其内部细节!正确的做法是:
/* 在调用处申请内存,并传入func函数 */
char *p=malloc(…);
if(p!=NULL)
{
func(p);
…
free(p);
p=NULL;
}
/* 函数func则接收参数p */
void func(char *p)
{
… /* 一系列针对p的操作 */
}
规则5 free后一定要置指针为NULL,防止其成为“野”指针
(9)“函数add编译生成的符号就是add”
int add(int x,int y)
{
return x + y;
}
float add(float x,float y)
{
return x + y;
}
更多内容请看C/C++技术专题 Java编程开发手册专题,或
即便是在C语言中,add函数被多数C编译器编译后在符号库中的名字也不是add,而是_add。 而在C++编译器中,int add(int x,int y)会编译成类似_add_int_int这样的名字(称为“mangled name”),float add(float x,float y)则被编译成_add_float _float,mangled name包含了函数名、函数参数数量及类型信息,C++依靠这种机制来实现函数重载。
所以,在C++中,本质上int add( int x, int y )与float add( float x, float y )是两个完全不同的函数,只是在用户看来其同名而已。
这就要求初学者们能透过语法现象看问题本质。本质上,语言的创造者们就是在玩各种各样的花样,以使语言具备某种能力,譬如mangled name花样的目的在于使C++支持重载。而C语言没有玩这样的花样,所以int add( int x, int y )与float add( float x, float y )不能在C程序中同时存在。
(10)“没见过在C语言中调用C++的函数”、“C/C++不能调用Basic、Pascal语言的函数”
这又是一个奇天下之大怪的问题,“打死我都不相信C、C++、basic、pascal的函数能瞎调来调去”,可是有句话这么说:
没有你见不到的,只有你想不到的!
既然芙蓉姐姐也有其闻名天下的道理,那么C、C++、Basic、Pascal的函数为什么就不能互相调用呢?
能!
你可以用Visual C++写一个DLL在Visual Basic、Delphi(Pascal的孙子,Object Pascal的儿子)中调用,也可以在Visual Basic、Delphi中写一个DLL在Visual C++中调用不是?
让我们来透过现象看本质。首先看看函数的调用约定(以Visual C++来说明):
(1) _stdcall调用
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
#define WINAPI _stdcall 按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
(2) _cdecl调用
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。关于C/C++中变长参数(…)的问题,笔者将另文详述。
由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
(3) _fastcall调用
_fastcall调用较快,它通过CPU内部寄存器传递参数。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。
要害字_stdcall、_cdecl和_fastcall可以直接加在函数前,也可以在Visual C++中设置,如图1。
图1 在VC中设置函数调用约定 在创建DLL时,一般使用_stdcall调用(Win32 Api方式),采用_functionname@number命名规则,因而各种语言间的DLL能互相调用。也就是说,DLL的编制与具体的编程语言及编译器无关,只要遵守DLL的开发规范和编程策略,并安排正确的调用接口,不管用何种编程语言编制的DLL都具有通用性。
推而广之,假如有这样一个IDE开发环境,它能识别各种语言,所有语言采用相同的调用约定和命名规则,一个软件内各种语言书写的函数将能互相调用!
这个世界上可能永远不需要这样一个IDE。
(11)“英语、数学不好就学不好C/C++”
更多内容请看C/C++技术专题 Java编程开发手册专题,或
这也许是20世纪最大的谎言,这句话最先是哪位大师的名人名言已无可考证,可此后一批批的人被它误导。许多初学者因为这句话被吓倒,放弃了做程序员的理想。 还有许多后来成为优秀程序员的人,在他们的成长过程中并没有依靠深奥的数学,可他们还是在总结经验时制造恐慌,号称一定要具备高深的数学知识,唯恐别人笑话其学术水平不高。
在下则认为,大多数情况下,程序设计不需要太深奥的数学功底,除非你所从事的程序设计涉及特定的专业领域(如语音及图像处理、数字通信技术等)。在下这一观点也许是革旧立新,而革命必然要流血牺牲(谭嗣同),所以恭候大家板砖。
那么英语在C/C++的学习中处于什么地位呢?那就是能看懂资料,看懂MSDN。
学编程的终极之道不在看书,而在大量地不断地实践。
(12)“C++太难了,我学不会”
又不知是谁的悲观论调,许多初学者被C++吓倒,“太难了,我学不好”,如弱者自怜。假如C++真的难到学不会,那么C++的创造者们所从事的工作岂不是“非人力所能及也”?
在下认为,学习C++的态度应该是:战略上藐视它,战术上重视它,要敢于胜利(《毛主席语录》)。当然也不可轻敌,不能因为把握了一点皮毛就以为自己牛B轰轰了(笔者曾经牛B轰轰了好一阵子,现在想来,甚觉当时幼稚)。
假如你征服了C++,透彻理解了C++的语言特性及STL,那么,其他语言想不被你征服都难了。
本回书着落此处,更多错误语录,当然是待续。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
来看看一个变态程序:
/* xx.c:xx模块实现文件 */
int *pInt;
/* xx模块的初始化函数 */
xx_intial()
{
pInt = ( int * ) malloc ( sizeof( int ) );
...
}
/* xx模块的其他函数(仅为举例)*/
xx_otherFunction()
{
*Int = 10;
...
}
这个程序定义了一个全局整型变量指针,在xx模块的初始化函数中对此指针动态申请内存,并将pInt指向该内存首地址,并在xx模块的其他函数中都使用pInt指针对其指向的整数进行读取和赋值。
这个程序让我痛不欲生了好多天,扼腕叹息!这是我母校计算机系一位硕士的作品!作者为了用上malloc,拼命地把本来应该用一个全局整型变量摆平的程序活活弄成一个全局整型指针并在初始化函数中“动态”申请内存,自作聪明而正好暴露自己的无知!我再也不要见到这样的程序。
那么malloc究竟应该怎么用?笔者给出如下规则:
规则1 不要为了用malloc而用malloc,malloc不是目的,而是手段;
规则2 malloc的真正内涵体现在“动态”申请,假如程序的特性不需动态申请,请不要用malloc;
上面列举的变态程序完全不具备需要动态申请的特质,应该改为:
/* xx.c:xx模块实现文件 */
int example;
/* xx模块的初始化函数 */
xx_intial()
{
...
}
/* xx模块的其他函数(仅为举例) */
xx_otherFunction()
{
example = 10;
...
}
更多内容请看C/C++技术专题 java编程开发手册专题,或
规则3 什么样的程序具备需要动态申请内存的特质呢?包含两种情况:
(1)不知道有多少要来,来了的又走了
不明白?这么说吧,譬如你正在处理一个报文队列,收到的报文你都存入该队列,处理完队列头的报文后你需要取出队列头的元素。
你不知道有多少报文来(因而你不知道应该用多大的报文数组),这些来的报文处理完后都要走(释放),这种情况适合用malloc和free。
(2)慢慢地长大
譬如你在资源受限的系统中编写一文本编辑器程序,你怎么做,你需要这样定义数组吗?
char str[10000]; 不,你完全不应该这么做。即使你定义了一个10000字节大的字符串,用户假如输入10001个字符你的程序就完完了。
这个时候适合用malloc,因为你根本就不知道用户会输入多少字符,文本在慢慢长大,因而你也应慢慢地申请内存,用一个队列把字符串存放起来。
那么是不是应该这样定义数据结构并在用户每输入一个字符的情况下malloc一个CharQueue空间呢?
typedef strUCt tagCharQueue
{
char ch;
struct tagCharQueue *next;
}CharQueue;
不,这样做也不对!这将使每个字符占据“1+指针长度”的开销。
正确的做法是:
typedef struct tagCharQueue
{
char str[100];
struct tagCharQueue *next;
}CharQueue;
让字符以100为单位慢慢地走,当输入字符数达到100的整数倍时,申请一片CharQueue空间。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
规则4 malloc与free要成对出现
它们是一对恩爱夫妻,malloc少了free就必然会慢慢地死掉。成对出现不仅体现在有多少个malloc就应该有多少个free,还体现在它们应尽量出现在同一函数里,“谁申请,就由谁释放”,看下面的程序:
char * func(void)
{
char *p;
p = (char *)malloc(…);
if(p!=NULL)
…; /* 一系列针对p的操作 */
return p;
}
/*在某处调用func(),用完func中动态申请的内存后将其free*/
char *q = func();
…
free(q);
上述代码违反了malloc和free的“谁申请,就由谁释放”原则,代码的耦合度大,用户在调用func函数时需确切知道其内部细节!正确的做法是:
/* 在调用处申请内存,并传入func函数 */
char *p=malloc(…);
if(p!=NULL)
{
func(p);
…
free(p);
p=NULL;
}
/* 函数func则接收参数p */
void func(char *p)
{
… /* 一系列针对p的操作 */
}
规则5 free后一定要置指针为NULL,防止其成为“野”指针
(9)“函数add编译生成的符号就是add”
int add(int x,int y)
{
return x + y;
}
float add(float x,float y)
{
return x + y;
}
更多内容请看C/C++技术专题 Java编程开发手册专题,或
即便是在C语言中,add函数被多数C编译器编译后在符号库中的名字也不是add,而是_add。 而在C++编译器中,int add(int x,int y)会编译成类似_add_int_int这样的名字(称为“mangled name”),float add(float x,float y)则被编译成_add_float _float,mangled name包含了函数名、函数参数数量及类型信息,C++依靠这种机制来实现函数重载。
所以,在C++中,本质上int add( int x, int y )与float add( float x, float y )是两个完全不同的函数,只是在用户看来其同名而已。
这就要求初学者们能透过语法现象看问题本质。本质上,语言的创造者们就是在玩各种各样的花样,以使语言具备某种能力,譬如mangled name花样的目的在于使C++支持重载。而C语言没有玩这样的花样,所以int add( int x, int y )与float add( float x, float y )不能在C程序中同时存在。
(10)“没见过在C语言中调用C++的函数”、“C/C++不能调用Basic、Pascal语言的函数”
这又是一个奇天下之大怪的问题,“打死我都不相信C、C++、basic、pascal的函数能瞎调来调去”,可是有句话这么说:
没有你见不到的,只有你想不到的!
既然芙蓉姐姐也有其闻名天下的道理,那么C、C++、Basic、Pascal的函数为什么就不能互相调用呢?
能!
你可以用Visual C++写一个DLL在Visual Basic、Delphi(Pascal的孙子,Object Pascal的儿子)中调用,也可以在Visual Basic、Delphi中写一个DLL在Visual C++中调用不是?
让我们来透过现象看本质。首先看看函数的调用约定(以Visual C++来说明):
(1) _stdcall调用
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。
WIN32 Api都采用_stdcall调用方式,这样的宏定义说明了问题:
#define WINAPI _stdcall 按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
(2) _cdecl调用
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。关于C/C++中变长参数(…)的问题,笔者将另文详述。
由于Visual C++默认采用_cdecl 调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。
按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。
(3) _fastcall调用
_fastcall调用较快,它通过CPU内部寄存器传递参数。
按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。
要害字_stdcall、_cdecl和_fastcall可以直接加在函数前,也可以在Visual C++中设置,如图1。
图1 在VC中设置函数调用约定 在创建DLL时,一般使用_stdcall调用(Win32 Api方式),采用_functionname@number命名规则,因而各种语言间的DLL能互相调用。也就是说,DLL的编制与具体的编程语言及编译器无关,只要遵守DLL的开发规范和编程策略,并安排正确的调用接口,不管用何种编程语言编制的DLL都具有通用性。
推而广之,假如有这样一个IDE开发环境,它能识别各种语言,所有语言采用相同的调用约定和命名规则,一个软件内各种语言书写的函数将能互相调用!
这个世界上可能永远不需要这样一个IDE。
(11)“英语、数学不好就学不好C/C++”
更多内容请看C/C++技术专题 Java编程开发手册专题,或
这也许是20世纪最大的谎言,这句话最先是哪位大师的名人名言已无可考证,可此后一批批的人被它误导。许多初学者因为这句话被吓倒,放弃了做程序员的理想。 还有许多后来成为优秀程序员的人,在他们的成长过程中并没有依靠深奥的数学,可他们还是在总结经验时制造恐慌,号称一定要具备高深的数学知识,唯恐别人笑话其学术水平不高。
在下则认为,大多数情况下,程序设计不需要太深奥的数学功底,除非你所从事的程序设计涉及特定的专业领域(如语音及图像处理、数字通信技术等)。在下这一观点也许是革旧立新,而革命必然要流血牺牲(谭嗣同),所以恭候大家板砖。
那么英语在C/C++的学习中处于什么地位呢?那就是能看懂资料,看懂MSDN。
学编程的终极之道不在看书,而在大量地不断地实践。
(12)“C++太难了,我学不会”
又不知是谁的悲观论调,许多初学者被C++吓倒,“太难了,我学不好”,如弱者自怜。假如C++真的难到学不会,那么C++的创造者们所从事的工作岂不是“非人力所能及也”?
在下认为,学习C++的态度应该是:战略上藐视它,战术上重视它,要敢于胜利(《毛主席语录》)。当然也不可轻敌,不能因为把握了一点皮毛就以为自己牛B轰轰了(笔者曾经牛B轰轰了好一阵子,现在想来,甚觉当时幼稚)。
假如你征服了C++,透彻理解了C++的语言特性及STL,那么,其他语言想不被你征服都难了。
本回书着落此处,更多错误语录,当然是待续。
更多内容请看C/C++技术专题 Java编程开发手册专题,或
更多精彩
赞助商链接