【C++入门】虚函数与多态

【C++入门】虚函数与多态

@TOC

前言

C++ 是一种功能强大的编程语言,以其面向对象编程(OOP)特性而闻名。虚函数和多态是 C++ 面向对象编程中的两个重要概念,它们使得代码更灵活、更具扩展性。本篇文章将介绍虚函数与多态,包括其定义、使用方法及其在实际编程中的作用。

虚函数是什么?

虚函数是一种在基类中使用 virtual 关键字声明的函数,它允许在运行时通过基类指针或引用调用子类的重写版本。这种机制使得 C++ 能够实现多态性。

如何使用虚函数?

在基类中声明虚函数,并在子类中重写该虚函数。基类指针或引用可以指向子类对象,并在运行时调用子类的重写版本。

#include

class Base {

public:

virtual void show() {

std::cout << "Base show" << std::endl;

}

};

class Derived : public Base {

public:

void show() override {

std::cout << "Derived show" << std::endl;

}

};

int main() {

Base* ptr = new Derived();

ptr->show(); // 调用的是 Derived 的 show 方法

delete ptr;

return 0;

}

纯虚函数是什么?

纯虚函数是一种没有实现的虚函数,必须在派生类中实现。它使用 = 0 语法来声明。声明纯虚函数的类称为抽象类,不能直接实例化。

class AbstractBase {

public:

virtual void pureVirtualFunction() = 0; // 纯虚函数

};

class ConcreteDerived : public AbstractBase {

public:

void pureVirtualFunction() override {

std::cout << "ConcreteDerived implementation of pureVirtualFunction" << std::endl;

}

};

int main() {

// AbstractBase obj; // 错误,不能实例化抽象类

ConcreteDerived obj;

obj.pureVirtualFunction(); // 调用派生类实现的纯虚函数

return 0;

}

虚函数与普通函数的区别

调用时机:

普通函数:在编译时确定调用的函数。

虚函数:在运行时确定调用的函数(动态绑定)。

多态性:

普通函数:不支持多态性。

虚函数:支持多态性,可以通过基类指针或引用调用派生类的重写版本。

虚表

虚表是什么?

在 C++ 中,虚表(vtable)是一个由编译器维护的数据结构,用于实现虚函数的动态绑定。当一个类中包含虚函数时,编译器会为这个类创建一个虚表。虚表是一个指针数组,其中每个指针指向该类的虚函数实现。当通过基类指针或引用调用虚函数时,程序会通过虚表找到实际要调用的函数。

每个包含虚函数的类对象都有一个隐藏的指针,称为虚表指针(vptr),它指向该对象所属类的虚表。因此,通过虚表指针,程序可以在运行时动态地确定调用哪个函数。

含有虚表的类内存结构图

有下面这样的两个类:

class Base {

public:

virtual void func1();

int baseData;

};

class Derived : public Base {

public:

void func1() override;

void func2();

int derivedData;

};

它的内存结构图如下

+-----------------+

| Derived object |

+-----------------+

| vptr ---------->+-------------------------+

| derivedData | |

+-----------------+ |

| baseData | |

+-----------------+ |

|

|

|

+-----------------+ +-----------------+

| Virtual Table | | Virtual Table |

| for Derived | | for Base |

+-----------------+ +-----------------+

| func1() | | func1() |

| Derived::func1 | | Base::func1 |

+-----------------+ +-----------------+

| func2() |

| Derived::func2 |

+-----------------+

如何找到虚表的地址?

找到虚表地址的方法涉及使用一些底层的 C++ 技巧和对内存布局的理解。以下是一个简单的示例代码,用于展示如何找到一个类对象的虚表地址并打印虚表中的内容。

示例代码

#include

class Base {

public:

virtual void func1() {

std::cout << "Base::func1" << std::endl;

}

virtual void func2() {

std::cout << "Base::func2" << std::endl;

}

};

class Derived : public Base {

public:

void func1() override {

std::cout << "Derived::func1" << std::endl;

}

void func2() override {

std::cout << "Derived::func2" << std::endl;

}

};

typedef void(*FuncPtr)();

void printVTable(FuncPtr* vtable) {

std::cout << "Virtual Table Address: " << vtable << std::endl;

for (int i = 0; vtable[i] != nullptr; ++i) {

std::cout << "Function " << i << " address: " << vtable[i] << std::endl;

}

}

int main() {

Derived obj;

// 获取虚表指针的地址

FuncPtr* vptr = *reinterpret_cast(&obj);

printVTable(vptr);

return 0;

}

