C_and_CPP 板


LINE

C 语言新手十诫(The Ten Commandments for Newbie C Programmers) by Khoguan Phuann 请注意: (1) 本篇旨在提醒新手,避免初学常犯的错误(其实老手也常犯:-Q)。 但不能取代完整的学习,请自己好好研读一两本 C 语言的好书, 并多多实作练习。 (2) 强烈建议新手先看过此文再发问,你的问题极可能此文已经提出并 解答了。 (3) 以下所举的错误例子如果在你的电脑上印出和正确例子相同的结果, 那只是不足为恃的一时侥幸。 (4) 不守十诫者,轻则执行结果的输出数据错误,或是程式当掉,重则 引爆核弹、毁灭地球(如果你的 C 程式是用来控制核弹发射器的话)。 一、你不可以使用尚未给予适当初值的变数。 错误例子: int accumulate(int max) /* 从 1 累加到 max,传回结果 */ { int sum; /* 未给予初值的区域变数,其内容值是垃圾 */ int num; for (num = 1; num <= max; num++) { sum += num; } return sum; } 正确例子: int accumulate(int max) { int sum = 0; /* 正确的赋予适当的初值 */ int num; for (num = 1; num <= max; num++) { sum += num; } return sum; } 二、你不可以存取超过阵列既定范围的空间。 错误例子: int str[5]; int i; for (i = 0; i <= 5; i++) str[i] = i; 正确例子: int str[5]; int i; for (i = 0; i < 5; i++) str[i] = i; 说明:宣告阵列时,所给的阵列元素个数值如果是 N, 那麽我们在後面 透过 [索引值] 存取其元素时,所能使用的索引值范围是从 0 到 N-1, 也就是 C 和 C++ 的阵列元素是从第 0 个开始算起,最後一个元素的 索引值是 N-1, 不是 N。 C/C++ 为了执行效率,并不会自动检查阵列索引值是否超过阵列边界, 我们要自己写程式来确保不会越界。一旦越界,将导致无法预期的後果。 三、你不可以提取(dereference)不知指向何方的指标(包含 null 指标)。 错误例子: char *pc1; /* 未给予初值,不知指向何方 */ char *pc2 = 0; /* pc2 起始化为 null pointer */ *pc1 = 'a'; /* 将 'a' 写到不知何方,错误 */ *pc2 = 'b'; /* 将 'b' 写到「位址0」,错误 */ 正确例子: char c; /* c 的内容尚未起始化 */ char *pc1 = &c; /* pc1 指向字元变数 c */ /* 动态分配 10 个 char(其值未定),并将第一个char的位址赋值给 pc2 */ char *pc2 = (char *)malloc(10); *pc1 = 'a'; /* c 的内容变为 'a' */ pc2[0] = 'b'; /* 动态配置来的第 0 个字元,内容变为 'b' /* 最後记得 free() 掉 malloc() 所分配的空间 */ free(pc2); 说明:指标变数必需先指向某个明确的东西(object),才能进行操作。 四、你不可以将字串常数赋值(assign)给 char* 变数,然後透过该变数 改写字串的内容(只能读不能写)。 错误例子: char* pc = "john"; *pc = 'J'; printf("Hello, %s\n", pc); 正确例子: char pc[] = "john"; *pc = 'J'; /* 或 pc[0] = 'J'; */ printf("Hello, %s\n", pc); 说明:字串常数的内容是唯读的。上面的错误例子,是将其内容所在的位址赋 值给字元指标 pc, 我们透过指标只可以去读该字串常数的内容,而不应该做 写入的动作。而正确例子,则是另外宣告一个独立的字元阵列,它的大小我们 未明文指定([]),编译器会自动将其设为刚好可以容纳後面的字串常数起始 值的大小,包括字串後面隐含的 '\0' 字元,并将字串常数的内容复制到字元 阵列中,因此可以自由的对该字元阵列的内容进行读和写。 错误例子(2): char *s1 = "Hello, "; char *s2 = "world!"; /* strcat() 不会另行配置空间,只会将资料附加到 s1 所指唯读字串的後面, 造成写入到程式无权碰触的记忆体空间 */ char *s3 = strcat(s1, s2); 正确例子(2): /* s1 宣告成阵列,并保留足够空间存放後续要附加的内容 */ char s1[20] = "Hello, "; char *s2 = "world!"; /* 因为 strcat() 的返回值等於第一个参数值,所以 s3 就不需要了 */ strcat(s1, s2); 五、你不可以对尚未分配所指空间的 char* 变数,进行(字串)阵列的相关操作。 其他型别的指标亦然。 错误例子: char *name; /* name 尚未指向有效的空间 */ printf("Your name, please: "); gets(name); printf("Hello, %s\n", name); 正确例子(1): /* 如果编译期就能决定字串的最大空间,那就不要宣告成 char* 改用 char[] */ char name[21]; /* 字串最长 20 个字元,另加一个 '\0' */ printf("Your name, please: "); gets(name); printf("Hello, %s\n", name); 正确例子(2): /* 若是在执行时期才能决定字串的最大空间,则需利用 malloc() 函式来动态 分配空间 */ size_t length; char *name; printf("请输入字串的最大长度(含null字元): "); scanf("%u", &length); name = (char *)malloc(length); printf("Your name, please: "); scanf("%s", name); printf("Hello, %s\n", name); /* 最後记得 free() 掉 malloc() 所分配的空间 */ free(name); 注意:上例用 gets() 或 scanf() 来读入字串,是不安全的。 因为这些函式 不会帮我们检查使用者所输入的字串长度是否超过我们所分配的 buffer 空间, 很可能会发生 buffer overflow。比较安全的做法是用 fgets() 来取代。如: char *p; char name[21]; printf("Your name, please: "); fgets(name, sizeof(name), stdin); /* fgets()会连行末的'\n'也读进字串中,所以要找出存入'\n'的位置,填入 '\0' if ((p = strchr(name, '\n')) != NULL) *p = '\0'; printf("Hello, %s\n", name); 六、你不可以在函式中回传一个指向区域性自动变数的指标。否则,会得到垃圾值。 [感谢 gocpp 网友提供程式例子] 错误例子: char *getstr(char *name) { char buf[30] = "hello, "; /*将字串常数"hello, "的内容复制到buf阵列*/ strcat(buf, name); return buf; } 说明:区域性自动变数,将会在离开该区域时(本例中就是从getstr函式返回时) 被消灭,因此呼叫端得到的指标所指的字串内容就失效了。【不过,倒是可以从 函式中直接传回字串常数,赋值给呼叫端的一个 const char * 变数,它既是唯 读的(参见第四诫),同时也具有恒常的储存期(static storage duration),其 内容将一直有效。】 正确例子: void getstr(char buf[], int buflen, char const *name) { char const s[] = "hello, "; assert(strlen(s) + strlen(name) < buflen); strcpy(buf, s); strcat(buf, name); } [针对字串操作,C++提供了更方便安全的 string class, 能用就尽量用] #include <string> using std::string; string getstr(string const &name) { return string("hello, ") += name; } 七、你不可以只做 malloc(), 而不做相应的 free(). 否则会造成记忆体漏失。 但若不是用 malloc() 所得到的记忆体,则不可以 free()。已经 free()了 所指记忆体的指标,在它指向另一块有效的动态分配得来的空间之前,不可 以再被 free(),也不可以提取(dereference)这个指标。 [C++] 你不可以只做 new, 而不做相应的 delete. 八、你不可以在数值运算、赋值或比较中随意混用不同型别的数值,而不谨慎考 虑数值型别转换可能带来的「意外惊喜」(错愕)。必须随时注意数值运算 的结果,其范围是否会超出变数的型别。 错误例子(1): unsigned int sum = 2000000000 + 2000000000; /* 20 亿 */ double f = 10 / 3; 正确例子(1): /* 全部都用 unsigned int, 注意数字後面的 u, 大写 U 也成 */ unsigned int sum = 2000000000u + 2000000000u; /* 或是用显式的转型 */ unsigned int sum = (unsigned int)2000000000 + 2000000000; double f = 10.0 / 3.0; 说明:在目前最普遍的32位元PC作业平台上,整数常数2000000000的型别为 signed int(简写为 int),相加後,其结果仍为 int, 但是 signed int 放不下 4000000000, 造成算术溢位(arithmetic overflow),很可能无法 将正确的值指派给 unsigned int sum,纵使 unsigned int 放得下4000000000 的数值。注意:写成 unsigned int sum = (unsigned int)(2000000000 + 2000000000); 也是不对的。 例子(2):(感谢 sekya 网友提供) unsigned char a = 0x80; char b = 0x80; /* implementation-defined result */ if( a == 0x80 ) { /* 恒真 */ printf( "a ok\n" ); if( b == 0x80 ) { /* 不一定恒真 */ printf( "b ok\n" ); } 说明:在将 char 型别定义为范围从 -128 至 +127 的系统上,int 0x80 (其值等於 +128)要转成 char 会放不下,会产生编译器自行定义的值。 这样的程式就不具可移植性了。 九、你不可以在一个运算式(expression)中,对一个基本型态的变数修改其值 超过一次以上。否则,将导致未定义的行为(undefined behavior)。 错误例子: int i = 7; int j = ++i + i++; 正确例子: int i = 7; int j = ++i; j += i++; 你也不可以在一个运算式(expression)中,对一个基本型态的变数修改其值, 而且还在同一个式子的其他地方为了其他目的而存取该变数的值。(其他目的, 是指不是为了计算这个变数的新值的目的)。否则,将导致未定义的行为。 错误例子: int arr[5]; int i = 0; arr[i] = i++; 正确例子: int arr[5]; int i = 0; arr[i] = i; i++; [C++程式] 错误例子: int i = 10; cout << i << "==" << i++; 正确例子: int i = 10; cout << i << "=="; cout << i++; 十、你不可以在macro的定义中,不为它的参数个别加上括号。 错误例子: #include <stdio.h> #define SQUARE(x) (x * x) int main() { printf("%d\n", SQUARE(10-5)); return 0; } 正确例子: #include <stdio.h> #define SQUARE(x) ((x) * (x)) int main() { printf("%d\n", SQUARE(10-5)); return 0; } 说明:如果是用 C++, 请多多利用 inline function 来取代上述的 macro, 以免除 macro 定义的种种危险性。如: inline int square(int x) { return x * x; } macro 定义出的「伪函式」至少缺乏下列数项函式本有的能力: (1) 无法进行参数型别的检查。 (2) 无法递回呼叫。 (3) 无法用 & 加在 macro name 之前,取得函式位址。 (4) 呼叫时往往不能使用具有 side effect 的引数。例如: 错误例子:(感谢 yaca 网友提供) #define MACRO(x) (((x) * (x)) - ((x) * (x))) int main() { int x = 3; printf("%d\n", MACRO(++x)); return 0; } MACRO(++x) 展开来後变成 (((++x) * (++x)) - ((++x) * (++x))) 违反了第九诫。在 gcc 4.3.3 下的结果是 -24, 在 vc++ 下是 0. 後记:从「古时候」流传下来一篇文章 "The Ten Commandments for C Programmers"(Annotated Edition) by Henry Spencer http://www.lysator.liu.se/c/ten-commandments.html 一方面它不是针对 C 的初学者,一方面它特意模仿中古英文 圣经的用语,写得文诌诌。所以我现在另外写了这篇,希望 能涵盖最重要的观念以及初学甚至老手最易犯的错误。 作者:潘科元(Khoguan Phuann) (c)2005. 感谢 ptt.cc BBS 的 C_and_CPP 看板众多网友提供宝贵意见及程式实例。 --



