C++的多态与虚函数你了解吗

目录
  • 多态性
  • 虚函数
  • 总结

多态性

多态性是面向对象程序设计的关键技术之一,若程序设计语言不支持多态性,不能称为面向对象的语言,利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能

在C++中有两种多态性:

  • 编译时的多态

通过函数的重载和运算符的重载来实现的

  • 运行时的多态性

运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定;它是通过类继承关系public和虚函数来实现的,目的也是建立一种通用的程序;通用性是程序追求的主要目标之一

通过引用或指针调用时,才可以达到运行时的多态

虚函数

虚函数是一个类的成员函数,定义格式如下:

virtual 返回类型 函数名(参数表);

关键字virtual指明该成员函数为虚函数,virtual仅用于类定义中,如虚函数在类外定义,不可加virtual

我们来看下面代码

class Animal
{
private:
	string name;
public:
	Animal(const string& na):name(na)
	{}
public:
	virtual void eat(){}
	virtual void walk(){}
	virtual void tail(){}
	virtual void PrintInfo(){}

	string& get_name()
	{
		return name;
	}
	const string& get_name()const
	{
		return name;
	}
};

class Dog :public Animal
{
private:
	string owner;
public:
	Dog(const string& ow, const string na) :Animal(na), owner(ow)
	{}
	virtual void eat()
	{
		cout << "Dog Eat: bone" << endl;
	}
	virtual void walk()
	{
		cout << "Dog Walk: run" << endl;
	}
	virtual void tail()
	{
		cout << "Dog Tail: wangwang" << endl;
	}
	virtual void PrintInfo()
	{
		cout << "Dog owner" << owner << endl;
		cout << "Dog name:" << get_name() << endl;
	}
};
class Cat :public Animal
{
private:
	string owner;
public:
	Cat(const string& ow, const string na) :Animal(na), owner(ow)
	{}
	virtual void eat()
	{
		cout << "Cat Eat: fish" << endl;
	}
	virtual void walk()
	{
		cout << "Cat Walk: silent" << endl;
	}
	virtual void tail()
	{
		cout << "Cat Tail: miaomiao" << endl;
	}
	virtual void PrintInfo()
	{
		cout << "Cat owner: " << owner << endl;
		cout << "Cat name: " << get_name() << endl;
	}
};
 // 需要公有继承 公有继承代表是一个的意思
 // 需要引用或指针调用
void fun(Animal& animal)
{
	animal.eat(); //对象名称.虚方法()
	animal.walk();
	animal.tail();
	animal.PrintInfo();
}

int main()
{
	Dog dog("zyq", "hashiqi"); //const string& ow = "zyq"
	Cat cat("zyq", "bosimao");
	fun(dog);
	fun(cat);
	return 0;
}

在这里我们可以看到,当我们调用fun()函数时,传入dog对象则调用Dog的方法,传入cat调用Cat方法;这就是所谓的运行时的多态

要想达到运行时的多态(晚绑定)需要满足:

  • 公有继承
  • 有虚函数
  • 必须以指针或引用方式调用虚函数

若发生早绑定,则会调用Animal类型的方法

成员函数应尽可能的设置为虚函数,但必须注意一下几条:

1.派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型;否则被认为是重载,而不是虚函数。如基类中返回基类指针,派生类中返回派生类指针是允许的,这是一个例外

2.只有类的成员函数才能说明为虚函数,这是因为虚函数仅适用于有继承关系的类对象

3.静态成员函数,是所有同一类对象公有,不受限于某个对象,不能作为虚函数(友元函数也不可以)

4.实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性

5.内联函数每个对象一个拷贝,无映射关系,不能作为虚函数

6.析构函数可定义为虚函数,构造函数不可以定义为虚函数,因为在调用构造函数时对象还没有完成实例化;在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性

7.函数执行速度要稍慢一些,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现;所以多态性总要付出一定代价,但通用性是一个更高的目标

8.如果定义放在类外,virtual只能加在函数声明前面,不能加载函数定义前面;正确的定义必须不包括virtual

虚函数是覆盖,同名函数是隐藏

虚函数编译过程

class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{}
	virtual void add()
	{
		cout << "Object::add" << endl;
	}
	virtual void fun()
	{
		cout << "Object::fun" << endl;
	}
	virtual void print()const
	{
		cout << "Object::print" << endl;
	}
};
class Base:public Object
{
private:
	int sum;
public:
	Base(int x = 0) :Object(x+10),sum(x)
	{}
	virtual void add()
	{
		cout << "Base::add" << endl;
	}
	virtual void fun()
	{
		cout << "Base::fun" << endl;
	}
	virtual void print()const
	{
		cout << "Base::print" << endl;
	}
};

