定义抽象数据类型

引入 this

成员函数通过一个名为 this 的额外的隐式参数来访问调用它的那个对象,在成员函数内部,任何对类成员的直接访问都被看做 this 的隐式调用,this 是一个常量指针(顶层 const)。

引入 const 成员函数

因为 this 默认不是底层 const,所以不能指向常量对象,将 this 设置为底层 const 有助于提高函数的灵活性。

  1. string isbn() const {} // 将 const 关键字放在参数列表后
  2. // 此时 this 类型为 const class_name *const

常量对象及其指针和引用,都只能调用常量成员函数。

构造函数

因为构造过程需要修改成员变量的值,所以构造函数不能声明为 const 的。

合成的默认构造函数

如果存在类内初始值,用它来初始化成员;否则默认初始化该成员。

  • 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数
  • 如果类包含内置类型或者复合类型(比如数组和指针)的成员,只有这些成员全部被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数

在 C++11 中,如果我们需要默认的行为,那么可以通过在参数列表后写上 = default 来要求编译器生成构造函数。
这样写是因为我们既需要其它形式的构造函数,也需要默认构造函数。

  1. Sales_data() = default; // 默认构造函数

使用默认构造函数

  1. Sales_data obj(); // 错误,obj 是一个函数
  2. Sales_data obj; // 正确

构造函数初始化列表

  1. // 被初始化列表忽略的成员,将以与合成默认构造函数相同的方式隐式初始化
  2. Sales_data(const std::string &s, unsigned n): bookNo(s), units_sold(n) {}

访问控制

class 与 struct 唯一的区别是默认的访问权限不同,class 默认为 private,struct 默认为 public。
类可以允许其他类或者函数访问它的非公有成员,方法是令其成为它的友元( friend )。

  1. // 友元声明只能在类的内部
  2. friend std::istream &read(std::istream&, Sales_data&); // 友元函数声明

类的其他特性

一个可变数据成员永远不可能是 const,即使它是 const 对象的成员

  1. class Screen {
  2. private:
  3. mutable size_t access_ctr;
  4. public:
  5. void Screen::some_screen() const {
  6. ++access_ctr; // 即使在 const 函数内也能修改
  7. }
  8. }

返回 *this 的成员函数可以被方便地调用

inline Screen& Screen::set(char c) {
    content = c;
    return *this;
}
myScreen.set('#').move(4, 0);  // 一系列操作可以被写在一个表达式中

可以仅声明类而暂时不定义它,这种声明被称为前向声明

class Screen;

对于类型 Screen,在它声明之后定义之前是一个不完全类型
可以

  • 定义指向不完全类型的指针
  • 声明以不完全类型作为参数或返回值的函数,但不能定义

不可以

  • 创建不完全类型的对象
  • 一个类的成员类型不能是该类自己,否则无法定义

    构造函数再探

    如果成员是 const、引用,或者某种未提供默认构造函数的类类型,我们必须通过构造函数初始化列表为这些函数提供初值。

构造函数初始化列表不限定初始化的执行顺序,此时初始化顺序为成员在类定义中的出现顺序

class X {
    int i;
    int j;
public :
    X(int val): j(val), i(j) {}  // 错误,i 在 j 之前被初始化
};

委托构造函数

使用它所属类的其他构造函数执行它自己的初始化过程

class Sales_data {
public:
    // 非委托函数使用对应的实参初始化成员
    Sales_data(std::string s, unsigned cnt, double price):
            bookNo(s), units_sold(cnt), revenue(cnt *price) {}
    // 其余构造函数全部委托给另一个构造函数
    Sales_data(): Sales_data("", 0, 0) {}
    Sales_data(std::string &s): Sales_data(s, 0, 0) {}
    Sales_data(std::istream &is): Sales_data() { read(is, *this); }
};

隐式的类类型转换

如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换规则,这种构造函数称作转换构造函数

item.combine(string"12345");  // 正确,显式地转换成 string,隐式地转换成 Sales_data
item.combine(Sales_data("12345");  // 正确,隐式地转换成 string,显式地转换成 Sales_data
item.combine("12345");  // 错误,只允许一步类类型转换

若要抑制构造函数定义的隐式转换,可以将该构造函数声明为 explicit ;explicit 构造函数只能用于直接初始化。

类的静态成员

静态成员函数不与任何对象绑定在一起,也不包含 this 指针;因此静态成员函数不能声明成 const 的,也不能在 static 函数体内使用 this 指针。
当在类的外部定义静态成员时,不能重复 static 关键字,该关键字只出现在类内部的声明语句。

静态成员并不是在创建对象时被定义的,这意味着它们不是由类的构造函数初始化的,一般来说,只能在类的外部定义和初始化静态成员/

静态成员函数可以是不完全类型

class Bar {
public:
private:
    static Bar mem1;  // 正确,静态成员可以是不完全类型
    Bar *mem2;  // 正确,指针成员可以是不完全类型
    Bar mem3;  // 错误,数据成员必须是完全类型
};

静态成员可以作为默认实参,而非静态成员不能