1. gzyueqian
      13352868059
      首頁 > 新聞中心 > > 正文

      C語言漫談

      更新時間: 2008-05-17 10:59:13來源: 粵嵌教育瀏覽量:689

        C語言是被使用的廣泛的一種語言,其歷史相當久遠。而其發展也相當神速, 從當初的標準C發展到后來的C++。其性能也發生了很多很大的變化。C語言擁有眾多的編譯器,其中不乏者眾多。從當初的Turbo C引入集成化編譯環境后,C語言就以其靈活性,高效率,可移植性好深入人心。后來發展起來的C++,Java 等語言,無不是在 其基礎進行擴充,使其更為靈活,更方便易用。新的C++編譯器引入了很多特色。使得C語言語法更加靈活。摒棄了標準C對語法死板的要求。使得編程隨心所欲。這里推薦 Borland C++ 和Visual C++.當然這是指Dos應用,如果開發Windows應用程序,那么當 首推Visual C++.Visual C++的可視化及自動代碼生成功能相當強大。尤其它提供的 Wizard 和Appstudio,使得開發程序簡直成為一種享受。而且由于Visual C++在各版本之間的連續性,使得開發者不必經歷換版帶來的痛苦。從其1.5版直到的5.0版兼容性保持的很好。而且在VC中也包含了控制臺應用(Dos),以及Windows Application,application wizard各種應用,所以是一個強大的開發包。 學習C語言,起初會覺得要記的東西太多,這是由于它太靈活了。但是學到一定程度,就會嘗到甜頭了。這種靈活性帶來的是可讀性好,語法簡單,效率高。當然C語言的 特色還是它的指針對指針的透徹理解將是今后開發工作中的得力助手。因為在C++中指針無處不見,很多參數就完全是指針化的。雖然Java中摒棄了指針,那是從安全性 方面考慮。如果從性能上來說,那是大虧了。所以指針是一個核心。要學好C語言,無非要透徹理解書本概念,輔之以大量上機編程。要想提高應用水平, 就要多看些應用方面的書。比如看看數據結構,然后自己想辦法來實現其中的算法。總之,編程是靠編出來的,不是靠看出來的。在調試程序時,盡量自己解決,實在 解決不了,可以請教老師,總之,獨立思考很重要。有條件的話,在網上提問題,可受到事半功倍的效果。堅持下去,相信不久你就會成功的喜悅了。 以上所述,旨在拋磚引玉,若有不當,敬請見諒!

      編寫優質無錯代碼

      一. 引言
        八月上旬,深圳舉辦了一個討論會,主題是"編寫優質無錯代碼"。這個討論會吸引了深圳各大軟件公司,通信公司的程序員,系統分析員參加,并在討論會后紛紛表示,這種討論會很有實際價值,希望將這種形式的討論會繼續下去,形成一個論壇,以提高大家的編程水平和交換有價值的信息資料。
        這個活動的發起是從網絡上開始的。我偶然看到了這個討論會的論題,發生了興趣。本來周末的我一般是很懶的,沒什么事情是不會出門的。而當我看到這論題后,就給舉辦者發信表示愿意參加。于是,一個周六的下午,我就坐在了討論會的現場。參加完這個討論后,我覺得有必要把其中的精華部分寫下來,和網絡上的廣大程序員共享,于是就有了這篇文章。 二.主題:
        編寫優質無錯的代碼---討論會主題。相信每個程序員都有這種希望,誰都不愿意自己寫出來的代碼在release之后出錯,需要不停的修改維護。但是,主持人提出了這樣一個問題:"編寫優質無錯代碼是否必要?" 為什么呢?我稍微解釋一下。在項目的時間很緊張的時候,是按期完成任務重要,還是代碼的穩定性,優質無錯重要呢?
      主持人提出的四個具體問題是:
      1、編寫優質無錯的代碼的代價是什么?
      2、代碼的質量重要還是編寫效率重要?
      3、在壓力的情況下,你會犧牲質量來提高效率么?
      4、編寫優質無錯的代碼是否意味著效率的降低?
        對于個問題,編寫優質無錯代碼的代價當然是時間,不過隨著編程人員的經驗逐漸豐富,所需要的時間也逐漸減少。
        對于第二個問題,代碼的質量比編寫效率重要。當你花了1周時間寫出來的代碼需要你花一個月或者更長的時間去debug, 去修改錯誤,這種效率的損失是得不償失的。
        對于第三個問題, 這需要看項目經理或者產品經理的態度和專業精神了。如果在一個專業的項目經理或者產品經理的指揮下,當然是首先保證質量其次提高效率。而對于某些項目經理或者產品經理來說,按時完成任務是重要的,他們往往不在乎在軟件發布之后花比開發時間長得多的時間去修改程序,維護錯誤。因為,對于他們來說,首先是要完成任務,好給上級領導交差,至于后期維護,就是另外一個任務了,維護花的時間多,正說明了他這個項目的復雜性和難度。而對于開發人員來講,所希望的則正好相反。開發人員不喜歡花太多的時間在一個爛攤子上。所以,在討論會上,大家紛紛表示,應該讓項目經理或者產品經理也來聽一聽這個討論會:-)。
        對于第四個問題,當然優質無錯代碼不是意味著效率的降低,而是正好相反,對提高效率有很好的促進作用。一個版本發布之后,如果因為錯誤太多,開發人員不得不去花很多時間修改bug, 甚至要從系統的體系結構方面去做大的改動,重新編寫部分代碼,這種效率的降低才是更大,更不能承受的。而且,花了太多的時間在老版本的維護上,必然影響到新版本的工程進度,直接影響到整個產品線的質量和進度,嚴重的甚至會毀掉整個產品。

        對于這一個主題,我的回答是,在時間允許的范圍之內,盡量提高代碼的質量,不追求慢工出細活,不追求代碼的無錯,但是要保證99%以上的無錯。這樣,在時間的壓力下,在質量要求的束縛下,就要求程序員有一個良好的習慣,和穩健的編程風格,以保證代碼的優質無錯。這就是第二個問題:什么是編寫優質無錯的代碼的核心思想?優質無錯是相對的,而不是的。任何代碼,都不可能說是無錯的,但是在絕大部分情況下,是穩定的,強健的,優質的,無錯的。每次發布的時候,都會對上次的發布版本做若干修改,增強功能的同時,也要修改若干bug。
      那么,核心思想就是:
      怎樣才能自動地查出這個錯誤。
      怎樣才能避免這個錯誤。

      三.編寫優質無錯代碼的經驗
        在說了上面很多理論性的問題之后,來看一看具體問題。
      先來看一看一個具體的題目:(我本人就是先在網上看了這個題目,才對這個討論會發生興趣的)
      題目1:
      作為開發團隊的一員,你需要實現一些庫函數提供給其他人使用。假設你實
      現的一個函數原型如下:

      int DoSomeThing(char* pParam)
      {
      ...
      }

        你們約定好參數pParam不能為NULL,但為了防止調用者錯誤傳遞NULL,你需要在你的函數里做判斷處理。
      請問你會選擇那種方式,并說明原因?

      (a) if (!pParam)
      return 0;

      (b) if (!pParam)
      return ERROR_PARAM;

      (c) if (!pParam)
      pParam = "";
      ...

      (d) if (!pParam)
      throw EXCEPTION_ERROR_PARAM;

      (e) if (!pParam)
      MessageBox(...);

      (f) assert(!pParam);

        (附加說明一點,基于目前開發人員技術分布情況和參與討論會的人員的技術分布情況,這次所列舉的例子都是C/C++和Java方面的,不涉及到VB, PB,Delphi等語言。不過對于這些程序員,討論也是有借鑒作用的。)
      關于這個問題,大概是所有的程序員都會遇到的。所以,在網上和討論會上,都發生了激烈的爭論和意見交換。我大概把主要的幾種觀點記錄了一下,列舉在下面:

        1、選擇f的理由
        因為非NULL是約定,所以可以確定是調用者的問題,f可以明確地指出這一點,防止錯誤擴散。
      我的附加說明: 防止錯誤擴散的意思是,如果用其他方式,比如throwexception的方式,這個異常不一定會在調用此函數的上一層被捕捉到,可能會被繼續拋出直到上一層或者直到在某一層被catch到,這樣的話,錯誤就會距離發生地點很遠,擴散開來。
        這一觀點,代表了一大部分的程序員的觀點。

      2、反對用f
        不贊成assert, assert更重要的作用是程序體里面的一個注釋, 在閱讀程序的時候起作用不能依賴他來檢測錯誤, 很大程度上assert容易使使用者依賴它本不應該依賴的東西。這也代表了部分程序員的觀點,認為assert是不可依賴的,而應該依賴于錯誤檢測,比如返回值或者異常。

      3、另外一種觀點
        f和d都可取。如果沒有系統開銷的考慮,d則更好些。可以一舉兩得。如果沒人catch這個exception,其結果就跟f一樣,按bug處理,dump core留下一stack trace。如果有人catch,那就按運行錯誤處理......但是返回一特初值表示錯誤,只是將錯誤上交,掩耳盜鈴而已。終總得有個人assert,messagebox,throw exception,perror+exit,或別得什么的。既然已經是約定,就干脆付起責任。

      4、一種反對d的理由
        不可用d, 這就像你用人,卻不相信人一樣,偏要try,catch防范他。其實那個錯是自己造成的,如果看到異常就容易不檢討自己。

      5、關于觀點3的支持意見
        討論過程中,有人認為assert檢查的是bug, 而異常是可以恢復的意外情況。所以,觀點3的支持者說:可恢復的意外是可以理解的,但可恢復的bug就沒什么意義了。既然已經約定好了,你再違背,就屬于是bug而不是意外了(比如打不開文件什么的)。很多庫函數都不檢查指針的合法性(除了系統調用以外,因為總不能讓系統dump core吧),也不檢查指針是否為NULL(因為如果層層都檢查,必定勞民傷財,干脆讓上面調用的人在調用之外查)。

      6、選擇d+f
        選f+d, 好處如下:
      a以激烈的方式,充分暴露調用都的錯誤!能及時修改BUG
      b便于調試,問題出現后,直接到事故現場。比120還快!
      c對于realse版的代碼沒有任何副作用。
      d以處理的代價來看 采用斷言也是編寫小一種。
      e它是多語種,多平臺所通用的方式, 如:C /C++ VB,Java1.4 在win ,unix
      通吃, 便于移置!
      如果在現實中,測試沒有能找到所有的BUG,那可能就要用異常來幫忙了!

        當然,我也提出了我的觀點, 我支持觀點6。理由如下:
        assert只在debug標志的時候有用,而在編譯release版本的時候不起作用。assert對于檢查硬編碼的錯誤,是非常有用  的,能夠及時的查處編碼的錯誤。比如borland c++的類庫源代碼中就有很多這樣的assert。但是assert不是的,因為有很多錯誤的發生不是完全在編譯時發生的,而是運行時的錯誤。在release后,assert是不可能依賴的。那么,我們就需要exception這一機制來檢測運行時錯誤,并相應的做出處理。當然,在異常檢測和處理過程中還有許多需要討論的問題,由于不是這一題目的范圍,我們沒有必要繼續討論得太多,但是,提出來希望大家注意:異常不是捕獲了就完成任務了,而要對于不同的情況,采取不同的處理辦法,千萬不能只是捕獲,而不做任何處理,那樣和不捕獲異常沒有任何別。 在題目剛剛提出的時候,選擇各種答案的人都有,所以,我有必要在這里把其他答案為什么不能選的理由說一下。 (a) if (!pParam)
      return 0;
        這是很多初級程序員常常采取的一種方式。返回值設為0。 因為函數的返回值往往是計算的結果,不贊成把錯誤標志值和計算結果混在一起使用,容易造成使用者的誤會。當然,在很多unix函數中,由于歷史原因,還存在很多這樣子的函數,所以需要指出,不要沿用這種方式。

      (b) if (!pParam)
      return ERROR_PARAM;
       b比a稍微好一點點,返回了一個常量或者預定義的宏。 從返回值的字面上,調用者能知道發生了什么錯誤,但是,這也不是一種好的方法。


      (c) if (!pParam)
      pParam = "";
      ...
        這是不好的方式。直接給pParam賦予空字符串,然后繼續函數過程,這容易造成不可預料的后果,是程序不穩定的根源。


      (d) if (!pParam)
      throw EXCEPTION_ERROR_PARAM;拋出異常,剛剛已經討論過了,不再贅述。


      (e) if (!pParam)
      MessageBox(...);
        這是一種比較可笑的方式,當然也有不少人用。MessageBox是直接彈出一個對話框,告訴使用者,出錯了。但是并不做任何處理,程序繼續往下執行,直到出錯崩潰。呵呵


      (f) assert(!pParam);
        斷言,剛剛已經討論過了,不再贅述。

        以上這個題目,引發了所有與會者的興趣,討論異常熱烈,,主持人也給出了自己的觀點:d+f。當然這并不是標準答案,因為編程這一門課程本來就沒有什么標準答案,大家見仁見智,這個答案只是經驗的積累。

        主持人緊接著列出了"編寫優質無錯代碼的經驗":
      a.理想的編譯器和實際的編譯器
      b.使用斷言
      c.函數的界面設計
      d.考慮風險
      e.態度的問題

      以上是本節的主要內容。斷言,剛剛的問題中已經討論過了,來看看其他的內容。

      理想的編譯器和實際的編譯器:

      題目二:
      下面memcpy函數實現有什么問題:
      Void *memcpy(void *pvTo,void *pvFrom,size_t size){
      byte *pbTo=(byte *) pvTo;
      byte *pbFrom=(byte *)pvFrom;
      while(size -- >0);
      *pbTo++= *pbFrom++;
      return pvTo;
      }

        呵呵,粗略一看,這函數還真找不出問題來。但是仔細看看,你就會發現while(size -- >0);這里多了一個分號,導致下面的*pbTo++= *pbFrom++;不是在while循環中執行多次,而是只執行了一次。當然這不是函數設計者的預期結果,而編譯器卻不會報告錯誤,因為while(size -- >0);從語法上來講,并沒有錯誤。這就是理想的編譯器和實際的編譯器的區別所在。

      那么,該怎么檢查這種錯誤呢?主持人提出了如下辦法:
      while(size -- >0) NULL; 可以加入NULL來解決空語句. 這樣子,當你需
      要 while(size -- >0)
      *pbTo++= *pbFrom++;
      這種形式的時候,就不會發生錯誤了,只需要用眼睛看看,就能發現了。
      兩點好處 1 無冗余代碼,2 使人更明白。減少風險.

      還有人會這么寫
      if( (n=read(....)) == 1) ....
      在這里,賦值符號=和判斷相等的符號==容易敲錯,而編譯器又檢查不出來,可能就會有如下錯誤:
      If(ch = ‘ ’)...;這也是需要注意的問題。

      理想的編譯器和實際的編譯器小結:
      a.把屢次出錯的合法的C習慣用法看成程序中的錯誤
      b.增強編譯器的警告級別
      c.使用其它的工具來檢查代碼 如 Lint 等
      d.進行單元測試
      e.消除程序錯誤的方法是盡可能早、盡可能容易地發現錯誤,要尋求費力小的自動查錯的方法
      f.努力減少程序員查錯所需的技巧

      使用斷言
      題目三
      下面函數實現,哪一個好,為什么?
      a.
      char Uptolower(char ch){
      if(ch >= ‘A’ && ch <= ‘Z’)
      return ch+=‘a’-’A’;
      return -1;
      }
      b.
      char Uptolower(char ch){
      assert(ch >= ‘A’ && ch <= ‘Z’);
      if(ch >= ‘A’ && ch <= ‘Z’)
      return ch+=‘a’-’A’;
      return ch;
      }
      c.
      char Uptolower(char ch){
      assert(ch >= ‘A’ && ch <= ‘Z’);
      return ch+(‘a’-’A’);
      }
      分析:
      a.該函數檢查ch是否在A..Z之間,如果是,則返回相應的小寫字符,如果


      不是,則返回-1。
      缺點在于:把錯誤標志值和計算結果混在一起使用,容易造成使用者的誤會。

        b.該函數使用了斷言,如果ch在A..Z之間則返回相應的小寫字符,如果不是,斷言會起作用,程序發生錯誤并退出。而一個return ch;則是在release的時候,如果不是A..Z之間,則返回原來的字符。但是,從書寫效率上來說,這個函數稍微羅嗦了一點。因為它重復使用了斷言和if判斷。

      c.該函數也使用了斷言,返回相應大寫字母的小寫字母。

      使用斷言的好處:
      a.暴露了調用者的錯誤
      b.便于調試
      c.對代碼沒有代價
      d.少的處理代價

      斷言使用舉例:
      void memcpy(void * pvTo,void *pvFrom,size_t size){
      void *pbTo= (byte *)pvTo;
      void *pbFrom= (byte * pvFrom);
      assert(pvTo !=NULL && pvFrom !=NULL);
      assert(pbTo >= pbFrom +size' 'pbFrom >= pbTo+size);

      }

      使用斷言的規則:
      a.要使用斷言對函數參數進行確認
      b.要從程序中刪去無定義的特性或者在程序中使 用斷言來檢查出無定義特性的非法使用
      c.不要浪費別人的時間-詳細說明不清楚的斷言
      d.消除所做的隱式假定,或者利用檢查其正確性
      e.在進行防錯性程序設計時,不要隱瞞錯誤防錯性程序設計雖然被譽為有較好的編碼風格,但它卻隱瞞了錯誤。要記住,我們正在談論的錯誤決不應該再發生,而對這些錯所進行的安全處理
      又編寫無錯代碼變得更加困難
      f.要利用不同的算法對程序的結果進行確認
      g.不要等待錯誤發生,要使用初始檢查程序

      斷言小結:
      a.要同時維護交付和調試兩個版本。封裝交付的版本,應盡可能地使用調試版本進行自動查錯。
      b.斷言是進行調試檢查的簡單方法。要使用斷言捕捉不應該發生的非法情況。不要混淆非法情況與錯誤情況之間的區別,后者是在終產品中必須處理的。
      c.使用斷言對函數的參數進行確認,并且在程序員使用了無定義的特性時向程序員報警。涵數定義得越嚴格,確認其參數就越容易。
      d.防錯性程序設計會隱瞞錯誤。在進行防錯編碼時,如果”不可能發生”的情況確實發生了,要使用斷言進行報警。


      寫到這里,我們初步探討了編寫優質無錯代碼的必要性,原則,和相關經驗。留幾個練習題目,大家也參與一下討論吧。
      練習題目1:
      下面的memset函數實現有什么問題?

      void *memset(void *pv, byte b, size_t size)
      {
      byte *pb = (byte *)pv;
      unsigned long l;
      size_t sizeSize;

      l = (b << 8) | b; /* 用4個字節拼成一個long */
      l = (l << 16) | l;
      pb = (byte *)longfill((long *)pb, l, size/4);
      size = size % 4;

      while (size-- > 0)
      *pb++ = b;
      return (pv);
      }

      練習題目2:

      下面的代碼用memset將三個局部變量置為0,請問可能會有什么問題?
      void DoSomeThing(...)
      {
      int i;
      int j;
      int k;

      memset(&k, 0, 3*sizeof(int)); // 將i,j,k置為0
      ...
      }

      練習題目3:

      定義結構如下:
      typedef struct
      {
      char c1;
      char c2;
      int n;
      } stru;
      請問sizeof(stru)等于多少?并說明理由。

      練習題目4:

      下面是C語言中兩種if語句判斷方式。請問哪種寫法更好?為什么?
      int n;

      if (n == 10) // 種判斷方式
      if (10 == n) // 第二種判斷方式

      練習題目5:

      下面的代碼有什么問題?
      void DoSomeThing(...)
      {
      char* p;
      ...
      p = malloc(1024); // 分配1K的空間
      if (NULL == p)
      return;
      ...
      p = realloc(p, 2048); // 空間不夠,重新分配到2K
      if (NULL == p)
      return;
      ...
      }

      練習題目6:

      下面的代碼有什么問題?
      char *DoSomeThing(...)
      {
      char str[16];

      ...
      return str;
      }

      練習題目7:
      下面的代碼有什么問題?

      char *_strdup( const char *strSource )
      {
      static char str[MAX_STR_LEN];

      strcpy(str, strSource);
      return str;
      }

      練習題目8:

      下面的代碼有什么問題?并請給出正確的寫法。
      try{
      FILE* fp = fopen("c:1.dat");
      if (NULL != fp)
      {
      ...
      }
      fclose(fp);
      }
      except(EXCEPTION_EXECUTE_HANDLER){
      }

      免費預約試聽課

      亚洲另类欧美综合久久图片区_亚洲中文字幕日产无码2020_欧美日本一区二区三区桃色视频_亚洲AⅤ天堂一区二区三区

      
      

      1. 午夜福利网址入囗 | 中国精品少妇HD | 日本精品在线一区欧美 | 中文有码在线观看 | 你懂的网站亚洲欧美另类在线 | 日韩中文字幕在线 |