代码解释

类定义:

Base 类中定义了两个虚函数 func1 和 func2。

Derived 类继承自 Base 并重写了这两个虚函数。

函数指针类型:

typedef void(*FuncPtr)(); 定义了一个函数指针类型 FuncPtr,用于指向虚函数。

打印虚表函数:

printVTable 函数接受一个虚表指针数组并打印虚表地址和其中每个函数的地址。

main 函数:

创建一个 Derived 类对象 obj。

使用 reinterpret_cast 将对象的地址转换为虚表指针数组的指针,从而获取虚表地址。

调用 printVTable 函数打印虚表内容。

多态是什么?

多态性是面向对象编程的一种特性,它允许通过基类指针或引用在运行时调用不同子类的重写方法。多态性使得代码更灵活和可扩展。

如何使用多态?

通过基类指针或引用指向子类对象,并调用基类中的虚函数。在运行时,根据实际指向的对象类型调用相应的重写方法。

#include

class Animal {

public:

virtual void sound() {

std::cout << "Some generic animal sound" << std::endl;

}

};

class Dog : public Animal {

public:

void sound() override {

std::cout << "Bark" << std::endl;

}

};

class Cat : public Animal {

public:

void sound() override {

std::cout << "Meow" << std::endl;

}

};

void makeSound(Animal* animal) {

animal->sound(); // 根据实际对象类型调用相应的 sound 方法

}

int main() {

Dog dog;

Cat cat;

makeSound(&dog); // 输出 Bark

makeSound(&cat); // 输出 Meow

return 0;

}

为什么要使用多态?

代码复用:多态性允许通过统一的接口调用不同子类的方法,提高代码的复用性和可维护性。

灵活性:多态性使得程序在运行时根据实际情况调用不同的方法,提高了代码的灵活性和扩展性。

可扩展性:可以在不修改已有代码的情况下添加新的子类,并在运行时通过多态性使用它们。

多态遇到的所有情况

通过基类指针或引用调用子类方法:

使用基类指针或引用指向子类对象,调用虚函数实现多态性。

对象切片问题:

如果将子类对象赋值给基类对象,会发生对象切片问题,子类的附加属性和方法会被切掉。

Base baseObj = derivedObj; // 对象切片,丢失 Derived 部分

多重继承中的多态:

C++ 支持多重继承,可以通过虚继承解决多重继承中的菱形继承问题,使多态性依然有效。

虚析构函数:

在有多态的情况下,基类通常需要一个虚析构函数,以确保通过基类指针删除派生类对象时,正确调用派生类的析构函数。

class Base {

public:

virtual ~Base() {

std::cout << "Base destructor" << std::endl;

}

};

class Derived : public Base {

public:

~Derived() {

std::cout << "Derived destructor" << std::endl;

}

};

int main() {

Base* ptr = new Derived();

delete ptr; // 正确调用 Derived 的析构函数

return 0;

}

总结

虚函数和多态是 C++ 中面向对象编程的重要特性,通过虚函数可以实现多态性,使得代码更加灵活和可扩展。虚函数允许在运行时通过基类指针或引用调用子类的重写方法,而纯虚函数则强制派生类实现某些方法,使得抽象类无法实例化。多态性使得程序可以在运行时根据实际对象类型调用相应的方法,提升了代码的复用性、灵活性和可扩展性。然而,在使用多态时,需要注意对象切片问题和虚析构函数的使用,以确保正确的对象管理和方法调用。掌握虚函数和多态的概念和应用,对于提高 C++ 编程水平和开发效率至关重要。

相关推荐

✅8款非常好用的烘焙APP
o365邮箱登录

✅8款非常好用的烘焙APP

📅 07-02 👁️ 1830
橘子平台免费游戏排行榜TOP推荐(2025)
o365邮箱登录

橘子平台免费游戏排行榜TOP推荐(2025)

📅 07-16 👁️ 9756
「梦幻西游」多久合区一次
365直播电视

「梦幻西游」多久合区一次

📅 07-04 👁️ 4921
金牌红娘第二季女主角为什么换人
o365邮箱登录

金牌红娘第二季女主角为什么换人

📅 07-14 👁️ 378
电脑注销后有什么后果
365直播电视

电脑注销后有什么后果

📅 07-05 👁️ 1025
飞智社区
365bet体育在线15

飞智社区

📅 07-16 👁️ 6806