🌶️
type
status
date
slug
summary
tags
category
icon
password
Property
除了定义拷贝控制成员外,需要管理资源的类通常还会定义
swap
函数。定义 swap
函数对于希望改变元素的顺序的算法来说特别重要。这种算法在需要交换元素的顺序时调用 swap
函数。如果一个类定义了自己的 swap
函数,算法将使用类定义的版本。否则,将使用库中定义的版本,这个版本在概念上会执行一次拷贝和两次赋值。这样的定义需要多次拷贝值,如果是程序员自己定义的版本,那么只需要交换其中的指针即可。在设计类的
swap
时,虽然逻辑上是这样:但如果真的这样实现的话,还需要创建一个新的对象
tmp
,效率是很低的,造成了内存空间的浪费。因此实际上希望的是这样的逻辑实现:创建一个
string
类型总比创建一个A
类对象要省内存。具体实现:定义
swap
函数并不是必须的,然而定义swap
是对分配了资源的类的重大优化。swap 成员函数应该调用 swap,而不是 std::swap
🌶️
type
status
date
slug
summary
tags
category
icon
password
Property
一些类需要分配可变大小的内存,这种类最好是用标准库容器来保存数据。然而,这种策略并不适合于所有类,有些类需要自己分配数据。这些类通常定义自己的拷贝控制成员来管理内存分配。
当调用
reallocate
时,并不需要将就数据中的字符串拷贝到新的数据中,原因是旧数据马上就要被丢弃了,此时应该移动而不是拷贝这个数据,这样就避免了给字符串重新分配内存。移动构造函数和
std::move
通过移动构造函数可以将给定对象的资源移动到将要被构建的对象中去。移动构造函数将保证被移动的对象,其内部状态是可以被有效的析构的。如:指针变为空指针。
同时标准库中定义了一个新函数
std::move
在 utility
头文件中。使用move
函数有两点需要注意:- 如果希望调用移动构造函数需要调用
std::move
来告诉编译器使用移动构造函数,否则,将会使用拷贝构造函数。
- 调用
move
必须加上作用域限定符,显式告诉编译器我们调用的是哪个版本的函数:
调用
move
将返回一个结果,这个结果将导致 construct
函数使用 string
的移动构造函数。由于使用了移动构造函数,由这些 string
管理的内存将不会被拷贝。相反,新构建的 string
将获取旧 string
的所有权。在移动元素之后,就可以将旧的元素给 free
掉了,移动构造函数将保证可以安全的将 string
析构掉或者被赋予新的值。🌶️
type
status
date
slug
summary
tags
category
icon
password
Property
目录
新标准的一个特色就是可以移动对象而不是拷贝对象。当对象一旦拷贝完就销毁时,移动而不是拷贝对象将提供重大的性能提升。有一些类的资源是不可共享的,这种类型的对象可以被移动但不能被拷贝,如:
IO
或unique_ptr
。早期版本中,并没有什么方式可以直接移动对象。即便是在不需要拷贝的时候依然会执行拷贝。同样,在之前存储在容器中的对象必须是可拷贝的,在新标准中,可以在容器中使用不可拷贝但是可以移动的对象。
库容器、
string
和shared_ptr
支持拷贝和移动,IO
和unique_ptr
则只能移动不能拷贝。左值引用VS右值引用
左值是一个表示数据的表达式(如变量名或解引用的指针),有如下特性:
- 可以获取它的地址+可以对它赋值(不一定能赋值,但一定能取地址)
- 左值可以出现赋值符号的左边,右值不能出现在赋值符号左边
🫑
type
status
date
slug
summary
tags
category
icon
password
Property
目录
运算符重载
C++
定义了大量操作符,并且为内置类型之间的转换定义了自动转换。这些使得程序员可以很方便的写出混合类型的表达式。同时
C++
允许我们定义当将操作符运用于类类型对象时的含义。它还允许我们定义类类型之间的转换,和内置类型一样,在需要时将一个类型的对象隐式转为另外一个类型对象。操作符重载(operator overloading)允许我们定义运用于类类型的操作符的含义。谨慎地使用操作符重载可以使得程序更加容易读和写。如果之前地
Sales_item
类定义了重载的输入、输出和加操作符,那么可以如下操作:如果没有定义这些重载操作符的话,操作就会没有那么简洁:
🫑
type
status
date
slug
summary
tags
category
icon
password
Property
非
explicit
的具有一个参数的构造函数定义了隐式转换。这种构造函数将参数类型的对象转换到类类型对象。我们还可以定义从类类型到别的类型的转换。我们通过定义一个转换操作符来定义从类类型的转换。转换构造函数和转换操作符定义类类型转换。这些转换也被称为是用户定义的类型转换。类型转换操作符
类型转换操作符(conversion operator)是一种特殊的成员函数,可以将一个类类型的值转为一个其它类型的值。转换函数通常由这样一种通用的形式:
其中
type
表示一个类型。转换操作符可以为任何可以被函数返回的类型(除了 void)定义。转换成数组或者函数类型是被禁止的。转换成指针类型(数据和函数指针)以及引用类型是允许的。转换操作符没有显式说明返回值类型并且没有参数,它们必须被定义为成员函数。转换操作符通常不应该改变发生转换的对象的状态。因而,转换操作符通常被定义为 const 成员。
注:类型转换函数必须是成员函数,并且不指定返回值类型,其参数列表必须是空的。这个函数通常应该是 const 的。
定义具有类型转换操作符的类
🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
通过继承关联起来的类组成了层级。通常层级的顶端是一个基类(base class),其它类直接或间接地从基类继承而来,这些继承的类称之为派生类(derived classes)。基类定义层级中所类型都共通的成员,每个派生类定义各自特有的成员。
如果要设计一个图书管理系统,每个角色的权限是不同的,为了区分这些角色,就要设计一些类出来:
其存在大量冗余部分,有些信息是公共的,有些信息是每个角色独有的。对于有些数据和方法是每个角色都具有的,每次都写一遍,这就导致设计重复了。代码是要讲究复用的,要想办法去做一个 "提取" ,把共有的成员变量提取出来。
解决方案:设计一个Person类,使用 "继承" 去把这些大家公有的东西运送给各个角色:
在需要称为子类的类的类名后加上冒号,并跟上继承方式和父类类名即可,希望让
Student
以public
的继承方式继承自Person
:🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
C++
的动态绑定发生在虚成员函数通过基类的引用或指针调用时发生。由于直到运行时才知道哪个函数版本被调用,虚函数必须总是被定义。通常,不使用的函数时不需要提供定义的。然而,必须给每个虚函数提供定义而不管它有没有被使用,因为编译器无法知道一个虚函数是否被使用。存在继承关系的类之间的转换:
- 派生类到基类之间的转换仅被运用于指针或者引用类型
- 没有隐式的从基类到派生类之间的转换
private
或protected
继承的派生类有时不能执行派生类到基类的转换
由于自动转换只发生于指针和引用,绝大多数继承层级中的类隐式或显式地定义拷贝控制成员,因而,可以将派生对象拷贝、移动或赋值给基类对象。然而,这种拷贝、移动或赋值仅仅只处理派生对象中的基类部分,称之为裁剪(sliced down)。
调用虚函数将在运行时解析
🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
正如每个控制其自动成员的初始化,每个类也会控制其成员是否对派生类可见。
protected 成员
类使用
protected
修饰那些对派生类可见,但是不想被其他公共访问使用的成员。protected
可以认为是private
和public
的中和:- 与
private
成员类似,protected
成员对类的用户是不可见的
- 与
public
成员类似,protected
成员对派生类的成员和友元是可见的
还有一条很重要的关于
protected
特性是:派生类的成员和友元只能通过派生对象访问基类的protected
成员。派生类不能访问独立的基类对象的protected
成员。第二个函数不是
Base
的友元,因而,它不能访问 Base 对象的受保护成员。为了防止第二种用法,派生类的成员或友元只能访问嵌套在派生对象中的基类子对象中的受保护成员。对于独立的基类对象并不具有特殊的访问权限。🥒
type
status
date
slug
summary
tags
category
icon
password
Property
每个类定义其自己的作用域,在其中它的成员被定义。在继承下,派生类的作用域被嵌套在基类的作用域中。如果一个名字在派生类中无法被解析,那么将继续查找基类作用域。由于继承嵌套了类作用域,所以派生类的成员可以使用基类的成员就像是其自己的成员一样。
名称查找发生在编译期间
对象、引用、指针的静态类型决定了哪些对象的成员是可见的。即便静态类型和动态类型不一样,也是静态类型决定哪些成员是可以使用的。假设
Disc_quote
有一个名为 discount_policy
的成员:名称冲突和继承
与其它内嵌作用域一样,派生类可以复用其直接或间接基类的名字。与往常一样,定义在内部作用域的名字将隐藏定义在外部作用域的名字。
在派生类中定义与基类同名的成员,将隐藏基类成员的直接使用。
🥒
type
status
date
slug
summary
tags
category
icon
password
Property
目录
与其它类一样,继承层次中的类控制本类对象如何进行创建、拷贝、移动和赋值或析构。与任何别的类一样,如果基类或派生类本身没有定义自己的拷贝控制操作,编译器会合成这些操作。同样,在某些情况下,合成的版本可能是被删除的函数。
派生类构造函数
- 基类成员需调用自己的构造完成初始化, 即派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。
- 如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
- 派生类对象初始化先调用基类构造再调派生类构造。
调用基类构造函数初始化继承自基类的成员,自己再初始化自己的成员(规则参考普通类)。析构、靠别构造、赋值重载也是类似的。