C++:多态究竟是什么?为何能成为面向对象的重要手段之一?

C++学习——面向对象三大特性:封装、继承、多态 今天来更新面向更新的三大特性,分别是封装、继承、多态,是C++面试中常考也是非常重要的面试题之一,如果最近有春招的宝可以关注我噢,我会定时分享C++相关常考的知识与面试题哒! 阅读详情

前言

本篇博客基于VS2019X86环境下,后续关于多态原理相关验证都是基于vsX86环境,而虚表本质上是一个虚函数指针数组,在X86环境下VS编译器会在数组最后放一个unllptr!!

一、多态的概念

 多态指同一个行为,当不同的对象去完成时会产生出不同的状态。比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

二、多态的定义及实现

2.1 多态的构成条件

 多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。但在继承中要构成多态还有两个条件:

  1. 必须通过基类的指针或者引用调用虚函数。
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

比如Student继承了Person。Person对象买票全价,Student对象买票半价:

class Person
{
public:
	virtual void BuyTicket()//被virtual修饰的类成员函数称为虚函数
	{
		cout << "买票全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTicket()//对基类虚函数重写
	{
		cout << "买票半价" << endl;
	}
};

void Func(Person& people)
{
	people.BuyTicket();
}

int main()
{
	Person p;
	Func(p);//涉及赋值兼容规则
	Student s;
	Func(s);
	return 0;
}

【运行结果】:
在这里插入图片描述

2. 2 虚函数

 虚函数:即被virtual修饰的类成员函数称为虚函数。

class Person {
public:
	//BuyTicket()为虚函数
	virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

2.3 虚函数的重写

 虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

  • 派生类的虚函数在不加virtual关键字时,我们认为也构成重写。原因在于,我们认为虚函数的重写继承了基类虚函数的接口,在派生类中重写实现。
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
	/*注意:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因
	为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议
	这样使用*/
	/*void BuyTicket() { cout << "买票-半价" << endl; }*/
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Func(p);
	Func(s);
	return 0;
}

2.3.1 虚函数重写的例外1:协变(基类与派生类虚函数返回值类型不同)

 派生类重写基类虚函数时,与基类虚函数返回值类型可以不同,但必须是父子类关系的指针或者引用。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用,称为协变。

class A {};
class B : public A {};

class Person 
{
public:
	virtual A* f() { return new A; }
};

class Student : public Person 
{
public:
	virtual B* f() { return new B; }
};

2.3.2 虚函数重写的例外2:析构函数的重写(基类与派生类析构函数的名字不同)

 如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

【实例】:

class Person {
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};

class Student : public Person {
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函
//数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	
	delete p1;
	// delete p1编译器处理为: p1->destrutor() + operator delete(p1)

	delete p2;
	// delete p2编译器处理为: p2->destrutor() + operator delete(p2)
	return 0;
}

tips:大多数情况析构函数是不需要重写的。只有上述在上述情况下,如果普通析构函数,则p1、p2 delete时调用的都是Person类的析构。但实际上p2指向的是Student类对象,原则上因该调用Student类的析构函数。所以为了正确调用析构函数,需要将析构实现为虚函数并重写。(记住上述情况,只有这种情况下才需要重写析构函数)

2.4 C++11 override 和 final

 C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重写,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

2.4.1 final

final:修饰虚函数,表示该虚函数不能再被重写。

class Car
{
public:
	virtual void Drive() final {}
};

class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

2.4.2 override

override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Car {
public:
	virtual void Drive() {}
};

class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

2.5 重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述

三、抽象类

3.1 概念

 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,一般用于接口的定义。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数间接强制派生类重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
	virtual void Drive() = 0;//纯虚函数
};

class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};

class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};

void Test()
{
	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

3.2 接口继承和实现继承

 普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

四、虚函数及虚函数表

4.1 虚函数表

在了解虚函数前,我们先来看看这段代码中类对象的大小(X86环境下)

class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

int main()
{
	Base b1;
	cout << sizeof(b1) << endl;
	return 0;
}

【运行结果】:

在这里插入图片描述

 我们发现b的大小竟然是8bytes。为什么呢?这就不得不提到虚函数表了。
 原来Base对象中,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

在这里插入图片描述

4.2 虚函数重写覆盖虚表

我们再来看看下面这段代码:

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};

