C/C++復仇(上)

 html

因爲前幾天的筆試很大程度上刺激了我,讓我愈加感受到本身的C/C++基礎十分地薄弱,故而想要找幾本經典的C/C++書原本深刻了解一下C/C++語言特性,以及其中須要注意的問題。程序員

google了一下C語言經典著做,獲得了我想要的結果:web

《C專家編程》算法

《C語言詳解》編程

《C語言核心技術》數組

《C陷阱與缺陷》安全

《C和指針》服務器

  詳細請到C經典著做書單察看介紹。數據結構

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

語言細節 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 

相關文章
相關標籤/搜索