208-C++多态与虚函数

1.计算下面程序base的大小

class Object
{
	int value;
	int sum;
};
class Base : public Object  //公有继承
{
	int num;
};
int main()
{
	Base base;
	sizeof(Base);
	sizeof(base);
	return 0;
}

答案:12个字节
base有两个成员,一个是num,是4个字节,一个是隐藏父对象,是8个字节,隐藏父对象有两个成员,一个是value,是4个字节,一个是sum,是4个字节

注意:sizeof(Base) 和 sizeof(base) 两个的意思是一样的,结果也是一样的,都是12个字节

Object 和 Base 之间的继承关系在编译的时候就确定了

2.多态性与虚函数

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

在C++中有两种多态性:
编译时的多态(也叫早绑定或者静态绑定或者静态联编)
是通过函数的重载和运算符的重载来实现的

int Max(int a,int b)	{ return a>b?a:b; }
char Max(char a,char b) { return a>b?a:b; }
double Max(double a,double b) { return a>b?a:b; }
int main()
{
	int x = Max(12,23);
	char ch = Max('c','b');
	double dx = Max(12.23,34.45);
	return 0;
}

早期绑定在编译的时候就确定了关系,int x = Max(12,23); 12和23是整型就和整型的Max函数绑定,char ch = Max(‘c’,‘b’); 'c’和’b’是字符型,就和字符型的Max绑定,double dx = Max(12.23,34.45); 12.23和34.45是double型,就和double型的Max绑定

早期绑定是靠名字粉碎技术来实现的

可以根据故事来理解:可以理解为两对夫妻怀孕了,约定如果是两个男孩,就结为兄弟,如果是两个女孩,就结为姐妹,如果是一男一女,就结为夫妻,这种在还没有出生前就确定关系的就叫做早期绑定,如果一开始没有绑定娃娃亲,两个孩子在社会上长大了,结为兄弟、姐妹或者夫妻,就称为晚绑定

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

虚函数主要进行的是晚绑定

3.虚函数的定义

虚函数是一个类的成员函数,定义格式为:
virtual 返回类型 函数名 (参数列表) ;
关键字 virtual 指明该成员函数为虚函数,virtual 仅用于类定义中,如虚函数在类外定义,不能加 virtual

class Object
{
public:
	virtual void fun();//虚函数的声明,注意:这里的 virtural 必须加
};
void Object::fun()//注意:这里不能加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 // public 意味着"是一个"的意思,狗是一个动物
{
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 : wang wang..." << endl; }//狗的叫声
	virtual void PrintInfo()
	{
		cout << "Dog Owner : " << owner << endl;
		cout << "Dog name : " << get_name() << endl;
	}
};
class Cat : public Animal // public 意味着"是一个"的意思,狗是一个动物
{
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 : Miao Miao..." << 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("yhping","hashiqi");
	Cat cat("hm","bosimao");

	fun(dog);
	fun(cat);
	return 0;
}

class Dog : public Animal // 公有继承 public 意味着"是一个"的意思,狗"是一个"动物

因为每个动物的吃,走,和叫声是不一样的,所以设为虚函数,而获取狗的名字的方式都是一样的,所以不设为虚函数

当我把dog作为实参时,打印的是dog的内容,当我把cat作为实参时,打印的是cat的内容,这就是运行时的多态

我们要想达到运行时的多态,必须使用引用或者指针调动虚函数,才能达到运行时的多态

实现运行时的多态的三个步骤:
①必须是公有继承
②必须把函数定义为虚函数
③必须使用引用或者指针调动虚函数

条件①为什么必须是公有继承呢?因为只有公有继承才意味者“是一个”的意思

条件②
virtual void eat() { cout << "Cat Eat : fish " << endl; }
virtual void walk() { cout << “Cat Walk : silent” << endl; }
virtual void tail() { cout << “Cat Tail : Miao Miao…” << endl; }//狗的叫声

条件③ 引用:(void fun(Animal& animal)) 或者 指针:(void fun(Animal * animal))

如果fun函数既没有使用引用也没有使用指针(void fun(Animal animal)),那么它就变成了早期绑定(也叫静态联编),它既不打印 cat 的内容,也不打印 dog 的内容,而是打印 Animal 的内容

4.所有的构造函数(无论是构造还是拷贝构造还是移动构造)都不能写成虚函数,但是析构函数可以写成虚函数,内联函数,静态成员函数,友元函数都不能写成虚函数

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

