effective c++读书笔记(一)

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;
}
templateT>
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
};

//example
void doSomething(B bObject);
B bObject1;
doSomething(bObject1);  //true
B bObject1(28);  //true 
doSomething(28);  //fault, from int to B ,no implicit type conversions
doSomething(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 constructor
Widget w2(w1);   //copy constructor function
w1 = 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 behavior
char 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 const
std::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<0];
const TextBlock ctb("world");
std::cout<0];
tb[0] = 'x'; //true
ctb[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(static_case(*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 &phones);
private:
    std::string theName;
    std::string theAddress;
    std::list thePhones;
    int numTimesConsulted;
};
ABEntry::ABEntry(const std::string &name, const std::string &address, const std::list &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函数,可以消除与初始化有关的“竞速形势”。

你可能感兴趣的:(effective-c++)