多态性、虚函数

C++多态性

类或对象的多态性(运行时多态)【重点】

定义

类的成员函数在编译时,无法确定调用的位置,只有在运行时,依据函数名和参数表确认调用的位置。

运行时多态的原理(了解)

虚函数表(_vfptr虚函数指针)

_vfptr虚函数指针

运行时,确定虚函数的位置

_vfptr指针指向虚函数表vftable

vftable实际上是函数指针数组

存储的是虚函数地址:基类指针或引用指向具体派生类重写的虚函数地址

个数比实际的虚函数多一个

前提条件

继承 + 重写基类的虚函数(virtual)

应用场景

父类的引用或指针 指向 子类的对象

override关键字

确定此成员函数是否正确的重写父类的成员虚函数

简单情况:

cpp 复制代码
class Person {
public:
    virtual void hi() {//虚函数
        cout << "Person hi()" << endl;
    }
};
class Student :public Person {
    // 重写基类的虚函数
    //override关键字:确定此成员函数是否正确的重写父类的成员虚函数
    //重写虚函数的三同:返回类型、函数名、参数表
    virtual void hi()override {
        cout << "Student hi()" << endl;
    }
};

int main(){
    /*Person p1;
    p1.hi();*/

    //运行时多态的应用场景
    Person* p1 = new Student();
    p1->hi();//运行之后,调用的函数为Student对象的hi()

    return 0;
}

多子类情况:

cpp 复制代码
class Person {
public:
    virtual void hi() {//虚函数
        cout << "Person hi()" << endl;
    }
};
class Student :public Person {
    // 重写基类的虚函数
    //override关键字:确定此成员函数是否正确的重写父类的成员虚函数
    //重写虚函数的三同:返回类型、函数名、参数表
    virtual void hi()override {
        cout << "Student hi()" << endl;
    }
};
class Worker :public Person {
public:
    virtual void hi()override {
        cout << "Worker hi()" << endl;
    }
};
class Teacher :public Person {
public:
    virtual void hi()override {
        cout << "Teacher hi()" << endl;
    }
};

void hello(Person &person) {
    person.hi();//因为此处是Person类的引用,同时,hi()成员函数是虚函数
                //因此编译时无法确认调用的位置,只有在程序运行后
                //调用此函数传入具体的对象时,才能确定

}
int main() {
    Person p1;
    Worker w1;
    Teacher t1;
    Student s1;

    //运行时多态的最终体现
    hello(p1);
    hello(w1);
    hello(t1);
    hello(s1);

    return 0;
}

多态性练习:

cpp 复制代码
// 练习: 动物世界
class Animal {
public:
    virtual void eat() {
        cout << "Animal eat... " << endl;
    }
    virtual void say() {
        cout << "Animal say... " << endl;
    }
};
// 完成 Dog和Cat的派生类
class Dog : public Animal {
    // override 只是验证当前重写的虚函数是否OK
    void eat() override {//不用加virtual也是虚函数
        cout << "Dog eat  骨头 " << endl;
    }
    void say() override {//不用加virtual也是虚函数
        cout << "Dog say 旺旺旺 " << endl;
    }
};
class Cat : public Animal {
    void eat() override {//不用加virtual也是虚函数
        cout << "Cat eat  鱼 " << endl;
    }
    void say() override {//不用加virtual也是虚函数
        cout << "Cat say 喵喵喵 " << endl;
    }
};
// 运行时多态性:  一个对象具有多种形态
void animal_eat(Animal* animal) {
    animal->eat();
}
void animal_say(Animal* animal) {
    animal->say();
}
int main() {
    animal_eat(new Dog());
    animal_eat(new Cat());

    animal_say(new Dog());
    animal_say(new Cat());
    return 0;
}

全局函数或同类中成员函数的多态性(静态多态、函数重载)

虚函数的使用规则

1.重写父类的虚函数时必须 3同:返回类型、函数名、参数列表

2.虚函数只修饰类的成员函数或析构函数,外部的全局函数时无法使用virtual关键字的

3.类的静态成员函数(静态成员没有this指针)、构造函数(构造函数是用来初始化成员的,在创建类对象时,必须确定位置)、拷贝构造(原因同构造函数)、inline内联函数、全局函数,无法为虚函数。

4.析构函数可以是虚函数,在运行时多态的情况下,必须虚函数化,因为实际回收的空间是子类对象。如果没有加virtual,则不会回收子类的对象空间

5.派生类中重写基类的虚函数时,可以不需要virtual关键字

6.基类指针或引用,指向派生类的对象

7.虚函数声明与定义分开时,在类外禁止使用virtual关键字

