Effective C++ 之《讓自己習慣C++》

條款01:視C++爲一個聯邦語言

  一開始C++只是在C的基礎上加了一些面向對象 的特性。隨着語言的逐漸成熟。我們發現它相比其他語言更加的靈活和自由。有時候甚至對其產生迷惑。我在使用的時候也會經常遇到這樣的疑惑,好像它的每一個“適當用法”都是例外,這樣會讓人產生糾結。
  條款一告訴我們,我們應該視其爲一個語言聯邦而非單一語言,正因爲它的約束規則相比其他語言少了很多,它纔給了開發者更多的自用,來創造不同的使用方法。根據條款一的說法,我們爲了更好的理解C++這門語言,其實只要讀懂它的4個語言:

1. C。 C++仍是以C爲基礎,它的區塊、語句、預處理、內置數據類型、數組、指針等都來自C++,這是C的語法基礎,也是C++的語法基礎。
2. Object-Oriented C++。 這部分就是C with Classes,它包括類、封裝、繼承、多態、虛函數,這是面嚮對象語言的基礎。
3. Template C。 這是C++的泛型編程部分。掌握好這部分,可以使你的工作效率事半功倍,這也是大部分C++程序員(包括我)所缺乏的部分。
4. STL。 STL是一個模板程序庫。在C++項目工程中,我們會很常見。它對容器、算法、迭代器、函數對象的規約有極佳的緊密配合和協調。
  這四個次語言都有各自的規約,如果我們將C++視爲一個語言聯邦,當我們在C++中切換使用這四種次語言時,就不會感到奇怪了。

總結

C++ 高效編程守則視狀況而變化,取決於你使用C++的哪一部分。

條款02:儘量以const,enum, inline替換#define

1. 使用const替換#define
   通常情況下,我們會在頭文件中使用#define定義一些常量,考慮如下情況:

#define ASPECT_RATIO 1.653

   這樣使用確實沒有問題。但是,會有如下幾個小問題:
   (1). 如果在使用該常量的地方出現編譯錯誤,我們可能不會第一時間知道它錯誤的地方。編譯器提示的錯誤可能是1.653而不是ASPECT_RATIO,這是因爲ASPECT_RATIO在預處理器的   時候已經被替換成了1.653,編譯器並不知道記號ASPECT_RATIO的存在。
   (2). 使用 ASPECT_RATIO可能會導致更多的目標碼。這是因爲預處理器會將所有使用ASPECT_RATIO的地方替換成1.653,而使用const則不會出現這種情況。
   (3). 使用ASPECT_RATIO時,沒有安全類型檢查,與const相比,它時不安全的。
   所以,我們可以在頭文件中替換它爲:

const double AspectRatio = 1.653;

  當然在使用const的時候需要注意如下幾點:
  (1)對於指針,我們使用const,保證指針不被改變,以及指針所指向的內容不被改變。

const char* const authorName = "edwin";

或者使用標準庫:

const std::string authorName = "edwin";

  (2)如果需要在類中定義const成員,需要使用static進行修飾。(當然這種情況很少使用),通常在類中,我們會使用enum替代#define。
   2. 使用enum替換#define
  在實際項目中,我們常常會使用一些序列值表示類型。如讀寫操作。這時,又不想該類型爲全局的,所以就需要在類中定義枚舉來替代#define的使用:

class MyFile{
	private:
		enum {
			Read_Only = 0,
			Write_Only = 1,
			Read_Write = 2
		}
		...
}

   3. 使用inline替換#define
  Effective C++提供瞭如下使用#define的例子,這也是大多時候我們使用的方式:

#define CALL_WITH_MAX(a, b) f((a) > f(b) ? (a) : (b))

  這是比較兩個數的大小的宏函數。猶豫宏時預處理器自動替換的語法糖。所以如果不加下括號,在使用過程中很可能出現錯誤的輸出。但即使加了這麼多括號,也會出現我們意想不到的情況:

