对C++多态学习的一点感受

本文探讨了C++中多态的概念及其应用,包括重载、强制、包含和参数多态,并介绍了静态联编和动态联编的区别。通过具体代码示例展示了如何实现运算符重载。
对C++多态学习的一点感受
原创:郭萌 2002年9月3日

多态考虑的是类与类之间的层次关系以及类自身内部特定成员函数之间的关系问题,是解决功能和行为的再抽象问题。多态是指类中具有相似功能的不同函数是用同一个名称来实现,从而可以使用相同的调用方式来调用这些具有不同功能的同名函数。这也是人类思维方式的一种直接模拟,比如一个对象中有很多求两个数最大值的行为,虽然可以针对不同的数据类型,写很多不同名称的函数来实现,但事实上,它们的功能几乎完全相同。这时,就可以利用多态的特征,用统一的标识来完成这些功能。这样,就可以达到类的行为的再抽象,进而统一标识,减少程序中标识符的个数。

面向对象的多态性可以严格的分为四类:重载多态,强制多态,包含多态,和参数多态,前面两种统称为专用多态,而后面两种也称为通用多态。

包含多态是研究类族中定义于不同类中的同名成员函数的多态行为,主要是通过虚函数来实现。参数多态与类属(类模板)相关联,类属是一个可以参数化的模板,其中包含的操作所涉及的类型必须用类型参数实例化。这样,由类模板实例化的各类都具有相同的操作,而操作对象的类型却各不相同。

多态从实现的角度来讲可以划分为两类,编译时的多态和运行时的多态。前者是在编译的过程中确定了同名操作的具体操作对象,而后者则是在程序运行过程中才动态的确定操作所针对的具体对象。这种确定操作的具体对象的过程就是联编,也有的文献成为编联,束定或绑定。联编是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程;用面向对象的术语讲,就是把一条消息和一个对象的方法相结合的过程。按照联编进行的阶段的不同,可以分为两种不同的联编方法:静态联编和动态联编,这两种联编过程分别对应着多态的两种实现方式。

联编工作在编译连接阶段完成的情况成为静态联编。因为联编过程外程序开始执行之前进行的,因此有时也称为早期联编或前联编。在编译,连接过程中,系统就可以根据类型匹配等特征确定程序中操作调用与执行该操作代码的关系,其确定了某一个同名标识到底是要调用那一段程序代码。有些多态类型,其同名操作的具体对象能够在编译,连接阶段确定,通过静态联编解决,比如重载,强制和参数多态等。

和静态联编相对应,联编工作在程序运行阶段完成的情况称为动态联编,也称为晚期联编或后联编。在编译,连接过程中无法解决的联编问题,要等到程序开始运行之后再来确定,包含多态的操作对象的确定就是通过动态联编完成的。

下面我们们看看重载。

我们先看一个例子:

#include <iostream>
using namespace std;

class complex
{
public:
  complex(double r=0.0, double i=0.0) {real=r;imag=I;}
  complex operator +(complex c2); //运算符“+”重载成员函数
  complex operator -(complex c2); //运算符“+”重载成员函数
  void display();
private:
  double real;
  double imag;
};

complex complex::operator +(complex c2) //重载运算符函数实现
{
  complex c;
  c.real=c2.real+real;
  c.imag=c2.imag+imag;
  return complex(c.real,c.imag);
}

complex complex::operator –(complex c2) //重载运算符函数实现
{
  complex c;
  c.real=real-c2.real;
  c.imag=imag-c2.imag;
  return complex(c.real,c.imag);
}

void complex::display()
{
  cout<<real<<imag;<<endl;
}

void main()
{
  complex c1(5,4),c2(2,10),c3;
  cout<<"c1="; c1.display();
  cout<<"c2="; c2.display();
  c3=c1-c2; //使用重载函数符完成复数减法
  cout<<"c3-c2=";
  c3.display();
  c3=c1+c2; //使用重载函数符完成复数减法
  cout<<"c3+c2=";
  c3.display();
}

