文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

C++中关于多态实现和使用方法

2024-04-02 19:55

关注

都说 C++ 是面向对象的语言,其中的面向对象主要包括三部分:继承,封装,多态。继承和封装我们之前就简单介绍过,这里主要对多态的使用方法做一个简单说明。

赋值兼容

赋值兼容说的是在使用基类对象的地方可以使用公有继承类的对象来代替。赋值兼容是一种默认的行为,不需要进行显式转换就能够实现。

就比如在派生类拷贝构造函数的参数初始化列表中,我们会直接使用派生类对象作为基类拷贝构造函数的参数,而不会报错,这就是赋值兼容的表现。赋值兼容主要表现在:

实例

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
private:
    char *num;
};
 
int main()
{
    STUDENT st("zhangsan",'x',"100");
    st.display();
    PERSON per = st;
    per.display();
    PERSON &per2 = st;
    per2.display();
    PERSON *per3 = &st;
    per3->display();
 
    return 0;
}

结果为:

The name is zhangsan
The sex is x
The num is 100
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The name is zhangsan
The sex is x

上边的程序可以看出,基类对象,引用和指针都可以使用派生类对象或者指针进行赋值,从而进行访问。

其实也可以将基类指针强制转换为派生类指针,进行访问,但这种形式绝不是赋值兼容:

int main()
{
    PERSON per("zhangsan",'x');
    per.display();
 
    STUDENT *st = static_cast<STUDENT *>(&per);
    st->display();
 
    return 0;
}

结果为:

The name is zhangsan
The sex is x
The name is zhangsan
The sex is x
The num is 夽@

上边的程序中,是将基类的指针强制转换派生类的指针,从而调用派生类的对象。

多态

C++ 中的多态主要说的是,在面向对象中,接口的多种不同的实现方式。

静多态

C++ 中的多态是接口多种不同的实现方式。而我们之前提到过的函数重载也是接口的多种不同的实现方式,因此也可以称之为多态,只是函数重载是在编译阶段通过 name mangling 实现的,所以叫做静多态。

动多态

而不在编译阶段而是在运行阶段决定的多态就称为动多态。动多态的形成条件为:

格式

class classname
{
    virtual datatype func(argu);
}

实例

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
protected:
    char *num;
};
 
class POSTGRADUATE:public STUDENT
{
public:
    POSTGRADUATE(char *name_ = "***",char sex_ = '*',char *num_ = "***",char *job_ = "***")
        :STUDENT(name_,sex_,num_),job(job_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
        cout<<"The job is "<<job<<endl;
    }
protected:
    char *job;
};
 
int main()
{
    POSTGRADUATE po("zhsangsan",'x',"100","paper");
    po.display();
 
    PERSON *per = &po;
    per->display();
    STUDENT *st = &po;
    st->display();
 
    return 0;
}

结果为:

The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper
The name is zhsangsan
The sex is x
The num is 100
The job is paper

在基类中声明虚函数时需要使用 virtual 关键字,在类外实现虚函数时,不用再加 virtual

在派生类中重新定义此函数的过程称为 override,此过程要求函数的要素全都不能发生改变,包括函数名,返回值类型,形参个数和类型,只有函数体可以改变

当基类中的函数成员被声明为 virtual 时,其派生类中完全相同的函数都会变为虚函数,原则上派生类中的虚函数不用使用 virtual 关键字,但是为了程序的可读性,可以在派生类的对应函数前加上 virtual

定义一个指向基类的指针,并使其指向其子类对象的地址,通过该指针调用虚函数,此时调用的就是指针变量指向的对象

子类中 override 的函数,可以为任意访问类型

通过多态就避免了赋值兼容的问题

override

在虚函数的使用中,需要在派生类中 override 基类中的虚函数,表明该函数是从基类 override 得到的,override 的含义表明:

而有时为了可读性,也为了防止编写时出错,可以通过在函数后添加 override 关键字表明这是 override 得到的。如上边的例子中:

virtual void display() override

使用上边的形式可以严格语法书写。

纯虚函数

对于一些抽象基类来说,我们并不需要在其中的虚函数中编写什么语句,因此可以将之写成纯虚函数。

class classname
{
    virtual datatype func(argu) = 0;
}

如上例所示,可以将 STUDENT 中的 display 函数定义为纯虚函数:

virtual void display() = 0;

只是此时不能够调用 STUDENT 中的该函数了。

对于纯虚函数而言:

含有虚函数的析构函数

含有虚函数的类,析构函数也应该声明为虚函数。

#include <iostream>
 
using namespace std;
 
class PERSON
{
public:
    PERSON(char *name_ = "***",char sex_ = '*')
        :name(name_),sex(sex_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
    }
    ~PERSON(){cout<<"PERSON"<<endl;}
protected:
    char *name;
    char sex;
};
 
