C++ Primer 笔记(4)

Chapter 12 (cond)

关于构造函数

所谓构造函数初始化式(Constructor Initializer)是指在构造函数参数列表后面冒号里的那一列东西,这是一个很多熟练程序员都可能不太了解的地方,大家可能更习惯在构造函数的花括号里写东西。但是实际上只有在冒号后面的那些才是真正的初始化,在花括号执行的只是普通的赋值语句,明确地区分这两者看上去有点无聊,但某些情况下是必须要考虑的。一是效率上的原因,不管怎么写,在创建对象时都要先对每个成员进行初始化,然后再执行花括号里的普通计算。如果写在花括号里,实际上是先初始化一次,再赋值一次覆盖掉初始化的内容,这样就造成了浪费,如果成员是比较大的对象,效率上会有影响。二是某些对象是不允许赋值的,比如引用和const成员,这时只能在初始化式里初始化,不能在花括号里赋值。总之,尽量写在初始化式里比较好。

初始化的次序与成员声明的次序一致,与初始化式里的次序无关。尽量不要让初始化的值依赖其他成员变量,以免造成混乱。

如果初始化式里没有指明某个成员变量,则按照默认方式初始化:对于类类型成员调用默认构造函数,对于内置类型成员,如果是全局对象则置0,局部对象则为未定义值。

如果对类A声明了任何一个构造函数,则编译器不会再自动合成默认构造函数。这种情况下会带来诸多不便,比如如果某个类B以类A作为一个成员,则类B也不再有默认构造函数;不能声明A的静态数组;包含A的容器也不能只用一个表示大小的值来初始化,等等。所以通常情况下最好要有个默认构造函数。

如果类有接受一个参数的构造函数,这时就隐含了一种类型转换的可能,见下面代码:

class Loli {
public:
    Loli(int n) : age(n) {}
    int get_age() {return age;}
private:
    int age;
};

void print_loli_age(Loli loli) {
    cout<<loli.get_age();
}

虽然print_loli_age要接受一个Loli类型的参数,但我们可以直接调用print_loli_age(15)。因为Loli有一个接受一个int参数的构造函数,这样就把15自动转化生成了一个Loli类型。有的时候这样的隐式转换不是我们想要的,就需要在构造函数前面加一个explicit来禁止这种转化,此时如果想转化的必须显式调用:print_loli_age(Loli(15))

关于static成员

static成员独立于类的任何实例对象而存在。static成员函数没有this指针,也不能访问非static成员。static数据成员必须在类的定义体外定义恰好一次,在定义同时初始化,通常把它和类的成员函数的实现放在同一个源文件里。

Chapter 13

本章主要是三部分内容:复制构造函数、赋值运算符和析构函数

复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用 const 修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。

在初始化的时候,如果是直接在圆括号里写参数,例如 string s(10, ‘a’),则调用相应的构造函数,如果出现了赋值号(不知道这么说严谨不),例如 string s = “aaaa”,则先生成一个临时对象,再调用复制构造函数复制过去。

当对象作为非引用的参数传递给函数的时候,以及从函数里返回一个非引用的对象的时候,会调用复制构造函数生成一个副本。另外如果是定义了类似vector<T> v(n)之类的东西的话,会先用默认构造函数生成一个临时对象,然后用复制构造函数复制n份。

如果没有显式声明复制构造函数,编译器会自动生成一个。多数情况下这就够用了,但是有些情况下还是不行的,比如对象里面有个指针或引用而我们想实现一个deep copy的功能,或者对象里有一个不可复制的成员,这时候就要自己定制了。定制的复制构造函数和普通的构造函数没什么大区别,接受一个相同类型的引用(通常是const)作参数,推荐用初始化式进行初始化,然后在花括号里干其他的事。

如果我们想禁止对象被复制,应该写一个private的复制构造函数(不能不写,不写会自动生成),但这样的话自己的成员函数和友元还是可以调用它,进一步的方法是只声明而不实现(好强-_-)。这样的话,如果是外部调用,会编译错误,如果是自己的成员函数或友元调用,会链接错误。

重载赋值运算符时,代码的写法看上去就像把operater=当作类的一个成员函数一样,它接受一个相同类型的const引用作为参数,返回值是同一类类型的引用。注意处理好赋值给自己的情况,这种情况通常要特判一下。

如果是new出来的对象,只有在delete时才会被删除(此时调用析构函数)。如果忘了删除就会导致所谓内存泄漏。内存泄漏往往只有在程序长时间运行后内存消耗严重时才表现出来,比较难于发现。

关于智能指针:所谓智能指针是指保存了一个“引用计数”的指针,它可以避免当多个指针指向同一对象时,通过其中某个指针把对象删除了,但其他的指针并不知道,继续访问就会导致错误。通过保存一个引用计数,只有当指向这个对象的指针数为0时,才真正把对象删除。在这种情况下需要自己实现一个指针类,把原生指针包装一下,通过重写该指针类的构造、复制、析构函数实现引用计数的管理。在13章的最后给了一个很好的例子。(ps. 在boost里有标准的智能指针实现了?)

  1. 笔记总结的不错,简明扼要,学习了。

  2. 如果构造函数是string(char*)的话,string s = “aaaa”会调用拷贝构造函数?

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>