首页 > 编程语言 >C++的多态性

C++的多态性

时间:2023-07-17 19:24:06浏览次数:47  
标签:函数 Point 多态性 基类 多态 C++ double 派生类

C++面向对象中的多态性是指同一种类型的对象在不同的情况下表现出不同的行为。所谓消息是指对类成员函数的调用,不同的行为是指不同的实现,也就是调用了不同的函数,从广义上说,多态性是指一段程序能够处理多种类型对象的能力。

在C++中,虚函数是指在基类中声明的函数,在派生类中可以被重写,从而实现不同的行为。当使用基类指针或引用调用虚函数时,实际调用的是派生类中重写的函数,而不是基类中的函数。

通过使用虚函数,可以实现运行时多态性,即在程序运行时才确定调用哪个函数,而不是在编译时确定。这种多态性可以提高程序的灵活性和可扩展性,使得程序更加易于维护和扩展。

总之,C++面向对象中的多态性和虚函数是非常重要的概念,对于理解和使用C++的面向对象特性非常有帮助。

多态性的概念
派生一个类的原因并非总是为了继承或添加新成员,有时是为了重新定义基类的成员,使基类成员“获得新生”

面向对象程序设计的真正力量不仅仅是继承,而是允许派生类对象像基类对象一样处理,其核心机制就是多态和动态联编;

多态的实现

在C++中,这种多态性可以通过重载多态(函数和运算符重载)强制多态(类型强制转换)类型参数化多态(模板)包含多态(继承及虚函数)四种形式来实现。

(1)重载多态

重载是多态性的最简单形式,分为函数重载和运算符重载。

重定义已有的函数称为函数重载。在C++中既允许重载一般函数,也允许重载类的成员函数。如对构造函数进行重载定义,可使程序有几种不同途径对类对象进行初始化;

C++允许为类重定义已有运算符的语义,使系统预定义的运算符可操作于类对象。如流插入(<<)运算符和流提取(>>)运算符(原先语义是位移运算);

(2)强制多态

强制多态也称为类型转换。如C++定义了基本数据类型之间的转换规则,即:

char-short-int-unsigned-long-unsigned
long-float-double-long double

同时,可以在表达式中使用3种强制类型转换表达式:
1.static_cast< T >(E); 2.T(E); 3.(T)E ,其中E代表运算表达式,T代表一个类型表达式。上诉任意一种都可改变编译器所使用的规则,以便按自己的意愿进行所需的类型强制。

但是强制多态使类型检查复杂化,尤其在允许重载的情况下,导致无法消解的二义性。

(3)类型参数化多态

参数化多态:即将类型作为函数或类的参数,避免了为各种不同的数据类型编写不同的函数或类,减轻了设计者负担,提高了程序设计的灵活性。

模板是C++实现参数化多态性的工具,分为函数模板和类模板。类模板中的成员函数均为函数模板,因此函数模板是为类模板服务的。

(4)包含多态

C++中采用虚函数实现包含多态。虚函数为C++提供了更为灵活的多态机制,这种多态性在程序运行时才能确定,因此虚函数是多态性的精华,至少含有一个虚函数的类称为多态类。包含多态在面向对象程序设计中使用十分频繁。

派生类继承基类的所有操作,或者说,基类的操作能被用于操作派生类的对象。当基类的操作不能适应派生类时,派生类就需要重载基类的操作。

静态联编
联编(binding)又称绑定,就是将模块或者函数合并在一起生成可执行代码的处理过程,同时对每个模块或者函数分配内存地址,并且对外部访问也分配正确的内存地址

在编译阶段就将函数实现和函数调用绑定起来称为静态联编(static binding)。静态联编在编译阶段就必须了解所有的函数或模块执行所需要的信息,它对函数的选择是基于指向对象的指针(或者引用)的类型。C语言中,所有的联编都是静态联编,C++中一般情况下联编也是静态联编。

#include<iostream>
#include<string>
#include<algorithm>
#include<vector> 
using namespace std; 

