c++多态,为何只能通过指针或引用实现


本文目的

 
  在c++的多态中,如果一个基类的虚函数被派生类重写,那么把基类的指针指向派生类,就能够通过基类调用被派生类重写的这个虚函数。(对于这点有疑问的,可以看我之前写的C++多态,虚函数,虚函数表,gcc下的单继承,多继承,虚继承的内存布局)。那么如果不用指针或引用会如何?

 class Animal{
      public:
          virtual void eat()
          {
          	cout<<"能吃什么就吃什么"<<endl;
          }
          virtual void run(){}
      private:
          int name;
 };
 class Dog  : public Animal{
      public:
          virtual void eat(){
          	cout<<"吃狗粮"<<endl;
          }
          virtual void shout(){}
      private:
      	int age;
 };
 int main()
 {
 	Dog dog;
 	Animal&animal1 = dog;
 	Animal* animal2 = new Dog();
 	Ainmal animal3 = dog;
 	
 	animal1.eat();
 	animal2->eat();
 	animal3.eat();
 }

在这里插入图片描述
不用指针或引用,我们直接取值时,就不能调用多态。那么为什么呢?


内存切割

 
  之所以能够多态是因为虚函数的内存布局,以之前代码为例,Dog类的内存布局在gcc下如图:
在这里插入图片描述
当我们执行以下代码时

	Animal&animal1 = dog;
 	Animal* animal2 = new Dog();

将会产生内存切割,图中Dog类的Animal部分被animal1和animal2得到。从而实现多态机制。

	Ainmal animal3 = dog;

  那么为什么 这样调用就不能通过内存切割实现多态呢?

我在《深度探索C++对象模型》中找到了答案:

  “一个pointer或一个reference之所以支持多态,是因为它们并不引发内存任何“与类型有关的内存委托操作; 会受到改变的。只有它们所指向内存的大小和解释方式 而已”

对这句话解释就是:

  1. 指针和引用类型只是要求了基地址和这种指针所指对象的内存大小,与对象的类型无关,相当于把指向的内存解释成指针或引用的类型。
  2. 而把一个派生类对象直接赋值给基类对象,就牵扯到对象的类型问题,编译器就会回避之前的的虚机制。从而无法实现多态。

尾语

  《深度探索C++对象模型》有很多东西都比较解惑,不多第一遍读的时候,会有很多疑惑,也会遗漏很多细节,不过多读几遍,感觉就会很不一样,之前存在的问题,可能都会被解决。

以上

<think>嗯,用户问的是哪种多态是通过虚函数和指针引用实现的。首先我得回忆一下多态类型多态一般分为编译时多态和运行时多态。编译时多态比如函数重载和模板,而运行时多态应该就是通过虚函数实现的。 那问题中提到虚函数和指针引用,应该是在问运行时多态的具体实现方式。我记得在C++中,虚函数表(vtable)是实现运行时多态的关键。当类有虚函数时,编译器会为这个类生成虚函数表,对象中会有指向这个表的指针。当通过基类的指针引用调用虚函数时,实际调用的是派生类重写的函数,这就是动态绑定。 所以用户可能是在区分静态多态和动态多态。静态多态在编译期确定,比如模板特化和函数重载;而动态多态是在运行时通过虚函数机制确定调用的具体函数。这里的关键点确实是虚函数的使用以及必须通过基类指针引用来触发动态绑定。 另外,用户可能想知道为什么必须用指针引用。因为如果直接使用对象而不是指针引用,会发生对象切片,派生类的部分会被切割,无法正确调用到派生类的方法,这样多态就无法实现。所以必须通过指针引用来维持对象的完整信息,确保正确调用虚函数。 总结下来,答案应该是运行时多态者称为动态多态,通过虚函数和基类指针引用实现。需要解释虚函数的作用,动态绑定的过程,以及指针/引用的必要性,避免对象切片的问题。</think>在C++中,**通过虚函数结合基类指针引用实现多态属于运行时多态(动态多态)**,它是面向对象编程中多态性的核心实现方式。以下是其关键点: --- ### **核心机制** 1. **虚函数(Virtual Function)** - 在基类中用 `virtual` 关键字声明虚函数,派生类通过重写(override)该函数实现不同行为。 - 例如: ```cpp class Animal { public: virtual void speak() { cout << "Animal sound" << endl; } }; class Dog : public Animal { public: void speak() override { cout << "Woof!" << endl; } // 重写虚函数 }; ``` 2. **基类指针/引用** - 通过基类指针引用指向派生类对象时,调用虚函数会触发动态绑定: ```cpp Animal* ptr = new Dog(); ptr->speak(); // 输出 "Woof!"(调用的是Dog的speak) ``` --- ### **为什么需要指针引用?** - **对象切片问题**:若直接使用基类对象(而非指针/引用)存储派生类对象,派生类的特有部分会被“截断”,无法调用正确的函数。 - **动态绑定**:只有通过指针引用,编译器才能生成运行时查虚函数表(vtable)的代码,实现多态。 --- ### **实现原理** 1. **虚函数表(vtable)** - 每个包含虚函数的类会隐式生成一个虚函数表,存储该类虚函数的地址。 - 派生类的虚函数表会覆盖基类对应的函数地址。 2. **虚函数指针(vptr)** - 每个对象内部隐含一个指向其类的虚函数表的指针(vptr)。 - 调用虚函数时,通过 `vptr` 找到 `vtable`,再根据函数偏移量调用实际实现的函数。 --- ### **对比其他多态** - **编译时多态(静态多态)**:如函数重载、模板,在编译期确定具体调用。 - **运行时多态(动态多态)**:通过虚函数和指针/引用实现,运行期间动态解析。 --- ### **总结** 运行时多态通过以下组合实现: 1. **虚函数**声明和重写 2. **基类指针引用**指向派生类对象 3. **虚函数表**动态绑定实际调用的函数 这种机制使得程序能够以统一的接口处理不同派生类对象,增强代码扩展性和灵活性。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值