C++之重载、覆盖、隐藏、虚函数

C++的重载、覆盖、隐藏、虚函数、纯虚函数等概念,都体现于多态的体系中。

本文提及的 父类 即 基类,子类 即 派生类。


重载:
先看demo:

#include <stdio.h>

class OverLoadClass{
	public:
		void run(int i) {printf("int: %d\n", i);}
		void run(float i) {printf("float: %f\n", i);}
//		void run(float j) {printf("float: %f\n", j);} // error
		void run(double i) {printf("double: %f\n", i);}
//		double run(double i) {return i;} // error
		void run(int i, double j) {printf("int: %d, double: %f\n", i, j);}
		void run(double j, int i) {printf("double: %f, int: %d\n", j, i);}
};


void TestOverLoad() {
	OverLoadClass a;
	int var1 = 1;
	float var2 = 3.14;
	double var3 = 3.14;
	a.run(var1); // int: 1
	a.run(var2); // float: 3.140000
	a.run(var3); // double: 3.140000
	a.run(3.14); // double: 3.140000
	a.run(var1, var3); // int: 1, double: 3.140000
	a.run(var3, var1); // double: 3.140000, int: 1
}

int main() {
	TestOverLoad(); return 0;
}

以上是重载的应用场景:在同一个作用域内(所有的run函数都在OverLoadClass类),相同的函数名,若有不同的参数类型、顺序,则在调用时会选择最适合的函数来调用。
注意: 重载只对形式参数的类型和类型的顺序敏感,与形式参数的参数名(如demo里定义的6、7行)和函数的返回类型(如demo里定义的8、9行)是无关的。

另外从demo也能看出,floatdouble类型默认输出小数点后6位。


重载基本上over了,再看demo:

class BaseClass{
	public:
		void run1() {printf("BaseClass's run1\n");}
		virtual void run2() {printf("BaseClass's run2\n");}
		
};

class SubClass: public BaseClass{
	public:
		void run1() {printf("SubClass's run1\n");}
		void run2() {printf("SubClass's run2\n");}
};

void Test() {
	BaseClass *a;
	SubClass sub;
	a = &sub;
	// part 1 静态链接
	a->run1(); // BaseClass's run1
	// part 2 覆盖
	a->run2(); // SubClass's run2
	// part 3 隐藏
	sub.run1(); // SubClass's run1
	// part 4 覆盖
	sub.run2(); // SubClass's run2
	// part 5
	SubClass *subp = &sub;
	subp->run1(); // SubClass's run1
	subp->run2();  // SubClass's run2
}

int main() {
	Test(); return 0;
}

这里的5个part分别展示了不同的知识点,Let’s go!


	// part 1 静态链接
	a->run1(); // BaseClass's run1
	// part 5
	SubClass *subp = &sub;
	subp->run1(); // SubClass's run1
	subp->run2();  // SubClass's run2

首先part 1和part 5作对比,a是父类类型的指针,subp是子类类型的指针,并都指向了子类实例sub的内存地址&sub。通过->操作符调用所指内存的run1函数,发现两者的结果并不相同。
原因是a调用的run1被编译器设置为父类的版本,也就是静态链接,函数调用在程序执行之前就准备好了。而subp指针通过地址调用run1其实相当于sub变量直接调用run1,就会隐藏父类的同名函数(父类函数还存在),而直接调用子类的函数体(没关系,隐藏的概念稍后会详细解释)。

如果不能让父类的指针调用子类实现的函数体,继承多态等编程设计将会大打折扣。那么有没有办法解决呢?
请看part 2。


	// part 2 覆盖
	a->run2(); // SubClass's run2

part 2中,父类类型的指针a指向子类sub的内存地址&sub,并调用函数run2,发现调用的是子类实现的函数体内容。
因为在父类定义时,在run2函数上加了virtual修饰符,这个函数就会被定义为虚函数。加上这个修饰符,编译器就不会静态链接到父类定义的函数体,而是在程序执行过程中调用指针指向的函数(子类实例subrun2),相对地,这种方式叫动态链接
同时,也称作子类函数覆盖了父类函数(父类函数被覆盖,不存在了)。
覆盖的条件如下:

  1. 父类、子类的函数名、参数都相同;
  2. 父类函数带有virtual修饰符。

	// part 1 静态链接
	a->run1(); // BaseClass's run1
	// part 3 隐藏
	sub.run1(); // SubClass's run1

对比part 1和part 3,通过子类实例sub直接调用run1函数,实现的是子类的函数体。原因是子类的run1隐藏了父类的同名函数run1(父类函数还存在)。
隐藏的情况如下:

  1. 父类、子类的函数名相同,但参数不同,则父类函数被隐藏
  2. 父类、子类的函数名、参数都相同,但父类函数无virtual修饰符,则被隐藏

综上,带virtual,且函数名、参数类型相同、返回类型相同的是覆盖,否则是隐藏

顾名思义,覆盖会让程序无法再调用父类的虚函数,但隐藏只是借助了名称的查找方式让程序调用了子类的函数,父类的函数依然存在的。那么该如何调用父类的函数呢?


调用被隐藏的函数:

void func1() {printf("external func1\n");}

class ClassA{
	public:
		void func1() {printf("ClassA's func1\n");}
};

class ClassB: public ClassA{
	public:
		void func1() {
			printf("ClassB's func1\n");
			ClassA::func1();
			::func1();
		}
};

void TestHiding() {
	ClassA a;
	ClassB b;
	func1(); // external func1
	printf("-----\n");
	a.func1(); // ClassA's func1
	printf("-----\n");
	b.func1(); // ClassB's func1
			   // ClassA's func1
			   // external func1
	printf("-----\n");
}

int main() {
	TestHiding(); return 0;
}

案例中外部函数func1被ClassA隐藏了,ClassA的func1也被ClassB隐藏了。但原函数依然存在,调用方式就是通过作用域进行调用。


	// part 3 隐藏
	sub.run1(); // SubClass's run1
	// part 4 覆盖
	sub.run2(); // SubClass's run2
	// part 5
	SubClass *subp = &sub;
	subp->run1(); // SubClass's run1
	subp->run2();  // SubClass's run2

最后看part 3、part 4和part 5,通过子类类型的指针调用、以及通过子类变量直接调用,结果完全一致。


至于纯虚函数在demo中没有体现,但其实是虚函数的特殊情况。
虚函数可以在父类中定义函数体,可以被子类直接继承(不覆盖),也可以被子类覆盖。
纯虚函数则在父类中不进行任何函数体的定义,只声明了函数的名称、参数类型、返回类型,实现方式如下:

virtual void func(int, int) = 0;

把原本虚函数的函数体替换为= 0,就意味着在父类中声明了纯虚函数
继承了含纯虚函数的子类,必须覆盖所有父类的纯虚函数,才能实例化。否则编译器会报错。
在这里插入图片描述


总结

作用域函数名virtual形参类型/顺序返回类型
重载相同作用域相同可有可无不同可同可不同
隐藏不同作用域相同可有可无可同可不同可同可不同
覆盖不同作用域相同相同相同

参考资料:
菜鸟教程:C++多态
CSDN:C++ 重载、重写(覆盖)、隐藏的定义与区别

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值