史萊姆論壇

返回   史萊姆論壇 > 教學文件資料庫 > 應用軟體使用技術文件
忘記密碼?
論壇說明

歡迎您來到『史萊姆論壇』 ^___^

您目前正以訪客的身份瀏覽本論壇,訪客所擁有的權限將受到限制,您可以瀏覽本論壇大部份的版區與文章,但您將無法參與任何討論或是使用私人訊息與其他會員交流。若您希望擁有完整的使用權限,請註冊成為我們的一份子,註冊的程序十分簡單、快速,而且最重要的是--註冊是完全免費的!

請點擊這裡:『註冊成為我們的一份子!』

Google 提供的廣告


 
 
主題工具 顯示模式
舊 2006-05-08, 11:28 AM   #1
psac
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設 "C之詭譎"C語言之精華總結!

"C之詭譎"C語言之精華總結!

C之詭譎(上)
從研究生二年紀開始教學電腦也差不多兩年了,一路走來,有很多的收穫,也有不少的遺憾,現在正好有一段閒暇,就想對走過的路留下一些足跡,回憶。每個人都有自己不同的人生,說到這裡,就是程序人生了,歌德在《浮士德》中說過:"如果不曾在悲哀中咀嚼過麵包,不曾在哭泣中等待過明天,這樣的人就不知道你 --天的力量。"所以我想記下一些帶給我悲哀,帶給我哭泣的程序人生。其實教學電腦的基礎課程是非常重要的,離散數學,編譯原理,作業系統,形式語言……,如果你認真走過了這些路,在以後的日子你會發現你的路會越走越寬,以前的努力和汗水會不斷的給你靈感,給你支持,給你繼續的武器和勇氣。你會發現以後取得的很多成就,不過是朝花夕拾而已!

對於程序語言我喜歡的是C++,它能帶給你別的語言無法給予你的無上的智力快感,當然也會給你一門語言所能給你的魔鬼般的折磨。其實Java,C#, Python語言也非常的不錯,我也極為喜歡。它們都是非常成功的語言,我從來就不願意做某一種語言的盲目信仰者,每種語言都有它成功的地方,失敗的地方,都有它適合的地方,不如意的地方。所以每一次看到評價語言的文章,我看看,但從來不會發言。

C++的前世是C,而且C所留下的神秘以及精簡在C++中是青出於藍而勝於藍!C所帶給人的困惑以及靈活太多,即使一個有幾年經驗的高段C程序員仍然有可能在C語言的小水溝裡翻船。不過其實C語言真的不難,下面我想指出C語言中最神秘而又詭譎多變的四個地方,它們也繼續在C++語言中變幻莫測。

游標,陣列,檔案類型的識別,參數可變的函數。

一.游標。

它的本質是位址的檔案類型。在許多語言中根本就沒有這個概念。但是它卻正是C靈活,高效,在面向程序的時代所向披靡的原因所在。因為C的記憶體模型基本上對應了現在von Neumann(馮·諾伊曼)電腦的機器模型,很好的達到了對機器的映射。不過有些人似乎永遠也不能理解游標【注1】。

注1:Joel Spolsky就是這樣認為的,他認為對游標的理解是一種aptitude,不是通過訓練就可以達到的http://www.joelonsoftware.com/pr ... /fog0000000073.html

游標可以指向值、陣列、函數,當然它也可以作為值使用。

看下面的幾個例子:

int* p;//p是一個游標,指向一個整數

int** p;//p是一個游標,它指向第二個游標,然後指向一個整數

int (*pa)[3];//pa是一個游標,指向一個擁有3個整數的陣列

int (*pf)();//pf是一個指向函數的游標,這個函數返回一個整數

後面第四節我會詳細講解標幟符(identifier)檔案類型的識別。

1.游標本身的檔案類型是什麼?

先看下面的例子:int a;//a的檔案類型是什麼?

對,把a去掉就可以了。因此上面的4個聲明語句中的游標本身的檔案類型為:

int*

int**

int (*)[3]

int (*)()

它們都是復合檔案類型,也就是檔案類型與檔案類型結合而成的檔案類型。意義分別如下:

