《C++ Primer》读书笔记 第七章 类

第七章 类

7.1 定义抽象数据类型

  • 成员函数的定义。
  • this 指针的理解。
  • const 成员函数:意味着 this 是一个指向常量的指针,只能读取它的对象的数据成员,但是不能写入。
  • 常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
  • 合成的默认构造函数:
    • 如果存在类内的初始值,用它来初始化成员。
    • 否则,默认初始化该成员。
    • 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。
    • 如果声明了其他构造函数,有需要一个构造函数的默认行为,可以使用= default
  • 构造函数不应该轻易覆盖掉类内的初始值,除非新值与原值不同。如果你不能使用类内初始值,则所有构造函数都应该显式地初始化每个内置类型的成员。

7.2 访问控制与封装

l
private 和 public。

7.2.1 友元

7.3 类的其他特性

7.3.1 类成员再探

  • 定义一个类型成员:使用 typedef 或 using,达到隐藏实现细节的作用。
  • 可变数据成员:在成员变量的声明前使用mutable修饰,代表它永远不会是 const,即便在 const 函数中也能修改它。
  • 当我们提供一个类内初始值时,必须以=或者花括号表示。
1
2
3
4
5
class Window_mgr{
private:
std::vector<Screen> screens{Screen(24, 80, '')};
int i = 0;
};

7.3.2 返回 *this 的成员函数

  • 返回值用引用可实现连环调用,但对于返回常量引用的调用来说,这些还不够。
  • 一种比较好的设计:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Screen {
public:
Screen &display(std::ostream &os)
{
do_display(os);
return *this;
}
const Screen &display(std::ostream &os) const
{
do_display(os);
return *this;
}
private:
void do_display(std::ostream &os) const
{
os << contents;
}
};

7.3.3 类类型

  • 类可以做前向声明(forward declaration),在它声明之后定义之前是一个不完全类型(incomplete type),此时不能创建它的对象。
  • 所以一个类的成员类型不能是它自己,但可以是它的引用或指针。

7.3.4 友元再探

  • 可以声明友元类或友元类成员函数。
  • 友元关系不存在传递性。
  • 重载函数必须分别声明友元关系。
  • 要想令某个成员函数作为友元,必须严格遵守以下顺序:

    1. 先定义 Window_mgr 类,其中声明 clear 函数,但不能定义它。
    2. 定义 Screen 类,包括对于 clear 的友元声明。
    3. 定义 clear。
  • 声明友元函数不代表声明了该函数,该函数依然需要在类的外部进行声明,且在声明后才能被使用。(有的编译器并不强制执行该规则。)

7.4 类的作用域

名字查找(name lookup)的一般过程:

1. 在名字所在块中寻找声明,只考虑在名字使用之前出现的声明。
2. 如果没有找到,继续查找外层作用域。
3. 如果最终没有找到,程序报错。

对于定义在类内部的成员函数:

1. 首先,编译成员的声明。
2. 直到类全部可见后才编译函数体。

7.5 类的函数再探

7.5.1 构造函数初始值列表

  • 初始化和构造函数里赋值的区别。
  • 初始化顺序。
  • 构造函数的默认实参。

7.5.2 委托构造函数

C++11 允许构造函数把活交给别的构造函数干。

7.5.3 默认构造函数的作用

使用默认构造函数不要加(),不然是定义了一个函数。

7.5.4 隐式的类类型转换

如果构造函数只接受一个实参,则它实际上定义了一种类型的隐式转换机制,有时把它称之为转换构造函数(converting constructor)。

但是注意,只允许一步类类型转换。如 “xxxx” 转 sring 再转某 class 是非法的。除非显式地使用 string("xxx")进行转换。

想阻止这种转换,可将构造函数声明为explicit。它将只能以直接初始化的形式使用,而且,编译器将不会在自动转换过程中使用该构造函数(不能再使用=将参数类型的对象用于拷贝形式的初始化过程)。

但依然可以用显式转化实现。如static_cast<Sales_data>(xxx)Sales_data(xxx)

在标准库中:

- 接受 const char * 的 string 构造函数不是 explicit 的。
- 接受一个容器参数的 vector 构造函数是 explicit 的。

7.5.5 聚合类

聚合类(aggregate class)使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。需要满足以下条件:

  • 所有成员都是 public 的。
  • 没有定义任何构造函数。
  • 没有类内初始值。
  • 没有基类,没有 virtual 函数。

可以提供一个花括号括起来的成员初始值列表,用它来初始化聚合类数据成员,顺序必须一致。

这种类缺点也较为明显:

  • 要求所有成员 public。
  • 用户决定初始化。
  • 更改成员后修改代价大。

7.5.6 字面值常量类

数据成员都是字面值类型的聚合类是字面值常量类。或者满足以下条件也是:

  • 数据成员都必须是字面值类型。
  • 类必须至少含有一个 constexpr 构造函数。
  • 若数据成员含有初始值,则该值必须是一条常量表达式。如果该成员属于某类类型,初始值必须使用成员自己的 constexpr 构造函数。
  • 必须使用析构函数的默认定义。

7.6 类的静态成员

  • 既可使用类名,也可使用对象访问静态成员。
  • 在类的外部定义静态成员时,不能重复 static 关键字。
  • 由于静态成员并不是在创建类的对象时被定义的,所以一般不能在类内进行初始化。(除了返回值是常量表达式的情况。)
  • 和其他对象一样,一个静态数据成员只能定义一次,最好与其他非内联函数的定义放在同一个文件中。
  • 即使一个常量静态数据成员在类内部被初始化了,通常也应在类的外部定义一下该成员。

  • 由于静态成员独立于任何对象,所以有些特性:可以是不完全类型,可以就是它所属类的类型,可以用作默认实参。这些非静态成员都不可以。