[重要] 发文前务必阅读:常见问题十三诫

楼主: wtchen (没有存在感的人)   2016-04-18 00:15:48
C 语言新手十三诫(The Thirteen Commandments for Newbie C Programmers)
by Khoguan Phuann
请注意:
(1) 本篇旨在提醒新手,避免初学常犯的错误(其实老手也常犯:-Q)。
但不能取代完整的学习,请自己好好研读一两本 C 语言的好书,
并多多实作练习。
(2) 强烈建议新手先看过此文再发问,你的问题极可能此文已经提出并解答了。
(3) 以下所举的错误例子如果在你的电脑上印出和正确例子相同的结果,
那只是不足为恃的一时侥幸。
(4) 不守十三诫者,轻则执行结果的输出数据错误,或是程式当掉,重则
引爆核弹、毁灭地球(如果你的 C 程式是用来控制核弹发射器的话)。
============================================================================
目录: 2
01. 不可以使用尚未给予适当初值的变量 3
02. 不能存取超过阵列既定范围的空间 4
03. 不可以提取不知指向何方的指标 5
04. 不要试图用 char* 去更改一个"字串常数" 8
05. 不能在函式中回传一个指向区域性自动变量的指标 10
06. 不可以只做 malloc(), 而不做相应的 free() 13
07. 在数值运算、赋值或比较中不可以随意混用不同型别的数值 14
08. 在一个运算式中,不能对一个基本型态的变量修改其值超过一次以上 16
09. 在 Macro 定义中, 务必为它的参数个别加上括号 19
10. 不可以在 stack 设置过大的变量 21
11. 使用浮点数精确度造成的误差问题 22
12. 不要猜想二维阵列可以用 pointer to pointer 来传递 23
13. 函式内 new 出来的空间记得要让主程式的指标接住 27
直接输入数字可跳至该页码
01. 你不可以使用尚未给予适当初值的变量
错误例子:
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;
}
02. 你不可以存取超过阵列既定范围的空间
错误例子:
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++ 为了执行效率,并不会自动检查阵列索引值是否超过阵列边界,
我们要自己来确保不会越界。一旦越界,操作的不再是合法的空间,
将导致无法预期的后果。
03. 你不可以提取(dereference)不知指向何方的指标(包含 null 指标)。
错误例子:
char *pc1; /* 未给予初值,不知指向何方 */
char *pc2 = NULL; /* pc2 起始化为 null pointer */
*pc1 = 'a'; /* 将 'a' 写到不知何方,错误 */
*pc2 = 'b'; /* 将 'b' 写到“位址0”,错误 */
正确例子:
char c; /* c 的内容尚未起始化 */
char *pc1 = &c; /* pc1 指向字符变量 c */
*pc1 = 'a'; /* c 的内容变为 'a' */
/* 动态分配 10 个 char(其值未定),并将第一个char的位址赋值给 pc2 */
char *pc2 = (char *) malloc(10);
pc2[0] = 'b'; /* 动态配置来的第 0 个字符,内容变为 'b'
free(pc2);
说明:指标变量必需先指向某个可以合法操作的空间,才能进行操作。
( 使用者记得要检查 malloc 回传是否为 NULL,
碍于篇幅本文假定使用上皆合法,也有正确归还内存 )
错误例子:
char *name; /* name 尚未指向有效的空间 */
printf("Your name, please: ");
fgets(name,20,stdin); /* 您确定要写入的那块空间合法吗??? */
printf("Hello, %s\n", name);
正确例子:
/* 如果编译期就能决定字串的最大空间,那就不要宣告成 char* 改用 char[] */
char name[21]; /* 可读入字串最长 20 个字符,保留一格空间放 '\0' */
printf("Your name, please: ");
fgets(name,20,stdin);
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);
name = NULL;
04. 你不可以试图用 char* 去更改一个"字串常数"
错误例子:
char* pc = "john"; /* pc 现在指著一个字串常数 */
*pc = 'J'; /* 但是 pc 没有权利去更改这个常数! */
正确例子:
char pc[] = "john"; /* pc 现在是个合法的阵列,里面住着字串 john */
/* 也就是 pc[0]='j', pc[1]='o', pc[2]='h',
pc[3]='n', pc[4]='\0' */
*pc = 'J';
pc[2] = 'H';
说明:字串常数的内容是"唯读"的。您有使用权,但是没有更改的权利。
若您希望使用可以更改的字串,那您应该将其放在合法空间
错误例子:
char *s1 = "Hello, ";
char *s2 = "world!";
/* strcat() 不会另行配置空间,只会将资料附加到 s1 所指唯读字串的后面,
造成写入到程式无权碰触的内存空间 */
strcat(s1, s2);
正确例子(2):
/* s1 宣告成阵列,并保留足够空间存放后续要附加的内容 */
char s1[20] = "Hello, ";
char *s2 = "world!";
/* 因为 strcat() 的返回值等于第一个参数值,所以 s3 就不需要了 */
strcat(s1, s2);
05. 你不可以在函式中回传一个指向区域性自动变量的指标。否则,会得到垃圾值
[感谢 gocpp 网友提供程式例子]
错误例子:
char *getstr(char *name)
{
char buf[30] = "hello, "; /*将字串常数"hello, "的内容复制到buf阵列*/
strcat(buf, name);
return buf;
}
说明:区域性自动变量,将会在离开该区域时(本例中就是从getstr函式返回时)
被消灭,因此呼叫端得到的指标所指的字串内容就失效了。
正确例子:
void getstr(char buf[], int buflen, char const *name)
{
char const s[] = "hello, ";
strcpy(buf, s);
strcat(buf, name);
}
正确例子:
int* foo()
{
int* pInteger = (int*) malloc( 10*sizeof(int) );
return pInteger;
}
int main()
{
int* pFromfoo = foo();
}
说明:上例虽然回传了函式中的指标,但由于指标内容所指的位址并非区域变量,
而是用动态的方式抓取而得,换句话说这块空间是长在 heap 而非 stack,
又因 heap 空间并不会自动回收,因此这块空间在离开函式后,依然有效
(但是这个例子可能会因为 programmer 的疏忽,忘记 free 而造成
memory leak)
[针对字串操作,C++提供了更方便安全更直观的 string class, 能用就尽量用]
正确例子:
#include <string> /* 并非 #include <cstring> */
using std::string;
string getstr(string const &name)
{
return string("hello, ") += name;
}
06. 你不可以只做 malloc(), 而不做相应的 free(). 否则会造成内存漏失
但若不是用 malloc() 所得到的内存,则不可以 free()。已经 free()了
所指内存的指标,在它指向另一块有效的动态分配得来的空间之前,不可
以再被 free(),也不可以提取(dereference)这个指标。
[C++] 你不可以只做 new, 而不做相应的 delete
注:new 与 delete 对应,new[] 与 delete[] 对应,不可混用
切记,做了几次 new,就必须做几次 delete
小技巧: 可在 delete 之后将指标指到 NULL,由于 delete 本身会先做检查,
因此可以避免掉多次 delete 的错误
正确例子:
int *ptr = new int(99);
delete ptr;
ptr = NULL;
delete ptr; /* delete 只会处理指向非 NULL 的指标 */
07. 你不可以在数值运算、赋值或比较中随意混用不同型别的数值,而不谨慎考
虑数值型别转换可能带来的“意外惊喜”(错愕)。必须随时注意数值运算
的结果,其范围是否会超出变量的型别
错误例子:
unsigned int sum = 2000000000 + 2000000000; /* 超出 int 存放范围 */
unsigned int sum = (unsigned int) (2000000000 + 2000000000);
double f = 10 / 3;
正确例子:
/* 全部都用 unsigned int, 注意数字后面的 u, 大写 U 也成 */
unsigned int sum = 2000000000u + 2000000000u;
/* 或是用显式的转型 */
unsigned int sum = (unsigned int) 2000000000 + 2000000000;
double f = 10.0 / 3.0;
错误例子:
unsigned int a = 0;
int b[10];
for(int i = 9 ; i >= a ; i
作者: manoeuvre   2015-01-01 14:49:00
可以不要用动画吗?
作者: nowar100 (抛砖引玉)   2015-01-10 20:46:00
看来我以前的动画排版造成不少人的困扰 真抱歉 QQ
作者: kevin770111 (牛牛)   2015-03-03 22:35:00
动画好难用 By 初学者... = =a
作者: adrianshum (Alien)   2015-03-07 11:24:00
选择画面的排版很糟糕, 请全部靠左吧...
作者: nowar100 (抛砖引玉)   2015-03-19 08:35:00
全部靠左的话,就会有空白换行不均的排版问题了 :(
作者: apiod ( )   2015-04-02 22:33:00
可以不要用动画吗 =.=
作者: yonderknight (Joe_Black)   2015-04-10 02:33:00
推动画,虽然主选单有点乱
作者: BlazarArc (Midnight Sun)   2015-04-15 15:55:00
目录那边要多空行翻页才会对
作者: CP64 (( ̄▽ ̄#)﹏﹏)   2015-04-16 01:19:00
拿掉动画之后我觉得可以改成在后面标页数 ' -')毕竟 PTT 有支援在文章页面输入数字跳至第几页 ' -')
楼主: wtchen (没有存在感的人)   2015-04-16 01:46:00
....板工不会用,有强者可以代劳吗?
作者: poopop   2016-05-01 22:33:00
推,太有用了!
作者: NitroRider (Firedupandreadytoserve )   2016-05-09 16:06:00
刚开始写程式就连犯5诫以上 根本悲剧
作者: afes8863er (iNoyoka)   2016-06-11 13:41:00
好一个当头棒喝=一个完美的开始
作者: wawi2 (@@)   2016-06-22 00:14:00
第13点的最后一个例子是不是错了? int (&array)[10]??
作者: uefang (鸡~鸡~鸡~鸡~)   2016-06-26 15:09:00
可以不要用动话吗?
作者: loveme00835 (发箍)   2016-09-22 11:13:00
推一个唷~ 变动画了XDD
作者: xatier (一切重来就好了...)   2016-09-22 11:32:00
昨天晚上就看到变成动画了 只是没人推就不敢推下去XD
作者: VictorTom (鬼翼&娃娃鱼)   2016-09-22 14:16:00
推....:)
作者: hilorrk (Cary)   2016-09-24 20:21:00
改版了XD
作者: cosmosQQ ( )   2016-09-26 11:00:00
第8戒 int i = 7; int j = ++i + i++; 蛮多公司有这考题printf("i=%d, j=%d \n", i, j); => i=9, j=16一般推论答案无误, 但就与第8戒违反了@@ 该如何解....
作者: nowar100 (抛砖引玉)   2016-09-26 13:06:00
问题不存在 不应该出现的就是不应该 就算大家约定成俗讨论它也是没什么意义的
作者: snoopy0907   2016-09-28 20:33:00
推~真的很常犯...话说其实我比较希望有下载版XD
作者: storm654321 (P助)   2016-10-03 08:54:00
十诫~~~ 引以为戒
作者: coldstars   2016-10-10 19:47:00
有时间不是应该要专精自己的领域吗?
作者: holishing   2016-11-10 18:52:00
什么时候变13诫了!?
作者: df405102 (恩)   2016-11-16 22:22:00
有些看不太懂....
作者: nowar100 (抛砖引玉)   2016-11-16 22:25:00
楼上哪里看不懂 可以发文问 版上很热心的 :)
作者: linesa (八倍速自耕农)   2016-12-10 18:46:00
大推!! 神文!!
作者: antelope01 (羚羊)   2016-12-21 08:01:00
受益匪浅
作者: lucky1lk (赌到没钱的人)   2016-04-19 07:44:00
有版主 有推倒
作者: yvb   2016-04-21 22:44:00
我每页不是24行时, 页数就会错误... 可否加上行数? (###.)
楼主: wtchen (没有存在感的人)   2016-04-21 22:51:00
可能要等下次改版(加延伸阅读部份),不用急不会太久
作者: mythnc (迷小心)   2016-04-29 12:59:00
终于把动画改掉了~可喜可贺
作者: red0210 (My Name Is Red)   2016-04-30 13:28:00
页数错误是什么情况?可以说得更清楚一点吗
作者: yvb   2016-05-07 01:13:00
比方我目前视窗最大化, Rows 42 Columns 168;在这篇文章的第一页, 下方的浏览状态即显示:浏览 第 1/19 页 ( 8%) 目前显示: 第 01~40 行按页数 12 就已跳到 第10诫 了.
作者: red0210 (My Name Is Red)   2016-05-10 19:21:00
原来如此,不过为何会每页不是24行…
作者: yvb   2016-05-10 20:33:00
PuTTY Configuration => Window => When window is resized:Change the number of rows and columns用习惯很多列以后, 就回不去了 :P

Links booklink

Contact Us: admin [ a t ] ucptt.com