C++ 多态的表现形式:重载与虚函数

多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说:允许将子类类型的指针赋值给父类类型的指针(一个接口,多种方法)。
C++ 支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性(静态多态):通过重载函数实现
b、运行时多态性(动态多态):通过虚函数实现。

多态的作用

那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

一、函数重载的概念

     C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,即函数的参数列表不同,也就是说用同一个运算符完成不同的运算功能。这就是重载函数。重载函数常用来实现功能类似而所处理的数据类型不同的问题。

二、函数重载的作用:

重载函数通常用来在同一个作用域内 用同一个函数名 命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

重点:C++中const用于函数重载

(1)const是函数类型的一部分,在实现部分也要带该关键字。

(2)const关键字可以用于对重载函数的区分,主要常成员函数和非常成员函数之间的重载。

(3)常成员函数不能更新类的成员变量,也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。

(4)非常量对象也可以调用常成员函数,但是如果有重载的非常成员函数则会调用非常成员函数。

const修饰成员函数时的重载,一种情况下是允许重载,另一种允许,下面介绍允许情况:

class List{
private:
    int length;
    int weight;
public:
    List(int l, int w){
        length = l;
        weight = w;
    }
    int getLength() const;   //常成员函数
    int getWeight();         //非常成员函数
};

不允许情况

class List{
private:
    int length;
    int weight;
public:
    List(const int l,  const int w){
        length = l;
        weight = w;
    }
    List(int l, int w){
        length = l;
        weight = w;
    }
  
};

这里我们用的实参是int a,int b,那么这两个函数都不会改变a、b的值,这两个函数对于ab来说是没有任何区别的,所以不能通过编译,提示重定义。实际上他们没有区别,因为函数调用的时候,存在形实结合的过程,所以不管有没有const都不会改变实参的值。

但是:


class List{
private:
    int length;
    int weight;
    char *a;
public:
    void fun(char *a)  
    {  
      cout << "non-const fun() " << a;  
    }  
   
    void fun(const char *a)  
    {  
      cout << "const fun() " << a;  
    }  
   
  
};

​

 fun(char *a)和fun(const char *a)是一样的吗?答案是:不一样。因为char *a 中a指向的是一个字符串变量,而const char *a指向的是一个字符串常量,所以当参数为字符串常量时,调用第二个函数,而当函数是字符串变量时,调用第一个函数。

当然还有一种情况:


class List{
private:
    int length;
    int weight;
    char *a;
public:
    void fun(char *a)  
    {  
      cout << "non-const fun() " << a;  
    }  
   
    void fun(char * const a)  
    {  
      cout << "const fun() " << a;  
    }      
   
  
};

​

char *a和char * const a,这两个都是指向字符串变量,不同的是char *a是指针变量 而char *const a是指针常量,这就和int i和const int i的关系一样了,所以也会提示重定义。

对于引用,比如int &i 和const int & i 也是可以重载的,原因是第一个i引用的是一个变量,而第二个i引用的是一个常量,两者是不一样的,类似于上面的指向变量的指针的指向常量的指针。

三、c++函数重载的原理:

(一)
编译器在编译.cpp文件中当前使用的作用域里的同名函数时,根据函数形参的类型和顺序会对函数进行重命名(不同的编译器在编译时对函数的重命名标准不一样)但是总的来说,他们都把文件中的同一个函数名进行了重命名;

  • 在vs编译器中:
    根据返回值类型(不起决定性作用)+形参类型和顺序(起决定性作用)的规则重命名并记录在map文件中。
  • 在linux g++ 编译器中:
    根据函数名字的字符数+形参类型和顺序的规则重命名记录在符号表中;从而产生不同的函数名,当外面的函数被调用时,便是根据这个记录的结果去寻找符合要求的函数名,进行调用;

       例如void Swap(int a, int b)会被重命名为_Swap_int_intvoid Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,对于不同的返回值,实际上编译时不参与函数的命名,这也就为什么函数重载返回值不同不能作为重载一个重要原因,同时为了保持解析操作符或函数调用时,独立于上下文

不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此。

从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样

覆盖与重载的区别:

覆盖是指派生类中存在重新定义的函数,其函数名、参数列、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体(花括号中的部分)不同.

当派生类对象调用子类中该同名函数时会自动调用子类中的覆盖版本,而不是父类中的被覆盖函数版本,这种机制就叫做覆盖。
下面我们从成员函数的角度来讲述重载和覆盖的区别。
成员函数被重载的特征有:
1) 相同的范围(在同一个类中);
2) 函数名字相同;
3) 参数不同;
4) virtual关键字可有可无。

覆盖的特征有:
1) 不同的范围(分别位于派生类与基类);
2) 函数名字相同;
3) 参数相同;
4) 基类函数必须有virtual关键字。

隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

四、虚函数

C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现子类可以重写父类的虚函数实现子类的特殊化。

虚函数: 就是允许被其子类重新定义的成员函数,子类重新定义父类虚函数的做法,可实现成员函数的动态覆盖(Override)。

纯虚函数: 是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”

virtual void funtion()=0

抽象类: 包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能进行实例化。

纯虚函数的作用:

  1. 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
  2. 在很多情况下,基类本身生成对象是不合情理的。引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。纯虚函数也是一种“运行时多态”。

 

猜你喜欢

转载自blog.csdn.net/fsfsfsdfsdfdr/article/details/82848277