1. 定义抽象数据类型
- 成员函数:定义为类的一部分的函数,有时称作方法;成员函数声明必须在类的内部,定义可以在内部或外部;
- 非成员接口函数:形参或返回值包含类对象;
- 定义在类内部的函数是隐式的
inline
函数; - 结构体struct也可以定义类;
- 引入this:成员函数的调用:
total.isbn()
成员函数实际上是通过一个名为this
的额外的隐式参数(常量指针)来访问调用它的那个对象(相当于python中的self
);调用一个成员函数时,该对象的地址用于初始化this
,因此实际执行过程为:
Sales_data::isbn(&total)
注: 任何自定义为this
的变量或参数都是非法的; - 引入const成员函数:常量成员函数
string isbn()const {...}
在形参列表后添加const
声明,用于修改this
指针的类型;默认情况下,this
时指向非常量对象的常量指针,即Sales_data *const this
,但是这样就不能为常量对象所调用;(常量对象只能调用常量成员函数)
因此将this
指针变为指向常量对象的常量指针:const Sales_data *const this
- 类作用域及和成员函数:类本身是一个作用域;成员函数可以随意使用类中的成员而不必在意顺序;
- 类外部成员函数:其名字定义需要包含所属类,从而限定类作用域,访问其他类成员;
double Sales_data::
- 定义返回this对象的函数:
return *this
, 函数返回的引用类型,是左值; - 定义类相关的非成员函数:其操作属于类接口的组成部分,但是不属于类本身(为啥?),其声明应该与类在同一个头文件中;
1.4 构造函数(constructors)
- 构造函数用于初始化类对象的数据成员;
- 名字和类名相同,没有返回类型;
- 类可以有多个构造函数,类似于重载;
- 构造函数不可以是`const`
- 类似于python中的 init() ?
- 合成的默认构造函数:在没有显式构造函数的情况下;无需任何实参;
- 多数的类是不能依靠默认构造函数的,还是需要自己构建;
1. Saes_data() = default;
默认构造函数;若在类内部,则是内联的,反之则不然;
2. 构造函数初始值列表
Sales_data(const string &s, unsigned n, double p):
bookNo(s),units_sold(n),revenue(p*n){}
注意其函数体为空;
3. 外部定义构造函数
Sales_data::Sales_data(istream &is)
{
read(is, *this);
}
1.5 拷贝、赋值和析构(销毁)
- 拷贝:初始化变量、值传递或返回
- 赋值:
- 销毁:局部对象在块结束时被销毁;
一般编译器可以合成这些操作,但是当类用于动态内存管理时,合成版本会失效;不过可以通过使用vector
来避免分配和释放内存带来的复杂性;
2. 访问控制与封装
- 访问说明符:
- public:其后的成员可以被整个程序访问,用于定义类的接口(包括构造函数);
- private:其后的成员可以被类的成员函数访问,但不可以被外部接口函数访问;(可以被类对象直接访问吗?)
- struct和class定义类的唯一区别是默认访问权限:即在第一个访问说明符之前的成员,对于struct而言是public的,对于class而言是private的;
- 若类的所有成员是public,则用struct,否则用class;
- 封装encapsulation: is the separation of implementation from interface. It hides the implementation details of a type. (In C++, encapsulation is enforced by putting the implementation in the private part of a class)
- 确保用户代码不会无意间破坏封装对象的状态;
- 可以随时改变具体实现细节,而不必更改用户级代码;
2.1友元
- 普通的接口函数是无法访问私有数据成员的,通过使其他类或函数成为友元,就可以访问私有成员;
- 声明方式:在类内添加
friend Sales_data add(const Sales_data&, const Sales_data&);
, - 在类外还需要对友元函数再次声明;
3. 类的其他特性
类型成员、类成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this、定义类类型及友元类
3.1 类成员再探
- type member(类型成员)
using pos = string::size_type
,
与其他成员不同,类型成员必须定义在使用之前; - 显示说明成员函数内联时,可以仅在外部定义时进行内联声明
- 成员函数也可以被重载;
- 可变数据成员:即使时const成员函数也可以修改一个可变成员的值;
在变量声明时加mutable
关键字; - 类内初始值,必须等号或花括号;
3.2 返回*this的成员函数
inline Screen &Screen::set(char ch)
{
contents[cursor] = ch;
return *this;
}
- 返回引用的函数是左值的,即返回的是对象本身而非其副本;
- 从const成员函数返回*this,:则函数返回类型为
const Screen &
;且常量对象只能调用常量成员函数;
3.3 类类型
- 每个类定义了唯一的类型;
- 只有类被完全定义之后,数据成员才可以被声明称这种类型,故一个类的成员类型不能是他自己,但是允许包含指向他自己的指针或引用;
3.4 友元再探
- 友元可以是普通的非成员函数,其他类,或者其他类的成员函数;
- 一个类的友元类的成员函数可以访问该类的私有成员;
- 友元不存在传递性;
- 如果一个类要把一组重载函数作为友元,则需要对每个函数进行友元声明;
- 即使友元函数在类内定义,也必须在类外声明,使其可见;
4. 类的作用域
4.1 名字查找
- 编译器在处理完类的全部声明之后,才处理成员函数的定义,因此成员函数可以使用类中出现的所有名字;
- 如果成员使用了外部定义的类型名,则不要再去重定义它;
- 类型名定义通常在类的开始处;
5. 构造函数再探
5.1 初始值列表
- 注意初始化与赋值的差异,可能造成影响;
Sales_data::Sales_data(const string &s, unsigned cnt, double price)
{
bookNo = s;
unit_sold = cnt;
revenue = cnt * price;
}
//上述代码虽然也是构造函数,但实际上其执行的是赋值操作;
- 如果成员是const, 引用或者未提供默认构造函数的类类型,则其初始化只能通过构造函数初始值列表来实现;
- 尽量使初始值的顺序与成员声明顺序一致,避免用一个成员初始化另一个成员;
- 默认实参
5.2 委托构造函数
- 部分或全部地将职责委托给其他构造函数;
Sales_data_class(const string &s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p*n) {}
Sales_data_class(const string &s) :Sales_data_class(s,0,0) {}
5.3 默认构造函数
- 如果提供了其他构造函数,最好也提供一个默认构造函数;
5.4 隐式的类类型转换
- 单个实参的构造函数才能用于隐式类类型转换;
- 类类型转换只能执行一次
explicit
阻止隐式类型转换的使用,只针对单个实参的构造函数有效;
5.5 聚合类
所有成员都是public的
没有定义任何构造函数
没有类内初始值
没有基类,也没有virtual函数;
- 用户可以直接访问其成员
5.6 字面值常量类
constexpr
的参数和返回类型都需是字面值常量;数据成员都是字面值类型的聚合类是字面值常量类;
数据成员都必须是字面值类型。 类必须至少含有一个constexpr构造函数 如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。 类必须使用析构函数的默认定义,该成员负责销毁类的对象。
尽管构造函数不能是
const
的,但是字面值常量类的构造函数可以是constexpr
函数
6. 类的静态成员
- 有时需要类的成员与类本身有关,而非与类对象有关;
- 类的静态成员存在于任何对象之外,
- 静态成员函数即不予对象绑定,也不包含
this
指针,不能被声明为const
- 使用作用域运算符直接访问静态成员:
r = Account::rate();
- 类的对象、引用和指针仍然可以访问静态成员
- 静态数据成员最好定义在与其他非内联函数同一文件内;
- 常量静态数据成员即使被类内初始化了,也应该在类外定义一下;
- 静态成员可以是不完全类型,可以做默认实参;
静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>