class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

int main()
{
	Base b;
	Derive d;
	return 0;
}

在这里插入图片描述
通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 派生类的虚表生成总结:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

4.3 虚函数存在哪的?虚表存在哪的?

 后续所有代码都是基于vsX86环境,而虚表本质上是一个虚函数指针数组,VS编译器会在数组最后放一个unllptr!!
虚函数存在哪的?虚表存在哪的?比较简单的方法就是将虚函数、虚表以及每个存储区间地址都打印出来进行比较。

比如:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int _a;
};

void func()
{
	cout << "void func()" << endl;
}

int main()
{
	Base b1;
	int a;
	int* a2 = new int;
	//const int a3 = 1;
	const char* s = "hello world";
	static int d4 = 0;

	printf("栈:%p\n", &a);
	printf("堆:%p\n", a2);
	printf("静态区:%p\n", &d4);
	//printf("代码段:%p\n", &a3);
	printf("代码段:%p\n", s);
	printf("虚表:%p\n", *(int*)(&b1));
	//在类域中的函数地址比较特殊,要加&
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n", func);
	return 0;
}

【运行结果】:
在这里插入图片描述
我们发现虚表和代码段的存储在一起,虚函数和普通函数存储在一起,而普通函数又是存在代码段的。

总结:

  1. 虚函数和普通函数以及虚表都是存在代码段的!!!

五、多态原理

5.1 多态调用过程及原理

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

在这里插入图片描述
多态实现过程:

  1. 观察下图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
  2. 观察下图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
  3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

5.2 多态运行时确定?

多态运行时确定还是编译时确定?我们接着看:
在这里插入图片描述

void Func(Person* p)
{
 p->BuyTicket();
}
int main()
{
 Person mike;
 Func(&mike);
 mike.BuyTicket();
    
 return 0;
}
// 以下汇编代码中跟你这个问题不相关的都被去掉了
void Func(Person* p)
{
...
 p->BuyTicket();
// p中存的是mike对象的指针,将p移动到eax中
001940DE  mov         eax,dword ptr [p]
// [eax]就是取eax值指向的内容,这里相当于把mike对象头4个字节(虚表指针)移动到了edx
001940E1  mov         edx,dword ptr [eax]
// [edx]就是取edx值指向的内容,这里相当于把虚表中的头4字节存的虚函数指针移动到了eax
00B823EE  mov         eax,dword ptr [edx]
// call eax中存虚函数的指针。这里可以看出满足多态的调用,不是在编译时确定的,是运行起来
以后到对象的中取找的。
001940EA  call        eax  
00头1940EC  cmp         esi,esp  
}
int main()
{
... 
// 首先BuyTicket虽然是虚函数,但是mike是对象,不满足多态的条件,所以这里是普通函数的调
用转换成地址时,是在编译时已经从符号表确认了函数的地址,直接call 地址
 mike.BuyTicket();
00195182  lea         ecx,[mike]
00195185  call        Person::BuyTicket (01914F6h)  
... 
}
  1. 满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。

5.3 动态绑定与静态绑定

  1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
  2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

六、单继承和多继承关系的虚函数表

6.1 单继承中的虚函数表

我们来看看的段代码:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
	
class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
	void func5() { cout << "Derive::func5" << endl; }
private:
	int b;
};
	
class X :public Derive {
public:
	virtual void func3() { cout << "X::func3" << endl; }
};

int main()
{
	Base b;
	Derive d;
	X x;
	return 0;
}

【监视窗口】:
在这里插入图片描述
在上述监视窗口中我们发现虚函数表中竟然只存有父类的虚函数以及相关虚函数的重写。也就是说新增的虚函数不会存储到虚表中!但真的如此吗?

真正原因在于:VS监视窗口是微软工程师处理过方便使用者观察的,但不一定是真实的!编译器的监视窗口故意隐藏了这些东西,也可以认为是他的一个小bug。要了解虚函数的地址是否一定会被放进类的虚函数,我们可以通过其真实内存,打印虚函数表来观察?