在本例中,我们把复数的加减法这样的运算重载为负数类的成员函数。可以看出,除了在函数声明及实现的时候使用了关键字operator之外,运算符重载成员函数与类的普通成员函数没有什么区别。在使用的时候,可以直接通过运算符,操作数的方式来完成函数调用,这时,运算符“+,-”原有的功能都不变,对整型数,浮点数等基本类型数据的运算仍然遵循C++预定义的规则,同时添加了新的针对复数运算的功能。“+”这个运算符,作用于不同的对象上,就会导致完全不同的操作行为,具有了更广泛的多态特征。

虚函数是动态联编的基础,属于包含多态类型。虚函数是非静态的成员函数,虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

根据数值兼容性规则,可以使用派生来的对象代替基类对象。如果用基类类型的指针指向派生类,就可以通过这个指针访问到的之能是基类的同名成员;而如果将它设置为虚函数,则是用基类类型的指针就可以访问到该指针正在指向的派生类的同名函数。这样,通过基类类型的指针,就可以是属于不同派生类的不同对象产生不同的行为,从而实现了运行过程的多态。

 
<think>我们正在讨论C++多态特性。根据引用内容,多态的实现原理主要涉及虚函数和虚函数表(vtable)。使用方法是在基类函数前加virtual关键字,在派生类中重写该函数,通过基类指针或引用调用时会根据实际对象类型调用相应函数。 首先,我们需要明确多态分为静态多态(编译时多态,如函数重载)和动态多态(运行时多态,即虚函数机制)。用户问题主要关注动态多态。 原理部分: 1. 当类中有虚函数时,编译器会为该类生成一个虚函数表(vtable),表中存放虚函数的函数指针。 2. 每个对象在创建时,会在其内存布局的开头(通常)加入一个指向虚函数表的指针(vptr)。 3. 当通过基类指针或引用调用虚函数时,程序会根据vptr找到虚函数表,然后根据函数在表中的偏移量找到实际要调用的函数地址。 使用方法: 1. 在基类中声明虚函数(使用virtual关键字)。 2. 在派生类中重写(override)该虚函数(函数签名相同)。 3. 通过基类指针或引用来指向派生类对象,并调用虚函数,此时会调用派生类中的函数。 注意:构造函数不能是虚函数,而析构函数通常声明为虚函数(特别是基类),以确保正确释放派生类资源。 下面我们用代码示例来说明: 首先,定义一个基类Animal,其中有一个虚函数speak: ```cpp #include <iostream> using namespace std; class Animal { public: virtual void speak() { cout << "Animal speaks." << endl; } // 虚析构函数,确保正确释放资源 virtual ~Animal() {} }; class Dog : public Animal { public: void speak() override { // override关键字(C++11)表示重写,可加可不加,但推荐使用 cout << "Dog barks." << endl; } }; class Cat : public Animal { public: void speak() override { cout << "Cat meows." << endl; } }; int main() { Animal* animal1 = new Dog(); Animal* animal2 = new Cat(); animal1->speak(); // 输出:Dog barks. animal2->speak(); // 输出:Cat meows. delete animal1; delete animal2; return 0; } ``` 在上面的例子中,Animal类中的speak函数被声明为虚函数。当我们通过基类指针(Animal*)调用speak时,实际调用的是指针所指向的对象的实际类型(Dog或Cat)中的speak函数。 关于虚函数表(vtable)和虚指针(vptr): 每个有虚函数的类(或从有虚函数的类派生)都有一个虚函数表,该表在编译时创建。每个对象在构造时,其虚指针被初始指向相应的虚函数表。当调用虚函数时,通过虚指针找到虚函数表,再通过偏移量找到正确的函数地址。 注意:多态只能通过指针或引用来实现,直接使用对象不会发生多态(因为对象类型在编译时确定)。 另外,引用[4]提到MFC库使用消息映射机制而不是虚函数,这是为了避免每个类都拥有虚表带来的空间开销。但在一般情况下,虚函数机制是C++实现多态的标准方式。 最后,我们总结一下: 1. 动态多态通过虚函数实现,在运行时根据对象类型确定调用的函数。 2. 虚函数表是实现动态多态的关键,每个类一个,存放虚函数地址。 3. 每个对象有一个指向虚函数表的指针(vptr)。 4. 使用基类指针或引用调用虚函数时,通过vptr找到实际类的虚函数表,从而调用正确的函数。 相关问题: 1. C++中虚函数表是如何创建的? 2. 为什么构造函数不能是虚函数? 3. 虚析构函数的作用是什么? 4. 多态在什么情况下不会发生? 5. C++11中的override和final关键字有什么作用?</think>### C++多态的实现原理及使用方法 #### 一、多态的实现原理 C++多态的核心机制是**虚函数表(vtable)** 和 **虚表指针(vptr)**,其工作原理如下: 1. **虚函数表(vtable)** 当类中包含虚函数时,编译器会为该类生成一个虚函数表: - 表中存储该类所有虚函数的入口地址 - 每个有虚函数的类拥有独立的虚函数表 - 示例:`Animal` 类的虚函数表包含 `virtual void speak()` 的地址 2. **虚表指针(vptr)** 每个对象在创建时: - 编译器隐式添加一个指向vtable的指针(vptr) - vptr通常位于对象内存布局的首部 - 大小通常为4字节(32位系统)或8字节(64位系统) 3. **动态绑定过程** 当通过基类指针/引用调用虚函数时: ```cpp Animal* animal = new Dog(); animal->speak(); // 动态绑定 ``` 执行流程: - 通过对象的vptr找到对应的虚函数表 - 在虚函数表中查找函数偏移位置 - 调用实际指向的派生类函数[^3] $$ \text{调用过程} : \text{对象} \xrightarrow{\text{vptr}} \text{vtable} \xrightarrow{\text{偏移量}} \text{实际函数地址} $$ #### 二、多态的使用方法 实现多态需要三个关键步骤: 1. **基类声明虚函数** 使用 `virtual` 关键字声明接口函数: ```cpp class Animal { public: virtual void speak() { // 虚函数声明 cout << "Animal sound" << endl; } virtual ~Animal() {} // 虚析构函数(必需!) }; ``` 2. **派生类重写虚函数** 派生类使用相同签名覆盖函数: ```cpp class Dog : public Animal { public: void speak() override { // C++11推荐使用override cout << "Woof!" << endl; } }; ``` 3. **通过基类指针/引用调用** 运行时根据实际对象类型决定调用: ```cpp int main() { Animal* animals[2]; animals[0] = new Dog(); // 派生类对象 animals[1] = new Cat(); // 另一个派生类 animals[0]->speak(); // 输出"Woof!" (动态绑定) animals[1]->speak(); // 输出"Meow!" // 必须通过基类指针删除对象 delete animals[0]; delete animals[1]; } ``` #### 三、关键注意事项 1. **虚析构函数** $$ \text{基类析构函数必须声明为virtual} $$ 避免派生类资源泄漏: ```cpp Base* obj = new Derived(); delete obj; // 若基类析构非虚,仅调用~Base() ``` 2. **override关键字(C++11)** 显式标记重写,避免隐藏错误: ```cpp class Derived : public Base { void foo() override; // 编译检查是否成功重写 }; ``` 3. **final关键字(C++11)** 禁止进一步重写: ```cpp class Base { virtual void foo() final; // 禁止派生类重写 }; ``` 4. **纯虚函数与抽象类** 定义接口规范: ```cpp class Shape { // 抽象类 public: virtual double area() = 0; // 纯虚函数 }; ``` #### 四、多态类型对比 | 类型 | 实现方式 | 绑定时机 | 示例 | |------------|---------------|----------|--------------------| | 动态多态 | 虚函数 | 运行时 | `virtual void f()` | | 静态多态 | 函数重载/模板 | 编译时 | `template <typename T>` | > ⚠️ **重要限制**:构造函数不能是虚函数,但虚构造函数可通过工厂模式模拟[^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值