class STUDENT:public PERSON
{
public:
    STUDENT(char *name_ = "***",char sex_ = '*',char *num_ = "100")
        :PERSON(name_,sex_),num(num_){}
    virtual void display()
    {
        cout<<"The name is "<<name<<endl;
        cout<<"The sex is "<<sex<<endl;
        cout<<"The num is "<<num<<endl;
    }
    ~STUDENT(){cout<<"STUDENT"<<endl;}
protected:
    char *num;
};
 
int main()
{
    {
    STUDENT st("zhsangsan",'x',"100");
    st.display();
    }
 
    cout<<"****************"<<endl;
 
    PERSON *p = new STUDENT("zhsangsan",'x',"100");
    p->display();
    delete p;
 
    return 0;
}

 结果为:

The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
PERSON

此时如果将析构函数声明为 virtual:

virtual ~PERSON(){cout<<"PERSON"<<endl;}

 结果为:

The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON
****************
The name is zhsangsan
The sex is x
The num is 100
STUDENT
PERSON

可以看出,对于堆对象来说,含有虚函数的类对象析构与栈对象析构是有所差别的。为了防止这种情况出现,最好是将含有虚函数的析构函数声明为 virtual。

注意事项

RTTI

(Run Time Type Identification,RTTI) 也叫运行时类型信息,也是通过多态实现的。

typeid

typeid 返回包含操作数数据类型信息的 type_info 对象的一个引用,信息中包括数据类型的名称。要使用 typeid,需要在函数中包含:

#include <typeinfo>
#include <iostream>
#include <typeinfo>
 
using namespace std;
 
typedef void (*Func)();
 
class Base1
{
};
 
class Base2
{
public:
    virtual ~Base2(){}
};
 
class Derive1:public Base1
{
};
 
class Derive2:public Base2
{
};
 
int main()
{
    cout<<typeid(int).name()<<endl;
    cout<<typeid(double).name()<<endl;
    cout<<typeid(char *).name()<<endl;
    cout<<typeid(char **).name()<<endl;
    cout<<typeid(const char *).name()<<endl;
    cout<<typeid(const char * const ).name()<<endl;
    cout<<"********************"<<endl;
 
    cout<<typeid(Func).name()<<endl;
    cout<<typeid(Base1).name()<<endl;
    cout<<typeid(Base2).name()<<endl;
    cout<<typeid(Derive1).name()<<endl;
    cout<<typeid(Derive2).name()<<endl;
    cout<<"********************"<<endl;
 
    Derive1 d;
    Base1 &b = d;
    cout<<typeid(b).name()<<endl;
    cout<<typeid(d).name()<<endl;
    cout<<"********************"<<endl;
 
    Derive2 dd;
    Base2 &bb = dd;
    cout<<typeid(bb).name()<<endl;
    cout<<typeid(dd).name()<<endl;
    cout<<"********************"<<endl;
 
    Base1 *p = &d;
    cout<<typeid(p).name()<<endl;
    cout<<typeid(*p).name()<<endl;
    cout<<typeid(d).name()<<endl;
    cout<<boolalpha<<(typeid(*p)== typeid(d))<<endl;
    cout<<"********************"<<endl;
 
    Base2 *pp = &dd;
    cout<<typeid(pp).name()<<endl;
    cout<<typeid(*pp).name()<<endl;
    cout<<typeid(dd).name()<<endl;
    cout<<boolalpha<<(typeid(*pp)== typeid(dd))<<endl;
    cout<<"********************"<<endl;
 
    return 0;
}

结果为:

i
d
Pc
PPc
PKc
PKc
********************
PFvvE
5Base1
5Base2
7Derive1
7Derive2
********************
5Base1
7Derive1
********************
7Derive2
7Derive2
********************
P5Base1
5Base1
7Derive1
false
********************
P5Base2
7Derive2
7Derive2
true
********************

从上边可以看出,在 typeid 涉及到虚函数时,利用指针得到的结果就可能出现差别,因此在使用 typeid 时需要注意:

Notice how the type that typeid considers for pointers is the pointer type itself(both a and b are of type class Base *). However, when typeid is applied to objects(like *a and *b) typeid yields their dynamic type (i.e. the type of their most derived complete object).

If the type typeid evaluates is a pointer preceded by the dereference operator (*), and this pointer has a null value, typeid throws a bad_typeid exception.

typecast

在之前的文章中,我们简单介绍过 static_cast,reininterpreter_cast,const_cast 的用法,当时还剩下一个 dynamic_cast。

dynamic_cast 是一种运行时的类型转换方式,因此用于运行时的转换判断。该转换能够检查指针所指向的类型,然后判断这一类型与转换的目标类型是否相同,如果是返回对象地址,如果不是返回 NULL。

dynamic_cast 常用于多态继承中,来判断父类指针的真实指向。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class A
{
public:
    virtual ~A(){}
};
 
class B:public A
{
};
 
class C:public A
{
};
 
class D
{
};
 
