版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、c企業(yè)要求與實(shí)踐(講義)第1章c語言基礎(chǔ)1.1 main函數(shù)的參數(shù)與返回值1.1.1 main的參數(shù)命令行界而的程序,通常都需要輸入命令行參數(shù)幫助程序執(zhí)行。假定冇一個(gè) 可執(zhí)行程序名為test。那么運(yùn)行該程序的的命令行如2test帶命令行參數(shù)是同二亍小的附加項(xiàng):test - c test其屮-c和test就是命令行參數(shù)。c程序可以將這些附加參數(shù)讀出來,并 為自己所用,比如作為程序運(yùn)行的條件(經(jīng)常看到調(diào)試參數(shù)-d就是這么一個(gè))。 c程序通過使用main()的參數(shù)來讀取這些附加參數(shù),下而的repeat, c給出一個(gè) 讀出main參數(shù)的例子:例.1:include stdio.h>include
2、 <stdlib.h>int main(int argc, char argv)int count;printf (z/the command line has %d argumcnts:n", argc - 1);for (count = 1; count < argc; count+)printf(d: %sn,count, argvcount);printf(n);/system("pause”); return 0;這里先解釋一下main(int argc, char*argv)這個(gè)函數(shù)屮兩個(gè)參數(shù)的意義, argc記錄的是命令行中輸入?yún)?shù)的數(shù)目,a
3、rgv是一個(gè)擁有argc個(gè)元素的字符串 數(shù)組,每個(gè)元素保存一個(gè)命令行小輸入的參數(shù)。編譯這個(gè)文件為可執(zhí)行文件repeat:gee repeat c -o repeat按下列方式執(zhí)行repeat程序/repeat i "love you" 3輸出如卜:the command line has 3 arguments:1:12: love you3:3在這個(gè)例子中,argc的值為4,命令行一共輸入了四個(gè)參數(shù)“./repeat”、 “i”、“l(fā)ove you”、“3”。在dos和unix環(huán)境下,命令行參數(shù)中用” ”符 號表示其是一個(gè)字符串,視為一個(gè)參數(shù)。1.1.2 main函數(shù)的返
4、回值main函數(shù)的返回值用于說明程序的退出狀態(tài)。如果返回0,則代表程序正常 退出;返回其它數(shù)字的含義則由系統(tǒng)決定。通常,返回非零代表程序異常退岀。 下而我們在winxp環(huán)境下做一個(gè)小實(shí)驗(yàn)。首先編譯下而的程序:int main( void )return 0;然后打開附件里的“命令提示符”,在命令行里運(yùn)行剛才編譯好的可執(zhí)行文件, 然后輸入“ echo % errorlevel %",回車,就可以看到程序的返回值為0。假設(shè)剛方編譯好的文件是a.exe,如果輸入“a && dir”,則會列出當(dāng)前口錄 下的文件夾和文件。但是如果改成“return-1",或者別的非0
5、值,重新編譯后輸 入“a&& dir”,則dir不會執(zhí)行。因?yàn)?amp;&的含義是:如果&&前而的程序正常退 出,則繼續(xù)執(zhí)行&&后面的程序,否則不執(zhí)行。也就是說,利用程序的返回值, 我們可以控制要不要執(zhí)行下一個(gè)程序。這就是int main的好處。如果你有興趣, 也可以把main函數(shù)的返回值類型改成非int類型(如float),重新編譯后執(zhí)行“a && dir",看看會出現(xiàn)什么情況,想想為什么會岀現(xiàn)那樣的情況。順便提一下, 如果輸入a ii dir的話,則表示如果a異常退出,則執(zhí)行dir。1.2類型轉(zhuǎn)換隱式轉(zhuǎn)換隱式
6、轉(zhuǎn)換就是編譯器在自動幫助程序員做的類型轉(zhuǎn)換工作,既然是編譯器自 動轉(zhuǎn)換,那么這種轉(zhuǎn)換必須貝有足夠的安全性,這是編譯器的責(zé)任。double dl = 100;int il = 100;double d2 = i 1;這種轉(zhuǎn)換是安全的,double類型占據(jù)的內(nèi)存字節(jié)數(shù)比int類型的要大,而且 double類型能表示的整數(shù)范圍也比int人,因此int類型到double類型的轉(zhuǎn)換是 安全的,沒有精度損失的,上例中的轉(zhuǎn)換是隱式的,由編譯器自動完成的。強(qiáng)制轉(zhuǎn)換強(qiáng)制轉(zhuǎn)換是冇能導(dǎo)致安全問題的,如果占據(jù)內(nèi)存字節(jié)數(shù)較多的類型轉(zhuǎn)換為占 據(jù)內(nèi)存字節(jié)數(shù)較少的類型轉(zhuǎn)換,那么就有可能造成精度的損失。double d3 =1
7、.25e+20;double dl = 10.25;int i2 = (int)d3;int i3 = (int)d4;i2為d3轉(zhuǎn)換為int類型的結(jié)果,double類型轉(zhuǎn)換為int類型屬于大轉(zhuǎn)小,可 能會損失精度(13數(shù)字很大,i2的類型無法存儲如此大的數(shù)字,將會溢出。1.3選擇結(jié)構(gòu)1.3.1 if 分支if語句是c+/c語言中最簡單、最常用的語句,然而很多程序員用隱含錯(cuò)誤 的方式寫if語句。木節(jié)以“與零值比較”為例,展開討論。布爾變量與零值比較不可將布爾變量直接與true、false或者1、0進(jìn)行比較。根據(jù)布爾類型的語義,零值為“假”(記為false),任何非零值都是“真”(記為true)
8、o true的值究竟是什么并沒冇統(tǒng)一的標(biāo)準(zhǔn)。例如visual c+將 true定義為1,而visual basic則將true定義為1。假設(shè)布爾變量名字為flag,它與零值比較的標(biāo)準(zhǔn)訐語句如下:if (flag) / 表示 flag 為真if (!flag) / 表示 flag 為假 其它的用法都屬于不良風(fēng)格,例如:if (flag = true)if (flag = 1 )if (flag 二二 false)if (flag 二二 0)整型變量與零值比較應(yīng)當(dāng)將整型變量用“二二”或“!二”直接與0比較。假設(shè)整型變量的名字為value,它與零值比較的標(biāo)準(zhǔn)if語句如2if (value = 0)i
9、f (value != 0)不口j模仿布爾變量的風(fēng)格而寫成if (value) /會讓人誤解value是布爾變量if (lvalue)浮點(diǎn)變量與零值比較不可將浮點(diǎn)變量用或“!二”與任何數(shù)字比較。千萬要留意,無論是float還是double類型的變量,都有精度限制。所以 一定要避免將浮點(diǎn)變量用或“!二”與數(shù)字比較,應(yīng)該設(shè)法轉(zhuǎn)化成“二” 或y”形式。假設(shè)浮點(diǎn)變量的名字為x,應(yīng)當(dāng)將if (x二二0. 0) 隱含錯(cuò)誤的比較轉(zhuǎn)化為if (x二-epsin0m) && (x二epsin0m)其中epsinon是允許的誤差(即精度)。指針變量與零值比較應(yīng)當(dāng)將指針變量用“二二”或“!二”與nu
10、ll比較。指針變量的零值是“空”(記為null)。盡管null的值與0相同,但是兩者 意義不同。假設(shè)指針變量的名字為p,它與零值比較的標(biāo)準(zhǔn)if語句如2 if (p = null) / p與nuix顯式比較,強(qiáng)調(diào)p是指針變量 if (p != null)不要寫成if(p = 0)/容易讓人誤解p是整型變量if (p !二 0)或者if (p)/容易讓人誤解p是布爾變量if (!p)對if語句的補(bǔ)充說明有時(shí)候我們可能會看到if (null = p)這樣古怪的格式。不是程序?qū)戝e(cuò)了, 是程序員為了防止將if (p = null)誤寫成if (p = null),而有意把p和 null顛倒。編譯器認(rèn)為i
11、f (p二null)是合法的,但是會指出if (null二p) 是錯(cuò)誤的,因?yàn)閚ull不能被賦值。程序中有時(shí)會遇到if/else/return的組合,應(yīng)該將如下不良風(fēng)格的程序 if (condition)return x;return y;改寫為if (condition)return x;el sereturn y;或者改寫成更加簡練的return (condition ? x : y);在使用運(yùn)算符&&的表達(dá)式,耍盡量把最有可能為false的子表達(dá)式放在&& 的左邊;同樣在使用運(yùn)算符ii的表達(dá)式,要盡量把最有可能為true的子表達(dá) 式放在ii的左邊。因?yàn)閏語
12、言對邏輯表達(dá)式的判斷采取“突然死亡法”;如果 &&左邊的子表達(dá)式計(jì)算結(jié)果為false,則整個(gè)表達(dá)式就為false,后面的字表 達(dá)式?jīng)]有必要再計(jì)算;如果ii左邊的子表達(dá)式計(jì)算結(jié)果為true,則整個(gè)表達(dá) 式就為true,因此后面的子表達(dá)式?jīng)]有必要再計(jì)算。者可以提高程序的執(zhí)行效 率。1.3.2 switch 分支有了辻語句為什么述要switch語句?switch是多分支選擇語句,而if語句只冇兩個(gè)分支可供選擇。雖然可以用 嵌套的if語句來實(shí)現(xiàn)多分支選擇,但那樣的程序冗長難讀。這是switch語句存 在的理由。switch語句的基本格式是:switch (variable)case v
13、aluel : break;case value2 :break; default :break;每個(gè)case語句的結(jié)尾不要忘了加break,否則將導(dǎo)致多個(gè)分支重疊(除非 有意使多個(gè)分支重疊)。 不要忘記最后那個(gè)default分支。即使程序真的不需要default處理,也應(yīng) 該保留語句default : break;這樣做并非多此一舉,而是為了防止別人誤 以為你忘了 default處理。"l4循環(huán)結(jié)構(gòu)c循環(huán)語句中,for語句使用頻率最高,while語句其次,do語句很少用。 木節(jié)重點(diǎn)論述循環(huán)體的效率。提高循環(huán)體效率的基木辦法是降低循環(huán)體的復(fù)雜 性。在多重循環(huán)中,如杲有可能,應(yīng)當(dāng)將最長
14、的循環(huán)放在最內(nèi)層,最短的循環(huán)放 在最外層,以減少cpu跨切循環(huán)層的次數(shù)。例如示例1.4(b)的效率比示例1.4(a) 的高。for (row=0; row<100; row+)for ( col=0; col<5; col+ )sum = sum + a|row|col|;for (col=0; col<5; col+ )for (row=0; row<100; row+)sum = sum + arow|col|;示例1.4(a)低效率:長循環(huán)在最外層 示例1.4(b)高效率:長循環(huán)在授內(nèi)層如果循壞體內(nèi)存在邏輯判斷,并口循環(huán)次數(shù)很大,宜將邏輯判斷移到循壞體 的外面。示
15、例1.4(c)的程序比示例1.4(d)多執(zhí)行了 n-1次邏輯判斷。并且由于 前者老要進(jìn)行邏輯判斷,打斷了循壞“流水線”作業(yè),使得編譯器不能對循壞進(jìn) 行優(yōu)化處理,降低了效率。如果n非常大,最好采用示例1.4(d)的寫法,可以 提高效率。如果n非常小,兩者效率差別并不明顯,采用示例1.4(c)的寫法比 較好,因?yàn)槌绦蚋雍啙?。for (i=0; i<n; i+)if (condition)if (condition)for (i=0; i<n; i+)dosomethingo ;dosomethingo ;elsedootherthingo ;elsefor (i=0; i<n;
16、 i+)dootherthingo ;示例1.4(c)效率低但程序簡潔 示例1.4(d)效率高但程序不簡潔1.5常量常量是一種標(biāo)識符,它的值在運(yùn)行期間恒定不變。c語言用#define來定義常量 (稱為宏常量)。除了 define夕卜還可以用const來定義常量(稱為const常量)。1.5.1為什么需要常量如果不使用常量,直接在程序中填寫數(shù)字或字符串,將會有什么麻煩?(1)程序的可讀性(可理解性)變差。程序員門己會忘記那些數(shù)字或字符串是 什么意思,用戶則更加不知它們從何處來、表示什么。(2)在程序的很多地方輸入同樣的數(shù)字或字符吊,難保不發(fā)生書寫錯(cuò)誤。(3)如果要修改數(shù)字或字符申,則會在很多地方
17、改動,既麻煩又容易出錯(cuò)。盡量使用含義直觀的常量來表示那些將在程序中多次出現(xiàn)的數(shù)字例如:#define const int const float或字符串。max 100/* c語言的宏常量*/max 二 100;/ c+ 語言的 const 常量pi = 3. 14159; / c+ 語言的 const 常量1.5.2 const 與 #define 的比較可以用const來定義常量,也可以用ttdefine來定義常量。但是前者比后者 有更多的優(yōu)點(diǎn):(1)const常量有數(shù)據(jù)類型,而宏常量沒有數(shù)據(jù)類型。編譯器可以對前者進(jìn)行 類型安全檢杳。而對后者只進(jìn)行字符替換,沒有類型安全檢查,并且在字 符替
18、換可能會產(chǎn)生意料不到的錯(cuò)謀(邊際效應(yīng))。(2)有些集成化的調(diào)試工具可以對const常量進(jìn)行調(diào)試,但是不能對宏常量進(jìn) 行調(diào)試。1.5.3常量定義規(guī)則需要對外公開的常量放在頭文件中,不需要對外公開的常量放在定義文件的 頭部。為便于管理,可以把不同模塊的常量集屮存放在一個(gè)公共的頭文件屮。如果某一常量與其它常量密切相關(guān),應(yīng)在定義中包含這種關(guān)系,而不應(yīng)給出 一些孤立的值。例如:const float radius = 100;const float diameter = radius * 2;1.5.4 ansic 中的 const問題1: const所定義的到底是變量還是常量為什么象下面的例子一樣用一
19、個(gè)const變量來初始化數(shù)組,ansi c的編譯 器會報(bào)告一個(gè)錯(cuò)誤呢?const int n = 5;int an;答案與分析:1)、這個(gè)問題討論的是“常量”與“只讀變量'的區(qū)別。常量肯定是只讀的, 例如5, “abc”,等,肯定是只讀的,因?yàn)槌绦蛑懈緵]有地方存放它的值, 當(dāng)然也就不能夠去修改它。i只讀變量”則是在內(nèi)存屮開辟一個(gè)地方來存放 它的值,只不過這個(gè)值由編譯器限定不允許被修改。c語言關(guān)鍵字const就 是用來限定一個(gè)變量不允許被改變的修飾符(qualifier)。上述代碼屮變量n 被修飾為只讀變量,可惜再怎么修飾也不是常量。而ansic規(guī)定數(shù)組定義 時(shí)維度必須是“常量”,“只
20、讀變量”也是不可以的。2)、注意:在ansi c中,這種寫法是錯(cuò)誤的,因?yàn)閿?shù)組的大小應(yīng)該是 個(gè)常量,ifij const int n,n只是一個(gè)變量(常量!=不可變的變量,但在標(biāo)準(zhǔn) c+中,這樣定義的是一個(gè)常量,這種寫法是對的),實(shí)際上,根據(jù)編譯過 程及內(nèi)存分配來看,這種用法本來就應(yīng)該是合理的,只是ansic對數(shù)組的 規(guī)定限制了它。3) 、那么,在ansi c語言屮用什么來定義常量呢?答案是enum類型 和#define宏,這兩個(gè)都可以用來定義常量。問題2: const所限定的內(nèi)容下面的代碼編譯器會報(bào)一個(gè)錯(cuò)誤,請問,哪一個(gè)語句是錯(cuò)誤的呢? typedef char * pstr;char st
21、ring4 = ” abc”;const char *p1 = string;const pstr p2 = string;p1+;p2+;答案與分析:問題岀在p2+上。1) > const使用的基木形式:const char m;限定m不可變。2) > 替換 1 式中的 m, const char *pm;限定*pm不可變,當(dāng)然pm是可變的,因此問題中p1+是對的。3) 、替換 1 式 char, const newtype m;限定m不可變,問題中的pstr就是一種新類型,因此問題中p2不 可變,p2+是錯(cuò)誤的。問題3:字符串常量請問下而的代碼有什么問題? char *p =
22、”i'm hungry!”; p0= t;答案與分析:上面的代碼可能會造成內(nèi)存的非法寫操作。分析如下,hungry”實(shí)質(zhì) 上是字符串常量,而常量往往被編譯器放在只讀的內(nèi)存區(qū),不可寫。p初始 指向這個(gè)只讀的內(nèi)存區(qū),而p0 = t則企圖去寫這個(gè)地方,編譯器當(dāng)然不會 答應(yīng)。問題4:字符串常量2請問char a3 = uabcu合法嗎?使用它有什么隱患? 答案與分析:在標(biāo)準(zhǔn)c中這是合法的,但是它的生存環(huán)境非常狹??;它定義一個(gè)大小 為3的數(shù)組,初始化為“abc”,注意,它沒冇通常的字符串終止符0,因此 這個(gè)數(shù)組只是看起來像c語言中的字符串,實(shí)質(zhì)上卻不是,因此所有對字符 串進(jìn)行處理的函數(shù),如str
23、cpy、printf等,都不能夠被使用在這個(gè)假字符串上。問題5: const &指針類型聲明中const用來修飾一個(gè)常量,有如下兩種寫法,那么,請問, 下面分別用const限定不可變的內(nèi)容是什么?1) 、const在前面const int nvalue; nvalue 是 constconst char *pcontent; /廣pcontent 是 const, pcontent 可變 const (char *) pcontent;/pcontent 是 const,*pcontent 可變 char* const pcontent; /pcontent 是 con st,*pc
24、on tent 可變 const char* const pcontent; /pcontent 和*pcontent 都是 const2) 、const在后面,與上面的聲明對等int const nvalue; / nvalue 是 constchar const * pcontent;/ *pcontent 是 const, pcontent 可變 (char *) const pcon tent;/pc on tent 是 const,*pcontent 口j 變 char* const pcontent;/ pcon tent 是 con st,*pc on tent 可變 char
25、const* const pcontent;/ pcontent 和*pcontent 都是 const 答案與分析:const和指針一起使用是c語言中一個(gè)很常見的困惑之處,在實(shí)際 開發(fā)中,特別是在看別人代碼的時(shí)候,常常會因?yàn)檫@樣而不好判斷作者 的意圖,下面講一下我的判斷原則:沿著*號劃一條線,如果const位于的左側(cè),則const就是用來修飾 指針?biāo)赶虻淖兞?,即指針指向?yàn)槌A咳绻鹀onst位于啲右側(cè),const 就是修飾指針木身,即指針木身是常量。你可以根據(jù)這個(gè)規(guī)則來看上面 聲明的實(shí)際意義,相信定會一目了然。另外,需要注意:對于const (char *);因?yàn)閏har 是一個(gè)整體,ffl
26、 當(dāng)于一個(gè)類型(如char),因此,這是限定指針是consto第2章函數(shù)函數(shù)是c程序的基本功能單元,其重要性不言而喻。函數(shù)設(shè)計(jì)的細(xì)微缺點(diǎn)很 容易導(dǎo)致該函數(shù)被錯(cuò)用,所以函數(shù)的功能止確是不夠的。函數(shù)接口的兩個(gè)要素是參數(shù)和返冋值。c語言中,函數(shù)的參數(shù)和返冋值的傳 遞方式有兩種:值傳遞和地址傳遞。2.1參數(shù)的規(guī)則參數(shù)的書寫要完整。不耍貪圖省事只寫參數(shù)的類型而省略參數(shù)名字。如果函數(shù)沒有參數(shù),則用 void填充。例如:void setvalue(int width, int height) ; / 良好的風(fēng)格/不良的風(fēng)格/良好的風(fēng)格/不良的風(fēng)格void sctvalue(int, int); float
27、getvalue(void); float getvalue ();參數(shù)命名要恰當(dāng),順序要合理。例如編寫字符串拷貝函數(shù)stringcopy,它冇兩個(gè)參數(shù)。如果把參數(shù)名字起為 strl 和 str2,例如:void stringcopy(char *strl, char *str2);那么我們很難搞清楚究競是把strl拷貝到str2屮,還是剛好倒過來??梢园褏?shù)名字起得更冇意義,如叫strsource和strdestinationo這樣從 名字上就可以看岀應(yīng)該把strsource拷貝到strdestinationo述冇一個(gè)問題,這兩個(gè)參數(shù)那一個(gè)該在前那一個(gè)該在后?參數(shù)的順序耍遵循 程序員的習(xí)慣。
28、一般地,應(yīng)將目的參數(shù)放在前面,源參數(shù)放在后面。如果將函數(shù)聲明為:void stringcopy(char *strsource, char *strdestination);別人在使用時(shí)可能會不假思索地寫成如下形式:char str20;stringcopy(str, “hello world”);/ 參數(shù)順序顛倒指針參數(shù)如果參數(shù)是指針,且僅作輸入用,則應(yīng)在類型前加const,以防止該指針在 函數(shù)體內(nèi)被意外修改。例如:void str in gcopy (char strdcsti neiti on, const char strsource);避免函數(shù)有太多的參數(shù),參數(shù)個(gè)數(shù)盡量控制在5個(gè)以內(nèi)
29、。如果參數(shù)太多,在使用時(shí)容易將參數(shù)類型或順序搞錯(cuò)。盡量不要使用類型和數(shù)目不確定的參數(shù)。c標(biāo)準(zhǔn)庫函數(shù)printf是采用不確定參數(shù)的典型代表,其原型為:int printf (const chat format , argument );這種風(fēng)格的函數(shù)在編譯時(shí)喪失了嚴(yán)格的類型安全檢查。2.2返回值的規(guī)則不要省略返回值的類型。c語言中,凡不加類型說明的函數(shù),一律自動按整型處理。這樣做不會冇什 么好處,卻容易被誤解為void類型。函數(shù)名字與返回值類型在語義上不可沖突。違反這條規(guī)則的典型代表是c標(biāo)準(zhǔn)庫函數(shù)getcharo 例如:char c;c = getchar ();if (c 二二 eof)按照g
30、etchar名字的意思,將變量c聲明為char類型是很門然的事情。但 不幸的是getchar的確不是char類型,而是int類型,其原型如下:int getchar(void);由于c是char類型,取值范圍是-128, 127,如果宏eof的值在char的 取值范圍z外,那么if語句將總是失敗,這種“危險(xiǎn)”人們一般哪里料得到! 導(dǎo)致本例錯(cuò)誤的責(zé)任并不在用戶,是函數(shù)getchar誤導(dǎo)了使用者。不要將正常值和錯(cuò)誤標(biāo)志混在一起返回。正常值用輸出參數(shù)獲得,而錯(cuò)誤標(biāo)志用return語句返冋?;仡櫳侠?,c標(biāo)準(zhǔn)庫函數(shù)的設(shè)計(jì)者為什么要將getchar聲明為int類型呢?在正常情況下,getchar的確返冋單
31、個(gè)字符。但如果getchar碰到文件結(jié)束 標(biāo)志或發(fā)生讀錯(cuò)誤,它必須返冋一個(gè)標(biāo)志eof。為了區(qū)別于正常的字符,只好將 eof定義為負(fù)數(shù)(通常為負(fù)1)。因此函數(shù)getchar就成了 int類型。我們在實(shí)際工作屮,經(jīng)常會碰到上述令人為難的問題。為了避免出現(xiàn)誤解, 我們應(yīng)該將正常值和錯(cuò)誤標(biāo)志分開。b|j:正常值用輸出參數(shù)獲得,而錯(cuò)誤標(biāo)志用 return語句返冋。函數(shù) getchar 可以改寫成 bool getchar (char *c);雖然 gechar 比 getchar 靈活,例如 putchar (getchar ();但是如果 getchar 用錯(cuò)了,它的靈活性乂有什么用呢?有時(shí)候函數(shù)原本
32、不需要返回值,但為了增加靈活性如支持鏈?zhǔn)奖磉_(dá),可以附加返回值。例如字符串拷貝函數(shù)strcpy的原型:char *strcpy (char *strdcst, const char *strsrc);strcpy函數(shù)將strsrc拷貝至輸出參數(shù)strdcst中,同時(shí)函數(shù)的返回值又是 strdcsto這樣做并非多此一舉,可以獲得如下靈活性:char str20;int length = strlen( strcpy(str, “hello world” );2.3函數(shù)內(nèi)部實(shí)現(xiàn)的規(guī)則不同功能的函數(shù)其內(nèi)部實(shí)現(xiàn)各不相同,看起來似乎無法就“內(nèi)部實(shí)現(xiàn)”達(dá)成 一致的觀點(diǎn)。但根據(jù)經(jīng)驗(yàn),我們可以在函數(shù)體的“入口處
33、”和“出口處”從嚴(yán)把 關(guān),從而提高函數(shù)的質(zhì)量。在函數(shù)體的“入口處”,對參數(shù)的有效性進(jìn)行檢查。很多程序錯(cuò)誤是由非法參數(shù)引起的,我們應(yīng)該充分理解并正確使用“斷言” (assert)來防止此類錯(cuò)誤。詳見2. 5節(jié)“使用斷言”。在函數(shù)體的“出口處”,對return語句的正確性和效率進(jìn)行檢查。如果函數(shù)有返回值,那么函數(shù)的“出口處”是return語句。我們不要輕視 return語句。如果return語句寫得不好,函數(shù)要么岀錯(cuò),要么效率低下。注意事項(xiàng)如下: return語句不可返回指向“棧內(nèi)存”的“指針”或者“引用”,因?yàn)樵?內(nèi)存在函數(shù)體結(jié)束時(shí)被自動銷毀。例2. 1char * func(void)char
34、 st譏二 “hello world" ;/ str 的內(nèi)存位于棧上 return str;/將導(dǎo)致錯(cuò)誤要搞清楚返回的究競是“值”還是“指針”。2.4其它建議函數(shù)的功能要單一,不要設(shè)計(jì)多用途的函數(shù)。函數(shù)體的規(guī)模要小,盡量控制在50行代碼之內(nèi)。盡量避免函數(shù)帶有“記憶”功能。相同的輸入應(yīng)當(dāng)產(chǎn)生相同的輸出。帶有“記憶”功能的函數(shù),其行為可能是不可預(yù)測的,因?yàn)樗男袨榭?能取決于某種“記憶狀態(tài)”。這樣的函數(shù)既不易理解又不利于測試和維護(hù)。 在c語言屮,函數(shù)的static局部變量是函數(shù)的“記憶”存儲器。建議盡量少 用static局部變量,除非必需。不僅要檢查輸入?yún)?shù)的有效性,還要檢查通過其它途徑
35、進(jìn)入函數(shù)體內(nèi)的變量的有效性,例如全局變量、文件句柄等。用于出錯(cuò)處理的返回值一定要清楚,讓使用者不容易忽視或誤解錯(cuò)誤情況。2.5使用斷言程序一般分為debug版本和release版本,debug版本用丁內(nèi)部調(diào)試,release 版本發(fā)行給用戶使用。斷言assert是僅在debug版本起作用的宏,它用于檢查“不應(yīng)該”發(fā)生的情 況。示例65是一個(gè)內(nèi)存復(fù)制函數(shù)。在運(yùn)行過程中,如果assert的參數(shù)為假,那 么程序就會中止(一般地還會出現(xiàn)捉示對話,說明在什么地方引發(fā)了 assert)。例 2.2:void *mcmcpy(void *pvto, const void *pvfrom, size t si
36、ze)assert (pvto != null) && (pvfrom != null) ;/ 使用斷言byte *pbto = (byte *) pvto; / 防止改變 pvto 的地址 byte *pbfrom = (byte *) pvfrom; / 防止改變 pvfrom 的地址 while (size - > 0 )*pbto + = *pbfrom + ;return pvto;為了不在程序的debug版木和release版木引起羌另ij, assert不應(yīng)該產(chǎn)生任何 副作用。所以assert不是函數(shù),而是宏。程序員可以把a(bǔ)ssert看成一個(gè)在任何系 統(tǒng)狀態(tài)
37、卜都可以妥全使用的無害測試手段。如果程序在assert處終止了,并不是 說含有該assert的函數(shù)有錯(cuò)誤,而是調(diào)用者出了差錯(cuò),assert可以幫助我們找到 發(fā)生錯(cuò)誤的原因。如果搞不清楚斷言檢查的是什么,就很難判斷錯(cuò)誤是出現(xiàn)在程序中,還是出 現(xiàn)在斷言中。幸運(yùn)的是這個(gè)問題很好解決,只耍加上清晰的注釋即可。這本是顯 而易見的事情,可是很少有程序員這樣做。這好比一個(gè)人在森林里,看到樹上釘 著一塊“危險(xiǎn)”的大牌了。但危險(xiǎn)到底是什么?樹耍倒?有廢井?有野曽?除非 告訴人們“危險(xiǎn)”是什么,否則這個(gè)警告牌難以起到積極有效的作用。難以理解 的斷言常常被程序員忽略,甚至被刪除。在函數(shù)的入口處,使用斷言檢查參數(shù)的有
38、效性(合法性)。在編寫函數(shù)時(shí),要進(jìn)行反復(fù)的考查,并且自問:“我打算做哪些假定? ”一 旦確定了的假定,就要使用斷言對假定進(jìn)行檢査。第3章指針、數(shù)組和字符串3.1指針的本質(zhì)程序是由指令和數(shù)據(jù)組成的,其中數(shù)據(jù)在程序運(yùn)行時(shí)是存放在內(nèi)存單元中 的,指針的本質(zhì)就是這個(gè)內(nèi)存單元的地址。首先,指針是變量,它和常用的整型 變量、字符變量等沒有本質(zhì)區(qū)別,不同的是整型變量的值是整型,字符變量的值 是字符類型,而指針變量的值是一個(gè)內(nèi)存單元的地址,即通常是一個(gè)32位的二 進(jìn)制數(shù)字(32位系統(tǒng))。通過下面的程序,我們可以查看指針的真實(shí)面貌。例 3.1:#include <stdio.h>int main(
39、int argc, char *argv)int i = 15;char c = 'o'float f = i;int *pi = &i;int *ppi = πprintf("address of i is : ox%pn", &i);printf("value of pi is : ox%pn”, pi);printf(haddress of pi is : ox%pn”, &pi);printf("value of pi is : ox%pnn' ppi);printfc'val
40、ue of c is : %cn”, c); printf("address of c is : ox%pnn", &c);printfc'value of f is : %fn", f);printf("address of f is : ox%pnn", &f);getch();輸fl!結(jié)果:address ofiis: 0x0012ff7cvalue of pi is : 0x0012ff7caddress of pi is : 0x0012ff70value of ppi is : 0x0012ff70value
41、 of c is : 0address of i is : 0x0012ff78value of f is :15.000000address of i is : 0x0012ff74觀察輸出結(jié)果,變量pi的值就是i變量的地址的值,變量ppi的值就是pi變 量的地址的值。通過這個(gè)例子可以了解到指針的本質(zhì),可以將指針變量看作一個(gè) 32位的二進(jìn)制整數(shù),而這個(gè)整數(shù)正是一個(gè)內(nèi)存單元的地址,通過訪問這個(gè)內(nèi)存 地址口j以訪問到該指針指向的內(nèi)存單元的值。3.2指針的類型及其支持的運(yùn)算指針的類型實(shí)際上指針指向的內(nèi)存單元所存放的數(shù)據(jù)的類型。int *plnt;int *pplnt;char *pchar;voi
42、d *pvoid;pint的類型是int *,也就是整型指針。pplnt的類型是int*,即整型指針的 指針。pchar的類型是char*,即字符類型的指針。通常我們可以通過使用typedef把不同的指針類型定義下來,然后直接使用定義后的類型。 typedefint * typedefint* typedefchar* typedef void*intptr; intptrptr; charptr; voidptr;為什么要使用上面的定義呢,通過下面的例子我們可以看到它的作用。例 3.2:#include <stdio.h>typedef int* intptr;int main(
43、 int argc, char *argv)int i = 15;int* ipa, ipb;intptr pa, pb;ipa = &i;ipb = &i;pa = &i;pb = &i;getch();在該程序中設(shè)置斷點(diǎn)單步跟蹤ipa, ipb, pa, pb這四個(gè)變量,從跟蹤結(jié)果中可以發(fā)現(xiàn)ipa是int*類型,而ipb仍然是int類型;而pa、pb都是int*類型。這 說明在語句:int*ipa,ipb;中,*符號結(jié)合到了 ipa中,實(shí)際上是等效于:int *ipa; int ipb;rfn使用intptr的方式來定義的兩個(gè)變量等效于int* pa; in
44、t* pb;因此如果需 要將兩個(gè)變量都定義為指針類型就需要使用intptr,在使用int*時(shí)需耍注意其中* 號的結(jié)合性。全局指針變量的默認(rèn)初始值是null。而對于局部指針變量p,你必須顯示 地指定其初值,否則p的初始值是不可預(yù)測的(不是null)o當(dāng)你第一次使用 它的時(shí)候就可能會用if( p != null)來檢查p的有效性,然而此時(shí)if語句的確不 起作用。如果忘記給p賦初值,那么你第一次使用p的時(shí)候就會倒是運(yùn)行時(shí)錯(cuò)誤。作為一個(gè)好的編程習(xí)慣,不管指針變量是全局還是局部的、靜態(tài)的還是非靜 態(tài)的,都應(yīng)該要么為指針變量賦予一個(gè)有效的初始值,耍么將其初始化為null。從指針的本質(zhì)來分析,它實(shí)際上是一個(gè)
45、整數(shù),因此它應(yīng)該可以進(jìn)行整數(shù)能夠 參與的所有算術(shù)運(yùn)算,但是由于它的本質(zhì)是內(nèi)存地址,所以很多算術(shù)運(yùn)算對指針 都沒有意義。使用較多的運(yùn)算如下:指針自增(+)指針自減()指針加n(n為正整數(shù))指針減n(n為正整數(shù)) 指針比較(常用的是二二和!二)指針賦值取地址和反引用例 3.3:int a5();int* pa;int b=0;int* pb;pa = a; 指針賦值,a相當(dāng)與a數(shù)組的地址,也可以看作指針pa+;這時(shí)pa指向a數(shù)組的第二個(gè)元素,實(shí)際上地址不是加一/而是加上了 sizeof(int)pa+=20;/這時(shí)pa指向a數(shù)組的第二十二個(gè)元素,指針加上了 20*sizeof(int) if( p
46、a !=null)指針比較b = *pa;反引用,*pa表示pa所指的內(nèi)存單元的值pb = &b;取地址,pb取b變量的內(nèi)存單元的地址需要注意void*類型的指針不能參與算術(shù)運(yùn)算,只能進(jìn)行賦值、比較和sizeof 操作,同樣也不能對void*類型的指針使用*符號來反引用,因?yàn)槠渲羔標(biāo)傅闹?類型無法確定因此無法對其取值。3.3指針傳遞如果函數(shù)的參數(shù)或返冋值被聲明為指針類型,那么函數(shù)接受的參數(shù)或返冋值 就是地址,而不是指針?biāo)傅膬?nèi)存單元的值。例 3.4:#include <stdio.h>void funcptr( int *p)(*p)+;將指針?biāo)赶虻膬?nèi)存單元的值加一p+
47、;將指針本身加一,即指向a數(shù)組的下一個(gè)元索int main( int argc, char * argv|)int a=1,2,3,4,5;int *pn;pa = a;printf("value of pa is :0x%pn", pa); printf(hvalue of a0 is :%dn”, a0); funcptr( pa);printf("value of pa is :0x%pn", pa); printf("value of a0 is :%dn”, a0); getch();輸岀結(jié)果為:value of pa is: 0x(
48、)012ff6cvalue of a0 is: 1value of pa is: 0x0012ff6cvalue of a0 is: 2在執(zhí)行完函數(shù)funcptr()后,a0的值壇加了一,而指針pa本身的內(nèi)容并沒冇 發(fā)生改變,也就是pa所指向的位置仍然是a0o這是為什么呢?我們知道函數(shù) 的參數(shù)傳遞分為傳值方式和傳地址方式,傳值方式的參數(shù)本身在函數(shù)內(nèi)的修改在 函數(shù)結(jié)束后不會被保存,而傳地址方式參數(shù)的在函數(shù)內(nèi)的修改在函數(shù)結(jié)束后被保 存下來了。這里的函數(shù)funcptr()實(shí)際上就是采用的傳地址方式,因?yàn)閜a所指的 值在函數(shù)結(jié)束后的確被修改了,但為什么pa指針本身的值的修改卻沒有被保存 呢。實(shí)際上從嚴(yán)
49、格的意義上來說函數(shù)的參數(shù)傳遞全部都是值傳遞,沒有所謂的地 址傳遞,參數(shù)本身在函數(shù)中的修改都是不會被保存的,因?yàn)檫M(jìn)入函數(shù)時(shí),參數(shù)的 值都會被復(fù)制一個(gè)備份來在函數(shù)中使用,而不是直接使用參數(shù),這樣可以保證參 數(shù)的值在退出函數(shù)時(shí)不會發(fā)生變化。所謂的傳地址方式,實(shí)際上是將指針作為參 數(shù)傳遞,那么參數(shù)實(shí)際上一個(gè)指針,也就是一個(gè)內(nèi)存單元的地址(一個(gè)32位的 整數(shù)),而不是參數(shù)所指向的內(nèi)存單元的值,因此在函數(shù)小指針本身是被保護(hù)的, 而指針?biāo)赶虻膬?nèi)存單元的值并不是參數(shù),它沒有被作為參數(shù)來保護(hù),所以上例 小指針?biāo)赶虻膬?nèi)存單元的值被修改了,而指針本身并沒有被修改。下面是一個(gè)關(guān)于內(nèi)存屮請的函數(shù)的例了:例 3.5:
50、#include <stdio.h>void allocate( char *p, int size )中請size大小的內(nèi)存空間,并將首地址賦值給pp = ( char * )malloc(size*sizeof(char);int main( int argc, char* argv)int *pa;pa = null;allocate( pa, 50);if( pa = null)printf(" value of pa is null !n”);elseprintf("value of pa is :0x%pn”,pa);getch();可能大家認(rèn)為這個(gè)
51、程序是沒有問題的,但是allocate函數(shù)執(zhí)行后,pn的值確 為null。如果大家深刻理解了上一個(gè)例了,那么這個(gè)例了就容易分析了,原因 就是allocate函數(shù)中將char* p作為參數(shù),主程序中將pa作為參數(shù)傳入,那么函 數(shù)需要保護(hù)參數(shù)“并保證pa的值(是其木身的值,即地址木身,而不是指針指 向的內(nèi)存單元的值)不被修改,而函數(shù)中對參數(shù)p做了賦值,而且這個(gè)賦值是直 接對指針木身的修改,因此這個(gè)修改不會被保存。很多時(shí)候我們都需要一個(gè)像allocate這樣的函數(shù)來完成內(nèi)存的中請,那怎么 樣才能在函數(shù)中完成這一功能并將分配的內(nèi)存的地址傳遞出函數(shù)呢?有兩種方 法:第一種方法是將這個(gè)地址作為返冋值來傳遞
52、出函數(shù)。例 3.6:char* allocate 1( size )return( (char * )malloc( size * sizeof(char);allocate 1函數(shù)直接返回了中請的內(nèi)存的首地址,如果在主程序屮使用pa來接 收返回值就可以得到申請的內(nèi)存的首地址了。第二種方法是使用指針的指針作為參數(shù),因?yàn)閷⒅羔樀闹羔樧鳛閰?shù)時(shí),函數(shù)保護(hù)的是指針的指針,而不會保護(hù)指針本身了。例 3.7:#include <stdio.h>void allocate2( char int size )申請size大小的內(nèi)存空間,并將首地址賦值給*p*p = ( char * )mallo
53、c(size*sizeof(char);int main( int argc, char* argv)int *pa;pa = null;取pa的地址,即指針的指針allocate2( &pa, 50);if(pa = null)printf(u value of pa is null !nu);elseprintf("value of pa is :0x%pn", pa);getch();allocate2函數(shù)將申請內(nèi)存的地址傳遞給了*p,也就是pa,這樣實(shí)現(xiàn)了內(nèi)存首 地址的參數(shù)傳遞。最后需耍注意的是:我們可以將指針或指針的指針作為參數(shù)或變量進(jìn)行傳 遞,但是在傳遞
54、的過程當(dāng)屮要注意指針?biāo)赶虻牡刂芬欢ㄒ怯行У?,如果訪問 的指針無效可能會使整個(gè)程序崩潰。如果使用指針接收malloc申請的內(nèi)存的首 地址,一定要在程序結(jié)朿以前將申請的內(nèi)存釋放(通過free函數(shù)),不要在指針 的傳遞或運(yùn)算過程中丟失了首地址的值,或是忘記了內(nèi)存釋放,這樣都會造成內(nèi) 存泄露,內(nèi)存泄露會使你的程序出現(xiàn)問題,特別是在嵌入式設(shè)備上運(yùn)行的程序, 其至?xí)?dǎo)致程序終止,這絕不是危言聳聽。3.4數(shù)組的本質(zhì)任何數(shù)組,其所有元素的內(nèi)存小都是連續(xù)字節(jié)存放的,也就是說保存在i大 塊連續(xù)的內(nèi)存區(qū)屮。數(shù)組元素的下標(biāo)編號從0開始,最后一個(gè)元素的下標(biāo)等于元 素個(gè)數(shù)減-。下標(biāo)必須是整數(shù)或整數(shù)的表達(dá)式。通過下標(biāo)引
55、用一個(gè)數(shù)組元素,在 本質(zhì)上和引用一個(gè)同類型的變量沒什么區(qū)別,編譯器通過下標(biāo)值來計(jì)算你所引用 的元素在內(nèi)存小的地址。因此,在語義上,下標(biāo)操作符返回的是一個(gè)元素的引用。int a5 = 1,2, 3,4,5 ;int *pa = a;a3 = 100;假設(shè)數(shù)組a的地址為0x0012ff6c,如果需要計(jì)算a3,那么編譯器會計(jì)算 a3的地址,即 a+3*sizeof(int) = 0x0012ff6c + 3*4 = oxoo12ff78,然后將 oxoo12ff78所指向的內(nèi)存單元的值修改為100。當(dāng)然,這個(gè)計(jì)算是由編譯器來 完成的,我們只需耍理解它的完成方法就可以了。pa和a的值實(shí)際上是相同的,
56、pa指向的就是數(shù)組a的首地址,那么a3也可以表示為*(p+3),對于這里的 (p+3),編譯器會白動將其轉(zhuǎn)變?yōu)閜 + 3*sizeof(int),所以可以認(rèn)為pa也是數(shù)組a 的別名,甚至可以這樣來訪問:a3和pa3是等效的,*(a + 3)和*(p+3)也是等 效的。也就是說數(shù)組名就是指針,但是a作為數(shù)組a的首地址,它是不能被修改 的,rfop是可以被修改的。a=a+l;p = p+1;上面第一個(gè)語句是錯(cuò)誤的,a不能被修改,但是第二句是正確的。在聲明數(shù)組時(shí),一般有如下三種方式:明確地指出它的元素個(gè)數(shù),編譯器會按照給定的元素個(gè)數(shù)來分配存儲空 間。例如:iiha10;不指明元素個(gè)數(shù)而直接初始化,編譯器會根據(jù)你提供的初始值的個(gè)數(shù)來 確定數(shù)組的元素個(gè)數(shù)。例如:int a = 1,2, 3,4,5 ;同時(shí)指定元素個(gè)數(shù)并且初始化。例如:int a5 = 1,2, 3,4,5 ;但是不允許既不指定元素個(gè)數(shù)、乂不初始化,因?yàn)榫幾g器不知道到底該為數(shù) 組分配多少存儲空間。另外需要注意的是定義數(shù)組時(shí),不
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 關(guān)于2025年度市政道路施工管理協(xié)議書3篇
- 2025年度生態(tài)公園清工承包服務(wù)合同3篇
- 2025年度生態(tài)園區(qū)土石方整治與生態(tài)修復(fù)合作協(xié)議3篇
- 二零二五年度農(nóng)村自來水管網(wǎng)租賃服務(wù)合同
- 二零二五年度農(nóng)村家庭資產(chǎn)分配協(xié)議范本2篇
- 2025清潔合同樣板
- 2025年度創(chuàng)新型企業(yè)監(jiān)事聘用合同標(biāo)準(zhǔn)模板3篇
- 二零二五年度農(nóng)村土地租賃與農(nóng)業(yè)產(chǎn)業(yè)扶貧合同
- 2025年度數(shù)據(jù)中心防火門緊急更換與安全評估服務(wù)協(xié)議3篇
- 二零二五年度農(nóng)業(yè)種植項(xiàng)目環(huán)境保護(hù)責(zé)任書3篇
- 安全生產(chǎn)事故案例分析
- 期末檢測卷(一)(試卷)-2024-2025學(xué)年外研版(三起)英語六年級上冊(含答案含聽力原文無音頻)
- 2023-2024學(xué)年北京市通州區(qū)九年級(上)期末語文試卷
- 2023-2024學(xué)年廣東省深圳市龍崗區(qū)八年級(上)期末英語試卷
- DB23-T 3768-2024北方種鵝節(jié)水生態(tài)旱養(yǎng)管理技術(shù)規(guī)程
- 事業(yè)單位招聘《綜合基礎(chǔ)知識》考試試題及答案
- 2024年電工(高級技師)考前必刷必練題庫500題(含真題、必會題)
- 墊江縣中醫(yī)院2018年11月份臨床技能中心教學(xué)設(shè)備招標(biāo)項(xiàng)目招標(biāo)文件
- 2024年《浙江省政治學(xué)考必背內(nèi)容》(修訂版)
- 2024-2025學(xué)年初中數(shù)學(xué)七年級下冊滬教版(五四學(xué)制)(2024)教學(xué)設(shè)計(jì)合集
- 廣東省惠州市(2024年-2025年小學(xué)四年級語文)統(tǒng)編版綜合練習(xí)(上學(xué)期)試卷及答案
評論
0/150
提交評論