Effective C++ 读书笔记(一)

1 让自己习惯C++

条款01:视C++为一个语言联邦

C++是多重范型编程语言,同时支持过程形式,面向对象形式,函数形式,泛型形式,元编程形式(什么是元编程?)的语言。

         C++主要包括4部分:C, Object-Oriented C++, Template C++和STL。

 

条款02:尽量以const, enum, inline替换#define

1, 对于单纯变量最好以const对象或enums替换#defines.

#define ASPECT_RATIO 1.653

const double ASPECT_RATION = 1.653;

 

常量定义式通常放在头文件中,若要在头文件内定义一个常量的char * -- based 字符串:

const char * const autherName = “Scott Meyers”;

Or

const std::string autherName(“Scott Meyers”);

 

Class专属常量:

class GamePlayer{

Private:

static const int NumTurns = 5;

int scores[NumTures];

};

 

Const int Gameplayer::NumTurns;

class GamePlayer{

Private:

static const int NumTurns;

Int scores[NumTures];

};

 

Const int Gameplayer::NumTurns = 5;//位于实现文件内

 

若在编译期间需要一个class常量值,可以通过所谓的”the enum hack”补偿做法。

class GamePlayer{

Private:

enum {NumTurns = 5};

int scores[NumTures];

};

 

2, 对于形似函数的宏,最好改用inline函数替换#defines.

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

 

int a = 5, b = 0;

CALL_MAX(++a, b); // a被累加二次

CALL_MAX(++a, b + 10) // a被累加一次

 

可以替换成:

 

template<typername T>

inline void CALL_MAX(const T& a, const T& b) //T未知, 采用pass-by-ref-to-const, See条款20.

{

         f(a > b ? a : b);

}

 

条款03:尽可能使用const

1, const基本用法

         const语法: 若const出现在星号左边,表示被指物是常量;出现在星号右边,表示自身是常量;出现在两边,都是常量。

char greeting[] = “Hello”;

char * p = greeting;                           //non-const pointer, non-const data

const char * p = greetring;                //non-const pointer, const data

char * const p = greeting;                 //const pointer, non-const data

const char * const p = greeting;      //const pointer, const data

        

         如下两种写法意义相同:

void f(const widget * pw)         ßà      void f(widget const * pw); // pw指向一个常量的widget对象

 

2, const迭代器

std::vector<int> vec;

const std::vector<int>::iterator iter = vec.begin();

*iter = 10;

iter++; //error, iter is const

 

std::vector<int>:: const_iterator cter = vec.begin();

*cter = 10; //error, *cter is const

cter++;

 

令函数返回一个常数值,可以降低因客户错误而造成的意外。

class Rational{}

const Rational operator* (const Rational& ls, const Rational& rhs);

Rational a, b, c;

(a * b) = c; //error

 

3, const成员函数

class TextBlock{

         const char& operator[] (std::size_t pos)const //operator for const对象

         {return text[pos];}

         char& operator[] (std::size_t pos) //operator for non-const对象

         {return text[pos];}

private: std::string text;

};

         TextBlock tb(“Hello”);

         std::cout<<tb[0]; //调用non-const TextBlock::operator[]

         tb[0] = ‘x’; //OK

         const TextBlock ctb(“world”);

         std::cout<<ctb[0];//调用const  TextBlock::operator[]

         ctb[0]=’x’; //error! 写一个const TextBlock

 

         成员函数只有在不改变任何成员变量时才可以说是const。许多成员函数虽然不十足具备const性质,但能通过编译测试。

class TextBlock{

         char& operator[] (std::size_t pos)const //operator for const对象

         {return text[pos];}

private: std::string text;

};

         const TextBlock ctb(“Hello”);

         char * pc = &ctb[0];

         *pc = ‘J’; //ctb现在是Jello

 

class TextBlock{

         std::size_t length()const

private: std::string text;

std::size_t textLen;

};

std::size_t TextBlock::length()const

{

textLen = text.len(); //error,不能给textLen赋值

return textLen;

}

class TextBlock{

         std::size_t length()const

private: std::string text;

mutable std::size_t textLen;
//这些成员变量可能总是会被更改,即使在const成员函数中

};

std::size_t TextBlock::length()const

{

textLen = text.len();

return textLen;

}

 

4, 在const和non-const成员函数中避免重复

class TextBlock{

const char& operator[](std::size_t pos) const

{

…//边界检验

…//数据访问

…//检验数据完整性

}

 

char& operator[](std::size_t pos)

{

…//边界检验

…//数据访问

…//检验数据完整性

}

 

private: std::string text;

};