point to int(指向一個整數的游標)

pointer to pointer to int(指向一個指向整數的游標的游標)

pointer to array of 3 ints(指向一個擁有三個整數的陣列的游標)

pointer to function of parameter is void and return value is int (指向一個函數的游標,這個函數參數為空,返回值為整數)

2.游標所指物的檔案類型是什麼?

很簡單,游標本身的檔案類型去掉 "*"號就可以了,分別如下:

int

int*

int ()[3]

int ()()

3和4有點怪,不是嗎?請擦亮你的眼睛,在那個用來把"*"號包住的"()"是多餘的,所以:

int ()[3]就是int [3](一個擁有三個整數的陣列)

int ()()就是int ()(一個函數,參數為空,返回值為整數)【注2】

注2:一個小小的提醒,第二個"()"是一個運算符,名字叫函數使用運算符(function call operator)。

3.游標的算術運算。

請再次記住:游標不是一個簡單的檔案類型,它是一個和游標所指物的檔案類型復合的檔案類型。因此,它的算術運算與之(游標所指物的檔案類型)密切相關。

int a[8];

int* p = a;

int* q = p + 3;

p++;

游標的加減並不是游標本身的二進製表示加減,要記住,游標是一個元素的位址,它每加一次,就指向下一個元素。所以:

int* q = p + 3;//q指向從p開始的第三個整數。

p++;//p指向下一個整數。

double* pd;

……//某些計算之後

double* pother = pd - 2;//pother指向從pd倒數第二個double數。

4.游標本身的大小。

在一個現代典型的32位電腦上【注3】,機器的記憶體模型大概是這樣的,想像一下,記憶體空間就像一個連續的房間群。每一個房間的大小是一個字元(一般是二進制8位)。有些東西大小是一個字元(比如char),一個房間就把它給安置了;但有些東西大小是幾個字元(比如double就是8個字元,int就是4個字元,我說的是典型的32位),所以它就需要幾個房間才能安置。

注3:什麼叫32位?就是機器CPU一次處理的資料寬度是32位,機器的暫存器容量是32位,機器的資料,記憶體位址總線是32位。當然還有一些細節,但大致就是這樣。16位,64位,128位可以以此類推。

這些房間都應該有編號(也就是位址),32位的機器記憶體位址空間當然也是32位,所以房間的每一個編號都用32位的二進制數來編碼【注4】。請記住游標也可以作為值使用,作為值的時候,它也必須被安置在房間中(儲存於在記憶體中),那麼指向一個值的游標需要一個位址大小來儲存於,即32位,4個字元,4個房間來儲存於。

注4:在我們平常用到的32位電腦上,絕少有將32位真實記憶體位址空間全用完的(232 = 4G),即使是伺服器也不例外。現代的作業系統一般會實現32位的虛擬位址空間,這樣可以方便運用程序的編制。關於虛擬位址(線性位址)和真實位址的區別以及實現,可以參考《Linux來源碼情景分析》的第二章儲存於管理,在網際網路上關於這個主題的文章汗牛充棟,你也可以google一下。

但請注意,在C++中指向對像成員的游標(pointer to member data or member function)的大小不一定是4個字元。為此我專門編製了一些程序,發現在我的兩個編譯器(VC7.1.3088和Dev-C++4.9.7.0)上,指向對像成員的游標的大小沒有定值,但都是4的倍數。不同的編譯器還有不同的值。對於一般的普通類(class),指向對像成員的游標大小一般為4,但在引入多重虛擬繼承以及虛擬函數的時候,指向對像成員的游標會增大,不論是指向成員資料,還是成員函數。【注5】。

注5:在Andrei Alexandrescu的《Modern C++ Design》的5.13節Page124中提到,成員函數游標實際上是帶標記的(tagged)unions,它們可以對付多重虛擬繼承以及虛擬函數,書上說成員函數游標大小是16,但我的實踐告訴我這個結果不對,而且具體編譯器實現也不同。一直很想看看GCC的來源碼,但由於旁騖太多,而且心不靜,本身難度也比較高(這個倒是不害怕^_^),只有留待以後了。

