程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 精華:C++編程新手錯誤語錄(續一)

精華:C++編程新手錯誤語錄(續一)

編輯:C++入門知識
  廢話不說,直接進入正題,本文承接先前發布的《C/C++編程新手錯誤語錄》(http://www.pconline.com.cn/pcedu/empolder/gj/c/0508/691597.Html),繼續歸納錯誤語錄。 <!-- frame contents --> <!-- /frame contents --> (8)“我想用malloc”、“我用不好malloc”
  來看看一個變態程序:
  /* xx.c:xx模塊實現文件 */
  int *pInt;
  /* xx模塊的初始化函數 */
  xx_intial()
  {
  pInt = ( int * ) malloc ( sizeof( int ) );
  ...
  }
  /* xx模塊的其他函數(僅為舉例)*/
  xx_otherFunction()
  {
  *Int = 10;
  ...
  }
  這個程序定義了一個全局整型變量指針,在xx模塊的初始化函數中對此指針動態申請內存,並將pInt指向該內存首地址,並在xx模塊的其他函數中都使用pInt指針對其指向的整數進行讀取和賦值。
  
  這個程序讓我痛不欲生了好多天,扼腕歎息!這是我母校計算機系一位碩士的作品!作者為了用上malloc,拼命地把本來應該用一個全局整型變量擺平的程序活活弄成一個全局整型指針並在初始化函數中“動態”申請內存,自作聰明而正好暴露自己的無知!我再也不要見到這樣的程序。
  
  那麼malloc究竟應該怎麼用?筆者給出如下規則:
  規則1 不要為了用malloc而用malloc,malloc不是目的,而是手段;
  
  規則2 malloc的真正內涵體現在“動態”申請,假如程序的特性不需動態申請,請不要用malloc;
  
  上面列舉的變態程序完全不具備需要動態申請的特質,應該改為:
  /* xx.c:xx模塊實現文件 */
  int example;
  /* xx模塊的初始化函數 */
  xx_intial()
  {
  ...
  }
  /* xx模塊的其他函數(僅為舉例) */
  xx_otherFunction()
  {
  example = 10;
  ...
  }
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
  規則3 什麼樣的程序具備需要動態申請內存的特質呢?包含兩種情況:
  (1)不知道有多少要來,來了的又走了
  不明白?這麼說吧,譬如你正在處理一個報文隊列,收到的報文你都存入該隊列,處理完隊列頭的報文後你需要取出隊列頭的元素。
  
   <!-- frame contents --> <!-- /frame contents -->   你不知道有多少報文來(因而你不知道應該用多大的報文數組),這些來的報文處理完後都要走(釋放),這種情況適合用malloc和free。
  
  (2)慢慢地長大
  譬如你在資源受限的系統中編寫一文本編輯器程序,你怎麼做,你需要這樣定義數組嗎?
  char str[10000];  不,你完全不應該這麼做。即使你定義了一個10000字節大的字符串,用戶假如輸入10001個字符你的程序就完完了。
  
  這個時候適合用malloc,因為你根本就不知道用戶會輸入多少字符,文本在慢慢長大,因而你也應慢慢地申請內存,用一個隊列把字符串存放起來。
  
  那麼是不是應該這樣定義數據結構並在用戶每輸入一個字符的情況下malloc一個CharQueue空間呢?
  typedef strUCt tagCharQueue
  
   {
  char ch;
  struct tagCharQueue *next;
  }CharQueue;
  不,這樣做也不對!這將使每個字符占據“1+指針長度”的開銷。
  
  正確的做法是:
  typedef struct tagCharQueue
  {
  char str[100];
  struct tagCharQueue *next;
  }CharQueue;
  讓字符以100為單位慢慢地走,當輸入字符數達到100的整數倍時,申請一片CharQueue空間。
  
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
  規則4 malloc與free要成對出現
  它們是一對恩愛夫妻,malloc少了free就必然會慢慢地死掉。成對出現不僅體現在有多少個malloc就應該有多少個free,還體現在它們應盡量出現在同一函數裡,“誰申請,就由誰釋放”,看下面的程序:
   <!-- frame contents --> <!-- /frame contents --> char * func(void)
  {
  char *p;
  p = (char *)malloc(…);
  if(p!=NULL)
  …; /* 一系列針對p的操作 */
  return p;
  }
  /*在某處調用func(),用完func中動態申請的內存後將其free*/
  char *q = func();
  …
  free(q);
  上述代碼違反了malloc和free的“誰申請,就由誰釋放”原則,代碼的耦合度大,用戶在調用func函數時需確切知道其內部細節!正確的做法是:
  /* 在調用處申請內存,並傳入func函數 */
  char *p=malloc(…);
  if(p!=NULL)
  {
  func(p);
  …
  free(p);
  p=NULL;
  }
  /* 函數func則接收參數p */
  void func(char *p)
  {
  … /* 一系列針對p的操作 */
  }
  規則5 free後一定要置指針為NULL,防止其成為“野”指針
  
  (9)“函數add編譯生成的符號就是add”
  int add(int x,int y)
  {
  return x + y;
  }
  float add(float x,float y)
  {
  return x + y;
  }
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
  即便是在C語言中,add函數被多數C編譯器編譯後在符號庫中的名字也不是add,而是_add。 <!-- frame contents --> <!-- /frame contents --> 而在C++編譯器中,int add(int x,int y)會編譯成類似_add_int_int這樣的名字(稱為“mangled name”),float add(float x,float y)則被編譯成_add_float _float,mangled name包含了函數名、函數參數數量及類型信息,C++依靠這種機制來實現函數重載。
  
  所以,在C++中,本質上int add( int x, int y )與float add( float x, float y )是兩個完全不同的函數,只是在用戶看來其同名而已。
  
  這就要求初學者們能透過語法現象看問題本質。本質上,語言的創造者們就是在玩各種各樣的花樣,以使語言具備某種能力,譬如mangled name花樣的目的在於使C++支持重載。而C語言沒有玩這樣的花樣,所以int add( int x, int y )與float add( float x, float y )不能在C程序中同時存在。
  
  (10)“沒見過在C語言中調用C++的函數”、“C/C++不能調用Basic、Pascal語言的函數”
  
  這又是一個奇天下之大怪的問題,“打死我都不相信C、C++、basic、pascal的函數能瞎調來調去”,可是有句話這麼說:
  沒有你見不到的,只有你想不到的!
  
  既然芙蓉姐姐也有其聞名天下的道理,那麼C、C++、Basic、Pascal的函數為什麼就不能互相調用呢?
  能!
  
  你可以用Visual C++寫一個DLL在Visual Basic、Delphi(Pascal的孫子,Object Pascal的兒子)中調用,也可以在Visual Basic、Delphi中寫一個DLL在Visual C++中調用不是?
  
  
  讓我們來透過現象看本質。首先看看函數的調用約定(以Visual C++來說明):
  (1) _stdcall調用
  
  _stdcall是Pascal程序的缺省調用方式,參數采用從右到左的壓棧方式,被調函數自身在返回前清空堆棧。
  WIN32 Api都采用_stdcall調用方式,這樣的宏定義說明了問題:
  #define WINAPI _stdcall  按C編譯方式,_stdcall調用約定在輸出函數名前面加下劃線,後面加“@”符號和參數的字節數,形如_functionname@number。
  
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
  (2) _cdecl調用
  _cdecl是C/C++的缺省調用方式,參數采用從右到左的壓棧方式,傳送參數的內存棧由調用者維護。_cedcl約定的函數只能被C/C++調用,每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。
  
   <!-- frame contents --> <!-- /frame contents -->   由於_cdecl調用方式的參數內存棧由調用者維護,所以變長參數的函數能(也只能)使用這種調用約定。關於C/C++中變長參數(…)的問題,筆者將另文詳述。
  
  由於Visual C++默認采用_cdecl 調用方式,所以VC中中調用DLL時,用戶應使用_stdcall調用約定。
  按C編譯方式,_cdecl調用約定僅在輸出函數名前面加下劃線,形如_functionname。
  
  (3) _fastcall調用
  _fastcall調用較快,它通過CPU內部寄存器傳遞參數。
  
  按C編譯方式,_fastcall調用約定在輸出函數名前面加“@”符號,後面加“@”符號和參數的字節數,形如number。
  
  要害字_stdcall、_cdecl和_fastcall可以直接加在函數前,也可以在Visual C++中設置,如圖1。
    圖1 在VC中設置函數調用約定  在創建DLL時,一般使用_stdcall調用(Win32 Api方式),采用_functionname@number命名規則,因而各種語言間的DLL能互相調用。也就是說,DLL的編制與具體的編程語言及編譯器無關,只要遵守DLL的開發規范和編程策略,並安排正確的調用接口,不管用何種編程語言編制的DLL都具有通用性。
  
  推而廣之,假如有這樣一個IDE開發環境,它能識別各種語言,所有語言采用相同的調用約定和命名規則,一個軟件內各種語言書寫的函數將能互相調用!
  
  這個世界上可能永遠不需要這樣一個IDE。
  
  (11)“英語、數學不好就學不好C/C++”
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
  這也許是20世紀最大的謊言,這句話最先是哪位大師的名人名言已無可考證,可此後一批批的人被它誤導。許多初學者因為這句話被嚇倒,放棄了做程序員的理想。 <!-- frame contents --> <!-- /frame contents --> 還有許多後來成為優秀程序員的人,在他們的成長過程中並沒有依靠深奧的數學,可他們還是在總結經驗時制造恐慌,號稱一定要具備高深的數學知識,唯恐別人笑話其學術水平不高。
  
  在下則認為,大多數情況下,程序設計不需要太深奧的數學功底,除非你所從事的程序設計涉及特定的專業領域(如語音及圖像處理、數字通信技術等)。在下這一觀點也許是革舊立新,而革命必然要流血犧牲(譚嗣同),所以恭候大家板磚。
  
  那麼英語在C/C++的學習中處於什麼地位呢?那就是能看懂資料,看懂MSDN。
  
  學編程的終極之道不在看書,而在大量地不斷地實踐。
  
  (12)“C++太難了,我學不會”
  
  
  又不知是誰的悲觀論調,許多初學者被C++嚇倒,“太難了,我學不好”,如弱者自憐。假如C++真的難到學不會,那麼C++的創造者們所從事的工作豈不是“非人力所能及也”?  
   
  在下認為,學習C++的態度應該是:戰略上藐視它,戰術上重視它,要敢於勝利(《毛主席語錄》)。當然也不可輕敵,不能因為把握了一點皮毛就以為自己牛B轟轟了(筆者曾經牛B轟轟了好一陣子,現在想來,甚覺當時幼稚)。
  
  假如你征服了C++,透徹理解了C++的語言特性及STL,那麼,其他語言想不被你征服都難了。
  
  本回書著落此處,更多錯誤語錄,當然是待續。
更多內容請看C/C++技術專題  Java編程開發手冊專題,或
 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved