亡羊補牢還是越錯越遠——“C99允許在函數中的復合語句中定義變量”


作者:starwing83網友

  譚書其實和C語言有一個很相像的地方,就是都出了很多個版本。然而C語言出新的版本是為了方面程序員、適應新的變化和開發風格。而一本教材出新的版本無非就是修正錯誤和描述語言的新方向。然而,如果一本教材的第一版就是概念不清胡說八道,卻又礙着面子不肯承認自己的錯誤,那最終的結果就是越錯越遠了。
  就比如這里:

  定義變量的位置:一般在函數開頭的聲明部分定義變量,也可以在函數外定義變量(即外部變量、全局變量,見第7章)。C99允許在函數中的復合語句(用一句花括號括起來)中定義變量。
————譚浩強 ,《C程序設計》(第四版),清華大學出版社, 2010年6月,(前言)p41

  我相信譚老一定沒有親自去看過C99標准,不知道是英文不好還是自信滿滿覺得自己比標准要牛。所以,雖然譚老的確知道有“標准化”這么一個說法,卻只有錯上加錯的份兒。事實上,我看不出這里的“一般”有什么憑據,難道譚老從沒有看過正式地C語言項目嗎?一般情況下,只有八十年代到九十年代的C語言項目才會“一般”在函數開頭定義變量,新的項目一般定義變量的位置是復合語句的開頭。將自己的臆測煞有介事地說成業界慣例,不知道這是無恥還是做得太多已經麻木了。
  讓我們來揭開謎底吧。實際上,這段話幾乎每一句都是錯誤的。”一般在函數開頭的聲明部分“,很遺憾地,C99根本就沒有“聲明部分”的說法,而C90中,聲明部分根本就不止限於函數開頭!“也可以在函數外定義變量”這一句是對的,然而括號壞事了:“(即外部變量,全局變量)”,事實上C語言根本沒有全局變量的說法,只有外部變量,靜態變量的說法,因為事實上這里的全局是有兩個含義的:文件域的全局,和整個程序范圍的全局。這兩個含義是根據變量的鏈接性來定義的,我們之前就提到了鏈接性的概念,而譚書對此只字不提。“C99運行在函數中的復合語句(用一對花括號括起來)中定義變量”,首先這是C90的特性,而不是C99的,其次,括號內的部分明顯代表譚老根本就不知道什么是復合語句,花括號本身是復合語句的一部分,復合語句花括號內的部分在標准中是被叫做“塊項列表(block-item-list)”的,而列表中的一個塊項既可以是聲明,也可以是語句。對基本概念都不清晰,也難怪錯的這么離譜了。
  我們總結一下:
  - C99實際上是允許你在任何地方聲明變量的。
  - C90聲明部分實際上是可以在任何復合語句的開頭的。
  也就是說,下面的代碼在C90里面仍然是合法的:

    void reverse(int array[], size_t size) {
        int i = 0, j = size - 1;
        for (; i < j; ++i, --j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }

   只有唯一一種情況是必須在函數開頭申請變量的:即該變量在整個函數的執行過程中都必須存在。而真正有這么一個需求是很罕見的。如同這里的 temp 變量,它只需要在交換的時候才存在,因此就不該把它和 i, j 聲明在一起。上面的代碼是合法的 C90 代碼,可以在任何嚴格支持 C90 (而不支持 C99)的編譯器下編譯。
  就算你用的是C90的編譯器,你也仍然可以獲得“隨處聲明變量”的特權,很簡單,在需要使用局部變量的時候,直接用復合即可,這也是復合語句作為一種單獨的語句類型出現的另一個理由。(前一個理由是作為其他語句(如if for)中的語句部分存在)
  在真正使用的時候申請變量是個好習慣,這也是 C99 真正的改變是允許變量在任意位置定義,而不僅限復合語句開頭的緣故。這可以防止變量的名字沖突,簡化邏輯——新申請的變量哪怕名字和之前申請的一致,也不會對舊的變量有任何影響,一旦新變量超過作用域,舊的變量仍然有效。這是C語言的一個重大特性,也被認為是C語言最優秀的一個特性,這個特性就是大名鼎鼎的“詞法作用域”。它被廣泛地用在了大量的語言上,是編程語言的一個約定俗成的基本特性。如果不允許在復合語句內申請變量,無異於讓C語言自廢武功。
  並且,我們知道,C語言的局部變量是分配在棧上的。那么局部變量的及時釋放也幫助節省棧空間:對於兩個平行的復合語句塊,其內部的局部變量事實上是共享一塊區域的,這就是詞法作用域的另一個實際好處。
  為什么譚老即使是願意讓C語言自廢武功,也不願意介紹真正的C99的變量聲明特性呢?原因是譚書的前一個版本言之鑿鑿地說明“變量只允許聲明在函數開頭”,記得我最開始看譚書的時候,就一直是這么申請變量的,直到自行翻閱C90標准才走出這個誤區。08年的時候我幫助一個上海交大的生物系研究生修改其 DNA 檢測代碼,就發現一堆的 i,j, k, l, m, n 被申請在函數開頭,整個程序十分難維護,后來花了很大功夫才將代碼整理完成,由此可見譚書危害之深。
  程序員一個重要的素質就是肯承認錯誤,肯承認失誤。程序本就是思維的結晶,是智慧最集中的產品。不肯承認錯誤,就一定會被更先進者替代。而譚書卻死死抓住“權威”兩字不放,寧願錯上加錯也不肯承認自己不是完人。這樣的心態怎么符合一個程序員的自我修養呢?又怎么培育合格的程序員呢?書的銷量越好,事實上是流毒越多而已。
  下面我們介紹一下C99真正的新特性,看看標准委員會為C語言的進步做出了什么樣的努力。
  C99從C++中引進了可以“隨處申請變量”的特性,並修改了 for 語句的語法,添加了一個新的 for 語句形式。對C99來說,符合語句內已經不區分“聲明部分”和“語句部分”,在任何地方都可以書寫聲明或者語句,而新加入的 for 語句的語法是允許 for 的第一個部分是聲明,后跟可選的表達式(注意聲明本身會帶上一個分號),后跟必須的分號,再跟可選的表達式。也就是說,上面的反轉函數實際上可以這么寫:

    void reverse(int array[], size_t size) {
        for (int i = 0, j = size - 1; i < j; ++i, --j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    }

 

   這時,i, j, temp 就同屬一個作用域了,即 for 語句的語句體。在 for 語句之外使用 i, j, temp 會造成編譯錯誤。
  注意和C++不同,對C99來說只有 for 才有在第一部分申請變量的殊榮, if、while等等語句都是不允許的。而在C++里面這些語句的條件判斷部分都是可以申請新變量的。
  從C99的新特性可以看出,C語言顯然易見地是支持並鼓勵“使用處聲明”的編程方法的。而譚書卻對此只字不提,掛着着C99的羊頭,賣着C89的狗肉,不願大大方方地承認自己的錯誤,甚至連一點點改過的意向都沒有,只是一門心思的一錯再錯,一條路走到黑。這種態度不止是一本教科書編撰者,就算只是一個普通的程序員,恐怕也逃不過被開除的命運。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM