[分享] ptt C 語言新手十誡 - 一個小小工程師的心情抒發天地
文章推薦指數: 80 %
C 語言新手十誡(The Ten Commandments for Newbie C Programmers) 本篇旨在提醒新手, ... 不能在函式中回傳一個指向區域性自動變數的指標; 6.
關閉廣告
一個小小工程師的心情抒發天地
跳到主文
Writethecode,changetheworld
部落格全站分類:心情日記
相簿
部落格
留言
名片
Dec07Thu201714:53
[分享]pttC語言新手十誡
C語言新手十誡(TheTenCommandmentsforNewbieCProgrammers)
本篇旨在提醒新手,避免初學常犯的錯誤(其實老手也常犯:-Q)。
但不能取代完整的學習,請自己好好研讀一兩本C語言的好書,並多多實作練習。
強烈建議新手先看過此文再發問,你的問題極可能此文已經提出並解答了。
以下所舉的錯誤例子如果在你的電腦上印出和正確例子相同的結果,那只是不足為恃的一時僥倖。
不守十誡者,輕則執行結果的輸出數據錯誤,或是程式當掉,重則引爆核彈、毀滅地球(如果你的C程式是用來控制核彈發射器的話)。
1.不可以使用尚未給予適當初值的變數
2.不能存取超過陣列既定範圍的空間
3.不可以提取不知指向何方的指標
4.不要試圖用char*去更改一個"字串常數"
5.不能在函式中回傳一個指向區域性自動變數的指標
6.不可以只做malloc(),而不做相應的free()
7.在數值運算、賦值或比較中不可以隨意混用不同型別的數值
8.在一個運算式中,不能對一個基本型態的變數修改其值超過一次以上
9.在Macro定義中,務必為它的參數個別加上括號
A(10).不可以在stack設置過大的變數
B(11).使用浮點數精確度造成的誤差問題
C(12).不要猜想二維陣列可以用pointertopointer來傳遞
D(13).函式內new出來的空間記得要讓主程式的指標接住
01.你不可以使用尚未給予適當初值的變數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
錯誤例子:
intaccumulate(intmax)/*從1累加到max,傳回結果*/
{
intsum;/*未給予初值的區域變數,其內容值是垃圾*/
intnum;
for(num=1;num<=max;num++){sum+=num;}
returnsum;
}
正確例子:
intaccumulate(intmax)
{
intsum=0;/*正確的賦予適當的初值*/
intnum;
for(num=1;num<=max;num++){sum+=num;}
returnsum;
}
02.你不可以存取超過陣列既定範圍的空間
1
2
3
4
5
6
7
8
9
錯誤例子:
intstr[5];
inti;
for(i=0;i<=5;i++)str[i]=i;
正確例子:
intstr[5];
inti;
for(i=0;i<5;i++)str[i]=i;
說明:宣告陣列時,所給的陣列元素個數值如果是N,那麼我們在後面
透過[索引值]存取其元素時,所能使用的索引值範圍是從0到N-1
C/C++為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界,我們要自己來確保不會越界。
一旦越界,操作的不再是合法的空間,將導致無法預期的後果。
03.你不可以提取(dereference)不知指向何方的指標(包含null指標)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
錯誤例子:
char*pc1;/*未給予初值,不知指向何方*/
char*pc2=0;/*pc2起始化為nullpointer*/
*pc1='a';/*將'a'寫到不知何方,錯誤*/
*pc2='b';/*將'b'寫到「位址0」,錯誤*/
正確例子:
charc;/*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,礙於篇幅本文假定使用上皆合法,也有正確歸還記憶體)
1
2
3
4
5
6
7
8
9
10
11
12
13
錯誤例子:
char*name;/*name尚未指向有效的空間*/
printf("Yourname,please:");
gets(name);/*您確定要寫入的那塊空間合法嗎???*/
printf("Hello,%s\n",name);
正確例子:
/*如果編譯期就能決定字串的最大空間,那就不要宣告成char*改用char[]*/
charname[21];/*可讀入字串最長20個字元,保留一格空間放'\0'*/
printf("Yourname,please:");
gets(name);
printf("Hello,%s\n",name);
正確例子(2):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*若是在執行時期才能決定字串的最大空間,則需利用malloc()函式來動態分配空間*/
size_tlength;
char*name;
printf("請輸入字串的最大長度(含null字元):");
scanf("%u",&length);
name=(char*)malloc(length);
printf("Yourname,please:");
scanf("%s",name);
printf("Hello,%s\n",name);
/*最後記得free()掉malloc()所分配的空間*/
free(name);
04.你不可以試圖用char*去更改一個"字串常數"
1
2
3
4
5
6
7
8
9
10
11
12
錯誤例子:
char*pc="john";/*pc現在指著一個字串常數*/
*pc='J';/*但是pc沒有權利去更改這個常數!*/
正確例子:
charpc[]="john";/*pc現在是個合法的陣列,裡面住著字串john*/
/*也就是pc[0]='j',pc[1]='o',pc[2]='h',pc[3]='n',pc[4]='\0'*/
*pc='J';
pc[2]='H';
說明:字串常數的內容是"唯讀"的。
您有使用權,但是沒有更改的權利。
若您希望使用可以更改的字串,那您應該將其放在合法空間
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
錯誤例子:
char*s1="Hello,";
char*s2="world!";
/*strcat()不會另行配置空間,只會將資料附加到s1所指唯讀字串的後面,造成寫入到程式無權碰觸的記憶體空間*/
strcat(s1,s2);
正確例子(2):
/*s1宣告成陣列,並保留足夠空間存放後續要附加的內容*/
chars1[20]="Hello,";
char*s2="world!";
/*因為strcat()的返回值等於第一個參數值,所以s3就不需要了*/
strcat(s1,s2);
05.你不可以在函式中回傳一個指向區域性自動變數的指標。
否則,會得到垃圾值
[感謝gocpp網友提供程式例子]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
錯誤例子:
char*getstr(char*name)
{
charbuf[30]="hello,";/*將字串常數"hello,"的內容複製到buf陣列*/
strcat(buf,name);
returnbuf;
}
說明:區域性自動變數,將會在離開該區域時(本例中就是從getstr函式返回時)
被消滅,因此呼叫端得到的指標所指的字串內容就失效了。
正確例子:
voidgetstr(charbuf[],intbuflen,charconst*name)
{
charconsts[]="hello,";
strcpy(buf,s);
strcat(buf,name);
}
1
2
3
4
5
6
7
8
9
10
11
正確例子:
int*foo()
{
int*pInteger=(int*)malloc(10*sizeof(int));
returnpInteger;
}
intmain()
{
int*pFromfoo=foo();
}
說明:上例雖然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數,
而是用動態的方式抓取而得,換句話說這塊空間是長在heap而非stack,
又因 heap空間並不會自動回收,因此這塊空間在離開函式後,依然有效
(但是這個例子可能會因為programmer的疏忽,忘記free而造成memoryleak)
[針對字串操作,C++提供了更方便安全更直觀的stringclass,能用就盡量用]
1
2
3
4
5
6
7
8
9
正確例子:
#include
已經free()了所指記憶體的指標,在它指向另一塊有效的動態分配得來的空間之前,不可以再被free(),也不可以提取(dereference)這個指標。
[C++]你不可以只做new,而不做相應的delete
注:new與delete對應,new[]與delete[]對應,不可混用
(切記,做了幾次new,就必須做幾次delete)
小技巧:可在delete之後將指標指到0,由於delete本身會先做檢查,因此可以避免掉多次delete的錯誤
1
2
3
4
5
正確例子:
int*ptr=newint(99);
deleteptr;
ptr=NULL;
deleteptr;/*delete只會處理指向非NULL的指標*/
07.你不可以在數值運算、賦值或比較中隨意混用不同型別的數值,而不謹慎考
慮數值型別轉換可能帶來的「意外驚喜」(錯愕)。
必須隨時注意數值運算的結果,其範圍是否會超出變數的型別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
錯誤例子:
unsignedintsum=2000000000+2000000000;/*超出int存放範圍*/
unsignedintsum=(unsignedint)(2000000000+2000000000);
doublef=10/3;
正確例子:
/*全部都用unsignedint,注意數字後面的u,大寫U也成*/
unsignedintsum=2000000000u+2000000000u;
/*或是用顯式的轉型*/
unsignedintsum=(unsignedint)2000000000+2000000000;
doublef=10.0/3.0;
1
2
3
4
錯誤例子:
unsignedinta=0;
intb[10];
for(inti=9;i>=a;i--){b[i]=0;}
說明:由於int與unsigned共同運算的時候,會提升int為unsigned,因此迴圈條件永遠滿足,與預期行為不符
1
2
3
4
5
6
錯誤例子:(感謝sekya網友提供)
unsignedchara=0x80;/*noproblem*/
charb=0x80;/*implementation-definedresult*/
if(b==0x80){/*不一定恒真*/
printf("bok\n");
}
說明:語言並未規定char天生為unsigned或signed,因此將0x80放入char型態的變數,將會視各家編譯器不同作法而有不同結果
08.你不可以在一個運算式(expression)中,對一個基本型態的變數修改其值超過一次以上。
否則,將導致未定義的行為(undefinedbehavior)
1
2
3
4
5
6
7
8
錯誤例子:
inti=7;
intj=++i+i++;
正確例子:
inti=7;
intj=++i;
j+=i++;
你也不可以在一個運算式(expression)中,對一個基本型態的變數修改其值,而且還在同一個式子的其他地方為了其他目的而存取該變數的值。
(其他目的,是指不是為了計算這個變數的新值的目的)。
否則,將導致未定義的行為。
1
2
錯誤例子:
x=x++;
1
2
3
4
5
6
7
8
9
10
錯誤例子:
intarr[5];
inti=0;
arr[i]=i++;
正確例子:
intarr[5];
inti=0;
arr[i]=i;
i++;
1
2
3
4
5
6
7
8
錯誤例子:
inti=10;
cout<
#defineSQUARE(x)(x*x)
intmain()
{
printf("%d\n",SQUARE(10-5));
return0;
}
正確例子:
#include
如:
inlineintsquare(intx){returnx*x;}
macro定義出的「偽函式」至少缺乏下列數項函式本有的能力:
(1)無法進行參數型別的檢查。
(2)無法遞迴呼叫。
(3)無法用&加在macroname之前,取得函式位址。
(4)呼叫時往往不能使用具有sideeffect的引數。
例如:
1
2
3
4
5
6
7
8
錯誤例子:(感謝yaca網友提供)
#defineMACRO(x)(((x)*(x))-((x)*(x)))
intmain()
{
intx=3;
printf("%d\n",MACRO(++x));
return0;
}
備註:副作用(sideeffect)
表达式有两种功能:每个表达式都产生一个值(value),同时可能包含副作用(sideeffect)。
副作用是指改变了某些变量的值。
1:20//这个表达式的值是20;它没有副作用,因为它没有改变任何变量的值。
2:x=5//这个表达式的值是5;它有一个副作用,因为它改变了变量x的值。
3:x=y++//这个表达示有两个副作用,因为改变了两个变量的值。
4:x=x++//这个表达式也有两个副作用,因为变量x的值发生了两次改变。
10.不可在stack設置過大的變數,否則會造成stackoverflow
(感謝VictorTom版友幫忙)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
錯誤例子:
intarray[10000000];//僅舉例說明
/*說明:由於編譯器會自行決定stack的上限,某些預設是數KB或數十KB
當變數所需的空間過大時,很容易造成stackoverflow,程式亦隨之
當掉,若真正需要如此大的空間,那麼建議配置在heap上,或是採用
static/globlavariable,亦或是改變編譯器的設定
使用heap時,雖然整個process可用的空間是有限的,但採用動態抓取
的方式,new無法配置時會丟出std::bad_alloc例外,malloc無法配置
時會回傳null,不會影響到正常使用下的程式功能*/
正確例子:
int*array=(int*)malloc(10000000*sizeof(int));
說明:由於此時stack上只需配置一個int*的空間,可避免stackoverflow
更多說明請參考精華區z-10-13
11.使用浮點數千萬要注意精確度所造成的誤差問題
根據IEEE754的規範,又電腦中是用有限的二進位儲存數字,因此常有可
能因為精確度而造成誤差,例如加減乘除,等號大小判斷,分配律等數學上
常用到的操作,很有可能因此而出錯(不成立)
更詳細的說明可以參考精華區z-8-11
或參考冼鏡光老師所發表的一文"使用浮點數最最基本的觀念"
http://blog.dcview.com/article.php?a=VmgBZFE5AzI%3D
12.不要猜想二維陣列可以用pointertopointer來傳遞
(感謝loveme00835legnaleurc版友的幫忙)
首先必須有個觀念,C語言中陣列是無法直接拿來傳遞的!
不過這時候會有人跳出來反駁:
1
2
3
4
voidpass1DArray(intarray[]);
inta[10];
pass1DArray(a);/*可以合法編譯,而且執行結果正確!!*/
事實上,編譯器會這麼看待
1
2
3
4
voidpass1DArray(int*array);
inta[10];
pass1DArray(&a[0]);
我們可以順便看出來,array變數本身可以decay成記憶體起頭的位置
因此我們可以int*p=a;這種方式,拿指標去接陣列。
也因為上述的例子,許多人以為那二維陣列是不是也可以改成int**
1
2
3
4
5
6
7
錯誤例子:
voidpass2DArray(int**array);
inta[5][10];
pass2DArray(a);
/*這時候編譯器就會報錯啦*/
/*expected‘int**’butargumentisoftype‘int(*)[10]’*/
在一維陣列中,指標的移動操作,會剛好覆蓋到陣列的範圍,例如,宣告了一個a[10],那我可以把a當成指標來操作*a至*(a+9)
因此我們可以得到一個概念,在操作的時候,可以decay成指標來使用,也就是我可以把一個陣列當成一個指標來使用(again,陣列!=指標)
但是多維陣列中,無法如此使用,事實上這也很直觀,試圖拿一個pointertopointertoint來操作一個int二維陣列,這是不合理的!
儘管我們無法將二維陣列直接decay成兩個指標,但是我們可以換個角度想,二維陣列可以看成"外層大的一維陣列,每一維內層各又包含著一維陣列"
如果想通了這一點,我們可以仿造之前的規則,把外層大的一維陣列decay成指標,該指標指向內層的一維陣列
1
2
3
4
voidpass2DArray(int(*array)[10]);//array是個指標,指向int[10]
inta[5][10];
pass2DArray(a);
這時候就很好理解了,函數pass2DArray內的array[0]會代表什麼呢?答案是它代表著a[0]外層的那一維陣列,裡面包含著內層[0]~[9],也因此array[0][2]就會對應到a[0][2],array[4][9]對應到a[4][9]
結論就是,只有最外層的那一維陣列可以decay成指標,其他維陣列都要明確的指出陣列大小,這樣多維陣列的傳遞就不會有問題了
也因為剛剛的例子,我們可以清楚的知道在傳遞陣列時,實際行為是在傳遞指標,也因此如果我們想用sizeof來求得陣列元素個數,那是不可行的
1
2
3
4
錯誤例子:
voidprint1DArraySize(int*arr){
printf("%u",sizeof(arr)/sizeof(arr[0]));/*sizeof(arr)只是*/
}
受此限制,我們必須手動傳入大小
1
voidprint1DArraySize(int*arr,size_tarrSize);
註:typedefunsignedintsize_t與平台無關的,表示0-MAXINT的無號整數
C++提供reference的機制,使得我們不需再這麼麻煩,可以直接傳遞陣列的reference給函數,大小也可以直接求出
1
2
3
4
正確例子:
voidprint1DArraySize(int(&array)[10]){//傳遞reference
cout<
延伸文章資訊
- 1透視c語言指標ptt - 銀行資訊懶人包
透視c語言指標ptt在PTT/mobile01評價與討論, 提供C 語言、C 面試考題、C++ 重點就來銀行資訊懶人包,有最完整透視c語言指標ptt體驗分享訊息.
- 2透視c語言指標在PTT/Dcard完整相關資訊 - 萌寵公園
- 3[心得] 面試心得[上](文很長慎入) | 易春木ptt - 旅遊日本住宿評價
易春木ptt,大家都在找解答。 易春木整份講義(整理得非常不錯,狂推) 4. 透視C語言指標5. OS線上課程6. 計算機組織的一些重點(這我純粹看會考什麼慢慢google) 7.
- 4[問題] 函數呼叫傳參數- 看板C_and_CPP - PTT網頁版
- 5[分享] ptt C 語言新手十誡 - 一個小小工程師的心情抒發天地