嵌入式学习-C嘎嘎-Day05
目录
[1. 继承](#1. 继承)
[1.1 基本使用](#1.1 基本使用)
[1.2 构造函数](#1.2 构造函数)
[1.2.1 构造函数在继承中的特性](#1.2.1 构造函数在继承中的特性)
[1.2.2 透传构造](#1.2.2 透传构造)
[1.2.3 委托构造](#1.2.3 委托构造)
[1.2.3 继承构造](#1.2.3 继承构造)
[1.3 对象的创建与销毁流程](#1.3 对象的创建与销毁流程)
[1.4 多重继承](#1.4 多重继承)
[1.4.1 基本使用](#1.4.1 基本使用)
[1.4.2 二义性问题](#1.4.2 二义性问题)
[1.4.2.1 多个基类拥有同名成员](#1.4.2.1 多个基类拥有同名成员)
[1.4.2.2 菱形继承(钻石继承)](#1.4.2.2 菱形继承(钻石继承))
[1.5 权限](#1.5 权限)
[1.5.1 三种权限修饰符](#1.5.1 三种权限修饰符)
[1.5.2 公有继承](#1.5.2 公有继承)
[1.5.3 保护继承](#1.5.3 保护继承)
[1.5.4 私有继承](#1.5.4 私有继承)
[2. 多态 polymorphysm](#2. 多态 polymorphysm)
[2.1 概念](#2.1 概念)
[2.2 函数覆盖(函数重写)override](#2.2 函数覆盖(函数重写)override)
[2.3 实现](#2.3 实现)
[2.4 原理](#2.4 原理)
[2.5 虚析构函数](#2.5 虚析构函数)
[3. C++11类型转换](#3. C++11类型转换)
[3.1 static_cast](#3.1 static_cast)
[3.2 dynamic_cast](#3.2 dynamic_cast)
[3.3 const_cast](#3.3 const_cast)
[3.4 reinterpret_cast](#3.4 reinterpret_cast)
[4. 抽象类 abstract class](#4. 抽象类 abstract class)
[4.1 概念](#4.1 概念)
[4.2 用法](#4.2 用法)
[4.2.1 派生类继承抽象基类并实现所有纯虚函数](#4.2.1 派生类继承抽象基类并实现所有纯虚函数)
[4.2.2 派生类继承抽象基类没有实现所有纯虚函数](#4.2.2 派生类继承抽象基类没有实现所有纯虚函数)
[4.2.3 多态](#4.2.3 多态)
[4.2.4 纯虚析构函数(熟悉)](#4.2.4 纯虚析构函数(熟悉))
[4.2.5 抽象工厂模式](#4.2.5 抽象工厂模式)
1. 继承
1.1 基本使用
继承就是在一个已存在的类的基础上建立一个新的类,并拥有其特性。
继承是面向对象的三大特性之一。体现了代码复用的思想。
- 已存在的类被称为"基类(base class)"或"父类(father class)"
- 新建立的类被称为"派生类(derived class)"或"子类(sub class)"
#include <iostream>`
`using` `namespace` `std;`
`/**`
` * @brief The Father class 基类`
` */`
`class` `Father`
`{`
`private:`
`string first_name =` `"张";`
`public:`
`string` `get_first_name()` `const`
`{`
`return first_name;`
`}`
`void` `work()`
`{`
` cout <<` `"我是一个厨师"` `<< endl;`
`}`
`};`
`/**`
` * @brief The Son class 派生类`
` */`
`class` `Son:public Father`
`{`
`public:`
`// void test()`
`// {`
`// cout << first_name << endl; 无法访问`
`// }`
`};`
`int` `main()`
`{`
`Son s;`
` cout << s.get_first_name()` `<< endl;`
` s.work();`
`return` `0;`
`}
在上面的代码中,Son类对象确实继承到了Father类的私有成员first_name,但是无法直接访问,必须通过Father类的接口访问。
上面的派生类在实际开发中意义不大,通常派生类在继承基类的基础上做出一些修改和添加。
#include <iostream>`
`using namespace std;`
`/**`
` * @brief The Father class 基类`
` */`
`class Father`
`{`
`private:`
` string first_name = "张";`
`public:`
` string get_first_name() const`
` {`
` return first_name;`
` }`
` void work()`
` {`
` cout << "我是一个厨师" << endl;`
` }`
`};`
`/**`
` * @brief The Son class 派生类`
` */`
`class Son:public Father`
`{`
`public:`
` // 增加功能`
` void play()`
` {`
` cout << "我爱打游戏" << endl;`
` }`
` // 函数隐藏:派生类中增加一个与基类同名函数即可隐藏基类函数`
` void work(string lan)`
` {`
` cout << "我是一个程序员,我使用的编程语言是" << lan << endl;`
` }`
`};`
`int main()`
`{`
` Son s;`
` cout << s.get_first_name() << endl;`
`// s.work(); 错误:函数隐藏`
` s.work("C++");`
` s.play(); // 新增内容`
` // 调用被隐藏的函数`
` s.Father::work();`
` return 0;`
`}
一个基类可以有多个派生类,每个派生类又可以作为基类再有派生类,因此基类和派生类是相对的。
派生类是基类的具象化,基类是派生类的抽象化。
1.2 构造函数
1.2.1 构造函数在继承中的特性
构造函数和析构函数不能被继承。
#include <iostream>`
`using namespace std;`
`class Device`
`{`
`private:`
` string brand;`
`public:`
` Device(string brand):brand(brand){}`
` string get_brand() const`
` {`
` return brand;`
` }`
`};`
`class Screen:public Device`
`{`
`};`
`int main()`
`{`
`// Screen s("SAMSUNG"); 错误`
`// Screen s; 错误`
` return 0;`
`}
派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数。
在1.1节中,因为没有在派生类中手动编写构造函数,此时编译器会自动添加一个无参的构造函数,且函数体为空。
如果程序员没有手动在派生类的构造函数中直接或间接调用基类的构造函数,编译器会尝试在派生类的构造函数中调用基类的无参构造函数 。
在派生类中手动调用基类的构造函数有三种方式:
- 透传构造
- 委托构造
- 继承构造(C++11)
1.2.2 透传构造
可以使用透传构造在派生类的构造函数中直接调用基类的构造函数,实际上编译器尝试调用基类的无参构造函数就是使用这种方式。
#include <iostream>`
`using` `namespace std;`
`class` `Device`
`{`
`private:`
` string brand;`
`public:`
`Device(string brand)`
`:brand(brand){}`
` string get_brand()` `const`
`{`
`return brand;`
`}`
`};`
`class` `Screen:public Device`
`{`
`public:`
`Screen(string brand)`
`:Device(brand)` `// 透传构造:调用基类构造函数`
`{}`
`Screen()`
`:Device("AOC")` `// 透传构造`
`{}`
`};`
`int` `main()`
`{`
` Screen s("SAMSUNG");`
` cout << s.get_brand()` `<< endl;`
` Screen s2;`
` cout << s2.get_brand()` `<< endl;`
`return` `0;`
`}
1.2.3 委托构造
可以在派生类中使用委托构造间接调用基类的构造函数。
#include <iostream>`
`using` `namespace std;`
`class` `Device`
`{`
`private:`
` string brand;`
`public:`
`Device(string brand)`
`:brand(brand){}`
` string get_brand()` `const`
`{`
`return brand;`
`}`
`};`
`class` `Screen:public Device`
`{`
`public:`
`Screen(string brand)`
`:Device(brand)` `// 透传构造:调用基类构造函数`
`{}`
`Screen()`
`:Screen("AOC")` `// 委托构造`
`{}`
`};`
`int` `main()`
`{`
` Screen s("SAMSUNG");`
` cout << s.get_brand()` `<< endl;`
` Screen s2;`
` cout << s2.get_brand()` `<< endl;`
`return` `0;`
`}
需要注意的是,要避免委托闭环。
#include <iostream>`
`using` `namespace std;`
`class` `Device`
`{`
`private:`
` string brand;`
`public:`
`Device(string brand)`
`:brand(brand){}`
` string get_brand()` `const`
`{`
`return brand;`
`}`
`};`
`class` `Screen:public Device`
`{`
`public:`
`Screen(string brand)`
`:Screen()` `// 委托构造`
`{}`
`Screen()`
`:Screen("AOC")` `// 委托构造`
`{}`
`};`
`int` `main()`
`{`
` Screen s("SAMSUNG");`
` cout << s.get_brand()` `<< endl;`
` Screen s2;`
` cout << s2.get_brand()` `<< endl;`
`return` `0;`
`}`
`
1.2.3 继承构造
继承构造是C++11的新特性,通过继承构造的语句,可以让编译器为派生类添加n个(n为基类的构造函数数量)构造函数,每个派生类的构造函数都与基类的构造函数参数相同,并透传同参数的构造函数,因此继承构造的本质是编译器自动生成的透传构造。
#include <iostream>`
`using namespace std;`
`class Device`
`{`
`private:`
` string brand;`
`public:`
` Device(string brand)`
` :brand(brand){}`
` Device()`
` {`
` brand = "Dell";`
` }`
` string get_brand() const`
` {`
` return brand;`
` }`
`};`
`class Screen:public Device`
`{`
`public:`
` using Device::Device; // 继承构造`
` // 编译器自动添加:`
`// Screen(string brand):Device(brand){}`
`// Screen():Device(){}`
`};`
`int main()`
`{`
` Screen s("SAMSUNG");`
` cout << s.get_brand() << endl;`
` Screen s2;`
` cout << s2.get_brand() << endl;`
` return 0;`
`}
1.3 对象的创建与销毁流程
#include <iostream>`
`using namespace std;`
`/**`
` * @brief The Value class`
` * 作为其他类的变量,方便查看创建与销毁的时机`
` */`
`class Value`
`{`
`private:`
` string name;`
`public:`
` Value(string name):name(name)`
` {`
` cout << name << "创建了" << endl;`
` }`
` ~Value()`
` {`
` cout << name << "销毁了" << endl;`
` }`
`};`
`class Father`
`{`
`public:`
` static Value s_value;`
` Value value = Value("Father的成员变量");`
` Father()`
` {`
` cout << "Father的构造函数" << endl;`
` }`
` ~Father()`
` {`
` cout << "Father的析构函数" << endl;`
` }`
`};`
`Value Father::s_value = Value("Father的静态成员变量");`
`class Son:public Father`
`{`
`public:`
` static Value s_value;`
` Value value = Value("Son的成员变量");`
` Son()`
` {`
` cout << "Son的构造函数" << endl;`
` }`
` ~Son()`
` {`
` cout << "Son的析构函数" << endl;`
` }`
`};`
`Value Son::s_value = Value("Son的静态成员变量");`
`int main()`
`{`
` cout << "程序开始执行" << endl;`
` Son* s = new Son; // 创建了对象`
` // 使用对象`
` cout << "对象使用中" << endl;`
` // 销毁对象`
` delete s;`
` cout << "程序结束执行" << endl;`
` return 0;`
`}`
`
上面运行结果具有以下规律:
- 静态成员生命周期是整个程序运行的周期
- 同步骤在创建过程中,先基类后派生类;在销毁过程中,先派生类后基类。
- 创建与销毁过程对称。
1.4 多重继承
1.4.1 基本使用
之前的继承派生类只有一个基类,C++中规定派生类可以有多个基类,多重继承的派生类对每个基类的关系仍然可以单做是一个单继承。
#include <iostream>`
`using namespace std;`
`class Sofa`
`{`
`public:`
` void sit()`
` {`
` cout << "坐在沙发上" << endl;`
` }`
`};`
`class Bed`
`{`
`public:`
` void lay()`
` {`
` cout << "躺在床上" << endl;`
` }`
`};`
`/**`
` * @brief The SofaBed class 多重继承`
` */`
`class SofaBed:public Sofa,public Bed`
`{`
`};`
`int main()`
`{`
` SofaBed sb;`
` sb.lay();`
` sb.sit();`
` return 0;`
`}
1.4.2 二义性问题
1.4.2.1 多个基类拥有同名成员
当多个基类拥有同名成员时,派生类调用此成员会出现二义性问题,解决方法为使用 基类:: 区分。
#include <iostream>`
`using namespace std;`
`class Sofa`
`{`
`public:`
` void sit()`
` {`
` cout << "坐在沙发上" << endl;`
` }`
` void position()`
` {`
` cout << "放在客厅" << endl;`
` }`
`};`
`class Bed`
`{`
`public:`
` void lay()`
` {`
` cout << "躺在床上" << endl;`
` }`
` void position()`
` {`
` cout << "放在卧室" << endl;`
` }`
`};`
`/**`
` * @brief The SofaBed class 多重继承`
` */`
`class SofaBed:public Sofa,public Bed`
`{`
`};`
`int main()`
`{`
` SofaBed sb;`
` sb.lay();`
` sb.sit();`
`// sb.position(); 错误:二义性`
` // 增加 基类:: 区分重名成员`
` sb.Sofa::position();`
` sb.Bed::position();`
` return 0;`
`}
1.4.2.2 菱形继承(钻石继承)
如果一个派生类D拥有多个基类B和C,而B和C又具有公共的基类A,这种继承结构就是菱形继承(钻石继承),D在调用A的内容时,因为同时从B和C继承了两份,尽管两份内容相同,但是仍然会出现二义性问题。
#include <iostream>`
`using namespace std;`
`class Furniture`
`{`
`public:`
` void func()`
` {`
` cout << "这是家具的函数" << endl;`
` }`
`};`
`class Sofa:public Furniture{};`
`class Bed:public Furniture{};`
`class SofaBed:public Sofa,public Bed`
`{`
`};`
`int main()`
`{`
` SofaBed sb;`
`// sb.func(); 错误`
` return 0;`
`}
解决方法1:使用 基类:: 区
#include <iostream>`
`using namespace std;`
`class Furniture`
`{`
`public:`
` void func()`
` {`
` cout << "这是家具的函数" << endl;`
` }`
`};`
`class Sofa:public Furniture{};`
`class Bed:public Furniture{};`
`class SofaBed:public Sofa,public Bed`
`{`
`};`
`int main()`
`{`
` SofaBed sb;`
`// sb.func(); 错误`
` sb.Bed::func();`
` sb.Sofa::func();`
` return 0;`
`}
解决方法2:虚继承,在继承时使用virtual关键字。
#include <iostream>`
`using namespace std;`
`class Furniture`
`{`
`public:`
` void func()`
` {`
` cout << "这是家具的函数" << endl;`
` }`
`};`
`class Sofa:virtual public Furniture{};`
`class Bed:virtual public Furniture{};`
`/**`
` * @brief The SofaBed class 虚继承`
` */`
`class SofaBed:public Sofa,public Bed`
`{`
`};`
`int main()`
`{`
` SofaBed sb;`
` sb.func();`
` return 0;`
`}`
`
虚继承的实现原理由虚基类表和虚基类表指针实现,下图中的Furniture类会有一张虚基类表,记录了函数的调用地址,这张表属于Furniture类持有,多个对象共享这张表。
Sofa类和Bed类对象内部都会增加一个隐藏的指针成员------虚基类表指针,指向Furniture的虚基类表。
SofaBed对象则可以继承Sofa和Bed类的虚基类表指针,SofaBed对象在调用Furniture成员时,由于使用两个虚基类表指针调用的是同一个地址,经过查表可以避免重复调用问题。
虚继承的原理本质上就是通过查表来解决二义性问题,因此性能略低。
1.5 权限
1.5.1 三种权限修饰符
之前主要使用的是private和public,实际上还有一个protected,这三种权限的访问范围如下:
|---------------|----|------|-----------|
| | 类内 | 派生类内 | 全局(例如主函数) |
| private(私有)默认 | √ | X | X |
| protected(保护) | √ | √ | X |
| public(公有) | √ | √ | √ |
#include <iostream>`
`using namespace std;`
`class Animal`
`{`
`protected:`
` void eat()`
` {`
` cout << "吃东西" << endl;`
` }`
`public:`
` void test()`
` {`
` eat();`
` }`
`};`
`class Cat:public Animal`
`{`
`public:`
` Cat()`
` {`
` eat();`
` test();`
` }`
`};`
`int main()`
`{`
` Animal a;`
` a.test();`
`// a.eat(); 错误`
` Cat t;`
` return 0;`
`}`
`
1.5.2 公有继承
公有继承是使用的最多的一种继承,之前使用的都是公有继承,
在公有继承下,基类的私有成员可以被派生类继承,但是不能直接访问。基类的保护成员和公有成员,继承到派生类后,仍然作为派生类的保护和公有成员。
#include <iostream>`
`using namespace std;`
`class Father`
`{`
`protected:`
` void test_protected()`
` {`
` cout << "基类保护成员" << endl;`
` }`
`public:`
` void test_public()`
` {`
` cout << "基类公有成员" << endl;`
` }`
`};`
`class Son:public Father`
`{`
`};`
`class Grandson:public Son`
`{`
`public:`
` Grandson()`
` {`
` test_protected();`
` }`
`};`
`int main()`
`{`
` Son s;`
` s.test_public();`
`// s.test_protected();`
` Grandson gs;`
` return 0;`
`}
1.5.3 保护继承
在保护继承下,基类的私有成员可以被派生类继承,但是不能直接访问。基类的保护成员和公有成员,继承到派生类后都变成派生类的保护成员
#include <iostream>`
`using namespace std;`
`class Father`
`{`
`protected:`
` void test_protected()`
` {`
` cout << "基类保护成员" << endl;`
` }`
`public:`
` void test_public()`
` {`
` cout << "基类公有成员" << endl;`
` }`
`};`
`class Son:protected Father`
`{`
`};`
`class Grandson:public Son`
`{`
`public:`
` Grandson()`
` {`
` test_public();`
` test_protected();`
` }`
`};`
`int main()`
`{`
` Son s;`
`// s.test_public();`
`// s.test_protected();`
` Grandson gs;`
` return 0;`
`}
1.5.4 私有继承
在保护继承下,基类的私有成员可以被派生类继承,但是不能直接访问。基类的保护成员和公有成员,继承到派生类后都变成派生类的私有成员。
#include <iostream>`
`using namespace std;`
`class Father`
`{`
`protected:`
` void test_protected()`
` {`
` cout << "基类保护成员" << endl;`
` }`
`public:`
` void test_public()`
` {`
` cout << "基类公有成员" << endl;`
` }`
`};`
`class Son:private Father`
`{`
`public:`
` Son()`
` {`
` test_public();`
` test_protected();`
` }`
`};`
`class Grandson:public Son`
`{`
`public:`
` Grandson()`
` {`
` // test_public();`
` // test_protected();`
` }`
`};`
`int main()`
`{`
` Son s;`
` // s.test_public();`
` // s.test_protected();`
` Grandson gs;`
` return 0;`
`}`
`
2. 多态 polymorphysm
2.1 概念
多态从广义上讲分为静态多态和动态多态。
静态多态(编译时多态)发生在程序的编译阶段,主要指的就是之前学习的函数重载,因为函数重载本质上就是在编译期根据函数签名来决定调用关系。
动态多态(运行时多态)发生在程序的运行阶段,本章主要讨论的是动态多态,后续多态统一指动态多态。动态多态指的是"一个接口,多种状态",程序在运行时才确定调用的代码,接口因参数的不同,执行不同的策略。
多态的实现条件有三个:
- 公有继承
- 函数覆盖
- 基类引用/指针指向派生类对象
2.2 函数覆盖(函数重写)override
函数覆盖与函数隐藏非常相似,区别如下:
- 函数覆盖需要同签名
- 函数覆盖需要虚函数
- 函数覆盖可以使用override关键字验证(C++11)
使用virtual修饰的成员函数就是虚函数,虚函数具有以下性质:
- 虚函数具有传递性,当基类的某个函数声明为虚函数时,派生类的新覆盖函数也会自动变成虚函数。
- 构造函数与静态成员函数不能声明为虚函数,普通成员函数和析构函数可以声明为虚函数。
- 函数声明与定义分离时,只需要使用virtual修饰声明处,不需要修饰定义处。
函数覆盖主要的目的是实现多态,单独使用的意义不大。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`virtual` `void` `eat();` `// 只修饰声明处即可`
`};`
`void` `Animal::eat()`
`{`
` cout <<` `"吃东西"` `<< endl;`
`}`
`class` `Dog:public Animal`
`{`
`public:`
`void` `eat()`
`{`
` cout <<` `"吃翔"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
` Animal a;`
` a.eat();`
` Dog d;`
` d.eat();`
`return` `0;`
`}
可以在C++11中使用override关键字验证函数覆盖的成功性。
2.3 实现
#include <iostream>`
`using namespace std;`
`class Animal`
`{`
`public:`
` virtual void eat(); // 只修饰声明处即可`
`};`
`void Animal::eat()`
`{`
` cout << "吃东西" << endl;`
`}`
`class Dog:public Animal`
`{`
`public:`
` void eat() override`
` {`
` cout << "吃翔" << endl;`
` }`
`};`
`class Cat:public Animal`
`{`
`public:`
` void eat()`
` {`
` cout << "吃鱼" << endl;`
` }`
`};`
`// 基类引用派生类对象:栈内存多态`
`void test_poly(Animal& a)`
`{`
` a.eat();`
`}`
`// 基类指针指向派生类对象:堆内存多态`
`void test_poly(Animal* a)`
`{`
` a->eat();`
`}`
`int main()`
`{`
` // 栈内存对象+基类引用`
` Cat c1;`
` Dog d1;`
` test_poly(c1);`
` test_poly(d1);`
` // 堆内存对象+基类指针`
` Cat* c2 = new Cat;`
` Dog* d2 = new Dog;`
` test_poly(c2);`
` test_poly(d2);`
` // 先不管警告`
` delete c2;`
` delete d2;`
` return 0;`
`}
可以看到多态设计的接口,根据传入参数的类型不同,有不同的调用结果,一方面多态可以减少接口的数量,另一方面可以更加灵活。
2.4 原理
多态的原理是虚函数表与动态类型绑定。
当一个类有虚函数时,编译器会为这个类分配一张表专门记录此表的虚函数,这个类对象中有一个隐藏的成员变量指向这张表。
当基类的引用或指针指向派生类对象时,编译器会在内部产生一段检查对象真正类型的代码,程序在运行时会检查真正的数据类型,找到虚函数表指针,通过查表找到调用的函数地址,从而形成多态的调用效果。
多态在使用的过程中会产生一些额外的代码和调用关系,因此降低了程序执行的速度。
#include <iostream>`
`using namespace std;`
`class Animal`
`{`
`public:`
` virtual void eat(); // 只修饰声明处即可`
` virtual void sleep()`
` {`
` cout << "呼呼呼" << endl;`
` }`
`};`
`void Animal::eat()`
`{`
` cout << "吃东西" << endl;`
`}`
`class Dog:public Animal`
`{`
`public:`
` void eat() override`
` {`
` cout << "吃翔" << endl;`
` }`
` virtual void bark()`
` {`
` cout << "旺旺旺" << endl;`
` }`
`};`
`void test_poly(Animal& a)`
`{`
` a.eat();`
`}`
`int main()`
`{`
` Animal a;`
` cout << sizeof(a) << endl;`
` Dog d1;`
` test_poly(d1);`
` cout << sizeof(d1) << endl;`
` return 0;`
`}
2.5 虚析构函数
使用virtual修饰的析构函数就是虚析构函数。
在多态的使用过程中,通过delete销毁基类指针指向的派生类对象会导致派生类的析构函数不调用的问题,可能会导致内存泄漏。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`virtual` `void` `eat();`
`~Animal()`
`{`
` cout <<` `"Animal的析构函数"` `<< endl;`
`}`
`};`
`void` `Animal::eat()`
`{`
` cout <<` `"吃东西"` `<< endl;`
`}`
`class` `Dog:public Animal`
`{`
`public:`
`void` `eat()` `override`
`{`
` cout <<` `"吃翔"` `<< endl;`
`}`
`~Dog()`
`{`
` cout <<` `"Dog的析构函数"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// 基类指针指向派生类对象`
` Animal* a =` `new Dog;`
` a->eat();` `// 多态`
`delete a;`
`return` `0;`
`}
解决方法为设置基类的析构函数为虚析构函数,虚析构函数也具有传递性,可以通过查表调用的方式解决上述问题。
#include <iostream>`
`using` `namespace std;`
`class` `Animal`
`{`
`public:`
`virtual` `void` `eat();`
`virtual` `~Animal()`
`{`
` cout <<` `"Animal的析构函数"` `<< endl;`
`}`
`};`
`void` `Animal::eat()`
`{`
` cout <<` `"吃东西"` `<< endl;`
`}`
`class` `Dog:public Animal`
`{`
`public:`
`void` `eat()` `override`
`{`
` cout <<` `"吃翔"` `<< endl;`
`}`
`~Dog()` `// 虚函数的传递性`
`{`
` cout <<` `"Dog的析构函数"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// 基类指针指向派生类对象`
` Animal* a =` `new Dog;`
` a->eat();` `// 多态`
`delete a;`
`return` `0;`
`}
建议给所有基类都编写虚析构函数。
3. C++11类型转换
C++11增加了四种类型转换函数,用于代替传统的C语言类型转换语法。
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
3.1 static_cast
相比于C的类型转换,static_cast可以在编译时对类型进行检查,确保类型转换是合法的,如果转换不合法,编译器将报错,从而帮助开发者在编译阶段就可以提前发现并修复问题。
#include <iostream>`
`using` `namespace std;`
`int` `main()`
`{`
`int a =` `1;`
`double b =` `static_cast<double>(a);`
` cout << b << endl;`
`return` `0;`
`}`
`
static_cast还可以应用在类层次结构中,即基类和派生类指针或引用的转换,但需要注意:
- static_cast进行上行转换是安全的,即派生类的指针或引用转换为基类的;
- static_cast进行下行转换是不安全的,即基类的指针或引用转换为派生类的。
无论是上行还是下行,static_cast都不是最佳选择。
使用栈内存+引用方式进行类层次转换:
#include <iostream>`
`using` `namespace std;`
`class` `Father`
`{`
`public:`
` string a =` `"Father";`
`};`
`class` `Son:public Father`
`{`
`public:`
` string b =` `"Son";`
`};`
`int` `main()`
`{`
` Father f;`
` cout <<` `sizeof(f)` `<< endl;` `// 4`
` Son s;`
` cout <<` `sizeof(s)` `<< endl;` `// 8`
`// 栈内存对象需要使用引用转换`
`// 上行转换:派生类→基类`
` Father& f2 =` `static_cast<Father&>(s);`
` cout << f2.a << endl;`
` cout <<` `&f2 <<` `" "` `<<` `&s << endl;` `// 0x61fe80 0x61fe80`
`// 下行转换:基类→指针`
` Son& s2 =` `static_cast<Son&>(f);`
` cout <<` `&s2 <<` `" "` `<<` `&f << endl;` `// 0x61fe84 0x61fe84`
` cout << s2.a << endl;`
` cout << s2.b << endl;` `// 不确定`
`return` `0;`
`}
使用堆内存+指针方式进行类层次转换:
#include <iostream>`
`using` `namespace std;`
`class` `Father`
`{`
`public:`
` string a =` `"Father";`
`};`
`class` `Son:public Father`
`{`
`public:`
` string b =` `"Son";`
`};`
`int` `main()`
`{`
` Father* f =` `new Father;`
` Son* s =` `new Son;`
`// 堆内存对象需要使用指针转换`
`// 上行转换:派生类→基类`
` Father* f2 =` `static_cast<Father*>(s);`
` cout << f2->a << endl;`
` cout << f2 <<` `" "` `<< s << endl;` `// 0x10a1078 0x10a1078`
`// 下行转换:基类→指针`
` Son* s2 =` `static_cast<Son*>(f);`
` cout << s2 <<` `" "` `<< f << endl;` `// 0x61fe84 0x61fe84`
` cout << s2->a << endl;`
` cout << s2->b << endl;` `// 不确定`
`return` `0;`
`}
static_cast可以间接调用构造函数:
#include <iostream>`
`using namespace std;`
`class Student`
`{`
`private:`
` string name;`
`public:`
` Student(string name):name(name){}`
` string get_name()`
` {`
` return name;`
` }`
`};`
`int main()`
`{`
` string name = "赵六";`
` Student s4 = static_cast<Student>(name);`
` cout << s4.get_name() << endl;`
` return 0;`
`}
3.2 dynamic_cast
dynamic_cast专用于类层次的上下行转换。
在进行上行转换时,dynamic_cast与static_cast完全相同,但是下行转换dynamic_cast非常严谨,会遵循下面的判断依据:
#include <iostream>`
`using` `namespace std;`
`class` `Weather`
`{`
`public:`
`virtual` `void` `func()`
`{`
` cout <<` `"天气能干啥?"` `<< endl;`
`}`
`};`
`class` `Sunny:public Weather`
`{`
`public:`
`void` `func()`
`{`
` cout <<` `"晴天晒被子"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// 构成多态`
` Sunny s;`
` Weather& w = s;`
` Sunny& s1 =` `dynamic_cast<Sunny&>(w);`
` cout <<` `&s <<` `" "` `<<` `&w <<` `" "` `<<` `&s1 <<endl;` `// 0x61fe84 0x61fe84 0x61fe84`
` s1.func();` `// 晴天晒被子`
` Weather* w2 =` `new Sunny;`
` Sunny* s2 =` `dynamic_cast<Sunny*>(w2);`
` cout << w2 <<` `" "` `<< s2 << endl;` `// 0x10b1048 0x10b1048`
` s2->func();`
`// TODO 没虚析构函数,不要在意这些细节`
`return` `0;`
`}`
`
#include <iostream>`
`using namespace std;`
`class Weather`
`{`
`public:`
` virtual void func()`
` {`
` cout << "天气能干啥?" << endl;`
` }`
`};`
`class Sunny:public Weather`
`{`
`public:`
` void func()`
` {`
` cout << "晴天晒被子" << endl;`
` }`
`};`
`int main()`
`{`
` // 不构成多态`
`// Weather w;`
`// Sunny& s1 = dynamic_cast<Sunny&>(w); // 运行终止`
` Weather* w2 = new Weather;`
` Sunny* s2 = dynamic_cast<Sunny*>(w2);`
` cout << w2 << " " << s2 << endl; // 0x10b1048 0`
`// s2->func(); 错误:空指针`
` // TODO 没虚析构函数,不要在意这些细节`
` return 0;`
`}`
`
3.3 const_cast
const_cast能添加或删除对象的const修饰符,主要用于改变指针和引用的const修饰,以便于在一些情况下可以修改常量对象。
在正常情况下,应避免使用const_cast,而是考虑通过设计良好的接口或者一些其他手段避免此转换。
#include <iostream>`
`using namespace std;`
`class Computer`
`{`
`private:`
` string brand;`
`public:`
` string system = "Win11";`
` Computer(string brand):brand(brand){}`
` void set_brand(string brand)`
` {`
` this->brand = brand;`
` }`
` string get_brand() const`
` {`
` return brand;`
` }`
`};`
`int main()`
`{`
` // 常量对象`
` const Computer c1("惠普");`
` // 使用const_cast修改c1的属性值`
` Computer& c2 = const_cast<Computer&>(c1);`
` cout << &c1 << " " << &c2 << endl; // 0x61fe78 0x61fe78`
` c2.system = "Win12";`
` c2.set_brand("苹果");`
` cout << c1.get_brand() << endl;`
` cout << c1.system << endl;`
` cout << "----------------" << endl;`
` const Computer* c3 = new Computer("戴尔");`
` Computer* c4 = const_cast<Computer*>(c3);`
` cout << c3 << " " << c4 << endl; // 0x11210a8 0x11210a8`
` c4->system = "Win95";`
` c4->set_brand("华硕");`
` cout << c3->system << endl;`
` cout << c3->get_brand() << endl;`
`}
3.4 reinterpret_cast
reinterpret_cast可以把内存里的值重新解释,这种转换的风险极高,慎用!!!
#include <iostream>`
`using namespace std;`
`class A`
`{`
`public:`
` int a = 1;`
`};`
`class B`
`{`
`public:`
` int a = 2;`
` int b = 3;`
`};`
`int main()`
`{`
` A* a = new A;`
` cout << a << endl; // 0x1101048`
` // 把上面的地址认为是B类型`
` B* b = reinterpret_cast<B*>(a);`
` cout << b << endl; // 0x1101048`
` cout << b->a << endl; // 1`
` cout << b->b << endl; // 16122048`
` cout << "----------------------" << endl;`
` char c = 'A';`
` char* ptr_c = &c;`
` int* i = reinterpret_cast<int*>(ptr_c);`
` cout << *i << endl; // 不可控:1644068673`
`}`
`
4. 抽象类 abstract class
4.1 概念
如果一个基类只表达抽象概念,并不与具体的对象联系,这样类适合作为算法框架,就需要用到抽象类。
抽象类作为框架可以让后续的派生类严格遵守抽象类制定的规则。
纯虚函数 ←→ 抽象类
- 如果一个类拥有纯虚函数,则这个类是抽象类;
- 如果一个类是抽象类,则这个类一定有纯虚函数。
纯虚函数是一种特殊的虚函数,这种函数只有声明没有定义。
#include <iostream>`
`using namespace std;`
`/**`
` * @brief The Shape class 形状`
` */`
`class Shape`
`{`
`public:`
` // 纯虚函数`
` virtual void area() = 0;`
` virtual void perimeter() = 0;`
`};
抽象类不能创建对象,因为抽象类型本身(不算引用和指针)无法被声明,即抽象类型不能成为返回值、参数和变量类型。
4.2 用法
4.2.1 派生类继承抽象基类并实现所有纯虚函数
实现(reimplement)指的是覆盖基类的纯虚函数,并增加函数体,使新的函数在派生类中称为普通函数。
#include <iostream>`
`using` `namespace std;`
`/**`
` * @brief The Shape class 形状`
` */`
`class` `Shape`
`{`
`public:`
`// 纯虚函数`
`virtual` `void` `area()` `=` `0;`
`virtual` `void` `perimeter()` `=` `0;`
`};`
`/**`
` * @brief The Circle class 圆形类`
` */`
`class` `Circle:public Shape`
`{`
`public:`
`void` `area()`
`{`
` cout <<` `"πR^2"` `<< endl;`
`}`
`void` `perimeter()`
`{`
` cout <<` `"2πR"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// Circle类是一个普通类,可以正常使用`
` Circle c;`
` c.area();`
` c.perimeter();`
`}
4.2.2 派生类继承抽象基类没有实现所有纯虚函数
这种情况下,由于派生类仍然有继承的纯虚函数,因此派生类还是抽象类,需要作为基类,继续等待其派生类实现剩余的纯虚函数。
#include <iostream>`
`using` `namespace` `std;`
`/**`
` * @brief The Shape class 形状`
` */`
`class` `Shape`
`{`
`public:`
`// 纯虚函数`
`virtual` `void` `area()` `=` `0;`
`virtual` `void` `perimeter()` `=` `0;`
`};`
`/**`
` * @brief The Polygon class 多边形`
` */`
`class` `Polygon:public Shape`
`{`
`public:`
`void` `perimeter()`
`{`
` cout <<` `"∑边长"` `<< endl;`
`}`
`};`
`/**`
` * @brief The Rectangle class 矩形`
` */`
`class` `Rectangle:public Polygon`
`{`
`public:`
`void` `area()`
`{`
` cout <<` `"w*h"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// Polygon p; 错误`
`Rectangle r;`
` r.area();`
` r.perimeter();`
`}
4.2.3 多态
抽象类支持多态,抽象基类引用和指针可以指向派生类对象,因此抽象类的析构函数应该被设置为虚析构函数。
4.2.4 纯虚析构函数(熟悉)
如果一个类中没有纯虚函数,又想成为抽象类,此时可以把析构函数设置为纯虚析构函数。
#include <iostream>`
`using` `namespace` `std;`
`/**`
` * @brief The Test class 抽象类`
` */`
`class` `Test`
`{`
`public:`
`virtual` `~Test()` `=` `0;` `// 声明`
`};`
`Test::~Test()`
`{`
` cout <<` `"析构函数"` `<< endl;`
`}`
`class` `Test2:public Test`
`{`
`};`
`int` `main()`
`{`
`// Test t; 错误`
`Test2 t2;` `// 可以创建`
`}
析构函数是一个类必不可少的函数,如果不能正常执行,可以会引发一些问题,轻则内存泄漏,重则代码报错。例如私有化析构函数,表示此对象在类外无法被销毁。
#include <iostream>`
`using` `namespace` `std;`
`class` `Test`
`{`
`private:`
`~Test()`
`{`
` cout <<` `"私有析构函数"` `<< endl;`
`}`
`};`
`int` `main()`
`{`
`// Test t1; 错误`
` Test* t2 =` `new Test;`
`// delete t2; 错误`
`}
4.2.5 抽象工厂模式
#include <iostream>`
`using` `namespace` `std;`
`//有抽象类键盘,键盘中有一个纯虚函数show;`
`//有抽象类鼠标,鼠标中有一个纯虚函数show;`
`//有一个微软键盘类继承键盘类,有一个苹果键盘继承键盘类;`
`//有一个微软鼠标类继承鼠标类,有一个苹果鼠标类继承鼠标类。`
`//上面四个派生类都覆盖实现纯虚函数show`
`//有一个工厂类Factory`
`//伪代码如下`
`//class Factory`
`//{`
`//public:`
`// virtual 键盘类指针 createKeyboard() = 0;`
`// virtual 鼠标类指针 createMouse() = 0;`
`//};`
`//有一个微软工厂类继承工厂类,覆盖实现工厂类所有的纯虚函数。`
`//有一个苹果工厂类继承工厂类,覆盖实现工厂类所有的纯虚函数。`
`//在主函数中使用两个工厂类的对象(苹果工厂和微软工厂)`
`//生产各自品牌的键盘和鼠标对象。`
`//并测试键盘鼠标的功能。`
`class` `Keyboard`
`{`
`public:`
`virtual` `void` `show()=0;`
`virtual` `~Keyboard(){}`
`};`
`class` `Mouse`
`{`
`public:`
`virtual` `void` `show()=0;`
`virtual` `~Mouse(){}`
`};`
`class` `MicrosoftKeyboard:public Keyboard`
`{`
`public:`
`void` `show()`
`{`
` cout <<` `"这是微软键盘"` `<< endl;`
`}`
`};`
`class` `MicrosoftMouse:public Mouse`
`{`
`public:`
`void` `show()`
`{`
` cout <<` `"这是微软鼠标"` `<< endl;`
`}`
`};`
`class` `AppleKeyboard:public Keyboard`
`{`
`public:`
`void` `show()`
`{`
` cout <<` `"这是苹果键盘"` `<< endl;`
`}`
`};`
`class` `AppleMouse:public Mouse`
`{`
`public:`
`void` `show()`
`{`
` cout <<` `"这是苹果鼠标"` `<< endl;`
`}`
`};`
`class` `Factory`
`{`
`public:`
`virtual Keyboard*` `createKeyboard()` `=` `0;`
`virtual Mouse*` `createMouse()` `=` `0;`
`virtual` `~Factory(){}`
`};`
`class` `MicrosoftFactory:public Factory`
`{`
`public:`
` Keyboard*` `createKeyboard()`
`{`
` cout <<` `"生产了微软键盘"` `<< endl;`
`return` `new MicrosoftKeyboard;`
`}`
` Mouse*` `createMouse()`
`{`
` cout <<` `"生产了微软鼠标"` `<< endl;`
`return` `new MicrosoftMouse;`
`}`
`};`
`class` `AppleFactory:public Factory`
`{`
`public:`
` Keyboard*` `createKeyboard()`
`{`
` cout <<` `"生产了苹果键盘"` `<< endl;`
`return` `new AppleKeyboard;`
`}`
` Mouse*` `createMouse()`
`{`
` cout <<` `"生产了苹果鼠标"` `<< endl;`
`return` `new AppleMouse;`
`}`
`};`
`int` `main()`
`{`
`MicrosoftFactory mf;`
` Keyboard* k1 = mf.createKeyboard();`
` k1->show();`
` Mouse* m1 = mf.createMouse();`
` m1->show();`
` AppleFactory* af =` `new AppleFactory;`
` Keyboard* k2 = af->createKeyboard();`
` k2->show();`
` Mouse* m2 = af->createMouse();`
` m2->show();`
`delete k1;`
`delete m1;`
`delete af;`
`delete k2;`
`delete m2;`
`return` `0;`
`}`
`