麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

Effective C++ - Accustoming Yourself to C++

2019-11-14 09:59:30
字體:
來源:轉載
供稿:網友

Effective C++ - Accustoming Yourself to C++

前言:如何有效運用C++,包括一般性的設計策略,以及帶有具體細節的特定語言特性。知道細節很重要,否則如果疏忽幾乎總是導致不可預期的程序行為(undefined behavior)。本文總結對于如何使用C++的一些建議,從而讓你成為一個有戰斗力的C++程序員。


Effective C - Accustoming Yourself to C構造函數的explicit對象的復制命名習慣TR1和Boost視C為一個語言聯邦盡量以const enum inline替換define盡量使用const確定對象被使用前已先被初始化

1 構造函數的explicit

被聲明為explicit的構造函數通常比non-explicit更受歡迎,因為它們禁止編譯器執行非預期的類型轉換。除非有一個好理由允許構造函數被用于隱式類型轉換,否則把它聲明為explicit

class foo {public: explicit foo(int x);};

2 對象的復制

copy構造函數被用來“以同型對象初始化自我對象”copy assignment操作符被用來“從另一個同型對象中拷貝其值到自我對象”。

class Widget {public: Widget(); // default構造函數 Widget(const Widget& rhs); // copy構造函數 Widget& Operator=(const Widget& rhs); // copy assignment操作符};Widget w1; // 調用default構造函數Widget w2(w1); // 調用copy構造函數w1 = w2; // 調用copy assignment操作符Widget w3 = w2; // 調用copy構造函數

copy構造和copy賦值的區別:如果一個新對象被定義,一定會有一個構造函數被調用,不可能調用賦值操作。如果沒有新對象被定義,就不會有構造函數被調用,那么就是賦值操作被調用。

3 命名習慣

構造函數和析構函數分別使用縮寫ctordtor代替。 使用lhs(left-hand side)和rhs(right-hand side)表示參數名稱。

4 TR1和Boost

TR1(Technical Report 1)是一份規范,描述加入C++標準程序庫的諸多新機能。這些機能以新的class templatesfunction templates形式體現。所有TR1組件都被置于命名空間tr1內。 Boost是個組織,亦是一個網站,提供可移植,源代碼開放的C++程序庫。大多數TR1機能是以Boost的工作為基礎。

5 視C++為一個語言聯邦

今天的C++已經是個多重范型編程語言(multiparadigm PRogramming language),一個同時支持以下特性的語言: * 過程形式(procedural) * 面向對象形式(object-oriented) * 函數形式(functional) * 泛型形式(generic) * 元編程形式(metaprogramming)

為了理解C++,你必須認識其主要的次語言(sublanguage)

C 說到底C++仍是以C為基礎。blocks, statements, preprocessor, built-in data types, arrays, pointers等統統來自C。許多時候C++對問題的解法其實不過就是較高級的C解法,但是C++提供了C沒有的templates, exceptions, overloading(重載)等功能。

C語言可以重載嗎

// http://www.cplusplus.com/reference/cstdlib/qsort//* qsort example */#include <stdio.h> /* printf */#include <stdlib.h> /* qsort */int values[] = { 40, 10, 100, 90, 20, 25 };int compare (const void * a, const void * b){ return ( *(int*)a - *(int*)b );}void fun(){ printf("fun()/n");}/*$gcc -o overload_test overload_test.c overload_test.c:18:6: error: redefinition of 'fun'void fun(int a) ^overload_test.c:13:6: note: previous definition is herevoid fun() ^1 error generated. */#if 0void fun(int a){ printf("fun(int a)/n");}#endifint main (){ // 測試C語言是否支持overload重載 fun(); // C語言可以通過不同的函數指針來模擬overload重載 int n; qsort (values, 6, sizeof(int), compare); for (n=0; n<6; n++) printf ("%d ",values[n]); return 0;}

Object-Oriented C++ 這部分就是C with Classes所訴求的:

classes(包括構造函數和析構函數)encapsulation(封裝)inheritance(繼承)polymorphism(多態)virtual function(虛函數動態綁定)etc.

Template C++ 這是C++的泛型編程(generic programming)部分,也是大多數程序員經驗最少的部分。

STL STL是個template程序庫,它對containers, iterators, algorithms以及function objects的規約有極佳的緊密配合與協調。

6 盡量以const, enum, inline替換#define

寧可以編譯器替換預處理器。當你做出這樣的事情:

#define aspECT_RATIO 1.653

記號名稱ASPECT_RATIO也許從未被編譯器看見,也許在編譯器開始處理源碼之前就被預處理器替換了,于是記號名稱有可能沒有進入記號表(symbol table)內,當你運用此常量但獲得一個編譯錯誤時可能會帶來困惑,因為這個錯誤信息提到的是1.653而不是ASPECT_RATIO。尤其是如果ASPECT_RATIO被定義在一個非你所寫的頭文件內,你肯定對1.653來自何處毫無概念。解決的方法是:以一個常量替換上述的宏(#define)

const double AspectRatio = 1.653; // 大寫名稱通常用于宏

好處是:

作為一個語言常量,AspectRatio肯定會被編譯器看到,當然就會進入記號表內。使用常量可能比使用#define導致較小量的目標代碼,因為預處理器盲目地將宏名稱進行替換會導致目標代碼出現多份1.653,而若改用常量則不會出現。字符串常量,string對象通常比char*-based合適。const char* const authorName = "gerry";const std::string authorName("gerry");class專屬常量。為了將常量的作用域(scope)限制在class內,你必須讓它成為class的一個成員(member),另外為了保證此常量至多只有一份實體,必須讓它成為一個static成員。#include<stdio.h>class GamePlayer {public: void set_scores() { for (int i = 0; i != NumTurns; ++i) { scores[i] = i; } } void get_scores() { for (int i = 0; i != NumTurns; ++i) { printf("%d ", scores[i]); } printf("/n"); } static int get_numturns() { //printf("addr GamePlayer::NumTurns[%p]/n", &GamePlayer::NumTurns); return GamePlayer::NumTurns; }private: static const int NumTurns = 5; // 常量聲明 int scores[NumTurns]; // 使用該常量};int main(){ printf("GamePlayer::NumTurns[%d]/n", GamePlayer::get_numturns()); GamePlayer player; player.set_scores(); player.get_scores(); GamePlayer player2; printf("player.NumTurns[%d] player2.NumTurns[%d]/n", player.get_numturns(), player2.get_numturns()); return 0;}/*GamePlayer::NumTurns[5]0 1 2 3 4 player.NumTurns[5] player2.NumTurns[5] */

然而,上面你所看到的是NumTurns的聲明式,而非定義式。通常C++要求所使用的任何東西提供一個定義式,但如果它是class專屬常量且又是static整數類型,只要不取它們的地址,你可以聲明并使用它們而無須提供定義式

但是,如果你需要取某個class專屬常量的地址,或者編譯器要求(比如,老編譯器)需要看到一個定義式,那么需要另外提供定義式

#include<stdio.h>class GamePlayer {public: void set_scores() { for (int i = 0; i != NumTurns; ++i) { scores[i] = i; } } void get_scores() { for (int i = 0; i != NumTurns; ++i) { printf("%d ", scores[i]); } printf("/n"); } static int get_numturns() { printf("addr GamePlayer::NumTurns[%p]/n", &GamePlayer::NumTurns); return GamePlayer::NumTurns; }private: static const int NumTurns = 5; // 常量聲明 int scores[NumTurns]; // 使用該常量};const int GamePlayer::NumTurns; // NumTurns的定義int main(){ printf("GamePlayer::NumTurns[%d]/n", GamePlayer::get_numturns()); GamePlayer player; player.set_scores(); player.get_scores(); GamePlayer player2; printf("player.NumTurns[%d] player2.NumTurns[%d]/n", player.get_numturns(), player2.get_numturns()); return 0;}/*addr GamePlayer::NumTurns[0x102092f30]GamePlayer::NumTurns[5]0 1 2 3 4 addr GamePlayer::NumTurns[0x102092f30]addr GamePlayer::NumTurns[0x102092f30]player.NumTurns[5] player2.NumTurns[5]*/

通過提供定義式,我們就可以獲取class專屬常量的地址。

注意:

NumTurns的定義式中沒有賦值是因為,class常量已在聲明時獲得了初值,因此定義時不可以再設置初值。 我們無法利用#define創建一個class專屬常量,因為#define并不能限制作用域(scope),一旦宏被定義,它就在其后的編譯過程中有效,除非在某處被#undef。因此,#define不僅不能用來定義class專屬常量,也不能提供任何封裝性。

如果想具備作用域,但又不想取地址,可以使用enum來實現這個約束。

class GamePlayer {public: void set_scores() { for (int i = 0; i != NumTurns; ++i) { scores[i] = i; } } void get_scores() { for (int i = 0; i != NumTurns; ++i) { printf("%d ", scores[i]); } printf("/n"); } static int get_numturns() { //printf("addr GamePlayer::NumTurns[%p]/n", &GamePlayer::NumTurns); return GamePlayer::NumTurns; }private: //static const int NumTurns = 5; // 常量聲明 enum { NumTurns = 5, // 令NumTurns成為5的一個記號名稱 }; int scores[NumTurns]; // 使用該常量};

預處理器和宏的陷阱:

宏看起來像函數,但是不會招致函數調用(function call)帶來的額外開銷。 糟糕的做法:(有效率,但不安全)

// 以a和b的較大值調用f函數#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

好的做法:(效率和安全同時得到保證)

template<typename T>inline void callWithMax(const T& a, const T& b){ f(a > b ? a : b);}

這個template根據實例化可以產出一整群函數,每個函數都接受兩個同類型對象,并以其中較大的調用f。這里不需要在函數本體中為參數加上括號,也不需要操心參數被計算的次數,同時,由于callWithMax是個真正的函數,它遵守作用域和訪問規則,因此可以寫出一個class內的private inline函數,而對于宏是無法完成的。

請記住:

對于單純常量,最好以const對象或enum替換#define 對于形似函數的宏,最好改用inline函數替換#define

7 盡量使用const

const允許你指定一個語義約束,也就是指定一個“不該被改動”的對象,而編譯器會強制實施該項約束。

char greeting[] = "Hello";char* p = greeting; // non-const pointer, non-const dataconst char* p = greeting; // non-const pointer, const datachar* const p = greeting; // const pointer, non-const dataconst char* const p = greeting; // const pointer, const data

如果關鍵字const出現在星號左邊,表示被指物是常量;如果出現在星號右邊,表示指針自身是常量;如果出現在星號兩邊,表示被指物和指針兩者都是常量。

注意:如果被指物是常量,將關鍵字const寫在類型之前,和寫在類型之后星號之前,這兩種寫法的意義相同。

void f1(const Widget* pw);void f2(Widget const * pw);

STL迭代器系以指針為根據塑模出來,所以迭代器的作用就像個T*指針。如果你希望迭代器所指的東西不可被改變,則需要使用const_iterator

std::vector<int> vec;const std::vector<int>::iterator iter = vec.begin();*iter = 10; // ok++iter; // errorstd::vector<int>::const_iterator citer = vec.begin();*citer = 10; // error++citer; // ok

const成員函數

const實施于成員函數的目的,是為了確認該成員函數可作用于const對象身上。這一類成員函數之所以重要,是因為:

它們使class接口比較容易被理解,可以得知哪個函數可以改動對象內容,而哪個函數不行。它們使“操作const對象”成為可能,這對編寫高效代碼是個關鍵,比如,改善程序效率的一個根本方法是以pass by reference-to-const方式傳遞對象,而此技術可行的前提是,我們有const成員函數可用來處理取得的const對象。

注意:兩個成員函數如果只是常量性不同,可以被重載(overload)。只有返回值類型不同的兩個函數不能重載(functions that differ only in their return type cannot be overloaded)。

#include<stdio.h>#include<iostream>#include<string>class TextBlock {public: TextBlock() { } TextBlock(const char* lhs) { text = lhs; }public: // operator[] for const object const char& operator[] (std::size_t position) const { return text[position]; } // operator[] for non-const object char& operator[] (std::size_t position) { return text[position]; }private: std::string text;};int main(){ TextBlock tb("gerry"); std::cout << tb[0] << std::endl; // 調用non-const TextBlock::operator[] const TextBlock ctb("yang"); // 調用const TextBlock::operator[] std::cout << ctb[0] << std::endl; return 0;}

成員函數如果是const意味著什么?—— bitwise constness或者physical constness VS logical constness

bitwise const指的是,成員函數只有在不更改對象之任何成員變量(static除外)時才可以說是const,即,const成員函數不可以更改對象內任何non-static成員變量。

注意:許多成員函數雖然不完全具備const性質,卻能通過bitwise測試。比如,一個更改了”指針所指物”的成員函數,如果只有指針隸屬于對象,那么此函數為bitwise const不會引發編譯器異議,但是實際不能算是const

下面這段代碼,可以通過bitwise測試,但是實際上改變了對象的值。

#include<stdio.h>#include<iostream>#include<string>class TextBlock {public: TextBlock() { } TextBlock(char* lhs) { pText = lhs; }public: // operator[] for const object char& operator[] (std::size_t position) const { return pText[position]; }#if 0 // operator[] for non-const object char& operator[] (std::size_t position) { return pText[position]; }#endifprivate: char* pText;};int main(){ char name[] = "gerry"; const TextBlock ctb(name); std::cout << ctb[0] << std::endl; // 調用const TextBlock::operator[] char* pc = &ctb[0]; *pc = 'J'; std::cout << ctb[0] << std::endl; // 調用const TextBlock::operator[] return 0;}

logical constness主張,一個const成員函數可以修改它所處理的對象的某些bits,但只有在客戶端偵測不出的情況才可以(即,對客戶端是透明的,但是實際上對象的某些值允許改變)。正常情況下,由于bitwise const的約束,const成員函數內是不允許修改non-static成員變量的,但是通過將一些變量聲明為mutable則可以躲過編譯器的bitwise const約束。

#include<stdio.h>#include<iostream>#include<string>#include<string.h>class TextBlock {public: TextBlock() : lengthIsValid(false) { } TextBlock(char* lhs) : lengthIsValid(false) { pText = lhs; }public: std::size_t length() const { if (!lengthIsValid) { printf("do strlen... "); textLength = std::strlen(pText); // error? 在const成員函數內不能修改non-static成員變量 lengthIsValid = true; // 同上 } return textLength; } // operator[] for const object char& operator[] (std::size_t position) const { return pText[position]; }#if 0 // operator[] for non-const object char& operator[] (std::size_t position) { return pText[position]; }#endifprivate: char* pText; mutable std::size_t textLength; // 最近一次計算的文本區域塊長度 mutable bool lengthIsValid; // 目前的長度是否有效};int main(){ char name[] = "gerry"; const TextBlock ctb(name); std::cout << ctb[0] << std::endl; // 調用const TextBlock::operator[] std::cout << "length: " << ctb.length() << std::endl; char* pc = &ctb[0]; *pc = 'J'; std::cout << ctb[0] << std::endl; // 調用const TextBlock::operator[] std::cout << "length: " << ctb.length() << std::endl; return 0;}/*$./mutable glength: do strlen... 5Jlength: 5 */

constnon-const成員函數中避免重復

方法是:運用const成員函數實現出其non-const孿生兄弟。

不好的做法(因為有重復代碼):

// operator[] for const object const char& operator[] (std::size_t position) const { // bounds checking // log access data // verify data integrity // ... return text[position]; } // operator[] for non-const object char& operator[] (std::size_t position) { // bounds checking // log access data // verify data integrity // ... return text[position]; }

好的做法(實現operator[]的機能一次并使用它兩次,令其中一個調用另一個):

#include<stdio.h>#include<iostream>#include<string>class TextBlock {public: TextBlock() { } TextBlock(const char* lhs) { text = lhs; }public: // operator[] for const object const char& operator[] (std::size_t position) const { // bounds checking // log access data // verify data integrity // ... std::cout << "const char& operator[]() const/n"; return text[position]; }#if 0 // operator[] for non-const object char& operator[] (std::size_t position) { // bounds checking // log access data // verify data integrity // ... return text[position]; }#endif char& operator[] (std::size_t position) { std::cout << "char& operator[]()/n"; return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]); }private: std::string text;};int main(){ TextBlock tb("gerry"); std::cout << tb[0] << std::endl; // 調用non-const TextBlock::operator[] const TextBlock ctb("yang"); // 調用const TextBlock::operator[] std::cout << ctb[0] << std::endl; return 0;}/*char& operator[]()const char& operator[]() constgconst char& operator[]() consty */

請記住:

將某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加于任何作用域內的對象、函數參數、函數返回類型、成員函數本體。 編譯器強制實施bitwise constness,但你編寫程序時應該使用“概念上的常量性”。 當constnon-const成員函數有著實質等價的實現時,令non-const版本調用const版本可避免代碼重復。

8 確定對象被使用前已先被初始化

關于“將對象初始化”這事,C++似乎反復無常(對象的初始化動作何時一定發生,何時不一定發生)。針對這種復雜的規則,最佳的處理方法是:永遠在使用對象之前先將它初始化

對于內置類型,必須手工完成初始化;對于內置類型以外的其他類型,初始化責任落在構造函數(constructors)身上,即,確保每一個構造函數都將對象的每一個成員初始化。

構造函數初始化的正確方法是:使用member initialization list(成員初值列),而不是在構造函數中的賦值。因為第一種方法的執行效率通常較高(對于大多數類型而言,比起先調用default構造函數,然后再調用copy assignment操作符,單只調用一次copy構造函數是比較高效的。對于內置類型,其初始化和賦值的成本相同,但為了一致性最好也通過成員初值列來初始化)。

ABEntry:ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones): theName(name), // 成員初值列表,這些都是初始化 theAddress(address), thePhones(phones), numTimesConsulted(0){ }ABEntry::ABEntry(): theName(), // 調用theName的`default`構造函數 theAddress(), // 同上 thePhones(), // 同上 numTimesConsulted(0) // 將內置類型int顯示初始化為0{ }

C++有著十分固定的”成員初始化次序”:總是base classes更早于其derived classes被初始化。而class的成員變量總是以其聲明次序被初始化,而和它們在成員初始值列中的出現次序無關。建議,當你在成員初值列中初始化各個成員時,最好總是和其聲明的次序一致

最后一個問題:不同編譯單元內定義的non-local static對象的初始化順序是怎么樣的?

函數內的static對象稱為local static對象,其他static對象稱為non-local static對象。

C++對定義于不同編譯單元內的non-local static對象的初始化次序并無明確定義。因此,如果某編譯單元內的某個non-local static對象的初始化動作依賴另一編譯單元內的某個non-local static對象,那么它所用到的這個對象可能尚未被初始化。

針對上面這個問題的解決方法是: 將每個non-local static對象搬到自己的專屬函數內,這些函數返回一個reference指向它所含的對象。即,non-local static對象被local static對象替換了。

class FileSystem { ... };FileSystem& tfs(){ static FileSystem fs; return fs;}

注意:這些函數內含static對象的事實使它們在多線程系統中帶有不確定性。處理這種麻煩的方法是,在程序的單線程啟動階段,手工調用所有reference-returning函數,這可消除與初始化有關的race conditions(競速形勢)

請記住

為內置類型對象進行手工初始化,因為C++不保證初始化它們。 構造函數最好使用成員初值列(member initialization list),而不要在構造函數本體內使用賦值操作(assignment)。初值列列出的成員變量,其排列次序應該和它們在class中的聲明次序相同。 為免除跨編譯單元的初始化次序問題,請以local static對象替換non-local static對象。

下一篇: Effective C++ - Constructors, Destructors, and Assignment Operators


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 天天舔夜夜操 | 国内毛片视频 | 高清国产免费 | 欧美精品成人 | 欧美性生交xxxxx免费观看 | 美女黄影院 | 欧美成人一级片 | 国产一区在线观看视频 | 99精品视频免费看 | 99激情视频 | 久久久久久免费 | 成人在线影视 | 亚洲第一成人在线视频 | 91色一区二区三区 | 久久伊人精品视频 | 欧产日产国产精品乱噜噜 | 久久网站热最新地址4 | 97精品国产高清在线看入口 | 久久91精品国产91久久yfo | 毛片视频网站 | 国产成人av免费观看 | 亚州视频在线 | 国产一区二区视频精品 | 成人短视频在线观看免费 | 青草av.久久免费一区 | 黄色大片在线免费观看 | 久久精品伊人网 | 中文字幕电影免费播放 | 国产午夜网| 久久一区三区 | 国产91av视频 | 成人毛片免费播放 | 国产成人精品自拍视频 | 欧美一a一片一级一片 | 5a级毛片| 久久成年网 | 超碰一区 | 999久久久精品 | 黄色av免费电影 | 日韩在线播放第一页 | 9797色|