int a = 5, b = 0;
CALL_WITH_MAX(++a, b);						// a被累加了兩次
CALL_WITH_MAX(++a, b + 10);				//a被累加了一次

  如上,a的累加次數,取決於它的比較對象。當它的比較對象小於它時,它被累加了兩次,這顯然不是我們想要的結果。
  所以對於這種宏函數,我們最好使用inline函數來替換:

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

總結

  1. 對於單純常量,最好以const對象或enums替換#define;
  2. 對於形似函數的宏(macros),最好使用inline函數替換#define;

條款03:儘可能使用const

   const在c++中經常使用。它指定了一個語義約束–指定一個不被改動的對象。const可以修飾全局變量、命名空間內的變量、類中的靜態和非靜態成員變量、指針、函數等。
   1. const指針
   考慮如下的關於const對於指針的修飾:

char cP[] = "hello"; 
const char* p1 = cP;					//const 指針所指向的數據
char* const p2 = cP;					//const 指針本身
const char* const p3 = cP;				//const指針且const指針所指向的數據

  這裏區分它們的方法也很簡單,如果const在*左邊這修飾的時指針所指向的數據,反之修飾的是指針本身。
   2. const迭代器
  c++標準庫提供一套迭代器的使用。

std::vector<int> vec;
...
const std::vector<int>::iterator iter1 = vec.begin(); //const修飾的是迭代器iter1,所以iter不可以改變
std::vector<int>::const_iterator iter2 = vec.begin(); //const修飾的是迭代器iter2所指向的內容

   3. 函數返回const常量
  設置函數返回值爲const常量,可以避免用戶的錯誤使用而造成的意外。這樣做即保證了其安全性,也保證了其安全性。

class Rational{...}
const Rational Add operator* (const Rational& a, const Rational& b);

  如上所示,如果用戶使用了重載操作符*,做如下的操作:

Rational a, b, c;
if(a * b = c) {...};

  這個時候編譯時,編譯器會提示錯誤,因爲重載*函數返回的時const常量,值不能被改變。如果我們不使用const修飾,這樣編譯會通過,但是運行會得到意想不到的結果。
   5. const成員函數
   const成員函數的作用於const對象上,即const成員函數內部不能對成員變量進行改寫。
   (1). c++中的一個重要特性。

兩個成員函數如果只是常量性不同,可以被重載。

   所以就會出現如下的情況:

class TextBlock {
public:
	...
	const char& operator[](std::size_t position) const {
		return text[position];
	}
	char& operator[](std::size_t position) {
		return text[position];
	}
private:
	std::string text;
}

   那麼該如何調用以上的重載函數呢。

void print(const TextBlock& ctb, TextBlock& tb) {
	std::cout << tb[0];
	tb[0] = 'x';
	std::cout << ctb[0];
	ctb[0] = 'x';				//錯誤,對const TextBlock對象進行改寫。
}

   上面的註釋是因爲operator[]的返回類型是const,另外non_const的operator[]返回類型是char&,如果是char的話,程序是無法編譯過的。
   (2). const成員函數修改成員變量。
  如果我們需要在const成員函數中寫成員變量,則需要使用mutable修飾成員變量。這個是logical constness的主張者。
   (3). 在const 和non-const成員函數中避免重複
  考慮當兩個重載函數中內容一樣,且代碼比較多時,如果分別實現了兩個函數後,就會出現很多的重複代碼。所以如何去解決這種問題呢。

class TextBlock {
public:
	...
	const char& operator[](std::size_t position) const {
		...
		return text[position];
	}
	char& operator[](std::size_t position) {
		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
	}
private:
	std::string text;
}

  如上,在non-const版本中將this先轉換成const對象,然後調用const的重載,然後再將返回的const對象轉換成非const引用類型。需要注意的是,我們不能用const成員函數調用non-const的版本,這是因爲const成員函數絕不改變其對象的邏輯狀態。
總結

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

條款04:確定對象被使用前已先被初始化

  該條款相對好理解。主要有如下:

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

  關於第三條,因爲non-local static對象不能保證初始化的次序,所以使用local static替換non-local static對象。這樣就可以保證第二條的初始化次序。

發佈了56 篇原創文章 · 獲贊 26 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章