還有一點,對一個類的static member來說,指向它的游標只是普通的函數游標,不是pointer to class member,所以它的大小是4。

5.游標運算符&和*

它們是一對相反的操作,&取得一個東西的位址(也就是游標),*得到一個位址裡放的東西。這個東西可以是值(對像)、函數、陣列、類成員(class member)。

其實很簡單,房間裡面居住著一個人,&操作只能針對人,取得房間號碼;

*操作只能針對房間,取得房間裡的人。

參照游標本身的檔案類型以及游標所指物的檔案類型很好理解。

小結:其實你只要真正理解了1,2,就相當於掌握了游標的牛鼻子。後面的就不難了,游標的各種變化和C語言中其它普通檔案類型的變化都差不多(比如各種轉型)。

二.陣列。

在C語言中,對於陣列你只需要理解三件事。

1.C語言中有且只有一維陣列。

所謂的n維陣列只是一個稱呼,一種方便的記法,都是使用一維陣列來模擬的。

C語言中陣列的元素可以是任何檔案類型的東西,特別的是陣列作為元素也可以。所以int a[3][4][5]就應該這樣理解:a是一個擁有3個元素的陣列,其中每個元素是一個擁有4個元素的陣列,進一步其中每個元素是擁有5個整數元素的陣列。

是不是很簡單!陣列a的記憶體模型你應該很容易就想出來了,不是嗎?:)

2.陣列的元素個數,必須作為整數常量在編譯階段就求出來。

int i;

int a;//不合法,編譯不會通過。

也許有人會奇怪char str[] = "test";沒有指定元素個數為什麼也能通過,因為編譯器可以根據後面的啟始化字元串在編譯階段求出來,

不信你試試這個:int a[];

編譯器無法推斷,所以會判錯說"array size missing in a"之類的訊息。不過在最新的C99標準中實現了變長陣列【注6】

注6:如果你是一個好奇心很強烈的人,就像我一樣,那麼可以檢視C99標準6.7.5.2。

3.對於陣列,可以獲得陣列第一個(即上標為0)元素的位址(也就是游標),從陣列名獲得。

比如int a[5]; int* p = a;這裡p就得到了陣列元素a[0]的位址。

其餘對於陣列的各種操作,其實都是對於游標的相應操作。比如a[3]其實就是*(a+3)的簡單寫法,由於*(a+3)==*(3+a),所以在某些程序的程式碼中你會看到類似3[a]的這種奇怪陳述式,現在你知道了,它就是a[3]的別名。還有一種奇怪的陳述式類似a[-1],現在你也明白了,它就是* (a-1)【注7】。

注7:你肯定是一個很負責任的人,而且也知道自己到底在幹什麼。你難道不是嗎?:)所以你一定也知道,做一件事是要付出成本的,當然也應該獲得多於成本的回報。

我很喜歡經濟學,經濟學的一個基礎就是做什麼事情都是要花成本的,即使你什麼事情也不做。時間成本,金錢成本,機會成本,健康成本……可以這樣說,經濟學的根本目的就是用最小的成本獲得最大的回報。

所以我們在自己的程序中最好避免這種邪惡的寫法,不要讓自己一時的智力過剩帶來以後自己和他人長時間的痛苦。用韋小寶的一句話來說:"賠本的生意老子是不幹的!"

但是對邪惡的瞭解是非常必要的,這樣當我們真正遇到邪惡的時候,可以免受它對心靈的困擾!

對於指向同一個陣列不同元素的游標,它們可以做減法,比如int* p = q+i;p-q的結果就是這兩個游標之間的元素個數。i可以是負數。但是請記住:對指向不同的陣列元素的游標,這樣的做法是無用而且邪惡的!

對於所謂的n維陣列,比如int a[2][3];你可以得到陣列第一個元素的位址a和它的大小。*(a+0)(也即a[0]或者*a)就是第一個元素,它又是一個陣列int[3],繼續取得它的第一個元素,*(*(a+0)+0)(也即a[0][0]或者*(*a)),也即第一個整數(第一行第一列的第一個整數)。如果採用這種陳述式,就非常的笨拙,所以a[0][0]記法上的簡便就非常的有用了!簡單明瞭!