int main()
{
}

此处虚函数表中进行的是同名覆盖,而不像继承关系中,同名成员进行隐藏,就近处理;虚函表仅有一份,存在数据区

在主函数创建对象

int main()
{
	Base base(10);
	Object* op = &base;
}

可以看到base的大小为12字节,因为其中基类对象Object,添加了虚表变为了8字节,且在构建过程,首先构建Object基类,此时虚表指针指向Object的虚表,而接着构建Base类的时候,会将虚表指针修改为指向Base的虚表

也就是,当有虚函数时,构造函数除了构建对象初始化对象的数据成员外,还会将虚表的地址给到虚表指针;同时这也是构造函数不可以作为虚函数的原因

int main()
{
	Base base(10);
	Object* op = NULL;
	Object obj(0);

	op = &base;
	op->add(); //指针或引用调动,则采用运行时多态
	op->fun();
	op->print();

	obj = base;
	obj.add(); //对象直接调动,则采用编译时多态
	obj.fun();
	obj.print();
}

也就是我们通过,对象名.方法 的方式调用虚函数,则通过编译时多态的方式

运行时的多态,是通过查询虚表进行调用;下面通过汇编进一步查看

只有进行以指针调用或引用调用的时候才会对虚表进行查询

三层继承

class Object
{
private:
	int value;
public:
	Object(int x = 0) :value(x)
	{}
	virtual void add()
	{
		cout << "Object::add" << endl;
	}
	virtual void fun()
	{
		cout << "Object::fun" << endl;
	}
	virtual void print()const
	{
		cout << "Object::print" << endl;
	}

	void fn_a()
	{
		fun();
	}
};
class Base:public Object
{
private:
	int sum;
public:
	Base(int x = 0) :Object(x+10),sum(x)
	{}
	virtual void add()
	{
		cout << "Base::add" << endl;
	}
	virtual void fun()
	{
		cout << "Base::fun" << endl;
	}
	virtual void show()
	{
		cout << "Base::show" << endl;
	}
};
class Test :public Base
{
private:
	int num;
public:
	Test(int x = 0) :Base(x + 10)
	{}
	virtual void add()
	{
		cout << "Test::add" << endl;
	}
	virtual void print() const
	{
		cout << "Test::print" << endl;
	}
	virtual void show()
	{
		cout << "Test::show" << endl;
	}
};

我们可以看到虚函数表,当我们构建派生类,会复制基类的虚函数表,将虚表指针指向新的虚函数表,并且将同名的虚函数进行覆盖

依旧使用上面代码

/*
	void fn_a()
	{
		fun();	//this->fun(); 属于动态绑定!
	}
*/
int main()
{
	Test t1;
	Base base;
	Object obj;

	t1.fn_a(); //fn_a(&t1);
	base.fun_a();
	obj.fn_a();
	return 0;
}