①派生类中定义虚函数必须与基类中的虚函数同函数名外,还必须是同参数表,同返回类型,否则被认为是函数的隐藏,而不是虚函数的重写。如基类中返回基类指针, 派生类中返回派生类指针是允许的,这是一个例外
②只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象
③静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数,静态成员函数、友元函数、内联函数、构造函数、拷贝构造函数、移动构造函数,只要是构造函数都不能作为虚函数,析构函数可以作为虚函数
④实现动态多态性时,必须使用基类类型的指针变量或引用,使该指针指向该基类的不同派生类的对象,并通过该指针指向虚函数,才能实现动态的多态性
⑤内联函数每个对象一个拷贝,无映射关系,不能作为虚函数
⑥析构函数可定义为虚函数,所有的构造函数(包括构造函数,拷贝构造函数,移动构造函数)都不能定义为虚函数,因为构造函数是置虚表的函数,如果把构造函数定义为虚函数,那么调用虚函数需要查表,但是此时,虚表还没有被创建,也就无表可查,所以不能把构造函数定义为虚函数,在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤销对象时的多态性
⑦函数执行速度要稍慢一些,为了实现多态性,每一个派生类中均要保存相应虚函数的入口地址表,函数的调用机制也是间接实现,所以多态性总是要付出一定代价,但通用性是一个更高的目标
⑧如果定义放在类外,virtual 只能加在函数声明前面,不能加在函数定义前面,正确的定义必须不包括 virtual

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

7.为什么通过指针可以达到运行时的多态,为什么不能通过对象达到运行时的多态?

8.虚函数表

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; }
};

在编译的时候到Object的时候,会生成一个虚函数表(Object::vftable),里面存放着add、fun、print函数的指针,当编译到Base时,编译器发现是共有继承,并且函数名相同,参数列表相同,就会用Base中add、fun、print函数的指针覆盖掉Object的add、fun、print函数指针,称为同名覆盖

有虚函数就会生成虚函数表

9.sizeof(base)的大小是多少?

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()
{
	Base base(10);
	Object *op = &base;
	sizeof(base);
	return 0;
}

答案:12个字节
当执行到Base base(10);时要调用Base的构造函数,因为Base是共有继承Object的,所以会又先调用Object的构造函数,由于Object中有虚函数,所以会创建一个虚函数表,虚函数指针指向Object的虚函数表,是4个字节,value是int类型占4个字节,回到Base的构造函数,由于Base中也有虚函数,所以也会创建虚函数表,这时虚函数指针就不指向Object了(在整个继承链上,虚表指针只有一个),改为指向Base的虚函数表,然后再初始化sum,sum是int类型,占4个字节,所以一共是12个字节

在Base的构造函数中加上memset(this,0,sizeof(Base));会导致什么样的后果?

答案:sizeof的大小是12个字节,其中包括虚函数表指针,如果置为0,就把虚表的指向弄丢了,所以在构造函数中使用memset函数是十分危险的

为什么不能把构造函数(包括构造、拷贝构造、移动构造)定义成虚函数?

答案:构造函数是建立虚表的前提。如果把构造函数定义成虚函数,调动虚函数需要查表,而构造函数是我们设置虚表指针的一个方案,通俗来说就是调动构造函数查表时,表还没有建立起来呢,没有表可查,所以不能把构造函数定义成虚函数

10.父指针调用函数和对象调用函数有什么区别?

class Object
{
   
private:
	int value;
public:
	Object(int x = 0) : value(x) {
   }
	virtual void add() {
    cout 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您介绍一下关于C++多态虚函数的实验。 首先,多态是指不同对象以不同的方式响应相同的消息的能力。在C++中,通过虚函数来实现多态虚函数是指在基类中被声明为虚函数函数,在派生类中被重写后,在程序中使用基类指针或引用调用该函数时,会调用派生类中的函数。 下面是一个简单的多态虚函数的实验示例: ```c++ #include<iostream> using namespace std; class Shape { public: virtual void draw() { cout << "Drawing Shape" << endl; } }; class Circle: public Shape { public: void draw() { cout << "Drawing Circle" << endl; } }; class Square: public Shape { public: void draw() { cout << "Drawing Square" << endl; } }; int main() { Shape *s; Circle c; Square sq; s = &c; s->draw(); s = &sq; s->draw(); return 0; } ``` 运行结果为: ``` Drawing Circle Drawing Square ``` 在上述示例中,我们定义了一个基类Shape和两个派生类Circle和Square,分别重写了基类的虚函数draw()。在程序中,我们先定义了一个基类指针s,然后将其指向第一个派生类对象c,再调用s的虚函数draw(),此时会调用派生类Circle的draw()函数,因为指针实际上指向的是Circle类型的对象。接着,我们将指针s重新指向第二个派生类对象sq,再次调用s的虚函数draw(),此时会调用派生类Square的draw()函数。 通过这个实验,我们可以看到多态虚函数的特性,即通过基类指针或引用调用虚函数时,会根据实际对象的类型来调用相应的派生类函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值