C/C++復仇(上)

 

由於前幾天的筆試很大程度上刺激了我,讓我越發感覺到自己的C/C++基礎十分地薄弱,故而想要找幾本經典的C/C++書本來深入瞭解一下C/C++語言特性,以及其中需要注意的問題。

google了一下C語言經典著作,得到了我想要的結果:

《C專家編程》

《C語言詳解》

《C語言核心技術》

《C陷阱與缺陷》

《C和指針》

  詳細請到C經典著作書單察看介紹。

正是我想要補的,之前學習C語言,只是泛泛地學習了一些語言特性,然後編寫了若干行的代碼,知道了怎麼使用指針,數組,結構體,以及用它編寫二叉樹,線性表等數據結構,還有實現了一遍大部分排序算法。

語言細節 AND 正確的程序

常常遇到這樣的情況,程序寫到一半,忘了這個代碼的語言細節是什麼的,例如++自增的使用,而這往往涉及到程序的正確性。有經驗的程序員往往會發現i++和++i在很多情況下有很大不同,一時的麻痹或者粗心,都有可能造成重大的錯誤。記得之前有一個這樣的故事:

美國宇航局發射航天飛機,但是因爲一個程序員將“;”寫成了“,” ,最後造成了飛機失事。據說這是最昂貴的錯誤。

 

代碼是人編寫的,難免會有錯誤,但是很多錯誤都是我們不清楚語言細節,或者某個語言的陷阱,從而失足跌倒。現實生活中很多代碼都有錯誤,而***總是能夠利用各種程序中的缺陷和漏洞,攻破我們的系統,拿走我們的數據和金錢。(我想起了《鹿鼎記》裏面的,我們的財寶和女人,哈哈)。所以要寫可靠的代碼,必須要懂得一些常用的語言細節和規避陷阱的方法。

具體的問題

我們編程的時候往往運行出結果就ok了,拋下程序去玩了。這是很不正確的學習方法,正確的學習方法應該深入理解程序在計算機程序在編譯和運行時做了什麼,故而多跟蹤調試程序是有好處的。看看每一步都發生了什麼,察看內存中的數據起了什麼變化。

關於高級語言的內存管理

這裏有一篇關於內存管理的很好的說明了哪些數據是存儲在什麼區的。學習過彙編語言的我們,很容易就想起來我們的程序包括一些段:數據段,代碼段,堆棧段。那麼在高級語言中,這些數據又是怎樣存儲的呢?如下:

棧stack: 存放局部變量,棧表示的是一種後進先出,表達了我程序調用的順序,故而棧裏面存放的是我們的函數中的局部變量,包括變量,指針,參數等等。棧內數據是共享的,回收工作是由內存管理來做的。

 

堆heap: 說白了就是程序申請的內存區,這裏一塊,那裏一塊,堆是受到保護的,不能互相訪問。而回收的時候,我們要收回指向他們的指針,然後GC(gabage collection)來處理。一般malloc,或者是new的對象都是存儲在堆裏面的。

 

(全局)靜態區: 全局區和靜態區是是在一起的。包括全局變量global修飾的變量,static修飾的變量。有的時候,儘管沒有static修飾,也算是靜態區的。例如 char *p = "hello";  字符串"hello"就屬於靜態區的,因爲沒有專門爲它分配空間,若是char c_arr[] = "hello"; 則有爲字符串分配內存,故而是在棧區。

 

文字常量區:常量字符串就是放在這裏的。例如cout<< "hello world"<<endl; 那麼字符串hello world就存放在常量區。

 

程序代碼區:存放程序的二進制代碼。

 

深刻理解程序和數據的存儲,會更加明白如何編寫節省資源和高效的程序,特別是分析由於存儲問題帶來的性能瓶頸問題,這在web和服務器編程上都很重要。

 

關於C語言的sizeof

我做了以下實驗,對指針進行了sizeof關鍵字操作,以及指針的加法操作。

  1. // 關於數組的sizeof運算 
  2.     int a[100]={0,1}; 
  3.     printf("%d\n",sizeof(a));           // 打印的是數組的很多信息, 
  4.     printf("%d\n",sizeof(a[100]));      // 
  5.     printf("%d\n",sizeof(&a));          // 是a的地址 
  6.     printf("%d\n",sizeof(&a[0]));       //  
  7.     fun(a);                            // 作爲參數傳遞的數組,其實是個指針 


fun的定義如下:

 

  1. void fun(int b[100]) 
  2.     printf("%d\n",sizeof(b)); 
我得到的結果是:
 
  1. // 得到的結果是 
  2. // 400  ​ 
  3. // 4 
  4. // 4 
  5. // 4 
  6. // 4 

要是想在子函數中得到數組的大小,那麼使用下面的fun:

 
  1. void fun(int (*a)[100]) 
  2.     printf("%d\n",sizeof(*a)); 

調用的時候是

 
  1. fun(&a); 

故而能夠得到預期的400。

 

關於數據類型的溢出和轉換

各種數據類型的轉換,某些情況下系統是能夠自動類型轉換的,如賦值,表達式,循環條件判斷中。遵循的是從低精度變到高精度的原則。而從高精度強制轉換到低精度,則需要損失一部分的數據,所以應該斟酌爲之,特別是不確定轉換結果的情況下。

 

只要弄明白了數據類型在計算機裏面怎麼存儲,就能夠明白什麼樣的轉換是行的,什麼樣的轉換是不行的。整形數據在內存中,存儲的都是補碼。對於負數,補碼是反碼+1,而對正數補碼就是本身的碼。例如char類型的(默認是unsigned)可以表示[0,255] ,如果是signed,那麼就可以表示[-128,127],也就是說分了一半去表示負數。int同理,一般的程序中,不會有太多的類型轉換,因爲這潛在着不安全因素,例如if裏面的表達式就是一個很好的例子。

對於bool型的,假如有個checked表示某個步驟是否通過檢查, 用if(checked)。

對於int型,直接if( 0 == num) 比較

對於浮點型,用 if ( 0-EP<num && num > 0+EP) 來確定是否和0相等

所以,我們要用同樣類型的去比較,C中可以while(a), a是一個int型,Java就比較嚴格,不允許將int轉換爲bool類型做判斷。

關於運算符的優先級

這個就要參考程序語言的手冊了,我們自己寫程序的時候,最保險的就是給各種表達式都加上括號,以避免意外的求值出現。同級的按結合順序來判斷。例如+,-,*,/ , %是左結合;而對於!,=這樣的,就屬於右結合。有什麼規律可循呢?所有的優先級中,只有三個優先級是從右至左結合的,它們是單目運算符、條件運算符、賦值運算符。其它的都是從左至右結合。當然,運算符是有級別的,級別高的先執行。具體請參考百度百科C運算符

 

關於C語言的特別運算符號

自增++等,++的位置很重要,i++,表示用完i再自增1;而++i表示先做自增,然後再使用新的i值。

自減--,同理。這兩個很有技巧的運算符使用時要特別小心。有時候讓i用完,自增1很有用,例如最尋常的for循環,以及排序裏面,上次做一道題目,是插入排序,其中我就搞岔了++i和i++。看來還需要好好修煉哪~

END

 

by bibodeng 2013-04-16 21:45:44 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章