C++中的多态特性

    前边我们知道,C++类之间有继承关系,子类能够哦继承父类并且拥有父类的属性,也能够定义子类自己的属性,古语就是:青出于蓝而胜于蓝。
    但是子类中是否可以定义父类中的同名成员,使用时如何区分该成员是父类中的还是子类中的?下边代码来验证一下

#include <iostream>

using namespace std;

class parent
{
public:
    int m_value;
    void getValue()
    {
	cout << "I'm parent, value = " << m_value << endl;
    }
};
class child : public parent
{
public:
    int m_value;	//定义与父类同名的成员
    void getValue()
    {
	cout << "I'm child, value = " << m_value << endl;
    }
};

void main()
{
    child c;
    c.m_value = 12;	//访问的是子类中的成员
    c.getValue();

    c.parent::m_value = 23;	//通过parent::作用域符访问父类中的成员
    c.parent::getValue();
    system("pause");
}

编译输出

从输出结果看
    - 子类中可以定义与父类中同名的成员
    - 通过子类对象访问与父类中同名的成员,那么子类中的同名成员会隐藏父类的同名成员
    - 父类的同名成员依旧存在于子类中,可以通过作用域分辨符(::)访问父类中的同名成员

上边的代码,我们在子类中定义了一个与父类同名同参数的函数(即重写了父类的成员函数),那么父类中的成员函数与子类中的成员函数能不能构成重载关系?我们知道类中的函数可以重载,但是对于父子类之间的成员函数能否重载,我们还未知,在此之前,先来回顾下重载函数需要的条件
    - 本质是多个不同的函数
    - 函数名参数列表是唯一的标识
    - 必须发生在同一个作用域中

根据几个条件以及上边的代码,我们来看下下边的代码

#include <iostream>

using namespace std;

class parent
{
public:
    void fun(int i)
    {
   	cout << "I'm parent, fun(int i) "<< endl;
    }
};
class child : public parent
{
public:
    void fun(int i, int j)
    {
	cout << "I'm child, fun(int i, int j) " << endl;
    }
};

void main()
{
    child c;
    c.fun(12, 34);	//OK,正常访问成员函数fun(int i, int j)
    c.fun(12);	//期望访问父类的成员函数fun(int i)
    c.parent::fun(12);	//OK,使用作用域分辨符(::)访问父类的同名函数
    system("pause");
}

编译结果如下

很明显,提示我们没有匹配参数的fun函数使用,也就是说,子类中的函数与父类中的函数不能构成重载
    - 子类中的函数将隐藏父类的同名函数
    - 子类无法重载父类中的成员函数
    - 使用作用域分辨符(::)可访问父类的同名成员函数
    - 子类中可以定义与父类完全相同的成员函数

知道了父类与子类之间的函数不能重载,而我们前边又知道,子类是特殊的父类,是直接可以初始化或赋值给父类的,并且父类的指针或引用也可以直接指向子类,那么我们在来看一段代码

#include <iostream>

using namespace std;

class parent
{
public:
    void fun()
    {
	cout << "I'm parent "<< endl;
    }
};
class child : public parent
{
public:
    void fun()
    {
	cout << "I'm child " << endl;
    }
};

void fun (parent* p)	//定义一个全局函数
{
    p->fun();
}

void main()
{
    parent p;
    child c;

    fun(&p);	//传入父类对象指针,期望输出:I'm parent
    fun(&c);	//传入子类对象指针,期望输出:I'm child
	
    system("pause");
}

    上边的代码中我们在子类中重定义了在父类中已经存在的函数fun(),这种情况在C++中叫函数重写,重写属于同名覆盖的一种特殊情况。
    在main函数中,我们分别把父类对象指针与子类对象指针传给全局函数fun(parent* p),我们希望的是传入父类对象指针时,调用的是父类中的fun()函数,传入子类对象指针时,调用的是子类中的fun(),编译执行如下

从输出结果看,貌似跟我们想要的不一样,当我们把子类对象的指针传入时,调用的却依旧是父类的fun()函数。在C++中,使用父类指针或引用指向子类对象时会发生如下变化
    - 子类对象退化为父类对象
    - 只能访问父类中的成员
    - 可以直接访问被子类覆盖的同名成员

    在编译期间,编译器只能根据指针的类型判断所指的对象,赋值兼容性原则,编译器会认为父类指针指向的是父类对象,所以也就调用了父类中的同名函数。

    要解决这个问题,就需要引入面向对象中的多态,面向对象中多态的概念就是:根据实际的对象类型决定函数调用的具体目标
    - 父类指针(引用)指向父类对象,便调用父类中定义的函数
    - 父类指针(引用)指向子类对象,便调用子类中定义的重写函数

C++中支持多态的概念,通过virtual关键字对多态进行支持
    - 被virtual声明的函数在子类中重写后具有多态特性
    - 被virtual声明的函数叫虚函数

有了以上多态的知识,我们来改写下上边的代码

#include <iostream>

using namespace std;

class parent
{
public:
    virtual void fun()    //使用virtual关键字修饰fun函数,使其重写后具有多态特性
    {
	cout << "I'm parent "<< endl;
    }
};
class child : public parent
{
public:
    void fun()
    {
	cout << "I'm child " << endl;
    }
};

void fun (parent* p)	//定义一个全局函数
{
    p->fun();
}

void main()
{
    parent p;
    child c;

    fun(&p);	//传入父类对象指针,期望输出:I'm parent
    fun(&c);	//传入子类对象指针,期望输出:I'm child
	
    system("pause");
}

我们将父类中的fun()函数修饰为virtual虚函数,使其具有多态特性,编译执行结果如下,这个时候就是我们想要的结果了

多态的意义
    - 在程序运行过程中展现出动态的特性
    - 函数重写必须实现多态,否则没意义
    - 多态是面向对象组件化程序设计的基础特性

从理论上来说,编译过程我们可以分为静态连编动态连编
 
  - 静态连编:编译期间就能确定具体的函数调用,例如:函数重载
    - 动态连编:运行时才能确定具体的函数调用,例如:函数重写

总结
    - 函数重写只能发生在父类与子类之间
    - C++中通过virtual关键字来是支持多态
    - virtual修饰的虚函数可以表现出多态的特性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值