• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            大龍的博客

            常用鏈接

            統(tǒng)計(jì)

            最新評(píng)論

            [轉(zhuǎn)載]Bjarne Stroustrup的FAQ:C++的風(fēng)格與技巧

            翻譯:左輕侯



            (譯注:本文的翻譯相當(dāng)艱苦。Bjarne Stroustrup不愧是創(chuàng)立C++語(yǔ)言的一代大師,不但思想博大精深,而且在遣詞造句上,也非常精微深?yuàn)W。有很多地方,譯者反復(fù)斟酌,都不能取得理想的效果,只能盡力而為。

            Html格式的文檔見(jiàn)譯者主頁(yè):http://www.wushuang.net

            如果你對(duì)這個(gè)翻譯稿有任何意見(jiàn)和建議,請(qǐng)發(fā)信給譯者:onekey@163.com。

            原文的地址為:http://www.research.att.com/~bs/bs_faq2.html)
            (Bjarne Stroustrup博士,1950年出生于丹麥,先后畢業(yè)于丹麥阿魯斯大學(xué)和英國(guó)劍撟大學(xué),AT&T大規(guī)模程序設(shè)計(jì)研究部門負(fù)責(zé)人,AT&T 貝爾實(shí)驗(yàn)室和ACM成員。1979年,B. S開(kāi)始開(kāi)發(fā)一種語(yǔ)言,當(dāng)時(shí)稱為"C with Class",后來(lái)演化為C++。1998年,ANSI/ISO C++標(biāo)準(zhǔn)建立,同年,B. S推出其經(jīng)典著作The C++ Programming Language的第三版。)



            這是一些人們經(jīng)常向我問(wèn)起的有關(guān)C++的風(fēng)格與技巧的問(wèn)題。如果你能提出更好的問(wèn)題,或者對(duì)這些答案有所建議,請(qǐng)務(wù)必發(fā)Email給我(bs@research.att.com)。請(qǐng)記住,我不能把全部的時(shí)間都花在更新我的主頁(yè)上面。



            更多的問(wèn)題請(qǐng)參見(jiàn)我的general FAQ。



            關(guān)于術(shù)語(yǔ)和概念,請(qǐng)參見(jiàn)我的C++術(shù)語(yǔ)表(C++ glossary.)。



            請(qǐng)注意,這僅僅是一個(gè)常見(jiàn)問(wèn)題與解答的列表。它不能代替一本優(yōu)秀教科書(shū)中那些經(jīng)過(guò)精心挑選的范例與解釋。它也不能象一本參考手冊(cè)或語(yǔ)言標(biāo)準(zhǔn)那樣,提供詳細(xì)和準(zhǔn)確的說(shuō)明。有關(guān)C++的設(shè)計(jì)的問(wèn)題,請(qǐng)參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》(The Design and Evolution of C++)。關(guān)于C++語(yǔ)言與標(biāo)準(zhǔn)庫(kù)的使用,請(qǐng)參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》(The C++ Programming Language)。



            目錄:

            我如何寫(xiě)這個(gè)非常簡(jiǎn)單的程序?

            為什么編譯要花這么長(zhǎng)的時(shí)間?

            為什么一個(gè)空類的大小不為0?

            我必須在類聲明處賦予數(shù)據(jù)嗎?

            為什么成員函數(shù)默認(rèn)不是virtual的?

            為什么析構(gòu)函數(shù)默認(rèn)不是virtual的?

            為什么不能有虛擬構(gòu)造函數(shù)?

            為什么重載在繼承類中不工作?

            我能夠在構(gòu)造函數(shù)中調(diào)用一個(gè)虛擬函數(shù)嗎?

            有沒(méi)有"指定位置刪除"(placement delete)?

            我能防止別人繼承我自己的類嗎?

            為什么不能為模板參數(shù)定義約束(constraints)?

            既然已經(jīng)有了優(yōu)秀的qsort()函數(shù),為什么還需要一個(gè)sort()?

            什么是函數(shù)對(duì)象(function object)?

            我應(yīng)該如何對(duì)付內(nèi)存泄漏?

            我為什么在捕獲一個(gè)異常之后就不能繼續(xù)?

            為什么C++中沒(méi)有相當(dāng)于realloc()的函數(shù)?

            如何使用異常?

            怎樣從輸入中讀取一個(gè)字符串?

            為什么C++不提供"finally"的構(gòu)造?

            什么是自動(dòng)指針(auto_ptr),為什么沒(méi)有自動(dòng)數(shù)組(auto_array)?

            可以混合使用C風(fēng)格與C++風(fēng)格的內(nèi)存分派與重新分配嗎?

            我為什么必須使用一個(gè)造型來(lái)轉(zhuǎn)換*void?

            我如何定義一個(gè)類內(nèi)部(in-class)的常量?

            為什么delete不會(huì)將操作數(shù)置0?

            我能夠?qū)?void main()"嗎?

            為什么我不能重載點(diǎn)符號(hào),::,sizeof,等等?

            怎樣將一個(gè)整型值轉(zhuǎn)換為一個(gè)字符串?

            "int* p"正確還是"int *p"正確?

            對(duì)于我的代碼,哪一種布局風(fēng)格(layout style)是最好的?

            我應(yīng)該將"const"放在類型之前還是之后?

            使用宏有什么問(wèn)題?



            我如何寫(xiě)這個(gè)非常簡(jiǎn)單的程序?



            特別是在一個(gè)學(xué)期的開(kāi)始,我常常收到許多關(guān)于編寫(xiě)一個(gè)非常簡(jiǎn)單的程序的詢問(wèn)。這個(gè)問(wèn)題有一個(gè)很具代表性的解決方法,那就是(在你的程序中)讀入幾個(gè)數(shù)字,對(duì)它們做一些處理,再把結(jié)果輸出。下面是一個(gè)這樣做的例子:



            #include<iostream>

            #include<vector>

            #include<algorithm>

            using namespace std;



            int main()

            {

            vector<double> v;



            double d;

            while(cin>>d) v.push_back(d); // 讀入元素

            if (!cin.eof()) { // 檢查輸入是否出錯(cuò)

            cerr << "format error\n";

            return 1; // 返回一個(gè)錯(cuò)誤

            }



            cout << "read " << v.size() << " elements\n";



            reverse(v.begin(),v.end());

            cout << "elements in reverse order:\n";

            for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';



            return 0; // 成功返回

            }



            對(duì)這段程序的觀察:



            這是一段標(biāo)準(zhǔn)的ISO C++程序,使用了標(biāo)準(zhǔn)庫(kù)(standard library)。標(biāo)準(zhǔn)庫(kù)工具在命名空間std中聲明,封裝在沒(méi)有.h后綴的頭文件中。



            如果你要在Windows下編譯它,你需要將它編譯成一個(gè)"控制臺(tái)程序"(console application)。記得將源文件加上.cpp后綴,否則編譯器可能會(huì)以為它是一段C代碼而不是C++。



            是的,main()函數(shù)返回一個(gè)int值。



            讀到一個(gè)標(biāo)準(zhǔn)的向量(vector)中,可以避免在隨意確定大小的緩沖中溢出的錯(cuò)誤。讀到一個(gè)數(shù)組(array)中,而不產(chǎn)生"簡(jiǎn)單錯(cuò)誤"(silly error),這已經(jīng)超出了一個(gè)新手的能力——如果你做到了,那你已經(jīng)不是一個(gè)新手了。如果你對(duì)此表示懷疑,我建議你閱讀我的文章"將標(biāo)準(zhǔn)C++作為一種新的語(yǔ)言來(lái)學(xué)習(xí)"("Learning Standard C++ as a New Language"),你可以在本人著作列表(my publications list)中下載到它。



            !cin.eof()是對(duì)流的格式的檢查。事實(shí)上,它檢查循環(huán)是否終結(jié)于發(fā)現(xiàn)一個(gè)end-of-file(如果不是這樣,那么意味著輸入沒(méi)有按照給定的格式)。更多的說(shuō)明,請(qǐng)參見(jiàn)你的C++教科書(shū)中的"流狀態(tài)"(stream state)部分。



            vector知道它自己的大小,因此我不需要計(jì)算元素的數(shù)量。



            這段程序沒(méi)有包含顯式的內(nèi)存管理。Vector維護(hù)一個(gè)內(nèi)存中的棧,以存放它的元素。當(dāng)一個(gè)vector需要更多的內(nèi)存時(shí),它會(huì)分配一些;當(dāng)它不再生存時(shí),它會(huì)釋放內(nèi)存。于是,使用者不需要再關(guān)心vector中元素的內(nèi)存分配和釋放問(wèn)題。



            程序在遇到輸入一個(gè)"end-of-file"時(shí)結(jié)束。如果你在UNIX平臺(tái)下運(yùn)行它,"end-of-file"等于鍵盤上的Ctrl+D。如果你在Windows平臺(tái)下,那么由于一個(gè)BUG它無(wú)法辨別"end-of-file"字符,你可能傾向于使用下面這個(gè)稍稍復(fù)雜些的版本,它使用一個(gè)詞"end"來(lái)表示輸入已經(jīng)結(jié)束。



            #include<iostream>

            #include<vector>

            #include<algorithm>

            #include<string>

            using namespace std;



            int main()

            {

            vector<double> v;



            double d;

            while(cin>>d) v.push_back(d); // 讀入一個(gè)元素

            if (!cin.eof()) { // 檢查輸入是否失敗

            cin.clear(); // 清除錯(cuò)誤狀態(tài)

            string s;

            cin >> s; // 查找結(jié)束字符

            if (s != "end") {

            cerr << "format error\n";

            return 1; // 返回錯(cuò)誤

            }

            }



            cout << "read " << v.size() << " elements\n";



            reverse(v.begin(),v.end());

            cout << "elements in reverse order:\n";

            for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';



            return 0; // 成功返回

            }



            更多的關(guān)于使用標(biāo)準(zhǔn)庫(kù)將事情簡(jiǎn)化的例子,請(qǐng)參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》中的"漫游標(biāo)準(zhǔn)庫(kù)"("Tour of the Standard Library")一章。



            為什么編譯要花這么長(zhǎng)的時(shí)間?



            你的編譯器可能有問(wèn)題。也許它太老了,也許你安裝它的時(shí)候出了錯(cuò),也許你用的計(jì)算機(jī)已經(jīng)是個(gè)古董。在諸如此類的問(wèn)題上,我無(wú)法幫助你。



            但是,這也是很可能的:你要編譯的程序設(shè)計(jì)得非常糟糕,以至于編譯器不得不檢查數(shù)以百計(jì)的頭文件和數(shù)萬(wàn)行代碼。理論上來(lái)說(shuō),這是可以避免的。如果這是你購(gòu)買的庫(kù)的設(shè)計(jì)問(wèn)題,你對(duì)它無(wú)計(jì)可施(除了換一個(gè)更好的庫(kù)),但你可以將你自己的代碼組織得更好一些,以求得將修改代碼后的重新編譯工作降到最少。這樣的設(shè)計(jì)會(huì)更好,更有可維護(hù)性,因?yàn)樗鼈冋故玖烁玫母拍钌系姆蛛x。



            看看這個(gè)典型的面向?qū)ο蟮某绦蚶樱?



            class Shape {

            public: // 使用Shapes的用戶的接口

            virtual void draw() const;

            virtual void rotate(int degrees);

            // ...

            protected: // common data (for implementers of Shapes)

            Point center;

            Color col;

            // ...

            };



            class Circle : public Shape {

            public:

            void draw() const;

            void rotate(int) { }

            // ...

            protected:

            int radius;

            // ...

            };



            class Triangle : public Shape {

            public:

            void draw() const;

            void rotate(int);

            // ...

            protected:

            Point a, b, c;

            // ...

            };



            設(shè)計(jì)思想是,用戶通過(guò)Shape的public接口來(lái)操縱它們,而派生類(例如Circle和Triangle)的實(shí)現(xiàn)部分則共享由protected成員表現(xiàn)的那部分實(shí)現(xiàn)(implementation)。



            這不是一件容易的事情:確定哪些實(shí)現(xiàn)部分是對(duì)所有的派生類都有用的,并將之共享出來(lái)。因此,與public接口相比,protected成員往往要做多得多的改動(dòng)。舉例來(lái)說(shuō),雖然理論上"中心"(center)對(duì)所有的圖形都是一個(gè)有效的概念,但當(dāng)你要維護(hù)一個(gè)三角形的"中心"的時(shí)候,是一件非常麻煩的事情——對(duì)于三角形,當(dāng)且僅當(dāng)它確實(shí)被需要的時(shí)候,計(jì)算這個(gè)中心才是有意義的。



            protected成員很可能要依賴于實(shí)現(xiàn)部分的細(xì)節(jié),而Shape的用戶(譯注:user此處譯為用戶,指使用Shape類的代碼,下同)卻不見(jiàn)得必須依賴它們。舉例來(lái)說(shuō),很多(大多數(shù)?)使用Shape的代碼在邏輯上是與"顏色"無(wú)關(guān)的,但是由于Shape中"顏色"這個(gè)定義的存在,卻可能需要一堆復(fù)雜的頭文件,來(lái)結(jié)合操作系統(tǒng)的顏色概念。



            當(dāng)protected部分發(fā)生了改變時(shí),使用Shape的代碼必須重新編譯——即使只有派生類的實(shí)現(xiàn)部分才能夠訪問(wèn)protected成員。



            于是,基類中的"實(shí)現(xiàn)相關(guān)的信息"(information helpful to implementers)對(duì)用戶來(lái)說(shuō)變成了象接口一樣敏感的東西,它的存在導(dǎo)致了實(shí)現(xiàn)部分的不穩(wěn)定,用戶代碼的無(wú)謂的重編譯(當(dāng)實(shí)現(xiàn)部分發(fā)生改變時(shí)),以及將頭文件無(wú)節(jié)制地包含進(jìn)用戶代碼中(因?yàn)?實(shí)現(xiàn)相關(guān)的信息"需要它們)。有時(shí)這被稱為"脆弱的基類問(wèn)題"(brittle base class problem)。



            一個(gè)很明顯的解決方案就是,忽略基類中那些象接口一樣被使用的"實(shí)現(xiàn)相關(guān)的信息"。換句話說(shuō),使用接口,純粹的接口。也就是說(shuō),用抽象基類的方式來(lái)表示接口:



            class Shape {

            public: //使用Shapes的用戶的接口

            virtual void draw() const = 0;

            virtual void rotate(int degrees) = 0;

            virtual Point center() const = 0;

            // ...



            // 沒(méi)有數(shù)據(jù)

            };



            class Circle : public Shape {

            public:

            void draw() const;

            void rotate(int) { }

            Point center() const { return center; }

            // ...

            protected:

            Point cent;

            Color col;

            int radius;

            // ...

            };



            class Triangle : public Shape {

            public:

            void draw() const;

            void rotate(int);

            Point center() const;

            // ...

            protected:

            Color col;

            Point a, b, c;

            // ...

            };



            現(xiàn)在,用戶代碼與派生類的實(shí)現(xiàn)部分的變化之間的關(guān)系被隔離了。我曾經(jīng)見(jiàn)過(guò)這種技術(shù)使得編譯的時(shí)間減少了幾個(gè)數(shù)量級(jí)。



            但是,如果確實(shí)存在著對(duì)所有派生類(或僅僅對(duì)某些派生類)都有用的公共信息時(shí)怎么辦呢?可以簡(jiǎn)單把這些信息封裝成類,然后從它派生出實(shí)現(xiàn)部分的類:



            class Shape {

            public: //使用Shapes的用戶的接口

            virtual void draw() const = 0;

            virtual void rotate(int degrees) = 0;

            virtual Point center() const = 0;

            // ...



            // no data

            };



            struct Common {

            Color col;

            // ...

            };



            class Circle : public Shape, protected Common {

            public:

            void draw() const;

            void rotate(int) { }

            Point center() const { return center; }

            // ...

            protected:

            Point cent;

            int radius;

            };



            class Triangle : public Shape, protected Common {

            public:

            void draw() const;

            void rotate(int);

            Point center() const;

            // ...

            protected:

            Point a, b, c;

            };



            為什么一個(gè)空類的大小不為0?



            要清楚,兩個(gè)不同的對(duì)象的地址也是不同的?;谕瑯拥睦碛?,new總是返回指向不同對(duì)象的指針。

            看看:



            class Empty { };



            void f()

            {

            Empty a, b;

            if (&a == &b) cout << "impossible: report error to compiler supplier";



            Empty* p1 = new Empty;

            Empty* p2 = new Empty;

            if (p1 == p2) cout << "impossible: report error to compiler supplier";

            }



            有一條有趣的規(guī)則:一個(gè)空的基類并不一定有分隔字節(jié)。

            struct X : Empty {

            int a;

            // ...

            };



            void f(X* p)

            {

            void* p1 = p;

            void* p2 = &p->a;

            if (p1 == p2) cout << "nice: good optimizer";

            }



            這種優(yōu)化是允許的,可以被廣泛使用。它允許程序員使用空類以表現(xiàn)一些簡(jiǎn)單的概念。現(xiàn)在有些編譯器提供這種"空基類優(yōu)化"(empty base class optimization)。



            我必須在類聲明處賦予數(shù)據(jù)嗎?



            不必須。如果一個(gè)接口不需要數(shù)據(jù)時(shí),無(wú)須在作為接口定義的類中賦予數(shù)據(jù)。代之以在派生類中給出它們。參見(jiàn)"為什么編譯要花這么長(zhǎng)的時(shí)間?"。



            有時(shí)候,你必須在一個(gè)類中賦予數(shù)據(jù)??紤]一下復(fù)數(shù)類的情況:



            template<class Scalar> class complex {

            public:

            complex() : re(0), im(0) { }

            complex(Scalar r) : re(r), im(0) { }

            complex(Scalar r, Scalar i) : re(r), im(i) { }

            // ...



            complex& operator+=(const complex& a)

            { re+=a.re; im+=a.im; return *this; }

            // ...

            private:

            Scalar re, im;

            };



            設(shè)計(jì)這種類型的目的是將它當(dāng)做一個(gè)內(nèi)建(built-in)類型一樣被使用。在聲明處賦值是必須的,以保證如下可能:建立真正的本地對(duì)象(genuinely local objects)(比如那些在棧中而不是在堆中分配的對(duì)象),或者使某些簡(jiǎn)單操作被適當(dāng)?shù)豬nline化。對(duì)于那些支持內(nèi)建的復(fù)合類型的語(yǔ)言來(lái)說(shuō),要獲得它們提供的效率,真正的本地對(duì)象和inline化都是必要的。



            為什么成員函數(shù)默認(rèn)不是virtual的?



            因?yàn)楹芏囝惒⒉皇潜辉O(shè)計(jì)作為基類的。例如復(fù)數(shù)類。



            而且,一個(gè)包含虛擬函數(shù)的類的對(duì)象,要占用更多的空間以實(shí)現(xiàn)虛擬函數(shù)調(diào)用機(jī)制——往往是每個(gè)對(duì)象占用一個(gè)字(word)。這個(gè)額外的字是非??捎^的,而且在涉及和其它語(yǔ)言的數(shù)據(jù)的兼容性時(shí),可能導(dǎo)致麻煩(例如C或Fortran語(yǔ)言)。



            要了解更多的設(shè)計(jì)原理,請(qǐng)參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》(The Design and Evolution of C++)。



            為什么析構(gòu)函數(shù)默認(rèn)不是virtual的?



            因?yàn)楹芏囝惒⒉皇潜辉O(shè)計(jì)作為基類的。只有類在行為上是它的派生類的接口時(shí)(這些派生類往往在堆中分配,通過(guò)指針或引用來(lái)訪問(wèn)),虛擬函數(shù)才有意義。



            那么什么時(shí)候才應(yīng)該將析構(gòu)函數(shù)定義為虛擬呢?當(dāng)類至少擁有一個(gè)虛擬函數(shù)時(shí)。擁有虛擬函數(shù)意味著一個(gè)類是派生類的接口,在這種情況下,一個(gè)派生類的對(duì)象可能通過(guò)一個(gè)基類指針來(lái)銷毀。例如:



            class Base {

            // ...

            virtual ~Base();

            };



            class Derived : public Base {

            // ...

            ~Derived();

            };



            void f()

            {

            Base* p = new Derived;

            delete p; // 虛擬析構(gòu)函數(shù)保證~Derived函數(shù)被調(diào)用

            }



            如果基類的析構(gòu)函數(shù)不是虛擬的,那么派生類的析構(gòu)函數(shù)將不會(huì)被調(diào)用——這可能產(chǎn)生糟糕的結(jié)果,例如派生類的資源不會(huì)被釋放。



            為什么不能有虛擬構(gòu)造函數(shù)?



            虛擬調(diào)用是一種能夠在給定信息不完全(given partial information)的情況下工作的機(jī)制。特別地,虛擬允許我們調(diào)用某個(gè)函數(shù),對(duì)于這個(gè)函數(shù),僅僅知道它的接口,而不知道具體的對(duì)象類型。但是要建立一個(gè)對(duì)象,你必須擁有完全的信息。特別地,你需要知道要建立的對(duì)象的具體類型。因此,對(duì)構(gòu)造函數(shù)的調(diào)用不可能是虛擬的。



            當(dāng)要求建立一個(gè)對(duì)象時(shí),一種間接的技術(shù)常常被當(dāng)作"虛擬構(gòu)造函數(shù)"來(lái)使用。有關(guān)例子,請(qǐng)參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》第三版15.6.2.節(jié)。



            下面這個(gè)例子展示一種機(jī)制:如何使用一個(gè)抽象類來(lái)建立一個(gè)適當(dāng)類型的對(duì)象。



            struct F { // 對(duì)象建立函數(shù)的接口

            virtual A* make_an_A() const = 0;

            virtual B* make_a_B() const = 0;

            };



            void user(const F& fac)

            {

            A* p = fac.make_an_A(); // 將A作為合適的類型

            B* q = fac.make_a_B(); // 將B作為合適的類型

            // ...

            }



            struct FX : F {

            A* make_an_A() const { return new AX(); } // AX是A的派生

            B* make_a_B() const { return new BX(); } // AX是B的派生

            };



            struct FY : F {

            A* make_an_A() const { return new AY(); } // AY是A的派生

            B* make_a_B() const { return new BY(); } // BY是B的派生



            };



            int main()

            {

            user(FX()); // 此用戶建立AX與BX

            user(FY()); // 此用戶建立AY與BY

            // ...

            }



            這是所謂的"工廠模式"(the factory pattern)的一個(gè)變形。關(guān)鍵在于,user函數(shù)與AX或AY這樣的類的信息被完全分離開(kāi)來(lái)了。



            為什么重載在繼承類中不工作?



            這個(gè)問(wèn)題(非常常見(jiàn))往往出現(xiàn)于這樣的例子中:



            #include<iostream>

            using namespace std;



            class B {

            public:

            int f(int i) { cout << "f(int): "; return i+1; }

            // ...

            };



            class D : public B {

            public:

            double f(double d) { cout << "f(double): "; return d+1.3; }

            // ...

            };



            int main()

            {

            D* pd = new D;



            cout << pd->f(2) << '\n';

            cout << pd->f(2.3) << '\n';

            }



            它輸出的結(jié)果是:



            f(double): 3.3

            f(double): 3.6



            而不是象有些人猜想的那樣:



            f(int): 3

            f(double): 3.6



            換句話說(shuō),在B和D之間并沒(méi)有發(fā)生重載的解析。編譯器在D的區(qū)域內(nèi)尋找,找到了一個(gè)函數(shù)double f(double),并執(zhí)行了它。它永遠(yuǎn)不會(huì)涉及(被封裝的)B的區(qū)域。在C++中,沒(méi)有跨越區(qū)域的重載——對(duì)于這條規(guī)則,繼承類也不例外。更多的細(xì)節(jié),參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》和《C++程序設(shè)計(jì)語(yǔ)言》。



            但是,如果我需要在基類和繼承類之間建立一組重載的f()函數(shù)呢?很簡(jiǎn)單,使用using聲明:



            class D : public B {

            public:

            using B::f; // make every f from B available

            double f(double d) { cout << "f(double): "; return d+1.3; }

            // ...

            };



            進(jìn)行這個(gè)修改之后,輸出結(jié)果將是:



            f(int): 3

            f(double): 3.6



            這樣,在B的f()和D的f()之間,重載確實(shí)實(shí)現(xiàn)了,并且選擇了一個(gè)最合適的f()進(jìn)行調(diào)用。



            我能夠在構(gòu)造函數(shù)中調(diào)用一個(gè)虛擬函數(shù)嗎?



            可以,但是要小心。它可能不象你期望的那樣工作。在構(gòu)造函數(shù)中,虛擬調(diào)用機(jī)制不起作用,因?yàn)槔^承類的重載還沒(méi)有發(fā)生。對(duì)象先從基類被創(chuàng)建,"基類先于繼承類(base before derived)"。



            看看這個(gè):



            #include<string>

            #include<iostream>

            using namespace std;



            class B {

            public:

            B(const string& ss) { cout << "B constructor\n"; f(ss); }

            virtual void f(const string&) { cout << "B::f\n";}

            };



            class D : public B {

            public:

            D(const string & ss) :B(ss) { cout << "D constructor\n";}

            void f(const string& ss) { cout << "D::f\n"; s = ss; }

            private:

            string s;

            };



            int main()

            {

            D d("Hello");

            }



            程序編譯以后會(huì)輸出:



            B constructor

            B::f

            D constructor



            注意不是D::f。設(shè)想一下,如果出于不同的規(guī)則,B::B()可以調(diào)用D::f()的話,會(huì)產(chǎn)生什么樣的后果:因?yàn)闃?gòu)造函數(shù)D:Very Happy()還沒(méi)有運(yùn)行,D::f()將會(huì)試圖將一個(gè)還沒(méi)有初始化的字符串s賦予它的參數(shù)。結(jié)果很可能是導(dǎo)致立即崩潰。



            析構(gòu)函數(shù)在"繼承類先于基類"的機(jī)制下運(yùn)行,因此虛擬機(jī)制的行為和構(gòu)造函數(shù)一樣:只有本地定義(local definitions)被使用——不會(huì)調(diào)用虛擬函數(shù),以免觸及對(duì)象中的(現(xiàn)在已經(jīng)被銷毀的)繼承類的部分。



            更多的細(xì)節(jié),參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》13.2.4.2和《C++程序設(shè)計(jì)語(yǔ)言》15.4.3。



            有人暗示,這只是一條實(shí)現(xiàn)時(shí)的人為制造的規(guī)則。不是這樣的。事實(shí)上,要實(shí)現(xiàn)這種不安全的方法倒是非常容易的:在構(gòu)造函數(shù)中直接調(diào)用虛擬函數(shù),就象調(diào)用其它函數(shù)一樣。但是,這樣就意味著,任何虛擬函數(shù)都無(wú)法編寫(xiě)了,因?yàn)樗鼈冃枰揽炕惖墓潭ǖ膭?chuàng)建(invariants established by base classes)。這將會(huì)導(dǎo)致一片混亂。



            有沒(méi)有"指定位置刪除"(placement delete)?



            沒(méi)有,不過(guò)如果你需要的話,可以自己寫(xiě)一個(gè)。



            看看這個(gè)指定位置創(chuàng)建(placementnew),它將對(duì)象放進(jìn)了一系列Arena中;



            class Arena {

            public:

            void* allocate(size_t);

            void deallocate(void*);

            // ...

            };



            void* operator new(size_t sz, Arena& a)

            {

            return a.allocate(sz);

            }



            Arena a1(some arguments);

            Arena a2(some arguments);



            這樣實(shí)現(xiàn)了之后,我們就可以這么寫(xiě):



            X* p1 = new(a1) X;

            Y* p2 = new(a1) Y;

            Z* p3 = new(a2) Z;

            // ...



            但是,以后怎樣正確地銷毀這些對(duì)象呢?沒(méi)有對(duì)應(yīng)于這種"placementnew"的內(nèi)建的"placement delete",原因是,沒(méi)有一種通用的方法可以保證它被正確地使用。在C++的類型系統(tǒng)中,沒(méi)有什么東西可以讓我們確認(rèn),p1一定指向一個(gè)由Arena類型的a1分派的對(duì)象。p1可能指向任何東西分派的任何一塊地方。



            然而,有時(shí)候程序員是知道的,所以這是一種方法:



            template<class T> void destroy(T* p, Arena& a)

            {

            if (p) {

            p->~T(); // explicit destructor call

            a.deallocate(p);

            }

            }



            現(xiàn)在我們可以這么寫(xiě):



            destroy(p1,a1);

            destroy(p2,a2);

            destroy(p3,a3);



            如果Arena維護(hù)了它保存著的對(duì)象的線索,你甚至可以自己寫(xiě)一個(gè)析構(gòu)函數(shù),以避免它發(fā)生錯(cuò)誤。



            這也是可能的:定義一對(duì)相互匹配的操作符new()和delete(),以維護(hù)《C++程序設(shè)計(jì)語(yǔ)言》15.6中的類繼承體系。參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》10.4和《C++程序設(shè)計(jì)語(yǔ)言》19.4.5。



            我能防止別人繼承我自己的類嗎?



            可以,但你為什么要那么做呢?這是兩個(gè)常見(jiàn)的回答:



            效率:避免我的函數(shù)被虛擬調(diào)用

            安全:保證我的類不被用作一個(gè)基類(例如,保證我能夠復(fù)制對(duì)象而不用擔(dān)心出事)



            根據(jù)我的經(jīng)驗(yàn),效率原因往往是不必要的擔(dān)心。在C++中,虛擬函數(shù)調(diào)用是如此之快,以致于它們?cè)谝粋€(gè)包含虛擬函數(shù)的類中被實(shí)際使用時(shí),相比普通的函數(shù)調(diào)用,根本不會(huì)產(chǎn)生值得考慮的運(yùn)行期開(kāi)支。注意,僅僅通過(guò)指針或引用時(shí),才會(huì)使用虛擬調(diào)用機(jī)制。當(dāng)直接通過(guò)對(duì)象名字調(diào)用一個(gè)函數(shù)時(shí),虛擬函數(shù)調(diào)用的開(kāi)支可以被很容易地優(yōu)化掉。



            如果確實(shí)有真正的需要,要將一個(gè)類封閉起來(lái)以防止虛擬調(diào)用,那么可能首先應(yīng)該問(wèn)問(wèn)為什么它們是虛擬的。我看見(jiàn)過(guò)一些例子,那些性能表現(xiàn)不佳的函數(shù)被設(shè)置為虛擬,沒(méi)有其他原因,僅僅是因?yàn)?我們習(xí)慣這么干"。



            這個(gè)問(wèn)題的另一個(gè)部分,由于邏輯上的原因如何防止類被繼承,有一個(gè)解決方案。不幸的是,這個(gè)方案并不完美。它建立在這樣一個(gè)事實(shí)的基礎(chǔ)之上,那就是:大多數(shù)的繼承類必須建立一個(gè)虛擬的基類。這是一個(gè)例子:



            class Usable;



            class Usable_lock {

            friend class Usable;

            private:

            Usable_lock() {}

            Usable_lock(const Usable_lock&) {}

            };



            class Usable : public virtual Usable_lock {

            // ...

            public:

            Usable();

            Usable(char*);

            // ...

            };



            Usable a;



            class DD : public Usable { };



            DD dd; // 錯(cuò)誤: DD:Very HappyD() 不能訪問(wèn)

            // Usable_lock::Usable_lock()是一個(gè)私有成員



            (來(lái)自《C++語(yǔ)言的設(shè)計(jì)和演變》11.4.3)



            為什么不能為模板參數(shù)定義約束(constraints)?



            可以的,而且方法非常簡(jiǎn)單和通用。



            看看這個(gè):



            template<class Container>

            void draw_all(Container& c)

            {

            for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

            }



            如果出現(xiàn)類型錯(cuò)誤,可能是發(fā)生在相當(dāng)復(fù)雜的for_each()調(diào)用時(shí)。例如,如果容器的元素類型是int,我們將得到一個(gè)和for_each()相關(guān)的含義模糊的錯(cuò)誤(因?yàn)椴荒軌驅(qū)?duì)一個(gè)int值調(diào)用Shape::draw的方法)。



            為了提前捕捉這個(gè)錯(cuò)誤,我這樣寫(xiě):



            template<class Container>

            void draw_all(Container& c)

            {

            Shape* p = c.front(); // accept only containers of Shape*s



            for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

            }



            對(duì)于現(xiàn)在的大多數(shù)編譯器,中間變量p的初始化將會(huì)觸發(fā)一個(gè)易于了解的錯(cuò)誤。這個(gè)竅門在很多語(yǔ)言中都是通用的,而且在所有的標(biāo)準(zhǔn)創(chuàng)建中都必須這樣做。在成品的代碼中,我也許可以這樣寫(xiě):



            template<class Container>

            void draw_all(Container& c)

            {

            typedef typename Container::value_type T;

            Can_copy<T,Shape*>(); // accept containers of only Shape*s



            for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

            }



            這樣就很清楚了,我在建立一個(gè)斷言(assertion)。Can_copy模板可以這樣定義:



            template<class T1, class T2> struct Can_copy {

            static void constraints(T1 a, T2 b) { T2 c = a; b = a; }

            Can_copy() { void(*p)(T1,T2) = constraints; }

            };



            Can_copy(在運(yùn)行時(shí))檢查T1是否可以被賦值給T2。Can_copy<T,Shape*>檢查T是否是Shape*類型,或者是一個(gè)指向由Shape類公共繼承而來(lái)的類的對(duì)象的指針,或者是被用戶轉(zhuǎn)換到Shape*類型的某個(gè)類型。注意這個(gè)定義被精簡(jiǎn)到了最?。?



            一行命名要檢查的約束,和要檢查的類型

            一行列出指定的要檢查的約束(constraints()函數(shù))

            一行提供觸發(fā)檢查的方法(通過(guò)構(gòu)造函數(shù))



            注意這個(gè)定義有相當(dāng)合理的性質(zhì):



            你可以表達(dá)一個(gè)約束,而不用聲明或復(fù)制變量,因此約束的編寫(xiě)者可以用不著去設(shè)想變量如何被初始化,對(duì)象是否能夠被復(fù)制,被銷毀,以及諸如此類的事情。(當(dāng)然,約束要檢查這些屬性的情況時(shí)例外。)

            使用現(xiàn)在的編譯器,不需要為約束產(chǎn)生代碼

            定義和使用約束,不需要使用宏

            當(dāng)約束失敗時(shí),編譯器會(huì)給出可接受的錯(cuò)誤信息,包括"constraints"這個(gè)詞(給用戶一個(gè)線索),約束的名字,以及導(dǎo)致約束失敗的詳細(xì)錯(cuò)誤(例如"無(wú)法用double*初始化Shape*")。



            那么,在C++語(yǔ)言中,有沒(méi)有類似于Can_copy——或者更好——的東西呢?在《C++語(yǔ)言的設(shè)計(jì)和演變》中,對(duì)于在C++中實(shí)現(xiàn)這種通用約束的困難進(jìn)行了分析。從那以來(lái),出現(xiàn)了很多方法,來(lái)讓約束類變得更加容易編寫(xiě),同時(shí)仍然能觸發(fā)良好的錯(cuò)誤信息。例如,我信任我在Can_copy中使用的函數(shù)指針的方式,它源自Alex Stepanov和Jeremy Siek。我并不認(rèn)為Can_copy()已經(jīng)可以標(biāo)準(zhǔn)化了——它需要更多的使用。同樣,在C++社區(qū)中,各種不同的約束方式被使用;到底是哪一種約束模板在廣泛的使用中被證明是最有效的,還沒(méi)有達(dá)成一致的意見(jiàn)。



            但是,這種方式非常普遍,比語(yǔ)言提供的專門用于約束檢查的機(jī)制更加普遍。無(wú)論如何,當(dāng)我們編寫(xiě)一個(gè)模板時(shí),我們擁有了C++提供的最豐富的表達(dá)力量??纯催@個(gè):



            template<class T, class B> struct Derived_from {

            static void constraints(T* p) { B* pb = p; }

            Derived_from() { void(*p)(T*) = constraints; }

            };



            template<class T1, class T2> struct Can_copy {

            static void constraints(T1 a, T2 b) { T2 c = a; b = a; }

            Can_copy() { void(*p)(T1,T2) = constraints; }

            };



            template<class T1, class T2 = T1> struct Can_compare {

            static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }

            Can_compare() { void(*p)(T1,T2) = constraints; }

            };



            template<class T1, class T2, class T3 = T1> struct Can_multiply {

            static void constraints(T1 a, T2 b, T3 c) { c = a*b; }

            Can_multiply() { void(*p)(T1,T2,T3) = constraints; }

            };



            struct B { };

            struct D : B { };

            struct DD : D { };

            struct X { };



            int main()

            {

            Derived_from<D,B>();

            Derived_from<DD,B>();

            Derived_from<X,B>();

            Derived_from<int,B>();

            Derived_from<X,int>();



            Can_compare<int,float>();

            Can_compare<X,B>();

            Can_multiply<int,float>();

            Can_multiply<int,float,double>();

            Can_multiply<B,X>();



            Can_copy<D*,B*>();

            Can_copy<D,B*>();

            Can_copy<int,B*>();

            }



            // 典型的"元素必須繼承自Mybase*"約束:



            template<class T> class Container : Derived_from<T,Mybase> {

            // ...

            };



            事實(shí)上,Derived_from并不檢查來(lái)源(derivation),而僅僅檢查轉(zhuǎn)換(conversion),不過(guò)這往往是一個(gè)更好的約束。為約束想一個(gè)好名字是很難的。



            既然已經(jīng)有了優(yōu)秀的qsort()函數(shù),為什么還需要一個(gè)sort()?



            對(duì)于初學(xué)者來(lái)說(shuō),



            qsort(array,asize,sizeof(elem),elem_compare);



            看上去太古怪了,而且比這個(gè)更難理解:



            sort(vec.begin(),vec.end());



            對(duì)于專家來(lái)說(shuō),在元素與比較方式(comparison criteria)都相同的情況下,sort()比qsort()更快,這是很重要的。而且,qsort()是通用的,所以它可以用于不同容器類型、元素類型、比較方式的任意有意義的組合。舉例來(lái)說(shuō):



            struct Record {

            string name;

            // ...

            };



            struct name_compare { // 使用"name"作為鍵比較Record

            bool operator()(const Record& a, const Record& b) const

            { return a.name<b.name; }

            };



            void f(vector<Record>& vs)

            {

            sort(vs.begin(), vs.end(), name_compare());

            // ...

            }



            而且,很多人欣賞sort()是因?yàn)樗穷愋桶踩模褂盟恍枰M(jìn)行造型(cast),沒(méi)有人必須去為基本類型寫(xiě)一個(gè)compare()函數(shù)。



            更多的細(xì)節(jié),參見(jiàn)我的文章《將標(biāo)準(zhǔn)C++作為一種新的語(yǔ)言來(lái)學(xué)習(xí)》(Learning C++ as a New language),可以從我的文章列表中找到。



            sort()勝過(guò)qsort()的主要原因是,比較操作在內(nèi)聯(lián)(inlines)上做得更好。



            什么是函數(shù)對(duì)象(function object)?



            顧名思義,就是在某種方式上表現(xiàn)得象一個(gè)函數(shù)的對(duì)象。典型地,它是指一個(gè)類的實(shí)例,這個(gè)類定義了應(yīng)用操作符operator()。



            函數(shù)對(duì)象是比函數(shù)更加通用的概念,因?yàn)楹瘮?shù)對(duì)象可以定義跨越多次調(diào)用的可持久的部分(類似靜態(tài)局部變量),同時(shí)又能夠從對(duì)象的外面進(jìn)行初始化和檢查(和靜態(tài)局部變量不同)。例如:



            class Sum {

            int val;

            public:

            Sum(int i) :val(i) { }

            operator int() const { return val; } // 取得值



            int operator()(int i) { return val+=i; } // 應(yīng)用

            };



            void f(vector v)

            {

            Sum s = 0; // initial value 0

            s = for_each(v.begin(), v.end(), s); // 求所有元素的和

            cout << "the sum is " << s << "\n";



            //或者甚至:

            cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n";

            }



            注意一個(gè)擁有應(yīng)用操作符的函數(shù)對(duì)象可以被完美地內(nèi)聯(lián)化(inline),因?yàn)樗鼪](méi)有涉及到任何指針,后者可能導(dǎo)致拒絕優(yōu)化。與之形成對(duì)比的是,現(xiàn)有的優(yōu)化器幾乎不能(或者完全不能?)將一個(gè)通過(guò)函數(shù)指針的調(diào)用內(nèi)聯(lián)化。



            在標(biāo)準(zhǔn)庫(kù)中,函數(shù)對(duì)象被廣泛地使用以獲得彈性。



            我應(yīng)該如何對(duì)付內(nèi)存泄漏?



            寫(xiě)出那些不會(huì)導(dǎo)致任何內(nèi)存泄漏的代碼。很明顯,當(dāng)你的代碼中到處充滿了new 操作、delete操作和指針運(yùn)算的話,你將會(huì)在某個(gè)地方搞暈了頭,導(dǎo)致內(nèi)存泄漏,指針引用錯(cuò)誤,以及諸如此類的問(wèn)題。這和你如何小心地對(duì)待內(nèi)存分配工作其實(shí)完全沒(méi)有關(guān)系:代碼的復(fù)雜性最終總是會(huì)超過(guò)你能夠付出的時(shí)間和努力。于是隨后產(chǎn)生了一些成功的技巧,它們依賴于將內(nèi)存分配(allocations)與重新分配(deallocation)工作隱藏在易于管理的類型之后。標(biāo)準(zhǔn)容器(standard containers)是一個(gè)優(yōu)秀的例子。它們不是通過(guò)你而是自己為元素管理內(nèi)存,從而避免了產(chǎn)生糟糕的結(jié)果。想象一下,沒(méi)有string和vector的幫助,寫(xiě)出這個(gè):



            #include<vector>

            #include<string>

            #include<iostream>

            #include<algorithm>

            using namespace std;



            int main() // small program messing around with strings

            {

            cout << "enter some whitespace-separated words:\n";

            vector<string> v;

            string s;

            while (cin>>s) v.push_back(s);



            sort(v.begin(),v.end());



            string cat;

            typedef vector<string>::const_iterator Iter;

            for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";

            cout << cat << '\n';

            }



            你有多少機(jī)會(huì)在第一次就得到正確的結(jié)果?你又怎么知道你沒(méi)有導(dǎo)致內(nèi)存泄漏呢?



            注意,沒(méi)有出現(xiàn)顯式的內(nèi)存管理,宏,造型,溢出檢查,顯式的長(zhǎng)度限制,以及指針。通過(guò)使用函數(shù)對(duì)象和標(biāo)準(zhǔn)算法(standard algorithm),我可以避免使用指針——例如使用迭代子(iterator),不過(guò)對(duì)于一個(gè)這么小的程序來(lái)說(shuō)有點(diǎn)小題大作了。



            這些技巧并不完美,要系統(tǒng)化地使用它們也并不總是那么容易。但是,應(yīng)用它們產(chǎn)生了驚人的差異,而且通過(guò)減少顯式的內(nèi)存分配與重新分配的次數(shù),你甚至可以使余下的例子更加容易被跟蹤。早在1981年,我就指出,通過(guò)將我必須顯式地跟蹤的對(duì)象的數(shù)量從幾萬(wàn)個(gè)減少到幾打,為了使程序正確運(yùn)行而付出的努力從可怕的苦工,變成了應(yīng)付一些可管理的對(duì)象,甚至更加簡(jiǎn)單了。



            如果你的程序還沒(méi)有包含將顯式內(nèi)存管理減少到最小限度的庫(kù),那么要讓你程序完成和正確運(yùn)行的話,最快的途徑也許就是先建立一個(gè)這樣的庫(kù)。



            模板和標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)了容器、資源句柄以及諸如此類的東西,更早的使用甚至在多年以前。異常的使用使之更加完善。



            如果你實(shí)在不能將內(nèi)存分配/重新分配的操作隱藏到你需要的對(duì)象中時(shí),你可以使用資源句柄(resource handle),以將內(nèi)存泄漏的可能性降至最低。這里有個(gè)例子:我需要通過(guò)一個(gè)函數(shù),在空閑內(nèi)存中建立一個(gè)對(duì)象并返回它。這時(shí)候可能忘記釋放這個(gè)對(duì)象。畢竟,我們不能說(shuō),僅僅關(guān)注當(dāng)這個(gè)指針要被釋放的時(shí)候,誰(shuí)將負(fù)責(zé)去做。使用資源句柄,這里用了標(biāo)準(zhǔn)庫(kù)中的auto_ptr,使需要為之負(fù)責(zé)的地方變得明確了。



            #include<memory>

            #include<iostream>

            using namespace std;



            struct S {

            S() { cout << "make an S\n"; }

            ~S() { cout << "destroy an S\n"; }

            S(const S&) { cout << "copy initialize an S\n"; }

            S& operator=(const S&) { cout << "copy assign an S\n"; }

            };



            S* f()

            {

            return new S; // 誰(shuí)該負(fù)責(zé)釋放這個(gè)S?

            };



            auto_ptr<S> g()

            {

            return auto_ptr<S>(new S); // 顯式傳遞負(fù)責(zé)釋放這個(gè)S

            }



            int main()

            {

            cout << "start main\n";

            S* p = f();

            cout << "after f() before g()\n";

            // S* q = g(); // 將被編譯器捕捉

            auto_ptr<S> q = g();

            cout << "exit main\n";

            // *p產(chǎn)生了內(nèi)存泄漏

            // *q被自動(dòng)釋放

            }



            在更一般的意義上考慮資源,而不僅僅是內(nèi)存。



            如果在你的環(huán)境中不能系統(tǒng)地應(yīng)用這些技巧(例如,你必須使用別的地方的代碼,或者你的程序的另一部分簡(jiǎn)直是原始人類(譯注:原文是Neanderthals,尼安德特人,舊石器時(shí)代廣泛分布在歐洲的猿人)寫(xiě)的,如此等等),那么注意使用一個(gè)內(nèi)存泄漏檢測(cè)器作為開(kāi)發(fā)過(guò)程的一部分,或者插入一個(gè)垃圾收集器(garbage collector)。



            我為什么在捕獲一個(gè)異常之后就不能繼續(xù)?



            換句話說(shuō),C++為什么不提供一種簡(jiǎn)單的方式,讓程序能夠回到異常拋出點(diǎn)之后,并繼續(xù)執(zhí)行?



            主要的原因是,如果從異常處理之后繼續(xù),那么無(wú)法預(yù)知擲出點(diǎn)之后的代碼如何對(duì)待異常處理,是否僅僅繼續(xù)執(zhí)行,就象什么也沒(méi)有發(fā)生一樣。異常處理者無(wú)法知道,在繼續(xù)之前,有關(guān)的上下文環(huán)境(context)是否是"正確"的。要讓這樣的代碼正確執(zhí)行,拋出異常的編寫(xiě)者與捕獲異常的編寫(xiě)者必須對(duì)彼此的代碼與上下文環(huán)境都非常熟悉才行。這樣會(huì)產(chǎn)生非常復(fù)雜的依賴性,因此無(wú)論在什么情況下,都會(huì)導(dǎo)致一系列嚴(yán)重的維護(hù)問(wèn)題。



            當(dāng)我設(shè)計(jì)C++的異常處理機(jī)制時(shí),我曾經(jīng)認(rèn)真地考慮過(guò)允許這種繼續(xù)的可能性,而且在標(biāo)準(zhǔn)化的過(guò)程中,這個(gè)問(wèn)題被非常詳細(xì)地討論過(guò)。請(qǐng)參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》中的異常處理章節(jié)。



            在一次新聞組的討論中,我曾經(jīng)以一種稍微不同的方式回答過(guò)這個(gè)問(wèn)題。



            為什么C++中沒(méi)有相當(dāng)于realloc()的函數(shù)?



            如果你需要,你當(dāng)然可以使用realloc()。但是,realloc()僅僅保證能工作于這樣的數(shù)組之上:它們被malloc()(或者類似的函數(shù))分配,包含一些沒(méi)有用戶定義的復(fù)制構(gòu)造函數(shù)(copy constructors)的對(duì)象。而且,要記住,與通常的期望相反,realloc()有時(shí)也必須復(fù)制它的參數(shù)數(shù)組。



            在C++中,處理內(nèi)存重新分配的更好的方法是,使用標(biāo)準(zhǔn)庫(kù)中的容器,例如vector,并讓它自我增長(zhǎng)。



            如何使用異常?



            參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》第4章,第8.3節(jié),以及附錄E。這個(gè)附錄針對(duì)的是如何在要求苛刻的程序中寫(xiě)出異常安全的代碼的技巧,而不是針對(duì)初學(xué)者的。一個(gè)關(guān)鍵的技術(shù)是"資源獲得即初始化"(resource acquisiton is initialization),它使用一些有析構(gòu)函數(shù)的類,來(lái)實(shí)現(xiàn)強(qiáng)制的資源管理。



            怎樣從輸入中讀取一個(gè)字符串?



            你可以用這種方式讀取一個(gè)單獨(dú)的以空格結(jié)束的詞:



            #include<iostream>

            #include<string>

            using namespace std;



            int main()

            {

            cout << "Please enter a word:\n";



            string s;

            cin>>s;



            cout << "You entered " << s << '\n';

            }



            注意,這里沒(méi)有顯式的內(nèi)存管理,也沒(méi)有可能導(dǎo)致溢出的固定大小的緩沖區(qū)。



            如果你確實(shí)想得到一行而不是一個(gè)單獨(dú)的詞,可以這樣做:





            #include<iostream>

            #include<string>

            using namespace std;



            int main()

            {

            cout << "Please enter a line:\n";



            string s;

            getline(cin,s);



            cout << "You entered " << s << '\n';

            }



            在《C++程序設(shè)計(jì)語(yǔ)言》(可在線獲得)的第3章,可以找到一個(gè)對(duì)諸如字符串與流這樣的標(biāo)準(zhǔn)庫(kù)工具的簡(jiǎn)介。對(duì)于使用C與C++進(jìn)行簡(jiǎn)單輸入輸出的詳細(xì)比較,參見(jiàn)我的文章《將標(biāo)準(zhǔn)C++作為一種新的語(yǔ)言來(lái)學(xué)習(xí)》(Learning Standard C++ as a New Language),你可以在本人著作列表(my publications list)中下載到它。



            為什么C++不提供"finally"的構(gòu)造?



            因?yàn)镃++提供了另外一種方法,它幾乎總是更好的:"資源獲得即初始化"(resource acquisiton is initialization)技術(shù)?;镜乃悸肥?,通過(guò)一個(gè)局部對(duì)象來(lái)表現(xiàn)資源,于是局部對(duì)象的析構(gòu)函數(shù)將會(huì)釋放資源。這樣,程序員就不會(huì)忘記釋放資源了。舉例來(lái)說(shuō):



            class File_handle {

            FILE* p;

            public:

            File_handle(const char* n, const char* a)

            { p = fopen(n,a); if (p==0) throw Open_error(errno); }

            File_handle(FILE* pp)

            { p = pp; if (p==0) throw Open_error(errno); }



            ~File_handle() { fclose(p); }



            operator FILE*() { return p; }



            // ...

            };



            void f(const char* fn)

            {

            File_handle f(fn,"rw"); //打開(kāi)fn進(jìn)行讀寫(xiě)

            // 通過(guò)f使用文件

            }



            在一個(gè)系統(tǒng)中,需要為每一個(gè)資源都使用一個(gè)"資源句柄"類。無(wú)論如何,我們不需要為每一個(gè)資源獲得都寫(xiě)出"finally"語(yǔ)句。在實(shí)時(shí)系統(tǒng)中,資源獲得要遠(yuǎn)遠(yuǎn)多于資源的種類,因此和使用"finally"構(gòu)造相比,"資源獲得即初始化"技術(shù)會(huì)產(chǎn)生少得多的代碼。



            什么是自動(dòng)指針(auto_ptr),為什么沒(méi)有自動(dòng)數(shù)組(auto_array)?



            auto_ptr是一個(gè)非常簡(jiǎn)單的句柄類的例子,在<memory>中定義,通過(guò)"資源獲得即初始化"技術(shù)支持異常安全。auto_ptr保存著一個(gè)指針,能夠象指針一樣被使用,并在生存期結(jié)束時(shí)釋放指向的對(duì)象。舉例:



            #include<memory>

            using namespace std;



            struct X {

            int m;

            // ..

            };



            void f()

            {

            auto_ptr<X> p(new X);

            X* q = new X;



            p->m++; // 象一個(gè)指針一樣使用p

            q->m++;

            // ...



            delete q;

            }



            如果在...部分拋出了一個(gè)異常,p持有的對(duì)象將被auto_ptr的析構(gòu)函數(shù)正確地釋放,而q指向的X對(duì)象則產(chǎn)生了內(nèi)存泄漏。更多的細(xì)節(jié),參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》14.4.2節(jié)。



            auto_ptr是一個(gè)非常簡(jiǎn)單的類。特別地,它不是一個(gè)引用計(jì)數(shù)(reference counted)的指針。如果你將一個(gè)auto_ptr賦值給另一個(gè),那么被賦值的auto_ptr將持有指針,而原來(lái)的auto_ptr將持有0。舉例:



            #include<memory>

            #include<iostream>

            using namespace std;



            struct X {

            int m;

            // ..

            };



            int main()

            {

            auto_ptr<X> p(new X);

            auto_ptr<X> q(p);

            cout << "p " << p.get() << " q " << q.get() << "\n";

            }



            將會(huì)打印出一個(gè)指向0的指針和一個(gè)指向非0的指針。例如:



            p 0x0 q 0x378d0



            auto_ptr::get()返回那個(gè)輔助的指針。



            這種"轉(zhuǎn)移"語(yǔ)義不同于通常的"復(fù)制"語(yǔ)義,這是令人驚訝的。特別地,永遠(yuǎn)不要使用auto_ptr作為一個(gè)標(biāo)準(zhǔn)容器的成員。標(biāo)準(zhǔn)容器需要通常的"復(fù)制"語(yǔ)義。例如:



            std::vector<auto_ptr<X> >v; // 錯(cuò)誤



            auto_ptr只持有指向一個(gè)單獨(dú)元素的指針,而不是指向一個(gè)數(shù)組的指針:



            void f(int n)

            {

            auto_ptr<X> p(new X[n]); //錯(cuò)誤

            // ...

            }



            這是錯(cuò)誤的,因?yàn)槲鰳?gòu)函數(shù)會(huì)調(diào)用delete而不是delete[]來(lái)釋放指針,這樣就不會(huì)調(diào)用余下的n-1個(gè)X的析構(gòu)函數(shù)。



            那么我們需要一個(gè)auto_array來(lái)持有數(shù)組嗎?不。沒(méi)有auto_array。原因是根本沒(méi)有這種需要。更好的解決方案是使用vector:



            void f(int n)

            {

            vector<X> v(n);

            // ...

            }



            當(dāng)...部分發(fā)生異常時(shí),v的析構(gòu)函數(shù)會(huì)被正確地調(diào)用。



            可以混合使用C風(fēng)格與C++風(fēng)格的內(nèi)存分派與重新分配嗎?



            在這種意義上是可以的:你可以在同一個(gè)程序中使用malloc()和new。



            在這種意義上是不行的:你不能使用malloc()來(lái)建立一個(gè)對(duì)象,又通過(guò)delete來(lái)釋放它。你也不能用new建立一個(gè)新的對(duì)象,然后通過(guò)free()來(lái)釋放它,或者通過(guò)realloc()在數(shù)組中再建立一個(gè)新的。



            C++中的new和delete操作可以保證正確的構(gòu)造和析構(gòu):構(gòu)造函數(shù)和析構(gòu)函數(shù)在需要它們的時(shí)候被調(diào)用。C風(fēng)格的函數(shù)alloc(), calloc(), free(), 和realloc()卻不能保證這一點(diǎn)。此外,用new和delete來(lái)獲得和釋放的原始內(nèi)存,并不一定能保證與malloc()和free()兼容。如果這種混合的風(fēng)格在你的系統(tǒng)中能夠運(yùn)用,只能說(shuō)是你走運(yùn)——暫時(shí)的。



            如果你覺(jué)得需要使用realloc()——或者要做更多——考慮使用標(biāo)準(zhǔn)庫(kù)中的vector。例如:



            // 從輸入中將詞讀取到一個(gè)字符串vector中



            vector<string> words;

            string s;

            while (cin>>s && s!=".") words.push_back(s);



            vector會(huì)視需要自動(dòng)增長(zhǎng)。



            更多的例子與討論,參見(jiàn)我的文章《將標(biāo)準(zhǔn)C++作為一種新的語(yǔ)言來(lái)學(xué)習(xí)》(Learning Standard C++ as a New Language),你可以在本人著作列表(my publications list)中下載到它。



            我為什么必須使用一個(gè)造型來(lái)轉(zhuǎn)換*void?



            在C語(yǔ)言中,你可以隱式地將*void轉(zhuǎn)換為*T。這是不安全的。考慮一下:



            #include<stdio.h>



            int main()

            {

            char i = 0;

            char j = 0;

            char* p = &i;

            void* q = p;

            int* pp = q; /* 不安全的,在C中可以,C++不行 */



            printf("%d %d\n",i,j);

            *pp = -1; /* 覆蓋了從i開(kāi)始的內(nèi)存 */

            printf("%d %d\n",i,j);

            }



            使用一個(gè)并不指向T類型的T*將是一場(chǎng)災(zāi)難。因此,在C++中,如果從一個(gè)void*得到一個(gè)T*,你必須進(jìn)行顯式轉(zhuǎn)換。舉例來(lái)說(shuō),要得到上列程序的這個(gè)令人別扭的效果,你可以這樣寫(xiě):



            int* pp = (int*)q;



            或者使用一個(gè)新的類型造型,以使這種沒(méi)有檢查的類型轉(zhuǎn)換操作變得更加清晰:



            int* pp = static_cast<int*>(q);



            造型被最好地避免了。



            在C語(yǔ)言中,這種不安全的轉(zhuǎn)換最常見(jiàn)的應(yīng)用之一,是將malloc()的結(jié)果賦予一個(gè)合適的指針。例如:



            int* p = malloc(sizeof(int));



            在C++中,使用類型安全的new操作符:



            int* p = new int;



            附帶地,new操作符還提供了勝過(guò)malloc()的新特性:



            new不會(huì)偶然分配錯(cuò)誤的內(nèi)存數(shù)量;

            new會(huì)隱式地檢查內(nèi)存耗盡情況,而且

            new提供了初始化。



            舉例:



            typedef std::complex<double> cmplx;



            /* C風(fēng)格: */

            cmplx* p = (cmplx*)malloc(sizeof(int)); /* 錯(cuò)誤:類型不正確 */

            /* 忘記測(cè)試p==0 */

            if (*p == 7) { /* ... */ } /* 糟糕,忘記了初始化*p */



            // C++風(fēng)格:

            cmplx* q = new cmplx(1,2); // 如果內(nèi)存耗盡,將拋出一個(gè)bad_alloc異常

            if (*q == 7) { /* ... */ }



            我如何定義一個(gè)類內(nèi)部(in-class)的常量?



            如果你需要一個(gè)通過(guò)常量表達(dá)式來(lái)定義的常量,例如數(shù)組的范圍,你有兩種選擇:



            class X {

            static const int c1 = 7;

            enum { c2 = 19 };



            char v1[c1];

            char v2[c2];



            // ...

            };



            乍看起來(lái),c1的聲明要更加清晰,但是要注意的是,使用這種類內(nèi)部的初始化語(yǔ)法的時(shí)候,常量必須是被一個(gè)常量表達(dá)式初始化的整型或枚舉類型,而且必須是static和const形式。這是很嚴(yán)重的限制:



            class Y {

            const int c3 = 7; // 錯(cuò)誤:不是static

            static int c4 = 7; // 錯(cuò)誤:不是const

            static const float c5 = 7; // 錯(cuò)誤:不是整型

            };



            我傾向使用枚舉的方式,因?yàn)樗臃奖?,而且不?huì)誘使我去使用不規(guī)范的類內(nèi)初始化語(yǔ)法。



            那么,為什么會(huì)存在這種不方便的限制呢?一般來(lái)說(shuō),類在一個(gè)頭文件中被聲明,而頭文件被包含到許多互相調(diào)用的單元去。但是,為了避免復(fù)雜的編譯器規(guī)則,C++要求每一個(gè)對(duì)象只有一個(gè)單獨(dú)的定義。如果C++允許在類內(nèi)部定義一個(gè)和對(duì)象一樣占據(jù)內(nèi)存的實(shí)體的話,這種規(guī)則就被破壞了。對(duì)于C++在這個(gè)設(shè)計(jì)上的權(quán)衡,請(qǐng)參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》。



            如果你不需要用常量表達(dá)式來(lái)初始化它,那么可以獲得更大的彈性:



            class Z {

            static char* p; // 在定義中初始化

            const int i; // 在構(gòu)造函數(shù)中初始化

            public:

            Z(int ii) :i(ii) { }

            };



            char* Z::p = "hello, there";



            你可以獲取一個(gè)static成員的地址,當(dāng)且僅當(dāng)它有一個(gè)類外部的定義的時(shí)候:



            class AE {

            // ...

            public:

            static const int c6 = 7;

            static const int c7 = 31;

            };



            const int AE::c7; // 定義



            int f()

            {

            const int* p1 = &AE::c6; // 錯(cuò)誤:c6沒(méi)有左值

            const int* p2 = &AE::c7; // ok

            // ...

            }



            為什么delete不會(huì)將操作數(shù)置0?



            考慮一下:



            delete p;

            // ...

            delete p;



            如果在...部分沒(méi)有涉及到p的話,那么第二個(gè)"delete p;"將是一個(gè)嚴(yán)重的錯(cuò)誤,因?yàn)镃++的實(shí)現(xiàn)(譯注:原文為a C++ implementation,當(dāng)指VC++這樣的實(shí)現(xiàn)了C++標(biāo)準(zhǔn)的具體工具)不能有效地防止這一點(diǎn)(除非通過(guò)非正式的預(yù)防手段)。既然delete 0從定義上來(lái)說(shuō)是無(wú)害的,那么一個(gè)簡(jiǎn)單的解決方案就是,不管在什么地方執(zhí)行了"delete p;",隨后都執(zhí)行"p=0;"。但是,C++并不能保證這一點(diǎn)。



            一個(gè)原因是,delete的操作數(shù)并不需要一個(gè)左值(lvalue)??紤]一下:



            delete p+1;

            delete f(x);



            在這里,被執(zhí)行的delete并沒(méi)有擁有一個(gè)可以被賦予0的指針。這些例子可能很少見(jiàn),但它們的確指出了,為什么保證"任何指向被刪除對(duì)象的指針都為0"是不可能的。繞過(guò)這條"規(guī)則"的一個(gè)簡(jiǎn)單的方法是,有兩個(gè)指針指向同一個(gè)對(duì)象:



            T* p = new T;

            T* q = p;

            delete p;

            delete q; // 糟糕!



            C++顯式地允許delete操作將操作數(shù)左值置0,而且我曾經(jīng)希望C++的實(shí)現(xiàn)能夠做到這一點(diǎn),但這種思想看來(lái)并沒(méi)有在C++的實(shí)現(xiàn)中變得流行。



            如果你認(rèn)為指針置0很重要,考慮使用一個(gè)銷毀的函數(shù):



            template<class T> inline void destroy(T*& p) { delete p; p = 0; }



            考慮一下,這也是為什么需要依靠標(biāo)準(zhǔn)庫(kù)的容器、句柄等等,來(lái)將對(duì)new和delete的顯式調(diào)用降到最低限度的另一個(gè)原因。



            注意,通過(guò)引用來(lái)傳遞指針(以允許指針被置0)有一個(gè)額外的好處,能防止destroy()在右值上(rvalue)被調(diào)用:



            int* f();

            int* p;

            // ...

            destroy(f()); // 錯(cuò)誤:應(yīng)該使用一個(gè)非常量(non-const)的引用傳遞右值

            destroy(p+1); // 錯(cuò)誤:應(yīng)該使用一個(gè)非常量(non-const)的引用傳遞右值



            我能夠?qū)?void main()"嗎?



            這種定義:



            void main() { /* ... */ }



            在C++中從未被允許,在C語(yǔ)言中也是一樣。參見(jiàn)ISO C++標(biāo)準(zhǔn)3.6.1[2]或者ISO C標(biāo)準(zhǔn)5.1.2.2.1。規(guī)范的實(shí)現(xiàn)接受這種方式:



            int main() { /* ... */ }







            int main(int argc, char* argv[]) { /* ... */ }



            一個(gè)規(guī)范的實(shí)現(xiàn)可能提供許多版本的main(),但它們都必須返回int類型。main()返回的int值,是程序返回一個(gè)值給調(diào)用它的系統(tǒng)的方式。在那些不具備這種方式的系統(tǒng)中,返回值被忽略了,但這并不使"void main()"在C++或C中成為合法的。即使你的編譯器接受了"void main()",也要避免使用它,否則你將冒著被C和C++程序員視為無(wú)知的風(fēng)險(xiǎn)。



            在C++中,main()并不需要包含顯式的return語(yǔ)句。在這種情況下,返回值是0,表示執(zhí)行成功。例如:



            #include<iostream>



            int main()

            {

            std::cout << "This program returns the integer value 0\n";

            }



            注意,無(wú)論是ISO C++還是C99,都不允許在聲明中漏掉類型。那就是說(shuō),與C89和ARM C++形成對(duì)照,當(dāng)聲明中缺少類型時(shí),并不會(huì)保證是"int"。于是:



            #include<iostream>



            main() { /* ... */ }



            是錯(cuò)誤的,因?yàn)槿鄙賛ain()的返回類型。



            為什么我不能重載點(diǎn)符號(hào),::,sizeof,等等?



            大多數(shù)的運(yùn)算符能夠被程序員重載。例外的是:



            . (點(diǎn)符號(hào)) :: ?: sizeof



            并沒(méi)有什么根本的原因要禁止重載?:。僅僅是因?yàn)?,我沒(méi)有發(fā)現(xiàn)有哪種特殊的情況需要重載一個(gè)三元運(yùn)算符。注意一個(gè)重載了 表達(dá)式1?表達(dá)式2:表達(dá)式3 的函數(shù),不能夠保證表達(dá)式2:表達(dá)式3中只有一個(gè)會(huì)被執(zhí)行。



            Sizeof不能夠被重載是因?yàn)閮?nèi)建的操作(built-in operations),諸如對(duì)一個(gè)指向數(shù)組的指針進(jìn)行增量操作,必須依靠它??紤]一下:



            X a[10];

            X* p = &a[3];

            X* q = &a[3];

            p++; // p指向a[4]

            // 那么p的整型值必須比q的整型值大出一個(gè)sizeof(X)



            所以,sizeof(X)不能由程序員來(lái)賦予一個(gè)不同的新意義,以免違反基本的語(yǔ)法。



            在N::m中,無(wú)論N還是m都不是值的表達(dá)式;N和m是編譯器知道的名字,::執(zhí)行一個(gè)(編譯期的)范圍解析,而不是表達(dá)式求值。你可以想象一下,允許重載x::y的話,x可能是一個(gè)對(duì)象而不是一個(gè)名字空間(namespace)或者一個(gè)類,這樣就會(huì)導(dǎo)致——與原來(lái)的表現(xiàn)相反——產(chǎn)生新的語(yǔ)法(允許 表達(dá)式1::表達(dá)式2)。很明顯,這種復(fù)雜性不會(huì)帶來(lái)任何好處。



            理論上來(lái)說(shuō),.(點(diǎn)運(yùn)算符)可以通過(guò)使用和->一樣的技術(shù)來(lái)進(jìn)行重載。但是,這樣做會(huì)導(dǎo)致一個(gè)問(wèn)題,那就是無(wú)法確定操作的是重載了.的對(duì)象呢,還是通過(guò).引用的一個(gè)對(duì)象。例如:





            class Y {

            public:

            void f();

            // ...

            };



            class X { // 假設(shè)你能重載.

            Y* p;

            Y& operator.() { return *p; }

            void f();

            // ...

            };



            void g(X& x)

            {

            x.f(); // X::f還是Y::f還是錯(cuò)誤?

            }



            這個(gè)問(wèn)題能夠用幾種不同的方法解決。在標(biāo)準(zhǔn)化的時(shí)候,哪種方法最好還沒(méi)有定論。更多的細(xì)節(jié),請(qǐng)參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》。



            怎樣將一個(gè)整型值轉(zhuǎn)換為一個(gè)字符串?



            最簡(jiǎn)單的方法是使用一個(gè)字符串流(stringstream):



            #include<iostream>

            #include<string>

            #include<sstream>

            using namespace std;



            string itos(int i) // 將int轉(zhuǎn)換成string

            {

            stringstream s;

            s << i;

            return s.str();

            }



            int main()

            {

            int i = 127;

            string ss = itos(i);

            const char* p = ss.c_str();



            cout << ss << " " << p << "\n";

            }



            自然地,這種技術(shù)能夠?qū)⑷魏问褂?lt;<輸出的類型轉(zhuǎn)換為字符串。對(duì)于字符串流的更多說(shuō)明,參見(jiàn)《C++程序設(shè)計(jì)語(yǔ)言》21.5.3節(jié)。



            "int* p"正確還是"int *p"正確?



            二者都是正確的,因?yàn)槎咴贑和C++中都是有效的,而且意義完全一樣。就語(yǔ)言的定義與相關(guān)的編譯器來(lái)說(shuō),我們還可以說(shuō)"int*p"或者"int * p"。



            在"int* p"和"int *p"之間的選擇與正確或錯(cuò)誤無(wú)關(guān),而只關(guān)乎風(fēng)格與側(cè)重點(diǎn)。C側(cè)重表達(dá)式;對(duì)聲明往往比可能帶來(lái)的問(wèn)題考慮得更多。另一方面,C++則非常重視類型。



            一個(gè)"典型的C程序員"寫(xiě)成"int *p",并且解釋說(shuō)"*p表示一個(gè)什么樣的int"以強(qiáng)調(diào)語(yǔ)法,而且可能指出C(與C++)的語(yǔ)法來(lái)證明這種風(fēng)格的正確性。是的,在語(yǔ)法上*被綁定到名字p上。



            一個(gè)"典型的C++程序員"寫(xiě)成"int* p",并且解釋說(shuō)"p是一個(gè)指向int的指針類型"以強(qiáng)調(diào)類型。是的,p是一個(gè)指向int的指針類型。我明確地傾向于這種側(cè)重方向,而且認(rèn)為對(duì)于學(xué)好更多的高級(jí)C++這是很重要的。



            嚴(yán)重的混亂(僅僅)發(fā)生在當(dāng)人們?cè)噲D在一條聲明中聲明幾個(gè)指針的時(shí)候:



            int* p, p1; // 也許是錯(cuò)的:p1不是一個(gè)int*



            把*放到名字這一邊,看來(lái)也不能有效地減少這種錯(cuò)誤:



            int *p, p1; // 也許是錯(cuò)的?



            為每一個(gè)名字寫(xiě)一條聲明最大程度地解決了問(wèn)題——特別是當(dāng)我們初始化變量的時(shí)候。人們幾乎不會(huì)這樣寫(xiě):



            int* p = &i;

            int p1 = p; // 錯(cuò)誤:int用一個(gè)int*初始化了



            如果他們真的這么干了,編譯器也會(huì)指出。



            每當(dāng)事情可以有兩種方法完成,有人就會(huì)迷惑。每當(dāng)事情僅僅是一個(gè)風(fēng)格的問(wèn)題,爭(zhēng)論就會(huì)沒(méi)完沒(méi)了。為每一個(gè)指針寫(xiě)一條聲明,而且永遠(yuǎn)都要初始化變量,這樣,混亂之源就消失了。更多的關(guān)于C的聲明語(yǔ)法的討論,參見(jiàn)《C++語(yǔ)言的設(shè)計(jì)和演變》。



            對(duì)于我的代碼,哪一種布局風(fēng)格(layout style)是最好的?



            這種風(fēng)格問(wèn)題屬于個(gè)人的愛(ài)好。人們往往對(duì)布局風(fēng)格的問(wèn)題持有強(qiáng)烈的意見(jiàn),不過(guò),也許一貫性比某種特定的風(fēng)格更加重要。象大多數(shù)人一樣,我花了很長(zhǎng)的時(shí)間,來(lái)為我的偏好作出一個(gè)固定的結(jié)論。



            我個(gè)人使用通常稱為"K&R"的風(fēng)格。當(dāng)使用C語(yǔ)言沒(méi)有的構(gòu)造函數(shù)時(shí),需要增加新的習(xí)慣,這樣就變成了一種有時(shí)被稱為"Stroustrup"的風(fēng)格。例如:



            class C : public B {

            public:

            // ...

            };



            void f(int* p, int max)

            {

            if (p) {

            // ...

            }



            for (int i = 0; i<max; ++i) {

            // ...

            }

            }



            比大多數(shù)布局風(fēng)格更好,這種風(fēng)格保留了垂直的空格,我喜歡盡可能地在合理的情況下對(duì)齊屏幕。對(duì)函數(shù)開(kāi)頭的大括弧的放置,有助于我第一眼就分別出類的定義和函數(shù)的定義。



            縮進(jìn)是非常重要的。



            設(shè)計(jì)問(wèn)題,諸如作為主要接口的抽象基類的使用,使用模板以表現(xiàn)有彈性的類型安全的抽象,以及正確地使用異常以表現(xiàn)錯(cuò)誤,比布局風(fēng)格的選擇要重要得多。



            我應(yīng)該將"const"放在類型之前還是之后?



            我把它放在前面,但那僅僅是個(gè)人愛(ài)好問(wèn)題。"const T"和"T const"總是都被允許的,而且是等效的。例如:



            const int a = 1; // ok

            int const b = 2; // also ok



            我猜想第一種版本可能會(huì)讓少數(shù)(更加固守語(yǔ)法規(guī)范)的程序員感到迷惑。



            為什么?當(dāng)我發(fā)明"const"(最初的名稱叫做"readonly",并且有一個(gè)對(duì)應(yīng)的"writeonly")的時(shí)候,我就允許它出現(xiàn)在類型之前或之后,因?yàn)檫@樣做不會(huì)帶來(lái)任何不明確。標(biāo)準(zhǔn)之前的C和C++規(guī)定了很少的(如果有的話)特定的順序規(guī)范。



            我不記得當(dāng)時(shí)有過(guò)任何有關(guān)順序問(wèn)題的深入思考或討論。那時(shí),早期的一些使用者——特別是我——僅僅喜歡這種樣子:



            const int c = 10;



            看起來(lái)比這種更好:



            int const c = 10;



            也許我也受了這種影響:在我最早的一些使用"readonly"的例子中



            readonly int c = 10;



            比這個(gè)更具有可讀性:



            int readonly c = 10;



            我創(chuàng)造的那些最早的使用"const"的(C或C++)代碼,看來(lái)已經(jīng)在全球范圍內(nèi)取代了"readonly"。



            我記得這個(gè)語(yǔ)法的選擇在幾個(gè)人——例如Dennis Ritchie——當(dāng)中討論過(guò),但我不記得當(dāng)時(shí)我傾向于哪種語(yǔ)言了。



            注意在固定指針(const pointer)中,"const"永遠(yuǎn)出現(xiàn)在"*"之后。例如:



            int *const p1 = q; // 指向int變量的固定指針

            int const* p2 = q; //指向int常量的指針

            const int* p3 = q; //指向int常量的指針



            使用宏有什么問(wèn)題?



            宏不遵循C++中關(guān)于范圍和類型的規(guī)則。這經(jīng)常導(dǎo)致一些微妙的或不那么微妙的問(wèn)題。因此,C++提供更適合其他的C++(譯注:原文為the rest of C++,當(dāng)指C++除了兼容C以外的部分)的替代品,例如內(nèi)聯(lián)函數(shù)、模板與名字空間。



            考慮一下:



            #include "someheader.h"



            struct S {

            int alpha;

            int beta;

            };



            如果某人(不明智地)地寫(xiě)了一個(gè)叫"alpha"或"beta"的宏,那么它將不會(huì)被編譯,或者被錯(cuò)誤地編譯,產(chǎn)生不可預(yù)知的結(jié)果。例如,"someheader.h"可能包含:



            #define alpha 'a'

            #define beta b[2]



            將宏(而且僅僅是宏)全部大寫(xiě)的習(xí)慣,會(huì)有所幫助,但是對(duì)于宏并沒(méi)有語(yǔ)言層次上的保護(hù)機(jī)制。例如,雖然成員的名字包含在結(jié)構(gòu)體的內(nèi)部,但這無(wú)濟(jì)于事:在編譯器能夠正確地辨別這一點(diǎn)之前,宏已經(jīng)將程序作為一個(gè)字符流進(jìn)行了處理。順便說(shuō)一句,這是C和C++程序開(kāi)發(fā)環(huán)境和工具能夠被簡(jiǎn)化的一個(gè)主要原因:人與編譯器看到的是不同的東西。



            不幸的是,你不能假設(shè)別的程序員總是能夠避免這種你認(rèn)為"相當(dāng)白癡"的事情。例如,最近有人報(bào)告我,他們遇到了一個(gè)包含goto的宏。我也見(jiàn)過(guò)這種情況,而且聽(tīng)到過(guò)一些——在很脆弱的時(shí)候——看起來(lái)確實(shí)有理的意見(jiàn)。例如:



            #define prefix get_ready(); int ret__

            #define Return(i) ret__=i; do_something(); goto exit

            #define suffix exit: cleanup(); return ret__



            void f()

            {

            prefix;

            // ...

            Return(10);

            // ...

            Return(x++);

            //...

            suffix;

            }



            作為一個(gè)維護(hù)的程序員,就會(huì)產(chǎn)生這種印象;將宏"隱藏"到一個(gè)頭文件中——這并不罕見(jiàn)——使得這種"魔法"更難以被辨別。



            一個(gè)常見(jiàn)的微妙問(wèn)題是,一個(gè)函數(shù)風(fēng)格的宏并不遵守函數(shù)參數(shù)傳遞的規(guī)則。例如:



            #define square(x) (x*x)



            void f(double d, int i)

            {

            square(d); // 好

            square(i++); // 糟糕:這表示 (i++*i++)

            square(d+1); //糟糕:這表示(d+1*d+1); 也就是 (d+d+1)

            // ...

            }



            "d+1"的問(wèn)題,可以通過(guò)在"調(diào)用"時(shí)或宏定義時(shí)添加一對(duì)圓括號(hào)來(lái)解決:



            #define square(x) ((x)*(x)) /*這樣更好 */



            但是, i++被執(zhí)行了兩次(可能并不是有意要這么做)的問(wèn)題仍然存在。



            是的,我確實(shí)知道有些特殊的宏并不會(huì)導(dǎo)致C/C++預(yù)處理宏這樣的問(wèn)題。但是,我無(wú)心去發(fā)展C++中的宏。作為替代,我推薦使用C++語(yǔ)言中合適的工具,例如內(nèi)聯(lián)函數(shù),模板,構(gòu)造函數(shù)(用來(lái)初始化),析構(gòu)函數(shù)(用來(lái)清除),異常(用來(lái)退出上下文環(huán)境),等等。

            posted on 2006-12-03 19:26 大龍 閱讀(146) 評(píng)論(0)  編輯 收藏 引用

            久久久久久久久久久久中文字幕| 国产精品亚洲综合久久| 久久综合狠狠综合久久激情 | 久久精品免费一区二区三区| 精品视频久久久久| 国产一区二区久久久| 久久精品午夜一区二区福利| 亚洲国产精品久久久久久| 亚洲国产精品嫩草影院久久| 亚洲精品国产字幕久久不卡 | 久久66热人妻偷产精品9| 国产69精品久久久久777| 久久久久亚洲?V成人无码| 国产精品久久久久免费a∨| 99久久这里只有精品| 久久亚洲国产精品123区| 久久亚洲AV成人出白浆无码国产| 91精品婷婷国产综合久久| 国产精品久久久久蜜芽| 久久国产精品久久国产精品| 久久夜色撩人精品国产| 久久亚洲精品国产精品| 亚洲精品97久久中文字幕无码| 丰满少妇人妻久久久久久| 欧美精品福利视频一区二区三区久久久精品 | 72种姿势欧美久久久久大黄蕉 | 国产精品福利一区二区久久| 亚洲国产香蕉人人爽成AV片久久| 99久久久精品免费观看国产| 伊人色综合久久天天网| 中文精品久久久久国产网址 | 99久久精品免费看国产一区二区三区| 久久香蕉国产线看观看99| 久久久久亚洲精品日久生情 | 999久久久无码国产精品| 无码乱码观看精品久久| 99久久久久| 99久久777色| 亚洲αv久久久噜噜噜噜噜| 久久久久九九精品影院| 岛国搬运www久久|