class TextBlock{

const char& operator[](std::size_t pos) const

{

…//边界检验

…//数据访问

…//检验数据完整性

}

 

char& operator[](std::size_t pos)

{

return const_cast<char &> ( static_cast<const TextBlock&> (*this) [pos]

);

}

 

private: std::string text;

};

 

综上:常成员函数         <类型标志符> 函数名(参数表)const

  1,const是函数类型的一部分,在实现部分也要带该关键字。

  2,const关键字可以用于对重载函数的区分。

  3,常成员函数不能更新类的成员变量,也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数和常数据成员。

  4,常成员函数可以被其他成员函数调用。

  5,但是不能调用其他非常成员函数。

  6,可以调用其他常成员函数。

 

条款04:确定对象被使用前已先被初始化

1, 构造函数最好使用成员初值列,而不要在构造函数体内使用赋值操作。初值列出的成员变量,其排列次序应该和它们在class中的声明次序相同。

class PhoneNumber{…}

class ABEntry{

public: ABentry(const std::string& name,

const std::string& address,

const std::list<PhoneNumber>& phones);

 

private:

         std::string thename;

std::string theaddress;

std::list<PhoneNumber> thephones;

int numTimesConsulted;

};

 

 

ABEntry: :ABentry(const std::string& name,

const std::string& address,

const std::list<PhoneNumber>& phones);

{

thename = name; //这些都是赋值,不是初始化

the address = address;

the phones = phones ;

numTimesConsulted = 0;

}

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。在ABentry构造函数内,theName, theAddress和thePhones都不是初始化,而是赋值。初始化的发生时间更早,发生于这些成员的default构造函数被自动调用之时。然后立刻对它们赋予新值,因此default构造函数的一切作为浪费了。

//测试代码:

class PhoneNumber

{

public:

         PhoneNumber(){cout<<"PhoneNumber default ctor"<<endl;}

         PhoneNumber(PhoneNumber& ){cout<<"PhoneNumber copy ctor"<<endl;}

         PhoneNumber& operator = (PhoneNumber&)

{cout<<"PhoneNumber operator ="<<endl;return *this;}

};

 

class TextBlock{

public:

         //TextBlock(PhoneNumber& _pn):pn(_pn){}

         TextBlock(PhoneNumber& _pn){

                   cout<<"----"<<endl;

                   pn = _pn;

         }

private:

         PhoneNumber pn;

};

 

PhoneNumber pn;

int main()

{

         cout<<"####"<<endl;

         TextBlock tb(pn);

}

 

//输出:

PhoneNumber default ctor

####

PhoneNumber default ctor

----

PhoneNumber operator =

 

因此,下面的构造构造函数效率比上面的要高。

ABEntry: :ABentry(const std::string& name,

const std::string& address,

const std::list<PhoneNumber>& phones)

:thename (name), the address(address), the phones(phones), numTimesConsulted(0) //这些都是初始始化

 { }

 

技巧: 总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值。

 

C++有着十分固定的“成员初始化次序”。是的,次序总是相同:base classes更早于derived classes初初始化,而class的成员变量总是以其声明次序被初始化。即thename成员永远最先初始化,然后是theaddress, 再来是thephones,最后是numTimesConsulted。

 

2, 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

注: 函数内的static对象称为local static对象,其他static对象称为non-local static对象。编译单元是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件。

 

现在关心的问题涉及至少两个源码文件,每一个内含至少一个non-local static对象(也就是说该对象是global或位于namespace作用域内,抑或在class内或file作用内被声明为static)。

 

class FileSystem{

public:

           …

           std::size_t numDisks()const;

           …

};

extern FileSystem tfs;

现在假设某些客户建立了一个class用以处理文件系统内的目录。

class Directory{

public:

           Directory(params);

           …

};

         Directory::Directory(params)

         {

                   …

                   std::size_t disks = tfs.numDisks(); //使用tfs对象

                   …

}

Directory  tempDir(params);

显然,除非tfs在tempDir之前先初始化,否则tempDir的构造函数会用到尚未初始化的tfs。但tfs和tempDir是不同人不同时候创建的,定义于不同编译单元内的non-local static对象。如何确定tfs会在tempDir之前先被初始化?

C++对“定义于不同编译单元内的non-local static对象”的初始化相对次序并无明确定义。

 

解决方法:

class FileSystem{…}

FileSystem& tfs()

{

         static FileSystem fs;

         return fs;

}

 

class Directory{         …};

         Directory::Directory(params)

         {

                   …

                   std::size_t disks = tfs().numDisks(); //使用tfs对象

                   …

}

Directory & tempDir()

{

         static Directory td;

         return td;

}

posted on 2012-11-21 22:37  ArcherXu  阅读(206)  评论(0编辑  收藏  举报

导航