※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.130.208.167 ※ 编辑: khoguan 来自: 220.130.208.168 (11/03 01:24)
1F:推 yichengliu:学很多,非常谢谢 02/05 02:04
2F:推 gn00695737:阅~好文! 08/04 20:28
3F:推 aszs:推~~推~~ 08/14 20:12
4F:推 NTUSTKnight:好家伙 10/03 18:33
5F:推 ainigi:这篇超赞的 10/14 12:22
6F:推 locationc:借转 11/18 17:03
7F:推 funfox:这篇超赞.... 借转 ^^ 11/18 22:36
8F:推 withrain:推推推… 12/23 05:37
9F:推 lutaki2002:好的没话说... 12/27 16:49
10F:→ masterenfant:推推推,借转 01/17 22:04
11F:推 fancybubble:借转 02/09 12:07
12F:推 becomesonumb:好教学 03/16 15:07
13F:推 seedamen:good job :) 06/14 01:12
14F:推 s213895:借转 06/26 17:40
15F:推 aneyoko:推推 o.o 07/01 10:07
16F:推 rogerliu0916:这一篇真的是超赞的!! 07/27 13:06
17F:推 mcsrO:推 10/01 12:16
18F:推 lingerkptor:借转 10/04 15:10
19F:推 et282523:推啊! 10/09 16:30
20F:推 a032195:写得好啊 对新手来说很有用 借转一下 10/28 23:06
21F:推 ckcraig:好! 10/29 22:14
22F:推 jim221:借转 11/19 22:21
23F:推 Jyie:我是C++新鲜人,昨天觉得无聊读读看!这十诫我看的不是很懂! 11/25 19:20
24F:→ Jyie:是不是我没有天份阿= = 但真很想学,就请各位老鸟多多照顾! 11/25 19:22
25F:推 chyihuann:多写一点程式,就会遇到较多的问题 就有感觉了 12/01 13:40
26F:推 TTGSP:GOOD!!! 01/10 18:03
27F:推 redgoing:GOOD... 01/25 10:48
28F:推 saicouter:推推,借转 02/19 10:37
29F:推 lingin1204:推~ 04/10 00:58
30F:推 pd0320:借转,谢谢~ 04/10 15:33
31F:推 NNyozi:推~借转~ 感谢~ 140.114.27.122 04/15 08:57
32F:推 snoop750205:好文!!! 140.124.42.58 04/24 22:04
33F:推 franckstone:又学到一课 05/01 01:11
34F:推 chrisdar:using namespace std; 06/14 19:48
35F:→ netsphere:如果是 if (n==1) return 5; 1F胜 XD 07/02 22:50
36F:推 lctwolf:借转!!谢谢!! 08/24 20:47
37F:推 cch0927:借转 11/12 03:37
cch0927:转录至某隐形看板 11/12 03:38
38F:推 ricky155030:感谢 11/17 19:59
39F:推 cygnus122:推! 好文 12/20 00:57
40F:推 puph:原来是这个原因 感谢你 明天马上来试试看!! 03/05 21:20
41F:推 sandr1983:这真的写的太好啦.很多时候写程式都会忽略@@ 感谢在三 04/15 15:04
42F:推 GUAlexander:感谢 的确是好文 04/25 22:41
43F:推 sunkill:不推不行 05/17 09:39
44F:推 kw9812:很好的建议。谢谢,好文。 06/25 18:39
45F:推 xatier:借转 07/28 12:43
46F:推 PhySeraph:顶高高 07/29 14:06
47F:→ pico2k:参考以下的文章 http://0rz.tw/vxEDw 07/31 09:06
48F:推 VictorTom:唉~~问完问题不用砍文吧??以後其他人也许可以参考啊Orz 10/16 13:55
49F:推 grc0621:写得很好, 一些常犯错误都注意了 10/18 17:53
50F:→ POSIX:........................................................ 10/27 21:40
51F:→ POSIX:推 @@" 上面推错 10/27 21:41
52F:推 u9211025:真的学很多!推一下罗~ 11/16 09:56
53F:推 noahleft:借转..推 11/24 01:31
54F:推 erase2004:借转...必推 12/08 22:50
55F:推 rabird:好文 12/23 16:46
56F:推 danielsig727:借转~ 01/09 11:10
57F:推 flash5408:借转~~ 01/29 20:32
58F:推 a60301:借印当教学讲义,谢谢。 02/09 18:59
59F:推 prettyfaces:就算不是初学一不小心还是会犯 02/18 13:41
60F:推 kilink:里面都是我的错误,超推! 03/05 15:41







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:BabyMother站内搜寻

TOP