什么是好的程序员?是不是懂得很多技术细节?还是懂底层编程?还是编程速度比较快?我觉得都不是。对于一些技术细节来说和底层的技术,只要看帮助,查资料就能找到,对于速度快,只要编得多也就熟能生巧了。
如果要了解一个程序员,我想首先最想看的就是他的程序代码,程序代码可以看出一个程序员的素质和修养,程序就像一个作品,有素质有修养的程序员的作品必然是一图精美的图画,一首美妙的歌曲,一本赏心悦目的小说。
“细微之处见真功”,真正能体现一个程序的功底恰恰在这些细微之处。
这就是程序员的——编程修养。我总结了在用C/C++语言(主要是C语言)进行程序写作上的三十二个“修养”,通过这些,你可以写出质量高的程序,同时也会让看你程序的人渍渍称道,那些看过你程序的人一定会说:“这个人的编程修养不错”。
6、if 语句对出错的处理
我看见你说了,这有什么好说的。还是先看一段程序代码吧。
if ( ch >&#61; &#39;0&#39; && ch <&#61; &#39;9&#39; ){
/* 正常处理代码 */
}else{
/* 输出错误信息 */
printf("error ......n");
return ( FALSE );
}
这种结构很不好&#xff0c;特别是如果“正常处理代码”很长时&#xff0c;对于这种情况&#xff0c;最好不要用else。先判断错误&#xff0c;如&#xff1a;
if ( ch <&#39;0&#39; || ch > &#39;9&#39; ){
/* 输出错误信息 */
printf("error ......n");
return ( FALSE );
}
/* 正常处理代码 */
......
这样的结构&#xff0c;不是很清楚吗&#xff1f;突出了错误的条件&#xff0c;让别人在使用你的函数的时候&#xff0c;第一眼就能看到不合法的条件&#xff0c;于是就会更下意识的避免。
7、头文件中的#ifndef
千万不要忽略了头件的中的#ifndef&#xff0c;这是一个很关键的东西。比如你有两个C文件&#xff0c;这两个C文件都include了同一个头文件。而编译时&#xff0c;这两个C文件要一同编译成一个可运行文件&#xff0c;于是问题来了&#xff0c;大量的声明冲突。
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用&#xff0c;你都要加上这个。一般格式是这样的&#xff1a;
#ifndef <标识>
#define <标识>
......
......
#endif
<标识>在理论上来说可以是自由命名的&#xff0c;但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写&#xff0c;前后加下划线&#xff0c;并把文件名中的“.”也变成下划线&#xff0c;如&#xff1a;stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif
&#xff08;BTW&#xff1a;预编译有多很有用的功能。你会用预编译吗&#xff1f;&#xff09;
8、在堆上分配内存
可能许多人对内存分配上的“栈 stack”和“堆 heap”还不是很明白。包括一些科班出身的人也不明白这两个概念。我不想过多的说这两个东西。简单的来讲&#xff0c;stack上分配的内存系统自动释放&#xff0c;heap上分配的内存&#xff0c;系统不释放&#xff0c;哪怕程序退出&#xff0c;那一块内存还是在那里。stack一般是静态分配内存&#xff0c;heap上一般是动态分配内存。
由malloc系统函数分配的内存就是从堆上分配内存。从堆上分配的内存一定要自己释放。用free释放&#xff0c;不然就是术语——“内存泄露”&#xff08;或是“内存漏洞”&#xff09;—— Memory Leak。于是&#xff0c;系统的可分配内存会随malloc越来越少&#xff0c;直到系统崩溃。还是来看看“栈内存”和“堆内存”的差别吧。
栈内存分配 —————
char *AllocStrFromStack()
{
char pstr[100];
return pstr;
}
堆内存分配 ————
char *AllocStrFromHeap(int len)
{
char *pstr;
if ( len <&#61; 0 ) return NULL;
return ( char* ) malloc( len );
}
对于第一个函数&#xff0c;那块pstr的内存在函数返回时就被系统释放了。于是所返回的char*什么也没有。而对于第二个函数&#xff0c;是从堆上分配内存&#xff0c;所以哪怕是程序退出时&#xff0c;也不释放&#xff0c;所以第二个函数的返回的内存没有问题&#xff0c;可以被使用。但一定要调用free释放&#xff0c;不然就是Memory Leak&#xff01;
在堆上分配内存很容易造成内存泄漏&#xff0c;这是C/C&#43;&#43;的最大的“克星”&#xff0c;如果你的程序要稳定&#xff0c;那么就不要出现Memory Leak。所以&#xff0c;我还是要在这里千叮咛万嘱付&#xff0c;在使用malloc系统函数&#xff08;包括calloc&#xff0c;realloc&#xff09;时千万要小心。
记得有一个UNIX上的服务应用程序&#xff0c;大约有几百的C文件编译而成&#xff0c;运行测试良好&#xff0c;等使用时&#xff0c;每隔三个月系统就是down一次&#xff0c;搞得许多人焦头烂额&#xff0c;查不出问题所在。只好&#xff0c;每隔两个月人工手动重启系统一次。出现这种问题就是Memery Leak在做怪了&#xff0c;在C/C&#43;&#43;中这种问题总是会发生&#xff0c;所以你一定要小心。一个Rational的检测工作——Purify&#xff0c;可以帮你测试你的程序有没有内存泄漏。
我保证&#xff0c;做过许多C/C&#43;&#43;的工程的程序员&#xff0c;都会对malloc或是new有些感冒。当你什么时候在使用malloc和new时&#xff0c;有一种轻度的紧张和惶恐的感觉时&#xff0c;你就具备了这方面的修养了。
对于malloc和free的操作有以下规则&#xff1a;
- 配对使用&#xff0c;有一个malloc&#xff0c;就应该有一个free。&#xff08;C&#43;&#43;中对应为new和delete&#xff09;
- 尽量在同一层上使用&#xff0c;不要像上面那种&#xff0c;malloc在函数中&#xff0c;而free在函数外。最好在同一调用层上使用这两个函数。
- malloc分配的内存一定要初始化。free后的指针一定要设置为NULL。
注&#xff1a;虽然现在的操作系统&#xff08;如&#xff1a;UNIX和Win2k/NT&#xff09;都有进程内存跟踪机制&#xff0c;也就是如果你有没有释放的内存&#xff0c;操作系统会帮你释放。但操作系统依然不会释放你程序中所有产生了Memory Leak的内存&#xff0c;所以&#xff0c;最好还是你自己来做这个工作。&#xff08;有的时候不知不觉就出现Memory Leak了&#xff0c;而且在几百万行的代码中找无异于海底捞针&#xff0c;Rational有一个工具叫Purify&#xff0c;可能很好的帮你检查程序中的Memory Leak&#xff09;
9、变量的初始化
接上一条&#xff0c;变量一定要被初始化再使用。C/C&#43;&#43;编译器在这个方面不会像JAVA一样帮你初始化&#xff0c;这一切都需要你自己来&#xff0c;如果你使用了没有初始化的变量&#xff0c;结果未知。好的程序员从来都会在使用变量前初始化变量的。如&#xff1a;
- 对malloc分配的内存进行memset清零操作。&#xff08;可以使用calloc分配一块全零的内存&#xff09;
- 对一些栈上分配的struct或数组进行初始化。&#xff08;最好也是清零&#xff09;
不过话又说回来了&#xff0c;初始化也会造成系统运行时间有一定的开销&#xff0c;所以&#xff0c;也不要对所有的变量做初始化&#xff0c;这个也没有意义。好的程序员知道哪些变量需要初始化&#xff0c;哪些则不需要。如&#xff1a;以下这种情况&#xff0c;则不需要。
char *pstr; /* 一个字符串 */
pstr &#61; ( char* ) malloc( 50 );
if ( pstr &#61;&#61; NULL ) exit(0);
strcpy( pstr, "Hello Wrold" );
但如果是下面一种情况&#xff0c;最好进行内存初始化。&#xff08;指针是一个危险的东西&#xff0c;一定要初始化&#xff09;
char **pstr; /* 一个字符串数组 */
pstr &#61; ( char** ) malloc( 50 );
if ( pstr &#61;&#61; NULL ) exit(0);
/* 让数组中的指针都指向NULL */
memset( pstr, 0, 50*sizeof(char*) );
而对于全局变量&#xff0c;和静态变量&#xff0c;一定要声明时就初始化。因为你不知道它第一次会在哪里被使用。所以使用前初始这些变量是比较不现实的&#xff0c;一定要在声明时就初始化它们。如&#xff1a;
Links *plnk &#61; NULL; /* 对于全局变量plnk初始化为NULL */
10、h和c文件的使用
H文件和C文件怎么用呢&#xff1f;一般来说&#xff0c;H文件中是declare&#xff08;声明&#xff09;&#xff0c;C文件中是define&#xff08;定义&#xff09;。因为C文件要编译成库文件&#xff08;Windows下是.obj/.lib&#xff0c;UNIX下是.o/.a&#xff09;&#xff0c;如果别人要使用你的函数&#xff0c;那么就要引用你的H文件&#xff0c;所以&#xff0c;H文件中一般是变量、宏定义、枚举、结构和函数接口的声明&#xff0c;就像一个接口说明文件一样。而C文件则是实现细节。
H文件和C文件最大的用处就是声明和实现分开。这个特性应该是公认的了&#xff0c;但我仍然看到有些人喜欢把函数写在H文件中&#xff0c;这种习惯很不好。&#xff08;如果是C&#43;&#43;话&#xff0c;对于其模板函数&#xff0c;在VC中只有把实现和声明都写在一个文件中&#xff0c;因为VC不支持export关键字&#xff09;。而且&#xff0c;如果在H文件中写上函数的实现&#xff0c;你还得在makefile中把头文件的依赖关系也加上去&#xff0c;这个就会让你的makefile很不规范。
最后&#xff0c;有一个最需要注意的地方就是&#xff1a;带初始化的全局变量不要放在H文件中&#xff01;
例如有一个处理错误信息的结构&#xff1a;
char* errmsg[] &#61; {
/* 0 */ "No error",
/* 1 */ "Open file error",
/* 2 */ "Failed in sending/receiving a message",
/* 3 */ "Bad arguments",
/* 4 */ "Memeroy is not enough",
/* 5 */ "Service is down; try later",
/* 6 */ "Unknow information",
/* 7 */ "A socket operation has failed",
/* 8 */ "Permission denied",
/* 9 */ "Bad configuration file format",
/* 10 */ "Communication time out",
......
......
};
请不要把这个东西放在头文件中&#xff0c;因为如果你的这个头文件被5个函数库&#xff08;.lib或是.a&#xff09;所用到&#xff0c;于是他就被链接在这5个.lib或.a中&#xff0c;而如果你的一个程序用到了这5个函数库中的函数&#xff0c;并且这些函数都用到了这个出错信息数组。那么这份信息将有5个副本存在于你的执行文件中。如果你的这个errmsg很大的话&#xff0c;而且你用到的函数库更多的话&#xff0c;你的执行文件也会变得很大。
正确的写法应该把它写到C文件中&#xff0c;然后在各个需要用到errmsg的C文件头上加上 extern char* errmsg[]; 的外部声明&#xff0c;让编译器在链接时才去管他&#xff0c;这样一来&#xff0c;就只会有一个errmsg存在于执行文件中&#xff0c;而且&#xff0c;这样做很利于封装。
我曾遇到过的最疯狂的事&#xff0c;就是在我的目标文件中&#xff0c;这个errmsg一共有112个副本&#xff0c;执行文件有8M左右。当我把errmsg放到C文件中&#xff0c;并为一千多个C文件加上了extern的声明后&#xff0c;所有的函数库文件尺寸都下降了20%左右&#xff0c;而我的执行文件只有5M了。一下子少了3M啊。
备注
有朋友对我说&#xff0c;这个只是一个特例&#xff0c;因为&#xff0c;如果errmsg在执行文件中存在多个副本时&#xff0c;可以加快程序运行速度&#xff0c;理由是errmsg的多个复本会让系统的内存换页降低&#xff0c;达到效率提升。像我们这里所说的errmsg只有一份&#xff0c;当某函数要用errmsg时&#xff0c;如果内存隔得比较远&#xff0c;会产生换页&#xff0c;反而效率不高。
这个说法不无道理&#xff0c;但是一般而言&#xff0c;对于一个比较大的系统&#xff0c;errmsg是比较大的&#xff0c;所以产生副本导致执行文件尺寸变大&#xff0c;不仅增加了系统装载时间&#xff0c;也会让一个程序在内存中占更多的页面。而对于errmsg这样数据&#xff0c;一般来说&#xff0c;在系统运行时不会经常用到&#xff0c;所以还是产生的内存换页也就不算频繁。权衡之下&#xff0c;还是只有一份errmsg的效率高。即便是像logmsg这样频繁使用的的数据&#xff0c;操作系统的内存调度算法会让这样的频繁使用的页面常驻于内存&#xff0c;所以也就不会出现内存换页问题了。