20240215
文章目录
继承
-
好处:减少代码重复
-
语法:
class 子类名: 继承方式 父类名,...
-
注意:
-
子类也叫派生类,父类也叫基类
-
子类中的成员包括两部分,一部分是从父类中继承过来的,一部分是子类特有的
-
从父类中继承过来的表现其共性,而新增的成员体现了其个性
-
继承基本语法
- 要求:开发一个系统页,展示不同语法的视频下载页面
不使用继承方式
-
实现方式:所有的页面都实现在各自的类模块中
-
代码示例:
#include <iostream>
using namespace std;
class Java {
public:
void header() {
cout << "公共头部内容" << endl;
}
void footer() {
cout << "底部公共内容" << endl;
}
void lefter() {
cout << "左侧公共部分" << endl;
}
void content() {
cout << "Java内容" << endl;
}
};
class Python {
public:
void header() {
cout << "公共头部内容" << endl;
}
void footer() {
cout << "底部公共内容" << endl;
}
void lefter() {
cout << "左侧公共部分" << endl;
}
void content() {
cout << "Python内容" << endl;
}
};
class Cpp {
public:
void header() {
cout << "公共头部内容" << endl;
}
void footer() {
cout << "底部公共内容" << endl;
}
void lefter() {
cout << "左侧公共部分" << endl;
}
void content() {
cout << "C++内容" << endl;
}
};
void test(){
cout << "---Java页面---";
Java ja;
ja.header();
ja.footer();
ja.lefter();
ja.content();
cout <<"---------------" << endl;
cout << "---Python页面---";
Python py;
py.header();
py.footer();
py.lefter();
py.content();
cout <<"---------------" << endl;
cout << "---C++页面---";
Cpp cpp;
cpp.header();
cpp.footer();
cpp.lefter();
cpp.content();
cout <<"---------------" << endl;
}
int main(){
test();
return 0;
}
使用集成方式
-
实现方式:将公共的方法写到父类中,不同的页面在子类中去继承父类公共页面的功能,然后在实现自己特有的功能
-
代码示例:
#include <iostream>
using namespace std;
class Base{
public:
void header() {
cout << "公共头部内容" << endl;
}
void footer() {
cout << "底部公共内容" << endl;
}
void lefter() {
cout << "左侧公共部分" << endl;
}
};
class Java : public Base {
public:
void content() {
cout << "Java内容" << endl;
}
};
class Python : public Base {
public:
void content() {
cout << "Python内容" << endl;
}
};
class Cpp : public Base {
public:
void content() {
cout << "C++内容" << endl;
}
};
void test(){
cout << "---Java页面---";
Java ja;
ja.header();
ja.footer();
ja.lefter();
ja.content();
cout <<"---------------" << endl;
cout << "---Python页面---";
Python py;
py.header();
py.footer();
py.lefter();
py.content();
cout <<"---------------" << endl;
cout << "---C++页面---";
Cpp cpp;
cpp.header();
cpp.footer();
cpp.lefter();
cpp.content();
cout <<"---------------" << endl;
}
int main(){
test();
return 0;
}
继承方式
-
继承方式图示
公有继承
-
父类中的访问权限不发生变化
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class Driver : public Base {};
void test(){
Driver d;
cout << d.a << endl;
}
int main(){
test();
return 0;
}
保护继承
-
父类中的公有权限变为保护权限
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class Driver : protected Base {};
void test(){
Driver d;
// error: 'a' is a protected member of 'Base'
//cout << d.a << endl;
}
int main(){
test();
return 0;
}
私有继承
-
父类中的公有权限和保护权限变为私有权限
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class Driver : private Base {};
void test(){
Driver d;
// error: 'a' is a private member of 'Base'
// cout << d.a << endl;
}
int main(){
test();
return 0;
}
继承中的对象模型
-
父类中所有非静态属性成员都会被子类继承下去
-
父类中私有成员是被编译器给隐藏了,因此访问不到,但确实是继承下去了
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
int a;
protected:
int b;
private:
int c;
};
class Driver : private Base {
public:
int d;
};
void test(){
Driver d;
// 16
cout << sizeof(d) << endl;
}
int main(){
test();
return 0;
}
继承中构造和析构顺序
-
构造顺序:先调用父类的构造函数,在调用子类的构造函数
-
析构顺序:先调用子类的析构函数在调用父类的析构函数
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
Base(){
cout << "父类构造函数" << endl;
}
~Base(){
cout << "父类析构函数" << endl;
}
};
class Driver : private Base {
public:
Driver(){
cout << "子类构造函数" << endl;
}
~Driver(){
cout << "子类析构函数" << endl;
}
};
void test(){
Driver d;
}
int main(){
test();
return 0;
}
继承中同名的成员处理方式
-
问题:当子类和父类中出现同名成员时,如何通过子类对象访问到子类的成员或父类的同名成员
-
方法:
-
访问子类同名成员:直接访问即可
-
访问父类同名成员:需要加作用域
-
-
如果子类中出现了父类的同名函数,子类的同名函数会隐藏掉父类中所有的同名函数
-
不管是普通成员还是静态成员,访问方法都和上面一样
-
代码示例:
#include <iostream>
using namespace std;
class Base {
public:
Base(){
A=1;
}
void func(){
cout << A << endl;
}
void func(int){
cout << A << endl;
}
static void foo() {
cout << "父类中的静态成员函数" << endl;
}
int A;
static int B;
};
class Driver : public Base {
public:
Driver(){
A=2;
}
void func(){
cout << A << endl;
}
static void foo(){
cout << "子类中的静态成员函数" << endl;
}
int A;
static int B;
};
void test(){
Driver d;
cout << "访问子类中的同名成员:" << d.A << endl;
cout << "访问父类中的同名成员:" << d.Base::A << endl;
cout << "访问子类中的同名函数:";
d.func() ;
cout << "访问父类中的同名函数:";
d.Base::func() ;
}
void test1() {
cout << "使用对象的方式访问静态成员" << endl;
Driver d;
cout << "访问子类中的静态成员变量:" << d.B << endl;
cout << "访问父类中的静态成员变量:" << d.Base::B << endl;
cout << "使用类名的方式访问静态成员" << endl;
cout << "访问子类中的静态成员变量:" << Driver::B << endl;
cout << "访问父类中的静态成员变量:" << Driver::Base::B << endl;
}
int main(){
test();
test1();
return 0;
}
多继承语法
-
C++允许一个类继承多个类
-
语法:
class 子类名: 继承方式 父类名,...
-
多继承可能会引发父类中有同名成员出现,需要加作用域区分
-
注意:c++实际开发中不建议使用多继承
#include <iostream>
using namespace std;
class Base1 {
public:
Base1(){
m_a = 1;
}
int m_a;
};
class Base2 {
public:
Base2(){
m_a = 2;
}
int m_a;
};
// 子类继承Base1和Base2
class Son : public Base1, public Base2 {
public:
Son(){
m_c=3;
m_d=4;
}
int m_c;
int m_d;
};
void test(){
Son s;
cout << "sizeof Son: " << sizeof(s) << endl;
// 访问不同的基类中的同名对象需要使用作用域区分
cout << "访问Base1中m_a: " << s.Base1::m_a << endl;
cout << "访问Base2中m_a: " << s.Base2::m_a << endl;
}
int main(){
test();
return 0;
}
菱形继承
菱形继承概念
-
两个派生类继承同一个基类A
-
又有某个类D同时继承两个派生类C、D
-
这种继承方式被称为菱形继承或者钻石继承
菱形继承问题
-
二义性问题:C、D两个派生类都继承成了基类A中的一部分数据,在D类使用数据时就会产生二义性
-
保留两份数据问题:D类继承了A类两份数据,其实只需要继承一份数据即可,类中的继承结构如下图所示
菱形继承问题解决
-
二义性问题:使用C、D的作用域来做区分
-
保留两份数据问题:利用(virtual )虚继承可以解决菱形继承的问题,继承前面加上virtual关键字,A类此时被称为虚基类
虚继承内部实现
-
在虚继承中,派生类通过虚基类指针(vptr)和虚基类表(vtable)来跟踪和访问虚基类的成员。当一个类通过虚继承声明虚基类时,派生类中不会直接包含虚基类的实例,而是包含对虚基类的指针。这个指针指向派生类对象中的虚基类子对象。
-
虚继承通过引入虚基类指针和虚基类表来解决菱形继承的问题。虚基类指针跟踪虚基类的位置,而虚基类表存储了虚基类的偏移量和其他信息,用于访问虚基类的成员。这种机制确保了在虚继承中只有一个共享的虚基类子对象,避免了冗余数据和二义性的问题。
代码示例
#include <iostream>
using namespace std;
// 虚基类
class Animal{
public:
int age;
};
// 虚继承:利用虚继承的方式解决菱形继承多分数据的问题
class Sheep : virtual public Animal{
};
class Camel : virtual public Animal{
};
// 菱形继承
class SheepCamel : public Sheep, public Camel{
public:
int age;
};
void test(){
SheepCamel sc;
// 基类二义性的问题:使用域名进行区分
sc.Sheep::age=10;
sc.Camel::age=20;
// 使用虚继承之后,这个age就会变成最后一次赋值的值
cout << "Sheep::age-> " << sc.Sheep::age << endl;
cout << "Camel::age-> " << sc.Camel::age << endl;
// 使用虚继承之后也可以不使用域名进行访问
cout << "使用虚继承之后:" << sc.age << endl;
}
int main(){
test();
return 0;
}
多态
多态基本概念
多态分类
-
静态多态
-
函数重载和运算符重载都属于静态多态,复用函数名
-
静态多态的函数地址早绑定,编译阶段确定函数地址
-
-
动态多态
-
派生类和虚函数实现动态多态
-
动态多态的函数地址晚绑定,运行阶段确定函数地址
-
动态多态满足条件
-
有继承关系
-
子类要重写父类的虚函数,子类在重写的时候virtual关键字可写可不写(重写:除了函数体实现之外,其它函数定义都相同)
动态多态的使用
- 父类的指针或者引用执行子类对象
动态多态和静态多态代码示例
#include <iostream>
using namespace std;
class Animal{
public:
void speak() {
cout << "Animal speak()" << endl;
}
// 父类的虚函数
virtual void speak1() {
cout << "Animal speak1()" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "Cat speak()" << endl;
}
// 子类重写父类的虚函数
void speak1() {
cout << "Cat speak1()" << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "Dog speak()" << endl;
}
// 子类重写父类的虚函数
void speak1() {
cout << "Dog speak1()" << endl;
}
};
void speak(Animal& animal) {
animal.speak();
}
// Animal& animal = 子类对象,父类的指针或者引用指向子类对象
void speak1(Animal& animal) {
animal.speak1();
}
void test(){
Cat cat;
speak(cat);
Dog dog;
speak(dog);
// 动态多态调用
speak1(cat);
speak1(dog);
}
int main(){
test();
return 0;
}
多态原理剖析
virtual修饰的普通成员函数
- 只有一个普通成员函数的类占1个字节,空类内部存储
- 使用virtual修饰的成员函数类占4/8个字节,具体取决于使用的32为还是64位系统
- 代码示例
#include <iostream>
using namespace std;
class Animal1{
public:
void speak(){
cout << "Animal1 speak()" << endl;
}
};
class Animal2{
public:
virtual void speak(){
cout << "Animal1 speak()" << endl;
}
};
void test() {
cout << "只有一个普通成员函数类的大小:" << sizeof(Animal1) << endl;
cout << "只有一个virtual修饰的普通成员函数类的大小:" << sizeof(Animal2) << endl;
}
int main() {
test();
return 0;
}
不重写父类虚函数和重写父类虚函数内存存储
-
父类使用虚函数,则父类的虚函数位置存储了一个虚函数指针(vfptr),虚函数指针指向的是虚函数表(vftable)
-
如果子类没有重写父类的虚函数,则虚函数表中存储的是父类的虚函数地址
-
如果子类重写了父类的虚函数地址,则虚函数表中存储的是子类重写后的函数地址
-
多态就是使用父类的指针或者引用在运行时,判断传入的是子类的对象还是父类的对象来实现不同的指向
-
代码实现
#include <iostream>
using namespace std;
class Animal{
public:
// 父类的虚函数
virtual void speak(){
cout << "Animal speak()" << endl;
}
};
// 子类未重写父类的虚函数指针
class Cat : public Animal {};
class Dog : public Animal {
public:
// 子类重写了父类的虚函数
void speak() {
cout << "Dog speak()" << endl;
}
};
void test(){
Cat cat;
Dog dog;
}
int main() {
test();
return 0;
}
多态优点
-
代码组织结构清晰(不同的功能只需要去写不同的抽象类,不需要去改其它的)
-
可读性强(所有的功能都分了不同的抽象类便于阅读)
-
有利于扩展和维护(符合开闭原则:对扩展开放,对修改关闭)
多态案例-计算器
#include <iostream>
using namespace std;
class AbstractOperator{
public:
virtual void getResult(){
return;
}
int a;
int b;
};
class AddgetResult : public AbstractOperator{
public:
void getResult(){
cout << a << "+" << b << "=" << a <<+b << endl;
}
};
class SubgetResult : public AbstractOperator{
public:
void getResult(){
cout << a << "-" << b << "=" << a-b << endl;
}
};
class MulgetResult : public AbstractOperator{
public:
void getResult(){
cout << a << "*" << b << "=" << a * b << endl;
}
};
class DivgetResult : public AbstractOperator{
public:
void getResult(){
cout << a << "/" << b << "=" << a / b << endl;
}
};
void test(){
AbstractOperator* addOperator = new AddgetResult();
addOperator->a = 10;
addOperator->b = 3;
addOperator->getResult();
delete addOperator;
AbstractOperator* subOperator = new SubgetResult();
subOperator->a = 10;
subOperator->b = 3;
subOperator->getResult();
delete subOperator;
AbstractOperator* mulOperator = new MulgetResult();
mulOperator->a = 10;
mulOperator->b = 3;
mulOperator->getResult();
delete mulOperator;
AbstractOperator* divOperator = new DivgetResult();
divOperator->a = 10;
divOperator->b = 3;
divOperator->getResult();
delete divOperator;
}
int main(){
test();
return 0;
}
纯虚函数抽象类
-
多态中,通常父类中的虚函数实现是毫无意义的,主要都是调用子类重写的内容,一次可以将虚函数直接改成纯虚函数
-
纯虚函数语法:
virtual 返回值类型 函数名(参数列表)=0;
-
当类中只要有一个纯虚函数,这个类就称为抽象类
抽象类特点
-
无法实例化对象
-
子类必须重写抽象类中的纯虚函数,否则子类也属于抽象类
代码示例
#include <iostream>
using namespace std;
// 抽象类
class Base{
public:
// 纯虚函数
virtual void func()=0;
};
class Son : public Base{
public:
virtual void func() {
cout << "Son" << endl;
}
};
void test(){
// error: variable type 'Base' is an abstract class
// Base b; // 栈上不能初始化
// new Base; // 堆上不能初始化
// 使用子类初始化
Son s;
}
int main(){
test();
return 0;
}
抽象类示例–制作饮品
#include <iostream>
using namespace std;
class Base{
public:
virtual void boilWater()=0;
virtual void pourMasterMaterials()=0;
virtual void pourCup()=0;
virtual void addOtherMaterials()=0;
void makeDrink(){
boilWater();
pourMasterMaterials();
pourCup();
addOtherMaterials();
}
};
class Coffee : public Base{
public:
virtual void boilWater(){
cout << "烧水" << endl;
}
virtual void pourMasterMaterials(){
cout << "加入咖啡" << endl;
}
virtual void pourCup(){
cout << "导入杯中" << endl;
}
virtual void addOtherMaterials(){
cout << "加入糖和牛奶" << endl;
}
};
class Tea : public Base{
public:
virtual void boilWater(){
cout << "烧水" << endl;
}
virtual void pourMasterMaterials(){
cout << "加入茶叶" << endl;
}
virtual void pourCup(){
cout << "导入杯中" << endl;
}
virtual void addOtherMaterials(){
cout << "加入柠檬" << endl;
}
};
void doWork(Base * b) {
b->makeDrink();
// 用完后记得释放指针
delete b;
}
void test(){
doWork(new Coffee);
cout << "----------------" << endl;
doWork(new Tea);
}
int main() {
test();
return 0;
}
虚析构和纯虚析构
引入背景
-
问题:多态使用时,如果子类中有属性开辟到了堆区,那么父类指针在释放时无法调用到子类的析构代码
-
代码示例
#include <iostream>
using namespace std;
class Animal{
public:
Animal(){
cout << "Animal()构造函数" << endl;
}
virtual void speak(){
cout << "Animal speak()" << endl;
}
~Animal(){
cout << "Animal()析构函数" << endl;
}
};
class Cat : public Animal{
public:
Cat(){
name = new string("cat");
cout << "Cat()构造函数" << endl;
}
virtual void speak(){
cout << "Cat speak()" << endl;
}
~Cat(){
cout << "Cat()析构函数" << endl;
}
string *name;
};
void test(){
Animal* animal = new Cat();
animal->speak();
}
int main(){
test();
return 0;
}#include <iostream>
using namespace std;
class Animal{
public:
Animal(){
cout << "Animal()构造函数" << endl;
}
virtual void speak(){
cout << "Animal speak()" << endl;
}
~Animal(){
cout << "Animal()析构函数" << endl;
}
};
class Cat : public Animal{
public:
Cat(){
name = new string("cat");
cout << "Cat()构造函数" << endl;
}
virtual void speak(){
cout << "Cat speak()" << endl;
}
~Cat(){
cout << "Cat()析构函数" << endl;
}
string *name;
};
void test(){
Animal* animal = new Cat();
animal->speak();
}
int main(){
test();
return 0;
}
- 运行结果中缺少子类析构函数的输出
解决方式
- 将父类中的析构函数改为虚析构或纯虚析构
虚析构和纯虚析构的异同
-
相同点
-
可以解决父类指针释放子类对象
-
都需要有具体的函数实现
-
-
区别:如果是纯虚析构,改类属于抽象类,无法实例化对象
虚析构实现
- 将父类中的析构函数前面加上
virtual
关键字即可
纯虚析构实现
- 将父类的虚构函数使用
virtual ~类名()=0
进行声明,然后在全局使用类名::~类名(){具体实现}
多态实战–组装电脑
#include <iostream>
using namespace std;
// 抽象不同的零件类
// 抽象cpu类
class Cpu{
public:
virtual void calculator()=0;
};
// 抽象显卡类
class VideoCard{
public:
virtual void show()=0;
};
// 抽象内存类
class Memory{
public:
virtual void storage()=0;
};
//电脑类
class Computer{
public:
Computer(Cpu* cpu, VideoCard* vc, Memory* mem){
m_cpu = cpu;
m_vc = vc;
m_mem = mem;
}
// 电脑组装方法
void assemble(){
m_cpu->calculator();
m_vc->show();
m_mem->storage();
}
// 适当电脑零件,防止内存泄露
~Computer(){
if (m_cpu != NULL){
delete m_cpu;
m_cpu = NULL;
}
if (m_vc != NULL){
delete m_vc;
m_vc = NULL;
}
if (m_mem != NULL){
delete m_mem;
m_mem = NULL;
}
}
private:
Cpu* m_cpu;
VideoCard* m_vc;
Memory* m_mem;
};
// 具体厂商
// Intel厂商
class IntelCpu: public Cpu{
public:
void calculator(){
cout << "IntelCpu calculator" << endl;
}
};
class IntelVideoCard: public VideoCard{
public:
void show(){
cout << "IntelVideoCard show()" << endl;
}
};
class IntelMemory: public Memory{
public:
void storage(){
cout << "IntelMemory storage" << endl;
}
};
// lenovo厂商
class LenovoCpu: public Cpu{
public:
void calculator(){
cout << "LenovoCpu calculator" << endl;
}
};
class LenovoVideoCard: public VideoCard{
public:
void show(){
cout << "LenovoVideoCard show()" << endl;
}
};
class LenovoMemory: public Memory{
public:
void storage(){
cout << "LenovoMemory storage" << endl;
}
};
void test(){
cout << "开始组装第一台电脑:"<< endl;
Cpu* cpu = new IntelCpu();
VideoCard* vc = new IntelVideoCard();
Memory* mem = new IntelMemory();
Computer* com1 = new Computer(cpu, vc, mem);
com1->assemble();
delete com1;
cout << "------------------" << endl;
cout << "开始组装第二台电脑:"<< endl;
Computer* com2 = new Computer(new LenovoCpu, new LenovoVideoCard, new LenovoMemory);
com2->assemble();
delete com2;
cout << "------------------" << endl;
cout << "开始组装第三台电脑:"<< endl;
Computer* com3 = new Computer(new IntelCpu, new LenovoVideoCard, new IntelMemory);
com3->assemble();
delete com3;
cout << "------------------" << endl;
}
int main(){
test();
return 0;
}