1多态
1.1问题引出
函数重写
在子类中定义与父类中原型相同的函数
函数重写只发生在父类与子类之间
#include "iostream"
using namespace std;
class Parent
{
public:
Parent(int a)
{
this->a=a;
cout<<"Parent a:"<<a<<endl;
}
virtual void print()
/*void print()*/
{
cout<<"Parent 打印 a:"<<a<<endl;
}
protected:
private:
int a;
};
class Child : public Parent
{
public:
Child(int b):Parent(10)
{
this->b=b;
cout<<"Child b:"<<b<<endl;
}
void print() //子类和父类的函数名称一样
{
cout<<"Child 打印 b:"<<b<<endl;
}
protected:
private:
int b;
};
void howToPrint(Parent *base)
{
base->print();
}
void howToPrint2(Parent &base)
{
base.print();
}
void main()
{
Parent *base=NULL;
Parent p1(20);
Child c1(30);
base=&p1;
base->print(); //执行父类打印函数
base=&c1;
base->print(); //执行谁的函数? 答:父类的
//面向对象新需求
{
Parent &base2=p1;
base2.print();
Parent &base3=c1; //base3是c1的别名
base3.print();
}
//函数调用
howToPrint(&p1);
howToPrint(&c1);
howToPrint2(p1);
howToPrint2(c1);
cout<<"hello..."<<endl;
system("pause");
return ;
}
父类中被重写的函数依然会继承给子类
默认情况下子类中重写的函数将隐藏父类中的函数
通过作用域分辨符::可以访问到父类中被隐藏的函数
在同一个类里面能实现函数重载
继承的情况下,发生重写
重载不一定;
重写的定义
静态联编 重载是
动态联编
1.2面向对象新需求
1.3解决方案
- C++中通过virtual关键字对多态进行支持
- 使用virtual声明的函数被重写后即可展现多态特性
1.4多态实例
#include "iostream"
using namespace std;
class HeroFighter
{
public:
virtual int power() //C++会对此函数特殊处理
{
return 10;
}
protected:
private:
};
class EnemyFighter
{
public:
int attack()
{
return 15;
}
protected:
private:
};
class AdvHeroFighter : public HeroFighter
{
public:
virtual int power()
{
return 20;
}
protected:
private:
};
class AdvHeroFighter3 : public HeroFighter
{
public:
virtual int power()
{
return 30;
}
protected:
private:
};
//多态的威力
//1 PlayObj给对象搭建舞台 看成一个框架
void PlayObj(HeroFighter *hf,EnemyFighter *ef)
{
//不写virtual会是静态联编 C++编译器很具Herofighter类型,去执行这个类型的power函数
//动态联编:迟绑定-> 在运行的时候
//hf->power() 将会有多态发生,根据对象(具体的类型),执行不同对象的函数,表现成多态
if (hf->power() > ef->attack())
{
cout<<"主角 win"<<endl;
}
else
{
cout<<"主角 挂了"<<endl;
}
}
//多态的思想
//面向对象3 大概念
//封装 突破c函数的概念 用类做函数参数的时候可使用独享的属性 和 对象的方法
//继承 :A B 代码复用
//多态 : 可以使用未来
//多态很重要
//C语言 间接赋值 是指针存在的最大意义
//是C语言的特有的现象 (1 定义两个变量
// (2 建立关联
// (3 *p在被调用函数中去间接地修改实参的值
//实现多态的三个条件
// 1 要有继承
// 2 要有虚函数重写
// 3 用父类指针(父类引用)指向子类对象...
void main()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
AdvHeroFighter3 Advhf3;
PlayObj(&hf,&ef);
PlayObj(&Advhf,&ef);
PlayObj(&Advhf3,&ef); //此框架可将后来人写的代码给调用起来
cout<<"hello..."<<endl;
system("pause");
return ;
}
void main1401()
{
HeroFighter hf;
AdvHeroFighter Advhf;
EnemyFighter ef;
if (hf.power() > ef.attack())
{
printf("主角win\n");
}
else
{
printf("主角挂了、、\n");
}
if (Advhf.power()>ef.attack())
{
printf("Adv 主角win\n");
}
else
{
printf("Adv 主角挂了\n");
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
实现多态的三个条件
- 1 要有继承
- 2 要有虚函数重写
- 3 用父类指针(父类引用)指向子类对象
1.5多态工程意义
面向对象3大概念
-
封装
突破了C语言函数的概念。。 -
继承
代码复用 。。。。我复用原来写好的代码。。。 -
多态
多态可以使用未来。。。。。80年代写了一个框架。。。。。。90人写的代码
多态是我们软件行业追寻的一个目标。。。
写了一个框架,可以调用后来人,写的代码的能力
1.6多态成立的条件
-
间接赋值成立的3个条件
//1 定义两个变量。。。
//2 建立关联 。。。。
//3 *p -
多态成立的三个条件
//1 要有继承
//2 要有函数重写。。。C 虚函数
//3 要有父类指针(父类引用)指向子类对象
//多态是设计模式的基础,多态是框架的基础
1.7多态的理论基础
静态联编和动态联编
1、联编是指一个程序模块、代码之间互相关联的过程。
2、静态联编(static binding),是程序的匹配、连接在编译阶段实现,也称为早期匹配。
重载函数使用静态联编。
3、动态联编是指程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)。
switch 语句和 if 语句是动态联编的例子。
1.8 虚析构函数
#define _CRT_SECURE_NO_WARNINGS
#include "iostream"
using namespace std;
class A
{
public:
A()
{
p = new char[20];
strcpy(p,"obja");
cout<<"A()"<<endl;
}
virtual ~A()
{
delete [] p;
cout<<"~A()"<<endl;
}
protected:
private:
char *p;
};
class B : public A
{
public:
B()
{
p=new char[20];
strcpy(p,"objb");
cout<<"B()"<<endl;
}
~B()
{
delete [] p;
cout<<"~B()"<<endl;
}
protected:
private:
char *p;
};
class C : public B
{
public:
C()
{
p=new char[20];
strcpy(p,"objc");
cout<<"C()"<<endl;
}
~C()
{
delete [] p;
cout<<"~C()"<<endl;
}
protected:
private:
char *p;
};
//只执行了 父类的析构函数
//想通过父类指针 把 所有的子类对象的析构函数都执行一遍,
//通过父类指针,释放所有的子类资源
void HowToDelete(A *base)
{
delete base; //不会表现出多态的属性
}
void main()
{
C *myC=new C;
HowToDelete(myC);
//delete myC;//直接通过子类对象释放资源,不需要virtual
cout<<"hello..."<<endl;
system("pause");
return ;
}
1.9 重载重写重定义
重写 重载 重定义
- 重写发生在2个类之间
- 重载必须在一个类之间
重写分为2类
- 1、虚函数重写 将发生多态
- 2、非虚函数重写 (重定义)
#include <iostream>
using namespace std;
//重写 重载 重定义
//重写发生在2个类之间
//重载必须在一个类之间
//重写分为2类
//1 虚函数重写 将发生多态
//2 非虚函数重写 (重定义)
class Parent
{
//这个三个函数都是重载关系
public:
void abc()
{
printf("parent abc");
}
virtual void func()
{
cout<<"func() do..."<<endl;
}
virtual void func(int i)
{
cout<<"func() do..."<<i<<endl;
}
virtual void func(int i, int j)
{
cout<<"func() do..."<<i<< " "<<j<<endl;
}
virtual void func(int i, int j, int m , int n)
{
cout<<"func() do..."<<i<< " "<<j<<endl;
}
protected:
private:
};
class Child : public Parent
{
public:
void abc()
{
printf("child abc");
}
/*
void abc(int a)
{
printf("child abc");
}
*/
virtual void func(int i, int j)
{
cout<<"func(int i, int j) do..."<<i<< " "<<j<<endl;
}
virtual void func(int i, int j, int k)
{
cout<<"func(int i, int j) do.."<< endl;
}
protected:
private:
};
//重载重写和重定义
void main()
{
//: error C2661: “Child::func”: 没有重载函数接受 0 个参数
Child c1;
c1.abc();
//c1.func();
//子类无法重载父类的函数,父类同名函数将被名称覆盖
//c1.Parent::func();
//1 C++编译器 看到func名字 ,因子类中func名字已经存在了(名称覆盖).所以c++编译器不会去找父类的4个参数的func函数
//2 c++编译器只会在子类中,查找func函数,找到了两个func,一个是2个参数的,一个是3个参数的.
//3 C++编译器开始报错..... error C2661: “Child::func”: 没有重载函数接受 4 个参数
//4 若想调用父类的func,只能加上父类的域名..这样去调用..
//c1.func(1, 3, 4, 5);
//c1.func();
//func函数的名字,在子类中发生了名称覆盖;子类的函数的名字,占用了父类的函数的名字的位置
//因为子类中已经有了func名字的重载形式。。。。
//编译器开始在子类中找func函数。。。。但是没有0个参数的func函数
cout<<"hello..."<<endl;
system("pause");
return ;
}
2. 多态相关面试题
2.1 请谈谈你对多态的理解
- 多态的实现效果
多态:同样的调用语句有多种不同的表现形态; - 多态实现的三个条件
有继承、有virtual重写、有父类指针(引用)指向子类对象。 - 多态的C++实现
virtual关键字,告诉编译器这个函数要支持多态;不是根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用 - 多态的理论基础
动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。 - 多态的重要意义
设计模式的基础 是框架的基石。 - 实现多态的理论基础
函数指针做函数参数
C函数指针是C++至高无上的荣耀。C函数指针一般有两种用法(正、反)。
2.2 对重写,重载理解
- 函数重载
必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被名称覆盖
重载是在编译期间根据参数类型和个数决定函数调用 - 函数重写
必须发生于父类与子类之间
并且父类与子类中的函数必须有完全相同的原型
使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
多态是在运行期间根据具体对象的类型决定函数调用
3多态原理探究
理论知识:
- 当类中声明虚函数时,编译器会在类中生成一个虚函数表
- 虚函数表是一个存储类成员函数指针的数据结构
- 虚函数表是由编译器自动生成与维护的
- virtual成员函数会被编译器放入虚函数表中
- 当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象、子类对象提前布局vptr指针;当进行howToPrint(Parent *base)函数是,C++编译器不需要区分子类对象或者父类对象,只需要再base指针中,找vptr指针即可。)
- VPTR一般作为类对象的第一个成员
3.1 多态的实现原理
C++中多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表是由编译器自动生成与维护的
virtual成员函数会被编译器放入虚函数表中
存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)
- 说明1:
通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。 - 说明2:
出于效率考虑,没有必要将所有成员函数都声明为虚函数 - 说明3 :C++编译器,执行HowToPrint函数,不需要区分是子类对象还是父类对象
#include "iostream"
using namespace std;
//多态成立三条件
//继承 虚函数重写 父类指针指向子类对象
class Parent
{
public:
Parent(int a=0)
{
this->a=a;
}
virtual void print() //1 动手脚 写virtual关键字 会特殊处理
// 生成虚函数表
{
cout<<"我是爸爸 。。。"<<endl;
}
protected:
private:
int a;
};
class Child : public Parent
{
public:
Child(int a=0,int b=0):Parent(a)
{
this->b=b;
}
virtual void print()
{
cout<<"我是儿子 。。。"<<endl;
}
protected:
private:
int b;
};
void howtoPlay(Parent *base)
{
base->print();//会有多态发生 //2动手脚
//效果:传来子类的对象 执行子类的print函数
//传来父亲对执行父类的print函数的
//编译器 根本不需要区分是子类对象还是父类对象
//父类对象 和 子类对象 分别有vptr指针,===>虚函数表 ===>函数的入口地址
//迟绑定 运行的时候,编译器才去判断
}
void main()
{
Parent p1; //3 动手脚 提前布局
// 用类定义对象的时候 编译器会在对象中添加vptr指针
Child c1; //子类中也有vptr指针
howtoPlay(&p1);
howtoPlay(&c1);
cout<<"hello..."<<endl;
system("pause");
return ;
}
3.2如何证明vptr指针的存在
#include "iostream"
using namespace std;
class Parent1
{
public:
Parent1(int a=0)
{
this->a=a;
}
void print() //1 动手脚 写virtual关键字 会特殊处理
// 生成虚函数表
{
cout<<"我是爸爸 。。。"<<endl;
}
protected:
private:
int a;
};
class Parent2
{
public:
Parent2(int a=0)
{
this->a=a;
}
virtual void print()
{
cout<<"我是爸爸 。。。"<<endl;
}
protected:
private:
int a;
};
void main()
{
printf("sizeof(Parent1) %d,sizeof(Parent2):%d \n",sizeof(Parent1),sizeof(Parent2));
cout<<"hello..."<<endl;
system("pause");
return ;
}
3.3构造函数中能调用虚函数,实现多态吗
1)对象中的VPTR指针什么时候被初始化?
对象在创建的时,由编译器对VPTR指针进行初始化
只有当对象的构造完全结束后VPTR的指向才最终确定
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表
2)分析过程
画图分析
#include "iostream"
using namespace std;
//构造函数中调用虚函数能发生多态吗
class Parent
{
public:
Parent(int a=0)
{
this->a=a;
print();
}
virtual void print()
{
cout<<"我是爸爸 。。。"<<endl;
}
protected:
private:
int a;
};
class Child : public Parent
{
public:
Child(int a=0,int b=0):Parent(a)
{
this->b=b;
print();
}
virtual void print()
{
cout<<"我是儿子 。。。"<<endl;
}
protected:
private:
int b;
};
void howtoPlay(Parent *base)
{
base->print();
}
void main()
{
Child c1; //定义一个子类对象,在这个过程中在父类构造函数中调用虚函数能发生多态吗
cout<<"hello..."<<endl;
system("pause");
return ;
}