class Point { //Point类表示平面上的点
    private:
        double x,y; //坐标值
    public:
         Point(double x1=0,double y1=0) : x(x1),y(y1) { } //构造函数
        double area() {
            return 0; //计算面积 
        } 
};

class Circle:public Point { //Circle类表示圆
    private:
        double r; //半径
    public:
        Circle(double x,double y,double r1) : Point(x,y),r(r1) { } //构造函数
        double area() {
            return 3.14*r*r; //计算面积 
        } 
};

int main()
{
    Point a(2.5,2.5);
    Circle c(2.5,2.5,1);
    cout<<"Point area="<<a.area()<<endl; //基类对象
    cout<<"Circle area="<<c.area()<<endl; //派生类对象
    Point *pc=&c,&rc=c; //基类指针,引用指向或引用派生类对象
    cout<<"Circle area="<<pc->area()<<endl; //静态联编基类调用
    cout<<"Circle area="<<rc.area()<<endl; //静态联编基类调用
    return 0; 
}

动态联编
在程序运行的时候才进行函数实现和函数调用的绑定称为动态联编(dynamic binding)

如果在编译“Point *pc=&c”时,只根据兼容性规则检查它的合理性,即检查它是否符合派生类对象的地址可以赋给基类的指针的条件。至于“pc->area()”调用哪个函数,等到程序运行到这里再决定

如果希望“pc->area()”调用Circle::area(),也就是使类Point的指针pc指向派生类函数area的地址,则需要将Point类的area函数设置成虚函数

虚函数的定义形式为:

virtual double area() { return 0; } //计算面积

例:动态联编举例

#include<iostream>
#include<string>
#include<algorithm>
#include<vector> 
using namespace std; 

class Point { //Point类表示平面上的点
    private:
        double x,y; //坐标值
    public:
         Point(double x1=0,double y1=0) : x(x1),y(y1) { } //构造函数
        virtual double area() {
            return 0; //计算面积 
        } 
};

class Circle:public Point { //Circle类表示圆
    private:
        double r; //半径
    public:
        Circle(double x,double y,double r1) : Point(x,y),r(r1) { } //构造函数
        double area() { //虚函数 
            return 3.14*r*r; //计算面积 
        } 
};

int main()
{
    Point a(2.5,2.5);
    Circle c(2.5,2.5,1);
    cout<<"Point area="<<a.area()<<endl; //基类对象
    cout<<"Circle area="<<c.area()<<endl; //派生类对象
    Point *pc=&a; //基类指针指向基类对象 
    cout<<"Circle area="<<pc->area()<<endl; 
    pc=&c; //基类指针指向派生类对象 
    cout<<"Circle area="<<pc->area()<<endl; //动态联编
    return 0; 
}

当编译器编译含有虚函数的类时,将为它建立一个虚函数表VTABLE(virtual table),它相当于一个指针数组,存放每个虚函数的入口地址。编译器为该类增加一个额外的数据成员,这个数据成员是一个指向虚函数表的指针,通常称为vptr

Point类只有一个虚函数area,所以虚函数表只有一项。如下图(a)是Point对象UML示意

如果派生类Circle没有重写这个虚函数area,则派生类的虚函数表里的元素所指向的地址就是基类Point的虚函数area的地址。如果派生类Circle重写这个虚函数area,这时编译器将派生类虚函数表里的vptr指向Circle::area(),即指向派生类area虚函数的地址。如图(b)

 

当调用虚函数时,先通过vptr找到虚函数表,然后再找出虚函数的真正地址,再调用它

**派生类能继承基类的虚函数表,而且只要是和基类同名(参数也相同)的成员函数,无论是否使用virtual声明,它们都自动成为虚函数。**如果派生类没有改写继承基类的虚函数,则函数指针调用基类的虚函数。如果派生类改写了基类的虚函数,编译器将重新为派生类的虚函数建立地址,函数指针会调用改写以后的虚函数
例:动态联编举例

#include<iostream>
#include<string>
#include<algorithm>
#include<vector> 
using namespace std; 