//将函数指针void (*)() 重定义为VFNC
typedef void (*VFUNC)();
//打印虚函数表
void printVFT(VFUNC* a)
{
	//由于VSX86环境下,编译器会在虚表结尾放一个nullptr,所以此处结束条件为a[i]!=0
	for (int i = 0; a[i] != 0; i++)
	{
		printf("[%d]->%p ", i, a[i]);
		//调用a[i]中存储的函数
		VFUNC f = a[i];
		f();
		//(*f)()
		cout << endl;
	}
}

int main()
{
	Base b;
// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数
//      指针的指针数组,这个数组最后面放了一个nullptr
// 1.先取b的地址,强转成一个int*的指针
// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。
// 4.虚表指针传递给PrintVTable进行打印虚表
// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最
//后面没有放nullptr,导致越界,这是编译器的问题。我们只需要点目录栏的-生成-清理解决方案,再编译就好了。
	printVFT((VFUNC*)(*((int*)(&b))));
	printf("----------------------------------\n");

	Derive d;
	printVFT((VFUNC*)(*(int*)(&d)));
	printf("----------------------------------\n");

	X x;
	printVFT((VFUNC*)(*(int*)(&x)));
	return 0;
}

【运行结果】:
在这里插入图片描述

总结:

1.我们发现实际上虚函数的地址一定会被放进类的虚函数表。

6.2 多继承中,子类新增虚函数存入那个虚表?

和6.1一样,我们只需要将虚表中存储的值都打印出来,即可确定这个问题答案!
【代码如下】:

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive :  public Base1, public Base2 {
public:
	virtual void func1() 
	{ 
		cout << "Derive::func1" << endl;
	}

	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

//打印虚函数
typedef void(*VFUNC)();
void printVFT(VFUNC* a)
{
	for (int i = 0; a[i] != 0; i++)
	{
		printf("[%d]->%p ", i, a[i]);
		VFUNC f = a[i];
		f();
	}
	cout << endl;
}

int main()
{
	Derive d;
	printVFT((VFUNC*)(*(int*)&d));
	printf("-----------------------------\n");
	//由于d对象中存有两张虚表,要打印第2张虚表,需要将&d偏移指向Base2开头,有如下几种方式
	//printvft((vfunc*)(*(int*)((char*)&d+sizeof(base1))));
	Base2* ptr = &d;
	printVFT((VFUNC*)(*(int*)ptr));
	return 0;
}

【运行结果】:
在这里插入图片描述
结论:

  1. 多继承中,子类新增虚函数存入到第一张虚表中!!

6.3 总结

  1. 虚函数的地址一定会被放进类的虚函数表。
  2. 多继承中,子类新增虚函数存入到第一张虚表中。
  3. 同一个类型共用同一张虚表,不同类型则可能存在多张虚表。(可以将虚表打印出来,各位自行验证)

七、菱形继承、菱形虚拟继承

实际中我们不建议设计出菱形继承及菱形虚拟继承,一方面太复杂容易出问题,另一方面这样的模型,访问基类成员有一定得性能损耗。所以博主就不介绍了。如果感兴趣,这里附上两篇相关文章:

  1. C++ 虚函数表解析
  2. C++ 对象的内存布局
Cesium 实战 28 - 三维视频融合(视频投影) Cesium 实战 27 - 三维视频融合(视频投影)。 阅读详情

相关推荐

FPGA防抄板实战:如何利用Xilinx芯片的“身份证”(DNA)实现你的加密锁?

本文深入探讨了如何利用Xilinx FPGA芯片的DNA(Device Native Authentication)功能实现硬件级加密保护,防止设计被抄袭。文章详细解析了DNA的物理实现、获取方式,并提供了从DNA到加密密钥的转换方法,以及构建商业级加密防护系统的实战方案。通过JTAG和原语两种读取方式的对比,帮助开发者选择最适合的方案。

weixin_42548829的博客 383

什么是多态?如何实现?只看这一篇就够了

1. 多态的概念 1.1 概念 多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。 2. 多态的定义及实现 2.1多态的构成条件 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。 那么在继承中要构成多态还有两个条件: 必须通过基类的指针或者引用调用虚函数 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 2.2 ...

海盗船长y的博客 13万+

频偏估计技术:从原理到5G应用的实战解析

本文深入解析了5G通信中的频偏问题及其核心技术。频偏如同调不准的旋钮,会导致信号失真与载波间干扰。文章详细拆解了整数倍与小数倍频偏估计两大核心方法,重点剖析了基于循环前缀和基于同步信号块的算法原理与优劣,并结合eMBB、mMTC、URLLC三大5G应用场景,提供了实战算法选择策略与参数调优避坑指南。

weixin_29218963的博客 262

什么是多态?

多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作。

buhuisuanfa的博客 1万+

维纳滤波和卡尔曼滤波

本文的主要内容是维纳滤波和卡尔曼滤波的介绍,包含匹配滤波器、维纳滤波器、维纳预测、卡尔曼滤波等内容。

西岸贤 9520

基于微信小程序的答题系统

学习视频:《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!5812)]《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

