1. 程式人生 > >Effective C++: 01讓自己習慣C++

Effective C++: 01讓自己習慣C++

編程語言 error tap span fin cal effective point amp

01:視C++為一個語言聯邦

1:今天的C++已經是個多重範型編程語言(multiparadigm programming language),一個同時支持過程形式(procedural)、面向對象形式(object-oriented)、函數形式(functional)、泛型形式(generic)、元編程形式(metaprogramming )的語言。所以,需要將C++視為一個由相關語言組成的聯邦而非單一語言。

2:這個聯邦中主要包含4個次語言:C、Object-Oriented C++(classes,封裝,繼承,多態等)、Template C++(泛型編程)、STL(template標準庫)。

3:當你從某個次語言切換到另一個,導致高效編程守則要求你改變策略時,不要感到驚訝。

02:盡量以const, enum, inline替換#define

1:該條款的本質是:以編譯器替換預處理器。

2:#define ASPECT_RATIO 1.653這樣的宏定義,ASPECT_RATIO也許從未被編譯器看見,因而也就沒有記錄到符號表中,所以調試器也有可能不認識它。可以使用下面的語句代替它:

const double AspectRatio = 1.653; 至少AspectRatio 肯定會被編譯器看見,從而進入符號表。

3:一個屬於枚舉類型的數值可以充當ints被使用,比如一個枚舉值就可以作為數組大小的定義:

class GamePlayer {
private:
  enum { NumTurns = 5 };

  int scores[NumTurns];              // fine
  ...
};

4:使用#define定義一個看起來像函數一樣的宏,雖然它不會招致函數調用帶來的額外開銷,但是卻也不會有必要的語法檢查,因此必須記住為宏中的所有實參加上小括號。此時,可以使用inline函數,inline函數具有宏一樣的效率,還具有一般函數的所有可預料行為和類型安全性。

03:盡可能使用const

1:如果關鍵字const出現在星號左邊,表示被指物是常量;如果出現在星號右邊,表示指針自身是常量;如果出現在星號兩邊,表示被指物和指針兩者都是常量。const寫在類型之前,或者寫在類型之後,星號之前,這兩種寫法的意義是相同的。

2:聲明叠代器為const就像聲明指針為const一樣(即聲明一個T* const指針),表示這個叠代器不得指向不同的東西,但它所指的東西的值是可以改動的。如果你希望叠代器所指的東西不可被改動(即希望STL模擬一個const T*指針),你需要的是const_iterator:

std::vector<int> vec;
...
// iter acts like a T* const
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;                                 // OK, changes what iter points to
++iter;                                     // error! iter is const

// cIter acts like a const T*
std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;                                // error! *cIter is const
++cIter;                                    // fine, changes cIter

3:讓函數返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而又不至於放棄安全性和高效性。比如:

class Rational {
public:
    Rational(int numerator = 0,int denominator = 1):numerator(numerator), denominator(denominator){};
    int getnumerator() const {return numerator;}; 
    int getdenominator() const {return denominator;};

private:
  int numerator;
  int denominator;
};

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.getnumerator() * rhs.getnumerator(),
                  lhs.getdenominator() * rhs.getdenominator());
}

讓operator*返回一個const對象,就可以防止下面錯誤的發生(程序員的原意可能是a*b == c):

    Rational a, b, c;
    ...
    a * b = c;

4:如果成員函數是const的,則該成員函數才可作用於const對象。

如果兩個成員函數如果只是常量性(一個是const,一個非const)不同,則這是一種重載。

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

1:永遠在使用對象之前先將它初始化,對於內置類型,必須手工完成此事。

2:在構造函數中,盡量使用成員初始化列表初始化所有成員。這樣做有時候絕對必要(比如具有const或reference的類),而且往往比賦值更高效。

3:不同編譯單元內定義的non-local static對象的初始化次序是未定的。所謂non-local static對象,是指定義在函數之外的static對象。

如果某編譯單元內的某個non-local static對象的初始化動作使用了另一編譯單元內的某個non-local static對象,它所用到的這個對象可能尚未被初始化,因為C++對“定義於不同編譯單元內的non-local static對象”的初始化次序並無明確定義。

編譯單元A內,有如下的定義:

class FileSystem {
public:
  ...
  std::size_t numDisks() const;
  ...
};

extern FileSystem tfs; 

而編譯單元B內有下面的定義:

class Directory {                       // created by library client
public:
   Directory( params );
  ...
};

Directory::Directory( params )
{
  ...
  std::size_t disks = tfs.numDisks();   // use the tfs object
  ...
}

Directory tempDir( params );            // directory for temporary files

現在,除非tfs在tempDir之前初始化,否則tempDir的構造函數會用到尚未初始化的tfs。

幸運的是一個小小的設計便可完全消除這個問題。唯一需要做的是:將每個non-local static對象搬到自己的專屬函數內(該對象在此函數內被聲明為statIc)。這些函數返回一個reference指向它所含的對象。然後用戶調用這些函數,而不直接指涉這些對象。換句話說,non-local static對象被local static對象替換了。

這個手法的基礎在於:C++保證,函數內的local state對象會在“該函數被調用期間,首次遇上該對象之定義式”時被初始化。

所以,上面的例子可以改成:

class FileSystem { ... };          

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

class Directory { ... };

Directory::Directory( params )
{
  ...
  std::size_t disks = tfs().numDisks();
  ...
}

Directory& tempDir()
{
  static Directory td;
  return td;
}

這麽修改之後,這個系統程序的客戶完全像以前一樣地用它,唯一不同的是他們現在使用tfs ()和tempDir()而不再是tfs和tempDir。也就是說他們使用函數返回的“指向static對象”的references,而不再使用static對象自身。

Effective C++: 01讓自己習慣C++