對於陣列,你只能取用在陣列有效範圍內的元素和元素位址,不過最後一個元素的下一個元素的位址是個例外。它可以被用來方便陣列的各種計算,特別是比較運算。但顯然,它所指向的內容是不能拿來使用和改變的!

關於陣列本身大概就這麼多,下面簡要說一下陣列和游標的關係。它們的關係非常曖昧,有時候可以交替使用。

比如 int main(int args, char* argv[])中,其實參數列表中的char* argv[]就是char** argv的另一種寫法。因為在C語言中,一個陣列是不能作為函數引數(argument)【注8】直接傳送的。因為那樣非常的損失效率,而這點違背了C語言設計時的基本理念--作為一門高效的系統設計語言。

注8:這裡我沒有使用函數實參這個大陸術語,而是運用了台灣術語,它們都是argument這個英文術語的翻譯,但在很多地方中文的實參用的並不恰當,非常的勉強,而引數表示被引用的數,很形象,也很好理解。很快你就可以像我一樣適應引數而不是實參。

dereferance,也就是*運算符操作。我也用的是提領,而不是解引用。

我認為你一定智勇雙全:既有寬容的智慧,也有面對新事物的勇氣!你不願意承認嗎?:)

所以在函數參數列表(parameter list)中的陣列形式的參數聲明,只是為了方便程序員的閱讀!比如上面的char* argv[]就可以很容易的想到是對一個char*字元串陣列進行操作,其實質是傳送的char*字元串陣列的首元素的位址(游標)。其它的元素當然可以由這個游標的加法間接提領(dereferance)【參考注8】得到!從而也就間接得到了整個陣列。

但是陣列和游標還是有區別的,比如在一個文件中有下面的定義:

char myname[] = "wuaihua";

而在另一個文件中有下列聲明:

extern char* myname;

它們互相是並不認識的,儘管你的本義是這樣希望的。

它們對記憶體空間的使用方式不同【注9】。

對於char myname[] = "wuaihua"如下

myname

w
u
a
i
h
u
a
\0


對於char* myname;如下表

myname




\|/

w
u
a
i
h
u
a
\0


注9:可以參考Andrew Konig的《C陷阱與缺陷》4.5節。

改變的方法就是使它們一致就可以了。

char myname[] = "wuaihua";

extern char myname[];

或者

char* myname = "wuaihua";//C++中最好換成const char* myname = "wuaihua"。

extern char* myname;

C之詭譎(下)
三.檔案類型的識別。

基本檔案類型的識別非常簡單:

int a;//a的檔案類型是a

char* p;//p的檔案類型是char*

……

那麼請你看看下面幾個:

int* (*a[5])(int, char*); //#1

void (*b[10]) (void (*)()); //#2

doube(*)() (*pa)[9]; //#3

如果你是第一次看到這種檔案類型聲明的時候,我想肯定跟我的感覺一樣,就如晴天霹靂,五雷轟頂,頭昏目眩,一頭張牙舞爪的猙獰怪獸撲面而來。

不要緊(Take it easy)!我們慢慢來收拾這幾個面目可憎的紙老虎!

1.C語言中函數聲明和陣列聲明。

函數聲明一般是這樣int fun(int,double);對應函數游標(pointer to function)的聲明是這樣:

int (*pf)(int,double),你必須習慣。可以這樣使用:

pf = &fun;//賦值(assignment)操作

(*pf)(5, 8.9);//函數使用操作

也請注意,C語言本身提供了一種簡寫方式如下:

pf = fun;// 賦值(assignment)操作

pf(5, 8.9);// 函數使用操作

不過我本人不是很喜歡這種簡寫,它對初學者帶來了比較多的迷惑。

陣列聲明一般是這樣int a[5];對於陣列游標(pointer to array)的聲明是這樣:

int (*pa)[5]; 你也必須習慣。可以這樣使用:

pa = &a;// 賦值(assignment)操作

int i = (*pa)[2]//將a[2]賦值給i;


2.有了上面的基礎,我們就可以對付開頭的三隻紙老虎了!:)

這個時候你需要複習一下各種運算符的優先順序和結合順序了,順便找本書看看就夠了。

