依旧还是来看一段代码
#include <iostream>
using namespace std;
class base
{
public:
void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<<endl;
}
};
class subclass :public base
{
public:
void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<<endl;
}
};
void test(base *p)
{
p->priMsg();
}
int main()
{
base obj1;
subclass obj2;
test(&obj1);
test(&obj2);
return 0;
}
运行结果:
priMsgline :14
priMsgline :14
首先我们要明白为什么obj2这个对象明明是subclass为什么也调用的14行的函数,是因为void test中的形参是 base* p,然后因为subclass是base的公有继承,所以向上转型,都调用的基类中的函数。
那怎么样才能实现不向上转型,分别调用父类和子类的函数呢?
加个 virtual就行了
#include <iostream>
using namespace std;
class base
{
public:
virtual void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<<endl;
}
};
class subclass :public base
{
public:
void priMsg()
{
cout<<__func__<<"line :"<<__LINE__<<endl;
}
};
void test(base *p)
{
p->priMsg();
}
int main()
{
base obj1;
subclass obj2;
test(&obj1);
test(&obj2);
return 0;
}
运行结果:
priMsgline :14
priMsgline :22
这其实就是多态,那么现在开始我们今天的知识点,多态。
一.什么是多态?
-多态:字面意思”多种状态“,一个接口,多种方法,子类和基类的方法不同。(程序在运行时决定调用哪个方法,是面向对象编程的核心概念)。
程序在运行时才决定调用哪个方法,是面向对象编程的核心概念。
-多态性:将接口与实现进行分离,也就是实现共同的方法,但因为个体差异不同,采用不同的策略。
**oop(面向对象编程)特点:**
** -封装(Wrap):实现细节隐藏,使代码模块化。**
** -继承(inheritance):扩展已存在的代码,目的是代码重用。**
** -多态(polymorphism):实现接口重用,不论传递过来的是哪个类的对象,函数都能通过同一个接口调用到适应各自对象的实现方法。**
二.多态的实现
-使用virtual修饰的成员函数(虚函数)
虚函数的设置条件:
-——非类的成员函数不能设置为虚函数(例如友元函数)
-——类的静态成员不能定义为虚函数
首先什么是static静态成员函数?静态成员函数不属于类中的任何一个对象和实例,属于类共有的一个函数。也就是说,它不能用this指针来访问,因为this指针指向的是每一个对象和实例。
对于virtual虚函数,它的调用恰恰使用this指针。在有虚函数的类实例中,this指针调用vptr指针,指向的是vtable(虚函数列表),通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是:this指针->vptr(4字节)->vtable ->virtual虚函数。
所以说,static静态函数没有this指针,也就无法找到虚函数了。所以静态成员函数不能是虚函数。他们的关键区别就是this指针。
构造函数不能为const函数,构造函数的目的就是为了给成员变量赋初值,不能为const函数
-——构造函数不能定义为虚函数,但是析构函数却可以设置为虚函数
-——成员函数声明时需要使用 virtual关键字修饰,定义时不需要
-——基类成员函数设置为虚函数,那么派生类中同名函数(函数名,形参类型,个数返回值完全一样)自动称为虚函数。
三.覆盖,重载及隐藏
成员函数覆盖(override,也称重写)
是指派生类重新定义基类的虚函数,特征如下:
A.不同的作用域(分别位于派生类与基类)
B.函数名字相同
C.参数相同
D.基类函数必须有virtual关键字,不能有static
E.返回值相同
F.重写函数的权限访问限定符可以不同
成员函数重载(overload)
是指函数名相同,参数不同(数量,类型,次序),特征如下:
A.相同的范围(在同一个作用域中)
B.函数名字相同
C.参数不同
D.virtual关键字可有可无
E.返回值可以不同
成员函数的隐藏(也称重定义)
A.不在同一个作用域
B.函数名字可以不同
C.返回值可以不同
D.参数不同,此时,不论有无virtual关键字,基类的函数将被隐藏(注意和重载的区别,重载在同一个定义域中)
E.参数相同,但是基类函数没有virtual关键字,此时,基类的函数被隐藏(注意和覆盖的区别,覆盖是有virtual关键字)
四.联编(链接)
就是将模块或者函数合并在一起生成可执行代码的处理过程。按照联编所进行的阶段不同 ,可分为两种不同的联编方法:静态联编和动态联编
1.静态联编(静态链接)
是指在编译阶段就将函数实现和函数调用关联起来,因此静态联编也叫早绑定
2.动态联编(动态链接)
是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定。
————c++中一般情况下是静态联编,但是一旦涉及多态和虚拟函数就要使用动态联编了。
tips:重载只是一种语言特性,编译器根据函数不同的参数表,把同名函数区分开来,属于静态联编,与多态无关。引用一句Bruce Eckel的话:”不要犯傻,如果它不是晚绑定它就不是多态。”
五.抽象类
含有纯虚函数的类就是抽象类。
抽象类没有完整的信息,只能是派生类的基类,抽象类不能有实例,不能有静态成员,派生类应该实现抽象类的所有方法。
例:
class Graphic
{
public:
virtual float Area()=0;
};
class Rectangle :public Graphic
{
public:
float Area()
{
return h*w;
}
private:
float h,w;
};
一般的,使用一个类,只关心public成员,故此需要隐藏类的其他成员方法。例如,动物作为一个基类可以派生出老虎,孔雀等子类,但是动物本身并不能作为任何实际明确的对象。
六.虚析构函数
用来避免子类中回收不完整的情况
例子:
#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
public:
base(){pri();}
//虚析构函数
/*virtual*/ ~base(){pri();}
};
class subclass :public base
{
public:
subclass(){pri();}
//派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
~subclass(){pri();}
};
int main()
{
subclass *p=new subclass;
delete p;
//设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
base *q=new subclass;
delete q;
return 0;
}
运行:
baseline::13
subclassline::21
~subclassline::23
~baseline::15
baseline::13
subclassline::21
~baseline::15
回收不完整
解决:
#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
public:
base(){pri();}
//虚析构函数
virtual ~base(){pri();}
};
class subclass :public base
{
public:
subclass(){pri();}
//派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
~subclass(){pri();}
};
int main()
{
subclass *p=new subclass;
delete p;
//设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
base *q=new subclass;
delete q;
return 0;
}
运行:
baseline::13
subclassline::20
~subclassline::21
~baseline::14
baseline::13
subclassline::20
~subclassline::21
~baseline::14
七.限制构造函数
构造函数权限不是public,那么这就是限制构造函数
#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
protected:
base(){pri();}
//虚析构函数
virtual ~base(){pri();}
};
class subclass :public base
{
public:
subclass(){pri();}
//派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
~subclass(){pri();}
};
int main()
{
subclass *p=new subclass;
delete p;
//设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
base *q=new subclass;
delete q;
return 0;
}
运行报错
基类限制构造函数不能创建实例,只能派生出子类,定义子类对象来访问接口函数
#include <iostream>
#define pri() cout<<__func__<<"line::"<<__LINE__<<endl;
using namespace std;
class base
{
protected:
base(){pri();}
//虚析构函数
virtual ~base(){pri();}
};
class subclass :public base
{
public:
subclass(){pri();}
//派生类的析构函数自动成为 虚析构函数,覆盖基类虚析构函数
~subclass(){pri();}
};
int main()
{
//subclass *p=new subclass;
//delete p;
//设置虚析构函数的目的是:当基类指针指向 派生类对象时,释放基类指针,能完全回收派生类的构造函数
subclass *q=new subclass;
delete q;
return 0;
}
可以运行:
baseline::13
subclassline::21
~subclassline::23
~baseline::15
如果是private限制呢?
这时候派生类也无法访问基类的私有成员,当然我们也有办法,那就是使用友元函数,打破基类私有限制的封装。
实例:
#include<iostream>
using namespace std;
#define pri() cout<<__func__<<" line: "<<__LINE__<<endl;
class Base{
private:
//构造函数权限不是public,那么这就是限制构造函数
Base(){ pri(); }
public:
virtual ~Base(){ pri(); }
void get(){ pri(); }
friend Base *getObj();
friend void freeObj(Base *);
};
#if 0
class Subclass : public Base{
public:
Subclass(){ pri(); }
~Subclass(){ pri(); }
};
#endif
Base *getObj() //友元成员函数,打破类的封装,在函数可以访问 类的保护和私有成员
{
return new Base; //开辟堆区空间时,系统会调用Base类默认构造函数
}
void freeObj(Base *p)
{
delete p;
}
int main(int argc, char *argv[])
{
// Base obj; //限制构造函数不能创建实例,
// 如果构造函数权限为 private,那么只能设计友元成员函数打破private的限制
// Subclass obj; //错误,因为基类构造函数为 private权限,而派生类不能访问基类的 私有成员。
// obj.get();
Base *p = getObj();
p->get();
freeObj(p);
return 0;
}
版权归原作者 飞赴 所有, 如有侵权,请联系我们删除。