【C++】15.类的细节[深蓝学院C++第13章]

前言

 

一.运算符重载

1.1 operator引入重载函数

auto operator+(Str a,Strb)

特性:

(1)重载不能发明新的运算,不能改变运算的优先级与结合性,通常不改变运算含义

(2)函数参数个数与运算操作数个数相同,至少一个为本类的类型

(3)除 operator() 外其它运算符不能有缺省参数,operator()是重载()

(4)可以选择实现为成员函数与非成员函数,实现为成员函数时会以*this作为第一个操作数

根据重载特性,可以类型划分:

(1)可重载且必须实现为成员函数的运算符,=、[]、()、->、与转型运算符

(2)可重载且可以实现为非成员函数的运算符

(3)可重载但不建议重载的运算符,&&、||、逗号运算符,与或存在短路逻辑,重载时不一定会实现会造成歧义

(4)不可重载的运算符,如?:运算符

1.2 对称运算符

通常定义为非成员函数以支持首个操作数的类型转换,为了支持交换律一般定义为非成员以免使用*this

1.3 移位运算符

一定要定义为非成员函数,因为其首个操作数类型为流类型,即对std::cout <<的重载std需要作为第一个操作数

1.4 赋值运算符

也可以接收一般参数,在内部作类型转换、数据提取

1.5 operator[]

通常返回引用

1.6 自增、自减运算符的前缀、后缀重载方法

通过()里面是否为空来判断是前缀还是后缀,通过该案例也能知道前缀自增的效率比后缀自增的效率高,后缀自增返回右值、存在拷贝构造、消耗资源

1.7 解引用运算符*与成员访问运算符->

使用解引用运算符( * )与成员访问运算符( -> )模拟指针行为,略

1.8 函数调用运算符

使用构造可调用对象,使得对数据的处理可以封装在类中,实现OOP编程

1.9 类型转换运算符

operator type() const,与单参数构造函数一样都引入了一种类型转换方式

1.10 C++20中对==和<=>的重载

 

二.类的继承

类的继承(派生)来引入“是一个”的关系

 

 特性:

(1)通常采用public继承,(struct VS class的区别:struct缺省时是public继承、class缺省是private继承)

(2)注意:继承部分不是类的声明

(3)使用基类的指针或引用可以指向派生类对象,里氏代换原则

(4)静态类型 V.S. 动态类型,静态类型在编译期就可以确定的类型,动态类型是运行时才能确定的类型。

(5)protected 限定符:派生类可访问

2.1 类的派生与嵌套域

类的派生会产生嵌套域

特性:

(1)派生类所在域位于基类内部

(2)派生类中的名称定义会覆盖基类

(3)使用域操作符显式访问基类成员,Base::xxx

(4)在派生类中调用基类的构造函数,系统会先隐式地调用基类构造、再调用派生类的构造,默认调用缺省构造,否则需要显式地调用自定义的基类构造,派生(xxx):Base(xxx)

2.2 虚函数

使用关键字virtual关键字引入,非静态、非构造函数可以声明为虚函数,

最重要的是,虚函数会引入vtable结构:

 

 

对于类derivedMember2的对象,其动态类型是derivedMember2,它的typeInfo就是这个table。

有了该vtable之后,可以使用dynamic_cast将基类引用转为派生类,在运行期运行。

 2.2.1 虚函数在基类中的定义

(1)引入缺省逻辑,可以在派生类中进行重写override

1 struct Base{
2 virtual void fun(){
3     std::cout << "Base::fun() is called" <<std::endl;        
4 }
5 }

当基类有虚函数时,借助vtable,可以实现强转为基类的指针依然调用派生类的方法

当基类没有虚函数时,没有vtable,已经从派生转为基类的指针只能调用基类的方法

动态/运行期多态:形参为基类类型,动态传入不同的派生类类型,实现不同的逻辑

(2)可以通过=0声明纯虚函数,成为接口,要求派生类去实现

包含纯虚函数的类叫做抽象基类,不能声明抽象基类的对象,可以声明指针或引用=派生类实例。

2.2.2 虚函数在派生类中的重写override

(1)函数签名保持不变,返回类型可以是原始返回指针/引用类型的派生指针/引用类型,不一定非得是虚函数的返回类型

(2)虚函数特性保持不变,派生1派生自Base,派生2派生自派生1,派生2中的实现仍然算虚函数、放入vtable中

(3)override关键字,C++11开始引入,override要求编译器检查重写入口

2.2.3虚函数引入的动态绑定与运行期行为

(1)虚函数的缺省实参只会考虑静态类型

(2)虚函数的调用成本高于非虚函数,final关键字

(3)为什么要使用指针(或引用)引入动态绑定

(4)在构造函数中调用虚函数要小心

(5)派生类的析构函数会隐式调用基类的析构函数

(6)通常来说要将基类的析构函数声明为 virtual 的

(7)在派生类中修改虚函数的访问权限

2.3 继承

所有的特殊成员函数在显式定义时都可能需要显式调用基类相关成员

 2.3.1 派生与构造

(1)缺省构造函数会隐式调用基类的缺省构造函数

(2)拷贝构造函数将隐式调用基类的拷贝构造函数

(3)赋值函数将隐式调用基类的赋值函数

(4)派生类的其它构造函数将隐式调用基类的缺省构造函数

2.3.2 派生与析构

(1)派生类的析构函数会调用基类的析构函数

2.3.3 构造与销毁顺序

(1)基类的构造函数会先调用,之后才涉及到派生类中数据成员的构造

(2)派生类中的数据成员会被先销毁,之后才涉及到基类的析构函数调用

2.3.4 public与private继承

影响派生类继承来的一些成员访问权限,基类里面的权限修饰符控制是否对派生类可见,派生类在继承时的权限修饰符控制继承来的内容是否继续对外面可见

public继承,描述“是一个”的关系

private继承:描述“根据基类实现出”的关系

protected继承:几乎不会使用

2.3.5 using与继承

使用using改变基类成员的访问权限:

(1)在派生类可以访问该成员的前提下,将基类protected的通过using改为public,或将基类public的通过using

改为private

改变数据成员——public: using Base::z;或private:Base::x;

改变函数方法——public:using Base::fun;会将基类中所有可见的fun函数的重载拿过来

(2)无法改变构造函数的访问权限

使用using继承基类的构造函数逻辑:using Base::Base;将所有的构造拿过来用。

using与部分重写,using Base::fun可以将基类中的fun的重载都拿过来,如果里面有virtual,仍然可以在派生类中进行override

2.3.6继承与友元

友元关系无法继承,但基类的友元可以访问派生类中基类的相关成员

2.3.7 其他内容

通过基类指针实现在容器中保存不同类型对象

多重继承,继承自多个基类

虚继承,class Base1:virtual Base, class Base2:virtual Base, 使Base1和Base2的共同派生类只从Base1或Base2中取成员,避免同一个Base成员的继承冲突

空基类优化与no unique address属性

posted @ 2023-03-02 20:12  啊原来是这样呀  阅读(26)  评论(0)    收藏  举报