#1:int* (*a[5])(int, char*);

首先看到標幟符名a,"[]"優先等級大於"*",a與"[5]"先結合。所以a是一個陣列,這個陣列有5個元素,每一個元素都是一個游標,游標指向 "(int, char*)",對,指向一個函數,函數參數是"int, char*",返回值是"int*"。完畢,我們幹掉了第一個紙老虎。:)

#2:void (*b[10]) (void (*)());

b是一個陣列,這個陣列有10個元素,每一個元素都是一個游標,游標指向一個函數,函數參數是"void (*)()"【注10】,返回值是"void"。完畢!

注10:這個參數又是一個游標,指向一個函數,函數參數為空,返回值是"void"。

#3. doube(*)() (*pa)[9];

pa是一個游標,游標指向一個陣列,這個陣列有9個元素,每一個元素都是"doube(*)()"【也即一個游標,指向一個函數,函數參數為空,返回值是"double"】。

現在是不是覺得要認識它們是易如反掌,工欲善其事,必先利其器!我們對這種表達方式熟悉之後,就可以用"typedef"來簡化這種檔案類型聲明。

#1:int* (*a[5])(int, char*);

typedef int* (*PF)(int, char*);//PF是一個檔案類型別名【注11】。

PF a[5];//跟int* (*a[5])(int, char*);的效果一樣!

注11:很多初學者只知道typedef char* pchar;但是對於typedef的其它用法不太瞭解。Stephen Blaha對typedef用法做過一個總結:"建立一個檔案類型別名的方法很簡單,在傳統的變數聲明陳述式裡用檔案類型名替代變數名,然後把關鍵字 typedef加在該語句的開頭"。可以參看《程序員》雜誌2001.3期《C++高手技巧20招》。

#2:void (*b[10]) (void (*)());

typedef void (*pfv)();

typedef void (*pf_taking_pfv)(pfv);

pf_taking_pfv b[10]; //跟void (*b[10]) (void (*)());的效果一樣!

#3. doube(*)() (*pa)[9];

typedef double(*PF)();

typedef PF (*PA)[9];

PA pa; //跟doube(*)() (*pa)[9];的效果一樣!


3.const和volatile在檔案類型聲明中的位置

在這裡我只說const,volatile是一樣的【注12】!

注12:顧名思義,volatile修飾的量就是很容易變化,不穩定的量,它可能被其它執行緒,作業系統,硬體等等在未知的時間改變,所以它被儲存於在記憶體中,每次取用它的時候都只能在記憶體中去讀取,它不能被編譯器最佳化放在內部暫存器中。

檔案類型聲明中const用來修飾一個常量,我們一般這樣使用:const在前面

const int;//int是const

const char*;//char是const

char* const;//*(游標)是const

const char* const;//char和*都是const

對初學者,const char*;和 char* const;是容易混淆的。這需要時間的歷練讓你習慣它。

上面的聲明有一個對等的寫法:const在後面

int const;//int是const

char const*;//char是const

char* const;//*(游標)是const

char const* const;//char和*都是const

第一次你可能不會習慣,但新事物如果是好的,我們為什麼要拒絕它呢?:)const在後面有兩個好處:

A. const所修飾的檔案類型是正好在它前面的那一個。如果這個好處還不能讓你動心的話,那請看下一個!

B. 我們很多時候會用到typedef的檔案類型別名定義。比如typedef char* pchar,如果用const來修飾的話,當const在前面的時候,就是const pchar,你會以為它就是const char* ,但是你錯了,它的真實含義是char* const。是不是讓你大吃一驚!但如果你採用const在後面的寫法,意義就怎麼也不會變,不信你試試!

不過,在真實項目中的命名一致性更重要。你應該在兩種情況下都能適應,並能自如的轉換,公司習慣,商業利潤不論在什麼時候都應該優先考慮!不過在開始一個新項目的時候,你可以考慮優先使用const在後面的習慣用法。


四.參數可變的函數

C語言中有一種很奇怪的參數"…",它主要用在引數(argument)個數不定的函數中,最一般的就是printf函數。