这里依然属于动态绑定,所以调用虚表指针指向的相对应类的虚表

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 详细分析C++ 多态和虚函数

    多态按字面的意思就是多种形态.当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态. C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数. 下面的实例中,基类 Shape 被派生为两个类,如下所示: #include <iostream> using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a;

  • C++的多态和虚函数你真的了解吗

    目录 一.C++的面试常考点 二.阿里真题 2.1 真题一 (1)虚函数表vtbl (2)构造一个派生类对象的过程 (3)析构一个派生类对象的过程 2.2 真题二 2.3 真题三 2.4 真题四 2.5 真题五 三.小结 总结 一.C++的面试常考点 阿里虽然是国内Java的第一大厂但是并非所有的业务都是由Java支撑,很多服务和中下层的存储,计算,网络服务,大规模的分布式任务都是由C++编写.在阿里所有部门当中对C++考察最深的可能就是阿里云. 阿里对C++的常考点: 1.STL 容器相关实现

  • 虚函数表-C++多态的实现原理解析

    参考:http://c.biancheng.net/view/267.html 1.说明 我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表 在 关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态 2.虚函数表 我们看一下下面的代码 class A { public: int i; virtual void func() { cout << "A func" <<

  • 深入浅析C++多态性与虚函数

    派生一个类的原因并非总是为了继承或是添加新的成员,有时是为了重新定义基类的成员,使得基类成员"获得新生".面向对象的程序设计真正的力量不仅仅是继承,而且还在于允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编. (一)多态性 多态是指同样的消息被不同的对象接收时导致不同的行为.所谓消息是指对类成员函数的调用,不同的行为是指的不同的实现,也就是调用了不同的函数. 1)多态的分类 广义上说,多态性是指一段程序能够处理多种类型对象的能力.在C++中,这种多态性可以通过重载多态(函

  • c++语言中虚函数实现多态的原理详解

    前言 自上一个帖子之间跳过了一篇总结性的帖子,之后再发,今天主要研究了c++语言当中虚函数对多态的实现,感叹于c++设计者的精妙绝伦 c++中虚函数表的作用主要是实现了多态的机制.首先先解释一下多态的概念,多态是c++的特点之一,关于多态,简而言之就是 用父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数,这种方法呢,可以让父类的指针具有多种形态,也就是说不需要改动很多的代码就可以让父类这一种指针,干一些很多子类指针的事情,这里是从虚函数的实现机制层面进行研究 在写这篇帖子之前

  • C++中的多态与虚函数的内部实现方法

    1.什么是多态 多态性可以简单概括为"一个接口,多种行为". 也就是说,向不同的对象发送同一个消息, 不同的对象在接收时会产生不同的行为(即方法).也就是说,每个对象可以用自己的方式去响应共同的消息.所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数.这是一种泛型技术,即用相同的代码实现不同的动作.这体现了面向对象编程的优越性. 多态分为两种: (1)编译时多态:主要通过函数的重载和模板来实现. (2)运行时多态:主要通过虚函数来实现. 2.几个相关概念 (1)覆盖.

  • 深入了解C++的多态与虚函数

    目录 1.多态的机制与虚函数的机制 1.1 多态的机制 1.2 虚函数的机制 1.3虚函数表的结构图 1.4 动态多态实现的三个前提件(很重要) 2.多态实例应用 3.多态的巨大问题与虚析构 3.1代码举例说明 3.2代码实现 4.纯虚函数与抽象类 4.1纯虚函数语法格式 4.2纯虚函数的定义 4.3抽象类的应用实例 1.多态的机制与虚函数的机制 1.1 多态的机制 1.当在类中使用virtual声明一个函数为虚函数时,在编译时,编译器会自动在基类中默默地安插一个虚函数表指针,同时的.rodat

  • C++的多态与虚函数你了解吗

    目录 多态性 虚函数 总结 多态性 多态性是面向对象程序设计的关键技术之一,若程序设计语言不支持多态性,不能称为面向对象的语言,利用多态性技术,可以调用同一个函数名的函数,实现完全不同的功能 在C++中有两种多态性: 编译时的多态 通过函数的重载和运算符的重载来实现的 运行时的多态性 运行时的多态性是指在程序执行前,无法根据函数名和参数来确定该调用哪一个函数,必须在程序执行过程中,根据执行的具体情况来动态地确定:它是通过类继承关系public和虚函数来实现的,目的也是建立一种通用的程序:通用性是

  • C++中继承与多态的基础虚函数类详解

    前言 本文主要给大家介绍了关于C++中继承与多态的基础虚函数类的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 虚函数类 继承中我们经常提到虚拟继承,现在我们来探究这种的虚函数,虚函数类的成员函数前面加virtual关键字,则这个成员函数称为虚函数,不要小看这个虚函数,他可以解决继承中许多棘手的问题,而对于多态那他更重要了,没有它就没有多态,所以这个知识点非常重要,以及后面介绍的虚函数表都极其重要,一定要认真的理解~ 现在开始概念虚函数就又引出一个概念,那就是重写(覆

  • C++ 多态虚函数的底层原理深入理解

    目录 1 多态的基本概念 1.1 什么是多态? 1.2 怎么实现多态 2 虚函数的底层原理 1 多态的基本概念 1.1 什么是多态? 多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为,通常是父类调用子类的重写函数,在C++中就是 父类指针指向子类对象,此时父类指针的向下引用就可以实现多态 比如看下面的代码: class Animal { public: //虚函数 virtual void speak() { cout << "动物在说话" <<

  • C++虚函数及虚函数表简析

    C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有"多种形态",这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以看看相关的C++的书籍.在这篇文章中,我只想从虚函数的实现机制上面为大家 一个

  • 详解C++纯虚函数与抽象类

    1.虚函数 1.1虚函数简介 虚函数可以毫不夸张的说是C++最重要的特性之一,我们先来看一看虚函数的概念. 在基类的定义中,定义虚函数的一般形式为: virtual 函数返回值类型 虚函数名(形参表) { 函数体 } 为什么说虚函数是C++最重要的特性之一呢,因为虚函数承载着C++中动态联编的作用,也即多态,可以让程序在运行时选择合适的成员函数.虚函数必须是类的非静态成员函数(且非构造函数),其访问权限是public.那么:  (1)为什么类的静态成员函数不能为虚函数?  如果定义为虚函数,那么

随机推荐