【黑马程序员】C++继承和多态

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;
}
  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

double_happiness

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值