虚函数进行方法重定义的写法及其调用关系

本文介绍了虚函数的定义及其实现动态多态性的方法。包括虚函数的正确使用方式、不可作为虚函数的特殊类型函数、虚函数的调用过程及虚表的构建原理。通过示例代码展示了不同类层次结构下虚函数的具体调用行为。

虚函数进行方法重定义的写法及其调用关系

1、虚函数进行方法重定义的写法

虚函数的作用是在派生类中进行函数的重写,或者说是把基类中继承来的同名同形参同返回类型的虚函数在派生类中进行重写,其中定义虚函数的返回类型、函数名和形参列表必须相同,否则会认为是函数的重载。但有唯一例外的写法:

class Object
{
	public:
	virtual Object* get(){return this;}
};
class Test:public Object
{
	public:
	virtual Test* get(){return this;}
};

其中,静态函数、友元函数、内联函数、构造函数、拷贝构造函数、移动构造函数都不能定义成虚函数,因为静态、友元函数没有this指针、内联函数不能调用、构造函数是用来放置虚表指针。析构函数可以定义成虚函数,因为虚函数是用来重置虚表指针。
只有类的成员函数才能定义成虚函数,因为虚函数只适用于有继承关系的类对象。
如果定义的虚函数要实现动态多态性时,必须使用类的对象指针或引用来调用。
当在类体中声明、在类外定义虚函数时,要在类体中声明,在类外定义时不能定义再加virtual。

2、虚函数的调用
在调用虚函数时,系统会生成一个对象对应的所有虚函数的函数地址构成的虚表,在调用时会检查指针的来源对象,找到对应的虚表,找出要调用的函数并调用。
虚表的构建方法:

#include<iostream>
using namespace std;
class Object
{
	private:
		int val;
	public:
		Object(int x=0):val(x){}
		virtual void fun()
		{
			cout<<"Object::fun"<<endl;
		}
		virtual void show()
		{
			cout<<"Object::show"<<endl;
		}
		virtual void print()
		{
			cout<<"Object::print"<<endl;
		}
};
class Base:public Object
{
	private:
		int num;
	public :
		Base(int x=0):num(x),Object(x+10) {}
		virtual void fun()
		{
			cout<<"Base::fun"<<endl;
		}
		virtual void show()
		{
			cout<<"Base::show"<<endl;
		}
		virtual void menu()
		{
			cout<<"Base::menu"<<endl;
		} 
};
class Test:public Base
{
	private:
		int count;
	public:
		Test(int x=0):count(x),Base(x+10) {}
		virtual void fun()
		{
			cout<<"Test::fun"<<endl;
		}
		virtual void menu()
		{
			cout<<"Test::menu"<<endl;
		}
		virtual void print()
		{
			cout<<"Test::print"<<endl;
		} 
};

void Vshow(Object *obj)
{
	obj->fun();
	obj->show();
	obj->print();
}

int main()
{
	Test t;
	Base b;
	Object o;
	Vshow(&t);
	Vshow(&b);
	Vshow(&o);
	return 0;
}

Object类没有继承其他类,它的虚表就是类体里的虚函数
|Object vtable |
|–|–|
| void Object::fun |
|void Object::show|
|void Object::print|
Base类继承了Object类,虚表也会继承过来,如果有同名虚函数,就要把Object的函数换成Base的函数
| Base vtable |
|–|–|
| void Base::fun |
|void Base::show|
|void Object::print|
|void Base::menu|
Test类继承了Base类,虚表也继承过来,如果有同名虚函数,就要把Base的函数换成Test的函数
| Test vtable |
|–|–|
| void Test::fun |
|void Base::show|
|void Test::print|
|void Test::menu|

当取Test类的对象t的地址传到Vshow,调用虚函数时系统会查找指针来源对象的虚表,找到对应的函数地址,就能调用对应的虚函数。Vshow函数的形参类型是Object类型的指针,所以函数体内通过(Object*)能调用的虚函数只能是Object虚表里的函数,当调用不属于Object虚表里的虚函数时编译器会报错。
在这里插入图片描述
当把以上代码作出一点小改动,在Test类内定义一个exam函数,exam用this指针调用虚函数show时,系统会查虚表调用Base类里的虚函数show,再在Base类里的show调用menu函数时,不会调用Base类里的show,而是取调Test类的show。由此可见,只有不管指针如何传递,系统调用虚函数时只会调用指针来源的那个对象的虚表。

#include<iostream>
using namespace std;
class Object
{
	private:
		int val;
	public:
		Object(int x=0):val(x){}
		virtual void fun()
		{
			cout<<"Object::fun"<<endl;
		}
		virtual void show()
		{
			cout<<"Object::show"<<endl;
		}
		virtual void print()
		{
			cout<<"Object::print"<<endl;
		}
};
class Base:public Object
{
	private:
		int num;
	public :
		Base(int x=0):num(x),Object(x+10) {}
		virtual void fun()
		{
			cout<<"Base::fun"<<endl;
		}
		virtual void show()
		{
			cout<<"Base::show"<<endl;
			this->menu();
		}
		virtual void menu()
		{
			cout<<"Base::menu"<<endl;
		} 
};
class Test:public Base
{
	private:
		int count;
	public:
		Test(int x=0):count(x),Base(x+10) {}
		virtual void fun()
		{
			cout<<"Test::fun"<<endl;
		}
		virtual void menu()
		{
			cout<<"Test::menu"<<endl;
		}
		virtual void print()
		{
			cout<<"Test::print"<<endl;
		} 
		void exam(){this->show();}
};

