《A Tour of C++ Third Edition》5. Classes
1 Introduction
本章和后续三章主要讲C++中的抽象和资源管理机制,但不会特别细
第五章说的主要是类的定义和使用,还说了具体类、抽象类和类层级的概念
第六章说的是构造、析构、赋值等操作如何影响对象的生存期和资源的管理
第七章讲的是将类型作为参数的模板技术以及一些算法。包括函数模板、函数对象等
第八章讲的是泛型编程,比如变参模板
这几章的编程风格主要是面向对象和泛型,第九章往后都是他们的应用,包括STL
2 Concrete Types
2.1 An Arithmetic Type
书中把具体类定义为像内置类型一样工作的类,比如复数、vector等等。这样的类是可以看作内置类型的,支持的操作有限,也没有什么资源管理的问题
class complex {
double re, im; // representation: two doubles
public:
complex(double r, double i) :re{ r }, im{ i } {} // construct complex from two scalars
complex(double r) :re{ r }, im{ 0 } {} // construct complex from one scalar
complex() :re{ 0 }, im{ 0 } {} // default complex: {0,0}
complex(const complex& z) :re{ z.re }, im{ z.im } {} // copy constructor
double real() const { return re; }
void real(double d) { re = d; }
double imag() const { return im; }
void imag(double d) { im = d; }
complex& operator +=(complex z) {
re += z.re; // add to re and im
im += z.im;
return *this; // return the result
}
complex& operator -= (complex z) {
re -= z.re;
im -= z.im;
return *this;
}
complex& operator *=(complex); // defined out-of-class somewhere
complex& operator /=(complex); // defined out-of-class somewhere
};
类内实现的函数默认inline(方便优化);没看懂这里什么叫The class definition itself contains
only the operations requiring access to the representation.
2.2 A Container
容器就是管理元素集合的对象,比如很经典的动态数组vector,数据并不属于他,但是他拥有指向数据集合的指针以及一些计数信息
class Vector {
public:
Vector(int s) :elem{ new double[s] }, sz{ s } // constructor: acquire resources
{
for (int i = 0; i != s; ++i) // initialize elements
elem[i] = 0;
}
~Vector() {}
Vector() { delete[] elem; } // destructor: release resources
double& operator[](int i);
int size() const;
private:
double* elem; // elem points to an array of sz doubles
int sz;
};
The technique of acquiring resources in a constructor and releasing them in a destructor, known as Resource Acquisition Is Initialization or RAII
binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the lifetime of an object.
2.3 Initializing Containers
以vector为例,初始化其中元素有很多方法,书中认为比较好的是初始化列表和push_back到容器尾部:
class Vector {
public:
Vector(); // default initalize to "empty"; that is, to no elements
Vector(std::initializer_list<double>); // initialize with a list of doubles
// ...
void push_back(double); // add element at end, increasing the size by one
// ...
};
Vector read(istream& is)
{
Vector v;
for (double d; is>>d; ) // read floating-point values into d
v.push_back(d); // add d to v
return v;
}
这个read函数就是从流中读取元素一个个加到容器尾部,但是如果不考虑编译器优化,当输入大量元素时,v的push_back会导致多次扩容,带来时间消耗,而且太大的vector返回也会有复制对象的过程,此时我们需要提供一个移动构造函数(后面会讲)
至于初始化列表类型的构造函数,用起来就很方便了,可能实现如下:
Vector::Vector(std::initializer_list<double> lst) // initialize with a list
:elem{ new double[lst.size()] }, sz{ static_cast<int>(lst.size()) } {
copy(lst.begin(), lst.end(), elem); // copy from lst into elem (§13.5)
}
后面还讲了四种cast,这个就很丑了,尽量避免使用
四种cast区别可以看C++笔记
3 Abstract Types
前面讲的具体类意思是定义中就包含实现,但是我们还可以使用抽象类定义更抽象的数据类型且不去实现只提供接口:
class Container {
public:
virtual double& operator[](int) = 0; // pure virtual function
virtual int size() const = 0; // const member function (§5.2.1)
virtual ~Container() {} // destructor (§5.2.2)
};
像这样含有纯虚函数的类叫做抽象类,它的子类必须实现这些纯虚函数
- 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数
- 定义一个函数为纯虚函数,才代表函数没有被实现
void use(Container& c) {
const int sz = c.size();
for (int i = 0; i != sz; ++i)
cout << c[i] << '\n';
}
抽象类可以这么用,我们不管c是什么容器,但是c一定有size()和下标操作是可以使用的,这样就实现了指针、引用的多态
抽象类本身是不能有对象的,他只能做接口
class Vector_container : public Container { // Vector_container implements Container
public:
Vector_container(int s) : v(s) {} // Vector of s elements
~Vector_container() {}
double& operator[](int i) override { return v[i]; }
int size() const override { return v.size(); }
private:
Vector v;//2.2中的Vector定义
};
class List_container : public Container { // List_container implements Container
public:
List_container() {} // empty List
List_container(initializer_list<double> il) : ld{ il } {}
~List_container() {}
double& operator[](int i) override;
int size() const override { return ld.size(); }
private:
std::list<double> ld; // (standard-library) list of doubles (§12.3)
};
double& List_container::operator[](int i) {
for (auto& x : ld) {
if (i == 0)
return x;
--i;
}
throw out_of_range{ "List container" };
}
事实上链表不能提供下标操作,这里只是举例说明不同的子类都可以继承抽象类去实现自己的函数
4 Virtual Functions

一个类如果有继承链且类定义了虚函数,那么对象会维护一个经典的虚函数表指针,这个指针指向此类的所有虚函数表,调用时根据指针来判断是哪个对象(动态绑定)
后面关于类层级的问题就不列在这了,无非是父类指针、引用指向子类对象完成多态,继承来的函数以及作用域等问题
