Effective C++ 笔记

class Widget{
public:
    Widget();    //构造函数
    Widget(const Widget& rhs);    //拷贝构造函数
    Widget& operator=(const Widget& rhs);   //重载“=”号
    ~Widget();
}

Widget w1;            //1、构造函数
widget w2(w1);        //2、拷贝构造函数
w1 = w2;              //3、调用重载
Widget w3 = w2;       //4、调用拷贝构造函数

【注】看第4个,因为w3是对象实例化,故肯定会调用构造函数,不可能是赋值操作;同理第3个,两者均为实例化对象,此时调用的是重载后的赋值操作。

bool hasAcceptableQuality(Widget w);
...
Widget aWidget;
if (hasAcceptableQuality(aWidget))...

参数w是按值传递,所以上述aWidget被复制到w的体内,这个复制动作是通过拷贝构造函数实现的;通常使用按值传递是个不好的行为,一般会选择引用进行传递。

int *p = 0;      //p是个null指针
std::cout<<*p;   //对一个null指针取值,会导致不明确行为

char name[] = "Darla";    //name是个数组,大小为6(别忘记最尾端的"/0")
char c = name[10];        //指向一个无效的数组索引,导致不明确行为

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

第一章:

关于const:

     class专属常量:为了将常量作用域限制于class内,必须让它成为class的一个成员(member),而为确保此常量至多只有一份实体,你必须让它成为一个static成员。

class GamePlayer{
private:
    static const int NumTurns = 5;
    int scores[NumTurns];
    ...
}

上述代码中NumTurns是一个声明式而非定义式,但如果它是个class专属常量又是static且为整数类型,则需要特殊处理:

  1. 只要不取它们的地址,可以声明并使用它们而无需提供定义式
  2. 如果取某个class专属常量的地址,或编译器坚持看到一个定义式,必须提供如下定义式:
    1. const int GamePlayer::NumTurns;
    2. 请把该式子放进一个实现文件而不是头文件,同时因为之前赋值了5,因此定义时不可以再赋初值

部分编译器不允许static变量在类内进行声明,因此这个时候可以选择枚举类型充当int使用,例:

class GamePlayer{
private:
    enum{NumTurns = 5};
    int scores[NumTurns];
    ...
}

【注】有时候enum类似于#define,当取const类型的地址是合法的,但是取enum和#define的地址是不合法的

关于define:

常见的#define误用情况是以它实现宏(macros)。宏看起来像函数,但不会招致函数调用带来的额外开销。例:

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

但是这样会有很多缺点,例如:

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

【注】1、对于单纯常量,最好以const对象或者enums替换#define

           2、对于形似函数的宏(macros),最好改用inline函数替换#define

尽可能用const

const语法变化多端,如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身的常量。

const char* p = greeting;    //非常量指针,常量数据
char* const p = greeting;    //常量指针,非常量数据

如果被指物是常量,可将关键字const写在类型之前,也可写在类型之后,星号之前,两者意义相同。

STL迭代器以指针为模型进行构造,迭代器的作用就像个T*指针。声明迭代器为const和声明指针为const一样,表示这个迭代器不能指向不同的东西,但它所指的对象的值是可以改动的。如:

std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();    //iter的作用像个T* const
*iter = 10;    //指针指向对象可以改变
++iter;        //错误!iter是const


std::vector<int>::const_iterator citer = vec.begin();    //iter的作用像个const T*
*citer = 10;    //错误!*citer是const
++citer;        //没问题,改变citer

const成员函数

目的:为了确认该成员函数可作用于const对象。

理由:1、使class接口比较容易被理解,可以知道哪个函数可以改动对象内容而别的不可以。

           2、使“操作const对象”成为可能。

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

如:

class Point{
    int x,y;
}
...
Point p;

在某些语境下x保证被初始化(为0),但在其他语境中却不保证;p的成员变量有时候初始化(为0),有时候不会;而读取未初始化的值,就可能让你的程序终止运行。

【最佳处理办法】永远在使用对象前将其初始化。

int x = 0;
const char* text = "A C-style string";

double d;
std::cin>>d;
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;        //这些都是赋值(assignments)
    theAddress = address;  //而非初始化(initializations)
    thePhones = phones;
    numTimesConsulted = 0;
}

这样可以使对象带有你期望的值,但不是最佳做法。C++规定,对象成员变量的初始化动作发生在进入构造函数本体之前。初始化的发生时间更早,发生于这些成员的default构造函数被自动调用之时(比进入构造函数本体时间更早),故而比较好的写法如下:

//这样效率更高
ABEntry::ABEntry(const std::string& name,const std::string& address,const std::list<PhoneNumber>& phones):theName(name),theAddress(address),thePhones(phones),numTimesConsulted(0){}

不同单元内定义之non-local static对象的初始化次序:

函数内的static对象称为local static对象,其他的统称为non-local static对象。程序结束时,static对象会被自动销毁,它们的析构函数会在main()结束前被自动调用;常见的问题:某编译单元内的某个non-local static对象初始化动作使用了另一编译单元内的某个non-local static对象,它所使用的这个对象可能尚未被初始化,如:

class FileSystem{
public:
    ...
    std::size_t numDisks() const;
    ...
}
extern FileSystem tfs;    //预备给客户使用的对象
                          //tfs代表“the file system”

如果客户在FileSystem对象构造完成前就使用它,后果不堪设想。

假设某些客户建立了一个class处理文件系统内的目录,很自然的就使用上了FileSystem对象:

class Direstory{
public:
    Directory(param);
    ...
};

Directory::Directory(param){
    ...
    std::size_t disks = tfs.numDisks();    //使用了tfs对象
    ...
}

进一步假设,客户决定创建一个Directory对象,用来放置临时文件:

Directory tempDir(param);    //为临时文件而做出的目录

现在,初始化次序重要性出来了,除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs;这时,我们无法确定是否初始化。

但是却可以通过小小的一个设计消除这个对象:将每个non-local static对象搬到自己专属函数内,该对象在函数内被声明为static,这些函数返回一个reference(引用,参考)指向它所含的对象;然后用户调用这些函数,而不直接指涉这些对象(即通过函数调用方式确保non-local static对象被初始化)。

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

猜你喜欢

转载自blog.csdn.net/weixin_37160123/article/details/89263083