effective c++读书笔记(一)

来源:互联网 发布:神经网络算法能做什么 编辑:程序博客网 时间:2024/05/18 09:20

1.声明式(declaration): 告诉编译器某个东西的名称和类型,但略去细节;每个函数的声明揭示其签名式(signature),也就是参数和返回类型。

extern int x;std::size_t numDigits(int number);template <typename T>class GraphNode;

2.定义式(definition): 的任务是提供编译器一些声明式所遗漏的细节。对 对象而言,定义是是编译器为此对象拨发内存的地点。。对function或function template而言,定义是提供了代码本体。对class 或class template 而言,定义式列出他们的成员;

int x;std::size_t numDigits(int number){    std::size_t digitsSoFar = 1;    while((numer /= 10) != 0) ++digitsSoFar;    return digitsSoFar;}template<typename T>class GraphNode{public:    GraphNode();    ~GraphNode();    //...};

3.初始化(initialization) 是“给予对象初值”的过程,对用户自定义类型的对象而言,初始化由构造函数执行。

class A{public:    A();  //default constructor};class B{public:    explicit B(int x = 0, bool b = true);  //default constructor};class C{public:    explicit C(int x);  // not default constructor};//examplevoid doSomething(B bObject);B bObject1;doSomething(bObject1);  //trueB bObject1(28);  //true doSomething(28);  //fault, from int to B ,no implicit type conversionsdoSomething(B(28));  //true ,explicit type conversions.

explitcit 可以阻止执行隐式类型转换,但是还是可以进行显示类型转换
被声明为explicit的构造函数通常比其non-explicit更受欢迎,因为他们禁止编译器进行非预期的类型转换
4.copy constructor function 被用来“以同类型对象初始化自我对象”, copy assignment operator 被用来“从另一个同类型对象拷贝其值到自我对象”:

class Widget{public:    Widget();    Widget(const Widget & rhs);    Widget& operator=(const Widget& rhs);    //...};Widget w1;  //default constructorWidget w2(w1);   //copy constructor functionw1 = w2;    // copy assignment operator
Widget w3 = w2; //copy constructor

区分copy constructor 和 copy assignment的区别,如果对象有一个新对象被定义,一定会有copy constructor called,不可能调用copy assignment function,如果没有新对象被定义,就不会有constructor函数被调用,应该是copy assignment function called。
copy constructor 定义一个对象如何passed by value

bool hasAccepttableQuality(Widget w);//...Widget aWidget;if(hasAccepttableQuality(aWidget)) //...

w是以by value方式传递给hasAccepttableQuality,所以aWidget被复制到w体内。复制动作是有copy constructor完成的。passed by value意味调用copy constructor。passed by reference to const是一个较好的选择。
5. undefined behavior

int *p = NULL;std::cout<<*p;  // undefined behaviorchar name[] = "Darla";char c = name[10];  //undefined behavior

Part1 accustoming yourself to c++

view c++ as a federation of languages

c++已经是一个多重范型编程语言,同时支持:过程形式(procedural), 面向对象形式(object-oriented), 函数形式(functional), 泛型形式(generic), 元编程形式(metaprogramming) 的语言。在某个此语言中,各种守则与通例都倾向简单,直观易懂,并且容易记住。
1. C。C++任是以C为基础。 blocks, statements, preprocessor, built-in data types, arrays, pointers 来自c。但是c没有template,exceptions,overloading等特性。
2. object-oriented c++。此部分包括:class, 封装(encapsulation), 继承(inheritance), 多态(polymorphism), virtual函数(动态绑定)。。。
3. template c++。 template metaprogramming。
4. STL。对容器(containers), 迭代器(iterator), 算法(algorithm)以及函数对象(functions objects) 的规约有极佳的紧密配合与协调。
对内置类型而言,pass-by-value通常比pass-by-reference高效,但是对于用户自定义类型而言pass-by-reference-to-const往往更好。对于STL而言,迭代器和函数对象是在C指针上塑造出来的,所以对于STL的迭代器和函数对象而言,旧式的pass-by-value再次适用

尽量以const,enum,inline替换 #define

  1. #define 不重视作用域,因为不能够称为class专属常量,不能提供任何封装性。enum 的行为某方面比较像#define而不像const。取enum的地址是不合法的,取define也是不合法的。enum和#define一样绝不会导致非必要的内存分配。
    用常量替换#define时,有两个特殊情况:
    1.定义常量指针(const pointers)。 由于常量定义式通常放头文件内,因此有必要将指针声明为const。
const char * const authorName = "Scott Meyers";const std::string authorName("Scott Meyers");  //string 对象通常比char*-based合适
  1. class专属常量。 为了将常量的作用域限制与class内,必须让它成为class 的一个成员,而为确保此常量最多只有一份实体,必须让他成为一个static成员:
class GamePlayer{private:    static const int NumTurns = 5;   //const declaration 而非定义    int scores[NumTurns];    //...};//只要不取它的地址,可以声明使用它,不许提供定义const int GamePlayer::NumTurns; // 但是如果取地址,或编译器必须看到定义时,使用此方法定义//因为声明的时候已经获得了初值,所以定义的时候不可以再设初值。
//如果上述方法编译器不支持,可以使用下面方法class CostEstimate{private:    static const double FudgeFactor;  //头文件内    //...};const double CostEstimate::FudgeFactor = 1.35;  //实现文件内
//如果编译器不允许static整数型class常量完成in class 初始值设定,可以改用the enum hack做法class GamePlayer{private:    enum{NumTurns = 5};    int scores[NumTurns];    //...};

尽可能使用const

const是一个语义约束,编译器会强制实施这个约束。const出现在*号左边,表示被指物是常量,如果出现在*右边,表示指针自身是常量,出现在两边,表示被指物和指针两者都是常量。

std::vector<int> vec;cosnt std::vector<int>::iterator iter = vec.begin();  //iter 的作用像个 T* const*iter = 10;   //true  ++iter;   //false  iter is conststd::vector<int>::const_iterator cIter = vec.begin();  //cIter的作用像个const T**cIter = 10;    //false++cIter;  //true
  1. 用 const修饰函数的返回值,可以避免因客户错误而造成的意外,又不至于放弃安全性和高效性。
class Rational{//...};const Rational operator*(const Rational& lhs, const Rational &rhs);Rational a,b,c;(a*b) = c;// false, 使用const修饰返回值,可以避免此操作
  1. const 成员函数,为了确认该成员函数可作用于const对象身上。两个理由:(1).使class接口比较容易理解,得知哪个函数可以改动对象内容,哪个不行。(2)。使得操作const对象成为可能。
    两个成员函数如果只是常量性不同,可以被重载。
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;};TextBlock tb("hello");std::cout<<tb[0];const TextBlock ctb("world");std::cout<<ctb[0];tb[0] = 'x'; //truectb[0] = 'x'; false

如果non-const operator[] 的返回类型是一个reference to char,不是char。如果返回一个char,下面的代码无法通过编译tb[0] = 'x';,因为函数的返回类型是一个内置类型。改动返回值重来就不合法。即使合法c++以by value返回对象这一个事实,意味着被改动的是他的一本副本,不是自身,不是我们想要的行为。
3. bitwise const:成员函数只有在不更改对象的任何成员变量时才可以说是const。编译器只需寻找成员变量的赋值动作即可。不幸的是,许多成员函数虽然不十足具备const性质却能通过bitwise测试

class CTextBlock{public:    char & operator[] (std::size_t position)const{        return pText[position];    }private:    char * pText;};//operator[] 不改变pText,可以通过编译器的bitwise检测const CTextBlock cctb("hello");char *pc = &cctb[0];*pc = 'J'; //cctb现在变成了Jello

上面这种情况,不应该出现错误,但是却改变了他们的值。因此提出了logical const
logical const:一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不到的情况下才得如此。

class CTextBlock{public:    std::size_t length() const;private:    char *pText;    std::size_t textLength;    bool lengthIsValid;};std::size_t CTextBlock::length()const{    if(!lengthIsValid){        textLength = std::strlen(pText);        lengthIsValid = true;    }    return textLength;}

length的实现当然不是bitwise const,因为里面有值的改变。但是对于const CTextBlock对象是可以接受的。但编译器不同意,它们坚持bitwise constness。可以通过mutable来释放掉non-static成员变量的bitwise constness的约束。在变量textLength和lengthIsValid前面加上mutable。
4. 在const和non-const成员函数中避免重复。

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;};//代码重复,可以通过常量性转除//例如:char &operator[](std::size_t position){    return const_cast<char&>(static_case<const TextBlock &>(*this)[position]);    //为了明确指出调用的是const operator[],这里将*this,从原始的TextBlock&转为const TextBlock&,第二次这是从const operator[]的返回值中移除const.}

不应该用const版本调用non-const版本来避免重复,const成员函数承诺绝不改变对象的逻辑状态,但是non-const成员函数却没有这样的承诺。

确定对象被使用前已被初始化

1.最佳的处理方法:永远在使用对象之前将它初始化,对于无任何成员的内置类型,你必须手动完成此事。

int x = 0;const char *text = "A C-style string";double d;std::cin>>d;

对于内置类型以外的任何其他东西,初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每个成员初始化.
不要混淆了赋值(assignment)和初始化(initialization).

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;   //赋值操作不是初始化    theAddress = address;    thePhones = phones;    numTimesConsulted = 0;}

c++规定对象的成员变量的初始化动作发生在进入构造函数之前。初始化应该发生在成员的default构造函数被自动调用之时(比进入构造函数本体的时间更早)
规定总是在初值列中列出所有成员变量,以免还得记住哪些成员变量可以无需初值,如果遗漏了内置类型的初始化,可能开启不明确行为的潘朵拉盒子
2. 初始化对象的顺序不确定时的处理方法,(不同编译单元内定义的non-local static对象的初始化次序)
问题;某个编译单元内的某个non-local static对象的初始化动作使用了另一个编译单元内的某个non-local static对象,他所用到的这个对象可能尚未被初始化。

class FileSystem{public:    std::size_t numDisks() const;};extern FileSystem tfs;  //预备给客户用的对象class Directory{public:    Director();    //...};Director::Director(params){    std::size_t disks = tfs.numDisks(); //使用tfs对象}Director tempDir(params); //如果tfs在tempDir之前未被初始化,会出现未定义行为//并且tfs和tempDir的初始化过程先后次序没有明确定义的。

解决这个问题的方法是将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static),然后返回一个reference指向它所含的对象。然后用户调用这些函数,而不是直接指向这些对象
C++保证,函数内的local static对象会在函数被调用期间,首次遇上该对象的定义式时被初始化。

class FileSystem{//...};FileSystem& tfs(){    static FileSystem fs;    return fs;}class Director{//....};Director::Director(params){    std::size_t disks = tfs().numDisks();}Director& tempDir(){    static Directory td;    return td;}

这种结构下的reference-returning函数往往十分单纯:第一行定义并初始化一个local static对象,第二行返回它。但是这样做不是线程安全的
任何一种non-const static对象,不论他是local或non-local的,在多线程环境下“等待某事发生”都会有麻烦,解决方法:在程序的单线程启动阶段手动调用所有reference-returning函数,可以消除与初始化有关的“竞速形势”。