类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节,类的用户只能使用接口而无法访问实现部分。
类要想实现数据抽象和封装,需要首先定义一个抽象数据类型。在抽象数据类型中,由类的设计者负责考虑类的实现过程:使用该类的程序则只需要抽象的思考类型做了什么,无需了解类型的工作细节。
7.1定义抽象数据类型
如果我们需要默认的行为,那么可以通过在参数列表后面写上=default 要求编译器生成构造函数。
拷贝、赋值和析构,如果没有定义,编译器会提供合成的默认版本。
7.2访问控制与封装
访问权限:class 默认成员是private;struct默认成员是public。
封装的优点:
确保用户代码不会无意间破坏封装对象的状态;
被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。
7.3类的其他特性
令成员函数作为内联函数
类内声明inline,或在类外定义的时候用inline修饰。
可变数据成员
有时我们希望修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量的声明中加入mutable关键字做到。
返回*this的成员函数
成员函数的返回值类型是类类型的引用,*this就是对象本身(不是拷贝),可以作为左值继续使用。
类之间的友元关系
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非共有成员在内的所有成员。友元关系不具有传递性。 每个类,负责控制自己的友元类或友元函数。
友元在类内定义,但是没有在类内声明!
7.4类的作用域
在类的作用域之外,普通的数据和函数成员只能由对象、引用或指针使用成员访问运算符来访问。ptr->mem →(*ptr).mem
对于类类型成员则使用作用域运算符访问。
一个类就是一个作用域。
使用类作用域的成员函数的定义中,就相当于在类内一样,不需要再加作用域了;返回类型在类的作用域之外,因此仍需要作用域限定。
名字查找与类的作用域
1一般程序中,名字查找:
首先在名字所在的块中寻找其声明语句,只考虑在名字的使用之前出现的声明;
如果没找到,则继续查找外层作用域;
如果最终没找到,则报错。
2对于定义在类内部的成员函数:
首先,编译成员的声明;
直到类全部可见后,才编译函数体。
先编译,后声明,后定义。
7.5构造函数再探
构造函数初始值列表 进行初始化;在函数体内的是赋值。
如果数据成员本身是引用或者是const的,则不能赋值了,只能用初始化。
构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行顺序。
委托构造函数
委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或全部)职责委托给了其他构造函数。
默认构造函数的作用
当对象被默认初始化或值初始化时,自动执行默认构造函数。
默认初始化在以下情况发生:
当我们在块作用域内不使用任何初始值定义一个非静态变量或者数组时;
当一个类本身含有类类型的成员且使用合成的默认构造函数时;
当类类型的成员没有在构造函数初始值列表中显示地初始化时。
值初始化在以下情况发生:
在数组初始化的过程中,如果我们提供的初始值数量少于数组的大小时;
当我们不使用初始值定义一个局部静态变量时;
当我们通过书写形如T{ }的表达式显示地请求值初始化时,其中T是类型名,它就是使用一个这种形式的实参来对它的元素初始化器进行值初始化。
也有时,当数据成员缺少默认构造函数的时候。
在实际中,如果定义了其他构造函数,那么最好也提供一个默认构造。
隐式的类型转换
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,称为转换构造函数。
抑制构造函数的隐式转换 explicit 清楚、明白的
explicit构造函数只能用于直接初始化,不能用于拷贝形式的初始化过程。
explicit只能出现在类内构造函数声明处。
聚合类
字面值类
7.6类的静态成员
与类本身关联,而不需要与每个对象关联。
静态成员存在于任何对象之外,所有对象共享。
声明静态成员
成员的声明加上关键字static。静态成员函数也不与任何对象绑定在一起,它们不包含this指针。
类内声明,类外定义(不需要定义的时候加static)
静态成员的类内初始化
因为它还不存在