int main()
{
    B b;
    A *pa = &b;
 
    B *pb = dynamic_cast<B*>(pa); //成功
    cout<<pb<<endl;
 
    C *pc = dynamic_cast<C*>(pa); //成功 安全
    cout<<pc<<endl;
 
    D *pd = dynamic_cast<D*>(pa); //成功 安全
    cout<<pd<<endl;
 
    pb = static_cast<B*>(pa); //成功
    cout<<pb<<endl;
 
    pc = static_cast<C*>(pa); //成功 不安全
    cout<<pc<<endl;
 
    pb = reinterpret_cast<B*>(pa); //成功 不安全
    cout<<pb<<endl;
 
    pc = reinterpret_cast<C*>(pa); //成功 不安全
    cout<<pc<<endl;
 
    pd = reinterpret_cast<D*>(pa); //成功 不安全
    cout<<pd<<endl;
 
    return 0;
}

结果为:

0x61fe8c
0
0
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c
0x61fe8c

在上述几种类型转换中,dynamic_cast 的转换用法算是比较安全的,因为这种转换方式是先比较再返回的,而 reininterpreter_cast 则是最不安全的,因为这种转换方式不做类型检查直接将源类型重新解释为目标类型,容易出错。

但 dynamic_cast 的目标类型必须是类的指针或者引用。

多态实现

虚函数

之前介绍函数重载,也就是静多态是通过 name mangling 实现的,而 C++ 的动多态则是通过虚函数表(virtual table)实现的。这个表中主要是一个类的虚函数的地址表,这张表包含了继承,override 的情况。在实际使用中,在含有虚函数的类对象中,该表会被分配到该对象的内存中,用于指明实际所要调用的函数。

C++ 编译器保证虚函数表的指针存在于实例对象的最前面,这表示实例对象的地址就是该虚函数表的位置,然后就可以遍历其中的函数指针,进行调用。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    void f() { cout << "Base::f" << endl; }
    void g() { cout << "Base::g" << endl; }
    void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    return 0;
}

结果为:

sizeof(Base) = 4
sizeof(b) = 4

如果基类中存在虚函数,则为:

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(b) = 8

可以看出有虚函数的基类的大小会比没有虚函数的基类大小多出一个指针的大小。这个多出来的指针就是虚函数表的位置。

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Base b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(b) = 8
0x61fe94
0x4029f0
0x402a24
0x402a58
0x3a434347
Base::f
Base::g
Base::h

上面的程序中:

一般继承(no override)

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
class Derive:public Base
{
    virtual void f1() { cout << "Base::f1" << endl; }
    virtual void g1() { cout << "Base::g1" << endl; }
    virtual void h1() { cout << "Base::h1" << endl; }
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Derive b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
    cout<<*((int **)*(int *)(&b)+4)<<endl;
    cout<<*((int **)*(int *)(&b)+5)<<endl;
    cout<<*((int **)*(int *)(&b)+6)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+3);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+4);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+5);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402ae0
0x402b14
0x402b48
0x402b94
0x402bc8
0x402bfc
0x3a434347
Base::f
Base::g
Base::h
Base::f1
Base::g1
Base::h1

在上边的例子中,派生类没有 override 任何父类的函数,并又重新定义了几个虚函数,因此对于派生类来说:

一般继承(override)

#include <iostream>
#include <typeinfo>
 
using namespace std;
 
class Base
{
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
private:
    int data;
};
 
class Derive:public Base
{
    virtual void f() { cout << "Base::f1" << endl; }
    virtual void g1() { cout << "Base::g1" << endl; }
    virtual void h1() { cout << "Base::h1" << endl; }
};
 
typedef void (*FUNC)(void);
 
int main()
{
    Derive b;
    cout<<"sizeof(Base) = "<<sizeof(Base)<<endl;
    cout<<"sizeof(Derive) = "<<sizeof(Derive)<<endl;
    cout<<"sizeof(b) = "<<sizeof(b)<<endl;
 
    cout<<&b<<endl;
    cout<<*((int **)*(int *)(&b)+0)<<endl;
    cout<<*((int **)*(int *)(&b)+1)<<endl;
    cout<<*((int **)*(int *)(&b)+2)<<endl;
    cout<<*((int **)*(int *)(&b)+3)<<endl;
    cout<<*((int **)*(int *)(&b)+4)<<endl;
    cout<<*((int **)*(int *)(&b)+5)<<endl;
 
    FUNC pf = NULL;
    pf = (FUNC)*((int **)*(int *)(&b)+0);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+1);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+2);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+3);
    pf();
    pf = (FUNC)*((int **)*(int *)(&b)+4);
    pf();
 
    return 0;
}

结果为:

sizeof(Base) = 8
sizeof(Derive) = 8
sizeof(b) = 8
0x61fe94
0x402b54
0x402ad4
0x402b08
0x402b88
0x402bbc
0x3a434347
Base::f1
Base::g
Base::h
Base::g1
Base::h1

在上边的例子中,派生类 override 了父类的 f 函数,并又重新定义了几个虚函数,因此对于派生类来说:

过程推断

Base *b = new Derive();
b->f();

这段代码的实际过程为:

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