2401_83330193的博客 499

Java中什么是多态?多态的优势和劣势是什么?

面向对象的三大特征:封装性、继承性、多态性,多态性就是多态,多态是在封装的升华,可以说没有封装就么有多态。

qq_46906413的博客 1万+

【分布式系统学习】CAP原理详解

概述:互联网最重要任务莫过于海量数据处理,即大规模分布式系统,分布式是互联网的核心技术!!!如上所述,分布式系统是无法同时满足CAP三个特性的。1.Atomicity(原子性)一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。2.Consistency(一致性)在事务开始之前和事务结束以后,数据库的完整性没有被破坏。3.Isolation(隔离性)

大龄烤红薯的博客 5024

多态的概念以及详解

多态的概念以及摘要 对于多态所需的方法以及条件

weixin_57074076的博客 6794

Frida实战:如何逆向分析某电商App的doCommandNative函数(附完整Hook脚本)

本文详细介绍了如何使用Frida逆向分析电商App中的doCommandNative函数,涵盖动态注册识别、SO加固对抗及高级Hook脚本编写等实战技巧。通过完整Hook脚本示例,帮助安全研究人员深入理解JNICLibrary的关键函数逆向分析方法,提升移动应用安全研究能力。

weixin_29327977的博客 615

ESP32S3 Korvo2V3四通道AEC配置实战:基于AFE的回声消除优化

本文详细介绍了在ESP32S3 Korvo2V3平台上配置四通道AEC(回声消除)的实战经验。通过AFE算法优化和硬件连接调试,实现高质量回声消除,显著提升智能音箱和会议设备的语音交互体验。重点解析了四通道配置、数据格式对齐和参数调优等关键技术要点。

elastic6hunter的博客 932

【Java】Java中的多态

在Java中,多态是面向对象编程中的一个重要概念,它允许不同类型的对象对同一方法进行不同的实现。具体来说,多态性指的是通过父类的引用变量来引用子类的对象,从而实现对不同对象的统一操作。例如:狗和猫都是动物,动物共同的行为都有吃这个动作,而狗可以表现为啃骨头,猫则可以表现为吃老鼠。这就是多态的表现,即同一件事情,发生在不同对象的身上,就会产生不同的结果。在面向对象编程中,重写(Override)指的是子类重新定义和实现了从父类继承而来的方法,以改变方法的行为。

qq_61635026的博客 3万+

嵌入式铁头山羊stm32:一文总结

本文是一篇STM32嵌入式开发的综合知识总结,涵盖了STM32基础概念、GPIO、通信协议、外设驱动等核心内容。文章首先介绍了STM32的基本概念和型号含义,然后详细讲解了GPIO的8种工作模式及实验案例。接着重点阐述了USART、I2C、SPI三种通信协议的原理和实现方法,包括硬件连接、编程接口和实验应用。此外还介绍了OLED显示原理、中断系统、定时器、ADC转换等关键技术,每个模块都配有对应的实验案例和代码实现参考。文章内容全面系统,既包含理论知识,又提供实践指导,适合作为STM32开发的快速参考手册。

古泽汉书的博客 2785

C++:多态(面向对象的主要手段之一)