cpp 复制代码
//重写父类的虚函数时必须 3同:返回类型、函数名、参数列表
//虚函数只修饰类的成员函数,外部的全局函数时无法使用virtual关键字的
//类的静态成员函数(静态成员没有this指针)、构造函数(构造函数是用来初始化成员的,在创建类对象时,必须确定位置)、拷贝构造(原因同构造函数),无法为虚函数。
class A {
public:
    //virtual不能修饰static函数,因为静态成员没有this指针
    static void hi() {

    }
    //virtual无法修饰构造函数,因为构造函数用来初始化成员的
    A() {

    }
    //virtual无法使用,原因同构造函数
    A(const A& other) {

    }
    //析构函数可以使用virtual,用于运行时多态的情况下回收具体的对象空间
    virtual ~A() {

    }
};
cpp 复制代码
//析构函数可以使用virtual,用于运行时多态的情况下,回收具体的对象空间
class Person {
private:
    int pid;
    char *name;
public:
    Person(int pid, const char* name) :pid(pid) {
        this->name = new char[32] {0};
        strcpy(this->name, name);
    }
    virtual void hi() {
        cout << "Pid:" << pid << ",Name:" << name << endl;
    }
    virtual~Person() {
        cout << "~Person()" << endl;
        delete[]name;
    }
};
class Student :public Person {
private:
    char* clsName;//班级名
public:
    Student(int pid, const char* name, const char* claName) : Person(pid, name) {
        this->clsName = new char[32]{0};
        strcpy(this->clsName, claName);
    }
    void hi() {
        Person::hi();
        cout << "clsName:" << clsName << endl;
    }
    ~Student() {
        cout << "~Student()" << endl;
        delete[]clsName;
    }
};
int main() {
    Person* p = new Student(1001, "lucy", "2502");
    p->hi();//只执行Person的析构函数,导致clsName的空间没有回收,需要给父类的析构函数前面加virtual
    delete p;

    return 0;
}
cpp 复制代码
//虚函数声明与定义分开时,在类外禁止使用virtual关键字
class Person {
private:
    int pid;
    char *name;
public:
    Person(int pid, const char* name) :pid(pid) {
        this->name = new char[32] {0};
        strcpy(this->name, name);
    }
    virtual void hi() {
        cout << "Pid:" << pid << ",Name:" << name << endl;
    }
    //声明
    virtual~Person();
};
//类外定义虚函数,禁止使用virtual
Person::~Person() {
    cout << "~Person()" << endl;
    delete[]name;
}

抽象类与接口类

抽象类

包含纯虚函数的类

纯虚函数

没有函数体(函数定义)

virtual 返回类型 函数名(参数表)=0;

例子:virtual void sayByte() = 0;

抽象类的特点

  1. 不能实例化(也是纯虚函数特点)
  2. 派生类未实现纯虚函数时,仍为抽象类
cpp 复制代码
class A {
public:
    virtual void hi() {
        cout << "hi"<<endl;
    }
    virtual void sayByte() = 0;//纯虚函数
};
class B :public A {

};
class C :public B {
public:
    void sayByte()override {
        cout << "C bye Abstract Class" << endl;
    }
};
int main() {
    //A a;//抽象类不能创建对象

    //B b;//抽象类的派生类未实现纯虚函数的功能,仍为抽象类

    C c;
    c.hi();
    c.sayByte();
    return 0;
}

接口类

类中所有的函数都是纯虚函数

作用

定义统一的行为规范、实现多继承、降低程序模块间耦合,提高系统的可扩展性与可维护性

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
//设计打印机接口类  :只有功能声明,未实现
class Printer {
public:
	virtual void print(string info) = 0;
	virtual void cpy(string info) = 0;
};
//设计wifi打印机接口类
class WifiPrinter :public Printer {
public:
	virtual void setWifi(string ssid, string pwd) = 0;
};
//定义Dell打印机的功能类
class DellPrinter :public WifiPrinter {
	void print(string info)override {
		cout << "DellPrinter print:" << info << endl;
	}
	void cpy(string info)override {
		cout << "DellPrinter cpy:" << info << endl;
	}
	void setWifi(string ssid, string pwd) {
		cout << "DellPrinter wifi:" << ssid << " " << pwd << endl;
	}
};
class XiaomiPrinter :public WifiPrinter {
	void print(string info)override {
		cout << "XiaomiPrinter print:" << info << endl;
	}
	void cpy(string info)override {
		cout << "XiaomiPrinter cpy:" << info << endl;
	}
	void setWifi(string ssid, string pwd) {
		cout << "XiaomiPrinter wifi:" << ssid<<" "<< pwd << endl;
	}
};
class SoniPrinter :public WifiPrinter {
	void print(string info)override {
		cout << "SoniPrinter print:" << info << endl;
	}
	void cpy(string info)override {
		cout << "SoniPrinter cpy:" << info << endl;
	}
	void setWifi(string ssid, string pwd) override {
		cout << "SoniPrinter wifi:" << ssid << " " << pwd << endl;
	}
};
//体现运行时多态
void print(WifiPrinter& printer, string info) {
	printer.print(info);//此处在编译时不能确定位置,只有在运行后,
						//调用此函数时,传入的对象,依据插入对象的_vfptr确认位置
}
void cpy(WifiPrinter& printer,string info) {
	printer.cpy(info);
}
void setWifi(WifiPrinter& printer,string ssid, string pwd) {
	printer.setWifi(ssid,pwd);
}
int main()
{
	DellPrinter p1;
	XiaomiPrinter p2;
	SoniPrinter p3;

	print(p1, "hi,Disen");

	cpy(p3, "I love C++");

	setWifi(p2, "desen", "12345678");

	return 0;
}
相关推荐
H Journey2 小时前
Windows 下 使用VSCode 编写C++程序中文乱码问题
c++·windows·vscode·cmake/gcc
kernelcraft2 小时前
Matlab读取CSV数据并处理:从入门到实战的完整指南
开发语言·其他·matlab
XMYX-02 小时前
14 - Go 结构体(struct):从基础到高级实战
开发语言·golang
承渊政道2 小时前
【递归、搜索与回溯算法】(二叉树深搜模型拆解与经典题型全面突破)
数据结构·c++·学习·算法·leetcode·macos·bfs
ShineWinsu2 小时前
百度搜索算法逆向思考的技术文章
开发语言
lhbian2 小时前
C# vs 汇编:编程世界的两极对比
开发语言·汇编·c#
handler012 小时前
Linux基础知识(1)
linux·服务器·c语言·开发语言·数据结构·c++
Rsun045512 小时前
12、Java 享元模式从入门到实战
java·开发语言·享元模式
枫叶丹42 小时前
【HarmonyOS 6.0】ArkWeb:Web组件销毁模式深度解析
开发语言·前端·华为·harmonyos