🌶️ 类的交换操作
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函数有两点需要注意:
  1. 如果希望调用移动构造函数需要调用 std::move 来告诉编译器使用移动构造函数,否则,将会使用拷贝构造函数。
  1. 调用move必须加上作用域限定符,显式告诉编译器我们调用的是哪个版本的函数:
    🌶️ 对象移动
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property

     
    新标准的一个特色就是可以移动对象而不是拷贝对象。当对象一旦拷贝完就销毁时,移动而不是拷贝对象将提供重大的性能提升。有一些类的资源是不可共享的,这种类型的对象可以被移动但不能被拷贝,如:IOunique_ptr
    早期版本中,并没有什么方式可以直接移动对象。即便是在不需要拷贝的时候依然会执行拷贝。同样,在之前存储在容器中的对象必须是可拷贝的,在新标准中,可以在容器中使用不可拷贝但是可以移动的对象。
    库容器、stringshared_ptr支持拷贝和移动,IOunique_ptr则只能移动不能拷贝。
     

    左值引用VS右值引用

    左值是一个表示数据的表达式(如变量名或解引用的指针),有如下特性:
    1. 可以获取它的地址+可以对它赋值(不一定能赋值,但一定能取地址)
    🫑 运算符重载
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property
     
    🫑

     

    运算符重载

    C++ 定义了大量操作符,并且为内置类型之间的转换定义了自动转换。这些使得程序员可以很方便的写出混合类型的表达式。
    同时C++允许我们定义当将操作符运用于类类型对象时的含义。它还允许我们定义类类型之间的转换,和内置类型一样,在需要时将一个类型的对象隐式转为另外一个类型对象。
     
    操作符重载(operator overloading)允许我们定义运用于类类型的操作符的含义。谨慎地使用操作符重载可以使得程序更加容易读和写。如果之前地 Sales_item 类定义了重载的输入、输出和加操作符,那么可以如下操作:
    🫑 重载 new 和 delete
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property
     
    当对内存分配有特别需求的时候可以重载 newdelete 操作符来控制内存分配。

    重载 new 和 delete

    重载 newdelete 操作符的方式与重载其它操作符的方式有非常大的不同。
     
    使用 new 表达式时会依次发生三件事:
    1. 调用库函数 operator new 或者 operator new[] 来分配足够的裸的没有类型的内存以存储对象或数组
    1. 调用构造函数构造对象
    1. 返回指向这个新分配构建的对象的指针
     
    使用delete表达式时将发生两件事:
    • 调用析构函数进行析构
    🫑 重载、类型转换与运算符
    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)。基类定义层级中所类型都共通的成员,每个派生类定义各自特有的成员。
     
    如果要设计一个图书管理系统,每个角色的权限是不同的,为了区分这些角色,就要设计一些类出来:
    🥒 虚函数和多态
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property
    🥒
    目录

    C++ 的动态绑定发生在虚成员函数通过基类的引用或指针调用时发生。由于直到运行时才知道哪个函数版本被调用,虚函数必须总是被定义。通常,不使用的函数时不需要提供定义的。然而,必须给每个虚函数提供定义而不管它有没有被使用,因为编译器无法知道一个虚函数是否被使用。
     
    存在继承关系的类之间的转换:
    • 派生类到基类之间的转换仅被运用于指针或者引用类型
    • 没有隐式的从基类到派生类之间的转换
    • privateprotected继承的派生类有时不能执行派生类到基类的转换
    由于自动转换只发生于指针和引用,绝大多数继承层级中的类隐式或显式地定义拷贝控制成员,因而,可以将派生对象拷贝、移动或赋值给基类对象。然而,这种拷贝、移动或赋值仅仅只处理派生对象中的基类部分,称之为裁剪(sliced down)
     
    🥒 抽象基类
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property

     

    纯虚函数

    纯虚函数在虚函数的后面加上了=0
     

    抽象类

    基类中添加了至少一个纯虚函数的基类称为抽象类。
    🥒 访问控制与继承
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property

     
    正如每个控制其自动成员的初始化,每个类也会控制其成员是否对派生类可见。

    protected 成员

    类使用protected修饰那些对派生类可见,但是不想被其他公共访问使用的成员。protected可以认为是privatepublic的中和:
    • private成员类似,protected成员对类的用户是不可见的
    • public成员类似,protected 成员对派生类的成员和友元是可见的
    还有一条很重要的关于protected特性是:派生类的成员和友元只能通过派生对象访问基类的protected成员。派生类不能访问独立的基类对象的protected成员。
    🥒 继承中的类作用域
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property
     
    每个类定义其自己的作用域,在其中它的成员被定义。在继承下,派生类的作用域被嵌套在基类的作用域中。如果一个名字在派生类中无法被解析,那么将继续查找基类作用域。由于继承嵌套了类作用域,所以派生类的成员可以使用基类的成员就像是其自己的成员一样。
     
    名称查找发生在编译期间
    对象、引用、指针的静态类型决定了哪些对象的成员是可见的。即便静态类型和动态类型不一样,也是静态类型决定哪些成员是可以使用的。假设 Disc_quote 有一个名为 discount_policy 的成员:
     
    名称冲突和继承
    与其它内嵌作用域一样,派生类可以复用其直接或间接基类的名字。与往常一样,定义在内部作用域的名字将隐藏定义在外部作用域的名字。
    在派生类中定义与基类同名的成员,将隐藏基类成员的直接使用。
    🥒 构造函数与拷贝控制
    type
    status
    date
    slug
    summary
    tags
    category
    icon
    password
    Property

    与其它类一样,继承层次中的类控制本类对象如何进行创建、拷贝、移动和赋值或析构。与任何别的类一样,如果基类或派生类本身没有定义自己的拷贝控制操作,编译器会合成这些操作。同样,在某些情况下,合成的版本可能是被删除的函数。
     

    派生类构造函数

    • 基类成员需调用自己的构造完成初始化, 即派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。
    • 如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显式调用。
    • 派生类对象初始化先调用基类构造再调派生类构造。
    调用基类构造函数初始化继承自基类的成员,自己再初始化自己的成员(规则参考普通类)。析构、靠别构造、赋值重载也是类似的。