int main()
{
	Test t(10);
	t.exam();
	return 0;
}
<think> 在用户的问题中,用户询问了父类调用子类成员函数的实现方法。根据提供的引用内容,有几个方法可以实现这一点。用户之前的问题历史显示他们已经在关注这个问题,现在需要更详细的解释和方法总结。 首先,引用[1]和[2]提到了使用函数指针的方法。引用[1]中的代码示例展示了在基类中定义一个函数指针成员,子类在初始化时设置该指针指向自己的成员函数。这样,基类的函数可以通过指针调用子类的函数。引用[4]中的代码也是类似的思路,只是在基类中将函数指针定义为protected,并在子类构造函数中初始化。 其次,引用[3]提到了使用虚函数方法。这是更常见的面向对象设计方法。如果子类重写了基类的虚函数,那么通过基类指针指向子类对象时,调用虚函数会触发子类的实现。此外,引用[3]还提到了强制类型转换的方法:将基类指针转换为子类指针,然后调用子类的非虚函数。但这种方法不安全,因为如果基类指针实际指向的不是该子类对象,会导致未定义行为。 现在需要整理这些方法,分点说明,确保逻辑清晰。同时,要根据引用内容正确标注引用来源。用户要求回答中引用的段落末尾添加引用标识,如[^1]。 还要注意用户提到的“系统级指令”,特别是关于LaTeX格式的要求。但在当前问题中,可能不需要使用数学表达式,所以主要关注代码块和文本格式即可。用户要求所有行内数学表达式用$...$,但这里可能不涉及。 此外,用户要求生成相关问题。需要基于当前主题提出几个相关问题,比如各种方法的优缺点、适用场景等。 最后,确保使用中文回答,结构清晰。可以分两部分:先详细解释方法,然后给出相关问题。 </think> 在C++中实现父类调用子类成员函数,主要有以下三种方法,每种方法各有适用场景和注意事项: ### 一、函数指针法(非虚函数) **实现原理** 在父类中定义函数指针成员,子类初始化时将该指针指向自身成员函数。父类通过函数指针间接调用子类方法。 ```cpp class Base { public: typedef void (Base::*pfn)(); // 定义函数指针类型 void CallFunction() { if (fn) (this->*fn)(); // 通过指针调用子类函数 [^1] } pfn fn = nullptr; // 初始化指针 }; class Derived : public Base { public: void Init() { fn = static_cast<pfn>(&Derived::fun); // 绑定子类函数 } void fun() { cout << "子类函数被父类调用!" << endl; } }; // 使用示例 Derived d; d.Init(); d.CallFunction(); // 输出:子类函数被父类调用! [^1][^4] ``` **特点** - ✅ 避免虚函数开销 - ❌ 需手动绑定函数指针 - ⚠️ 需确保函数签名完全匹配 ### 二、虚函数写法(标准面向对象方法) **实现原理** 父类声明虚函数,子类重写该函数,通过基类指针调用时自动触发子类实现。 ```cpp class Base { public: virtual void Execute() { // 声明虚函数 cout << "默认实现" << endl; } }; class Derived : public Base { public: void Execute() override { // 重写父类虚函数 cout << "子类重写函数" << endl; } }; // 使用示例 Base* obj = new Derived(); obj->Execute(); // 输出:子类重写函数 [^3] ``` **特点** - ✅ 符合多态设计原则 - ✅ 无需手动绑定 - ❌ 虚函数表带来轻微性能开销 ### 三、强制类型转换法(风险较高) **实现原理** 将父类指针强制转换为子类指针后调用子类方法。 ```cpp class Base { /* 空基类 */ }; class Derived : public Base { public: void ChildMethod() { cout << "强制转换调用" << endl; } }; // 使用示例 Base* basePtr = new Derived(); static_cast<Derived*>(basePtr)->ChildMethod(); // 输出:强制转换调用 [^3] ``` **特点** - ✅ 可调用虚函数 - ❌ 高风险:若对象非实际子类类型会导致未定义行为 - ⚠️ 破坏类型安全机制,不推荐常规使用 ### 方法对比总结 | **方法** | **多态支持** | **安全性** | **性能** | |------------------|-------------|-----------|---------------| | 函数指针法 | ❌ | 中 | 无虚函数开销 | | 虚函数写法 | ✅ | 高 | 有虚表开销 | | 强制类型转换法 | ❌ | 低 | 直接调用 | > **设计建议**:优先使用虚函数写法实现多态,函数指针法适用于性能敏感场景,强制转换仅在明确类型安全时使用[^1][^3][^4]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_200_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值