printf("Enjoy yourself everyday!\n");

printf("The value is %d!\n", value);

……

你想過它是怎麼實現的嗎?

1. printf為什麼叫printf?

不管是看什麼,我總是一個喜歡刨根問底的人,對事物的源有一種特殊的癖好,一段典故,一個成語,一句行話,我最喜歡的就是找到它的來歷,和當時的意境,一個外文翻譯過來的術語,最低要求我會盡力去找到它原本的外文術語。特別是一個字的命名來歷,我一向是非常在意的,中國有句古話:"名不正,則言不順。" printf中的f就是format的意思,即按格式列印【注13】。

注13:其實還有很多函數,很多變數,很多命名在各種語言中都是非常講究的,你如果細心觀察追溯,一定有很多樂趣和滿足,比如哈希表為什麼叫 hashtable而不叫hashlist?在C++的SGI STL實現中有一個專門用於遞增的函數iota(不是itoa),為什麼叫這個奇怪的名字,你想過嗎?

看文章我不喜歡意猶未盡,己所不欲,勿施於人,所以我把這兩個答案告訴你:

(1)table與list做為表講的區別:

table:

-------|--------------------|-------

item1 | kadkglasgaldfgl | jkdsfh

-------|--------------------|-------

item2 | kjdszhahlka | xcvz

-------|--------------------|-------

list:

****

***

*******

*****

That's the difference!

如果你還是不明白,可以去看一下hash是如何實現的!

(2)The name iota is taken from the programming language APL.

而APL語言主要是做數學計算的,在數學中有很多公式會借用希臘字母,

希臘字母表中有這樣一個字母,大寫為Ι,小寫為ι,

它的英文拼寫正好是iota,這個字母在θ(theta)和κ(kappa)之間!

你可以http://www.wikipedia.org/wiki/APL_programming_language

下面有一段是這樣的:

APL is renowned for using a set of non-ASCII symbols that are an extension of traditional arithmetic and algebraic notation. These cryptic symbols, some have joked, make it possible to construct an entire air traffic control system in two lines of code. Because of its condensed nature and non-standard characters, APL has sometimes been termed a "write-only language", and reading an APL program can feel like decoding an alien tongue. Because of the unusual character-set, many programmers used special APL keyboards in the production of APL code. Nowadays there are various ways to write APL code using only ASCII characters.

在C++中有函數重載(overload)可以用來區別不同函數參數的使用,但它還是不能表示任意數量的函數參數。

在標準C語言中定義了一個頭文件專門用來對付可變參數列表,它包含了一組巨集,和一個va_list的typedef聲明。一個典型實現如下【注14】:

typedef char* va_list;

#define va_start(list) list = (char*)&va_alist

#define va_end(list)

#define va_arg(list, mode)
((mode*) (list += sizeof(mode)))[-1]

注14:你可以檢視C99標準7.15節獲得詳細而權威的說明。也可以參考Andrew Konig的《C陷阱與缺陷》的附錄A。

ANSI C還提供了vprintf函數,它和對應的printf函數行為方式上完全相同,只不過用va_list取代了格式字元串後的參數序列。至於它是如何實現的,你在認真讀完《The C Programming Language》後,我相信你一定可以do it yourself!

使用這些工具,我們就可以實現自己的可變參數函數,比如實現一個系統化的錯誤處理函數error。它和printf函數的使用差不多。只不過將stream重新轉發IP到stderr。在這裡我借鑒了《C陷阱與缺陷》的附錄A的例子。

實現如下:

#include

#include

void error(char* format, …)

{

va_list ap;

va_start(ap, format);

fprintf(stderr, "error: ");

vfprintf(stderr, format, ap);

va_end(ap);

fprintf(stderr, "\n");

exit(1);

}

你還可以自己實現printf:

#include

int printf(char* format, …)

{

va_list ap;

va_start(ap, format);

int n = vprintf(format, ap);

va_end(ap);

return n;

}

我還專門找到了VC7.1的頭文件看了一下,發現各個巨集的具體實現還是有區別的,跟很多預處理(preprocessor)相關。其中va_list就不一定是char*的別名。