本文详细讲解了C++中的多态概念及其实现原理。主要内容包括:1.多态分为编译时多态(静态多态)和运行时多态(动态多态),重点分析了运行时多态的实现条件:必须通过基类指针或引用调用虚函数,且需完成虚函数重写。2.深入探讨了虚函数的重写规则、协变、析构函数重写等特性,并介绍了C++11的override和final关键字。3.通过实例分析了多态的实现原理,解释了虚函数表的作用机制。4.对比了动态绑定与静态绑定的区别,验证了虚函数表的存储位置。文章通过大量代码示例帮助读者理解多态的核心概念和应用场景。

2301_78901562的博客 5442

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

C语言模式实现C++继承和多态

这个问题主要考察的是C和C++的区别,以及C++中继承和多态的概念。C和C++的区别C语言是面向过程的语言,而C++是面向对象的过程。什么是面向对象和面向过程? 面向过程就是分析解决问题的步骤,然后用函数把这些步骤一步一步的进行实现,在使用的时候进行一一调用就行了,注重的是对于过程的分析。面向对象则是把构成问题的事进行分成各个对象,建立对象的目的也不仅仅是完成这一个个步骤,而是描述各个问题在解决的过

菜鸟成长记 1万+

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C++之三大特性 “封装”、“继承”、“多态”

C++之三大特性 “封装”、“继承”、“多态” 目录封装继承继承的概念及定义基类和派生类对象赋值转换继承中的作用域派生类的默认成员函数继承与友元继承与静态成员复杂的菱形继承及菱形虚拟继承继承的总结和反思多态二级目录三级目录 封装 继承 继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触

qq_49613557的博客 1万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

【C++】面向对象的三大特性:封装、继承、多态

本篇文章简单梳理了下C++面向对象的三种特性,即封装、继承、多态。

阿琪 1653

【C++进阶】多态要这样学才够清楚

深入理解多态,一步到位

小羊的博客 2974

[C++]——多态实现原理

多态实现原理

weixin_50720782的博客 1930

类型擦除:C++多态性的现代解决方案与新一代多态库Proxy

值语义与移动语义的优化:Proxy 对象的值行为与高效拷贝: 虽然 Microsoft Proxy 库的核心是基于指针语义的,即 proxy 对象本质上是对被代理对象指针的封装,但这并不妨碍 Proxy 库充分尊重和利用 C++ 的值语义和移动语义来优化性能。这样的代码是完全有效的,并且符合值语义的直觉。指针语义保证了 Proxy 对象的轻量和多态性,而对 值语义和移动语义的优化 则确保了 Proxy 对象在像值类型一样使用时,仍然能够保持极高的性能,避免了传统值类型拷贝的性能瓶颈。

weixin_73404922的博客 1716

面向对象程序设计思想及其C++实现:深入探索封装、继承与多态

它允许我们定义一个类(子类或派生类)来继承另一个类(父类或基类)的属性和方法,同时还可以在子类中添加新的属性和方法或重写继承自基类的方法。:封装是面向对象程序设计的核心原则之一,它指将对象的数据(属性)和操作这些数据的方法(函数)结合在一起,形成一个独立的单元,并尽可能隐藏对象的内部实现细节,仅对外提供有限的接口供其他对象访问。:抽象是面向对象程序设计的第一步,它要求我们从复杂的现实世界中提取出事物的本质特征,忽略非本质的细节,从而形成一个简洁、清晰的概念模型。函数中,我们展示了多态性的使用。

铭渊老黄 1168

面向对象的特征:继承封装和多态

面向对象的特征:继承、封装和多态 封装是指将某事物的属性和行为包装到对象中,这个对象只对外公布需要公开的属性和行为,而这个公布也是可以有选择性的公布给其它对象。在java中能使用private、protected、public三种修饰符或不用(即默认defalut)对外部对象访问该对象的属性和行为进行限制。 继承是子对象可以继承父对象的属性和行为,亦即父对象拥有的属性和行为,其子对象也就拥有了...

随我的博客 2428
上一篇: C++:继承
下一篇: C++:编译器对被const修饰变量的处理行为(替换)
小白debug~
小白debug~ 新星创作者: C/C++技术领域 新星创作者: C/C++技术领域
博客等级 码龄4年 3510粉丝 128原创
评论 68
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

小白debug~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值