class Base {
    public:
        virtual void print() { //虚函数 
            cout<<"Base"<<endl;
        }
};

class Derived: public Base {
    public:
        void print() { //虚函数 
            cout<<"Derived"<<endl;
        }
};

void display(Base *p) {
            p->print();
}

int main()
{
    Derived d;
    Base b;
    display(&d); //派生类对象,输出"Derived" 
    display(&b); //基类对象,输出"Base" 
    return 0;
}

虚函数的调用规则是:根据当前对象,优先调用对象本身的虚成员函数。这和名字支配规律类似,不过虚函数的动态联编的,是在运行时(通过虚函数表中的函数地址)“间接”调用实际上欲联编的函数。

 

标签:函数,Point,多态性,基类,多态,C++,double,派生类
From: https://www.cnblogs.com/zzzsj/p/17560945.html

相关文章

  • C++笔记(2)——函数
    六.函数6.1函数基础一个典型的函数(function)定义包括:返回类型(returntype)、函数名字,由0或多个形参(parameter)组成的列表以及函数体。我们通过调用运算符来执行函数,形式为"()"。函数调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调......
  • C++中的异常处理详细说明
    看代码的过程中,经常看到try{}catch{}语句块,而且还经常性的看到这样的语句try{//保护代码}catch(...){//处理任何异常的代码}刚开始我对catch(...)非常困惑,因为C#中并没有这样的用法.所以,特意来了解学习一下C++中的异常处理方式通常来说,try{}catch{}块中,try......
  • C++ 异常处理
     异常是程序在执行期间产生的问题。C++异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。异常提供了一种转移程序控制权的方式。C++异常处理涉及到三个关键字:try、catch、throw。throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。......
  • 常用语言的线程模型(Java、go、C++、python3)
    背景知识软件是如何驱动硬件的?硬件是需要相关的驱动程序才能执行,而驱动程序是安装在操作系统内核中。如果写了一个程序A,A程序想操作硬件工作,首先需要进行系统调用,由内核去找对应的驱动程序驱使硬件工作。而驱动程序怎么让硬件工作的呢?驱动程序作为硬件和操作系统之间的媒介,可以......
  • Java python C++
    Java和Python的区别编程范式:Java是一种面向对象的编程语言,而Python支持多种编程范式,包括面向对象、函数式和命令式等。这意味着Python在某些情况下可以比Java更简洁、易于理解和编写。代码可读性:Python是一种非常易于阅读和编写的编程语言,其语法和代码风格非常清晰......
  • 使用OpenCV中的DNN模块进行人脸识别的官方示例代码(C++版本):
    chatgpt生成#include<opencv2/core.hpp>#include<opencv2/dnn.hpp>#include<opencv2/imgproc.hpp>#include<opencv2/highgui.hpp>usingnamespacecv;usingnamespacednn;intmain(){//加载模型和配置文件Stringmodel_path="pa......
  • C++ vector使用方法
    WARNING!本博文为算法笔记,恐说明错误,不建议参考本文膜拜大佬教程看不懂的教程......
  • 进入C++
    helloeveryone!现在由我来带大家进入编程语言C++的学习相信大家有一点C的基础的,以下是一些对初学者困惑的解答和建议:为什么要将头文件写到写到程序里边呢?因为此时涉及到程序和外面世界的通信,将源代码文件和头文件组合成一个复合文件,编译的下一阶段需要该文件命名空间可以......
  • C++内存泄漏检测
    #pragmaonce#include<map>//TracerNew.hvoid*operatornew(size_tsize,constchar*file,longline);void*operatornew(size_tsize);voidoperatordelete(void*p);classTracerNew{ classTracerNewInfo { public: TracerNewInfo(constchar*......
  • 关于c++的右值引用
    引用Areferencecanbethoughtofasanameofanobject.左值引用:引用左值.右值引用:引用右值,用于参数传递,函数返回值,表达式中间结果.类似于常量引用,但是可以被修改.(左值)引用类型的变量只能绑定到左值表达式,只有const引用可以绑定到右值.右值引......