typedef struct {

char *a0; /* pointer to first homed integer argument */

int offset; /* byte offset of next parameter */

} va_list;

其它的定義類似。


經常在Windows進行系統編程的人一定知道函數使用有好幾種不同的形式,比如__stdcall,__pascal,__cdecl。在Windows下_stdcall,__pascal是一樣的,所以我只說一下__stdcall和__cdecl的區別。

(1)__stdcall表示被使用端自身負責函數引數的壓入推疊和出棧。函數參數個數一定的函數都是這種使用形式。

例如:int fun(char c, double d),我們在main函數中使用它,這個函數就只管本身函數體的執行,參數怎麼來的,怎麼去的,它一概不管。自然有main負責。不過,不同的編譯器的實現可能將參數從右向左壓入推疊,也可能從左向右壓入推疊,這個順序我們是不能加於利用的【注15】。

注15:你可以在Herb Sutter的《More Exceptional C++》中的條款20:An Unmanaged Pointer Problem, Part 1:Parameter Evaluation找到相關的細節論述。

(2)__cdecl表示使用端負責被使用端引數的壓入推疊和出棧。參數可變的函數採用的是這種使用形式。

為什麼這種函數要採用不同於前面的使用形式呢?那是因為__stdcall使用形式對它沒有作用,被使用端根本就無法知道使用端的引數個數,它怎麼可能正確工作?所以這種使用方式是必須的,不過由於參數參數可變的函數本身不多,所以用的地方比較少。

對於這兩種方式,你可以編製一些簡單的程序,然後反彙編,在彙編程式碼下面你就可以看到實際的區別,很好理解的!

重載函數有很多匹配(match)規則使用。參數為"…"的函數是匹配最低的,這一點在Andrei Alexandrescu的驚才絕艷之作《Modern C++ Design》中就有用到,參看Page34-35,2.7"編譯期間偵測可轉換性和繼承性"。


後記:

C語言的細節肯定不會只有這麼多,但是這幾個出現的比較頻繁,而且在C語言中也是很重要的幾個語言特徵。如果把這幾個細節徹底弄清楚了,C語言本身的神秘就不會太多了。

C語言本身就像一把異常鋒利的剪刀,你可以用它做出非常精緻優雅的藝術品,也可以剪出一些亂七八糟的廢紙片。能夠將一件武器用到出神入化那是需要時間的,需要多長時間?不多,請你拿出一萬個小時來,英國Exter大學心理學教授麥克.侯威專門研究神童和天才,他的結論很有意思:"一般人以為天才是自然而生、流暢而不受阻的閃亮才華,其實,天才也必須耗費至少十年光陰來教學他們的特殊技能,絕無例外。要成為專家,需要擁有頑固的個性和堅持的能力……每一行的專業人士,都投注大量心血,培養自己的專業才能。"【注16】

注16:台灣女作家、電視節目主持人吳淡如《拿出一萬個小時來》。《讀者》2003.1期。"不用太努力,只要持續下去。想擁有一輩子的專長或興趣,就像一個人跑馬拉松賽一樣,最重要的是跑完,而不是前頭跑得有多快。"

推薦兩本書:

K&R的《The C Programming language》,Second Edition。

Andrew Konig的《C陷阱與缺陷》。本文從中引用了好幾個例子,一本高段程序員的經驗之談。

但是對純粹的初學者不太合適,如果你有一點程序設計的基礎知識,花一個月的時間好好看看這兩本書,C語言本身就不用再花更多的精力了
__________________
http://bbsimg.qianlong.com/upload/01/08/29/68/1082968_1136014649812.gif
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次
 



發表規則
不可以發文
不可以回覆主題
不可以上傳附加檔案
不可以編輯您的文章

論壇啟用 BB 語法
論壇啟用 表情符號
論壇啟用 [IMG] 語法
論壇禁用 HTML 語法
Trackbacks are 禁用
Pingbacks are 禁用
Refbacks are 禁用


所有時間均為台北時間。現在的時間是 02:20 AM


Powered by vBulletin® 版本 3.6.8
版權所有 ©2000 - 2024, Jelsoft Enterprises Ltd.


SEO by vBSEO 3.6.1