[note]C++ 多态

静态(编译时)多态——运算符重载

  • 思考:用“+”、“-”能够实现复数的加减运算吗?
    实现复数加减运算的方法 ——重载“+”、“-”运算符
//双目运算符重载实例
#include <iostream>
using namespace std;

class Complex
{
	int real_;
	int image_;

public:
	Complex(int r, int i) : real_(r), image_(i){};
	Complex operator+(const Complex &a) { return Complex(real_ + a.real_, image_ + a.image_); } //这里不能传一个匿名对象给引用,引用临时变量是无意义的。
	friend ostream &operator<<(ostream &os, const Complex &c);
};

ostream &operator<<(ostream &os, const Complex &c) { return os << c.real_ << '+' << c.image_ << 'i'; }

int main()
{
	// Complex c1(1,2);
	// cout << c1+Complex(2,3) << endl;
	cout << Complex(1, 2) + Complex(2, 3) + Complex(3, 4) << endl;//事实证明,加号重载本身并不需要引用就能实现。
}

运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型的数据时导致不同的行为。这种不同是在编译时就决定的,所以叫静态多态。

重载特性

根据新类型数据的实际需要,C++几乎允许重载全部的运算符,但只能够重载C++中已经有的。

不能重载的运算符:“.”、“.*”、“::”、“?:”
重载之后运算符的优先级和结合性都不会改变。

重载形式

  • 重载为类的非静态成员函数;
  • 重载为非成员函数(部分要friend)。

单目运算符重载

前置无形参,后置有形参

前置单目运算符重载规则

  • 如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。
  • 经重载后,表达式 U oprd 相当于 oprd.operator U()

注意:无操作数

后置单目运算符 ++和–重载规则

  • 如果要重载 ++或–为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中 oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数,且具有一个 int 类型形参。
  • 经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)

为了区别这两个,我们利用一个很棒的示例

#include <iostream>
using namespace std;
class Clock
{
public:
	Clock(int hour = 0, int min = 0, int sec = 0);
	void ShowTime() const;
	Clock & operator++();//
	Clock operator++(int);//这里的int在某种程度上只是一个标识符,可以匿名
private:
	int hour_, minute_, second_;
};
Clock::Clock(int hour, int minute, int second)
{
	 if (0 <= hour && hour < 24 && 0 <= minute && minute < 60
        && 0 <= second && second < 60) {
        this->hour_ = hour;
        this->minute_ = minute;
        this->second_ = second;
    } else
        cout << "Time error!" << endl;
}

void Clock::ShowTime() const
{
	cout << hour_ << ' : ' << minute_  << ':' << second_ << endl;
}

Clock & Clock::operator++()
{
	second_++;
	if (second_ >= 60)//由于使用的是改变后的值,所以要考虑进位的问题。而另外由于后置自增没有考虑进位,所以这里需要使用'>='进行判断
	{
		second_ -= 60, minute_++;
		if (minute_ >= 60)
		{
			minute_ -= 60, hour_++;
		}
	}
	return *this;
}//++clock  返回变化后的原对象,所以返回值可以直接使用,从而可以使用传引用。

Clock Clock::operator++(int)//这个叫后置:clock++,会产生副本
{
	Clock old = *this;
	++(*this);
	return old;
}

运算符重载为非成员函数

有些运算符不能重载为成员函数,例如二元运算符的左操作数不是对象,或者是不能由我们重载运算符的对象

重载为非成员函数时

函数的形参代表依自左至右次序排列的各操作数。

  • 参数个数=原操作数个数(后置++、–除外。会多一个标识符,且标识符可以匿名)

  • 至少应该有一个自定义类型的参数,否则无意义。

  • 友元:

    1. 双目运算符重载为非成员函数时,重载为两个参数的友元。通常参数为const&型,可以减少copy的开销。
    2. 如果在运算符的重载函数中需要操作新类对象的私有成员,可以将此函数声明为该类的友元。
    3. 如果在运算符重载函数中需要对已有类成员函数进行重载,可以将该函数定义为新类的友元函数,特别注意 cout

运算符重载为非成员函数的形式

原表达式 等价的重载形式
oprd1 B oprd2 operator B(oprd1,oprd2)
B oprd operator B(oprd)
后置单目 operator B(oprd,0 )

将+、-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用。

class Complex
{
    /*-----------------*/
    friend Complex operator+(const Complex &c1, const Complex &c2);
    friend Complex operator-(const Complex &c1, const Complex &c2);
}
Complex operator+(const Complex &c1, const Complex &c2){ return Complex(c1.real_+c2.real_, c1.image_+c2.image_)};

将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream引用,右操作数为复数类的常引用,返回std::ostream 引用,用以支持下面形式的输出:

cout << a << b;
该输出调用的是:

operator << (operator << (cout, a), b);

运行时多态

利用virtual关键字声明虚函数,避免编译时binding。

实现机制:虚表

每一个多态类都有一个虚函数表,其中有当前类各个虚函数的入口地址,每个对象有指向该类虚表的指针(这说明多态类比普通类的对象多一个成员即虚表指针)。

由于派生类对象可以隐式转化称基类对象,所以用基类指针调用派生类的成员函数的时候,全部调用都是基类成员函数。

#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
    `virtual` void display() const {
        cout << "Base1::display()" << endl;
    }
};
class Base2: public Base1 { //公有派生类Base2定义
public:
    void display() const {
        cout << "Base2::display()" << endl;
    }
};
class Derived: public Base2 { //公有派生类Derived定义
public:
    void display() const {
        cout << "Derived::display()" << endl;
    }
};

void fun(Base1 *ptr) {  //参数为指向基类对象的指针
    ptr->display();     //"对象指针->成员名"
}
int main() {    //主函数
    Base1 base1;    //声明Base1类对象
    Base2 base2;    //声明Base2类对象
    Derived derived;    //声明Derived类对象

    fun(&base1);    //用Base1对象的指针调用fun函数
    fun(&base2);    //用Base2对象的指针调用fun函数
    fun(&derived);     //用Derived对象的指针调用fun函数

    return 0;
}
//输出结果:
//无virtual:
Base1::display()
Base1::display()
Base1::display()

//有virtual
Base1::display()
Base2::display()
Derived::display()

虚函数

定义:用virtual关键字说明的函数

作用

虚函数是实现运行时多态性基础

C++中的虚函数是动态绑定的函数,其实现机制是对每一个虚函数表。

虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。

  • 一般成员函数可以是虚函数
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数

一般虚函数成员

虚函数的声明

virtual 函数类型 函数名(形参表);

虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。

在派生类中可以对基类中的成员函数进行覆盖。
虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。

虚函数的覆盖

一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。

但派生类可以不显式地用virtual声明虚函数,这时就触发虚函数的判断机制:

  • 名称
  • 参数个数及对应参数类型;
  • 相同的返回值,或类型兼容基类指针和基类引用的返回值

但凡事总会失误,时常少一个const就会出现难以检查的错误。因此我们引入override关键字。

补充:

  • 派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。。
原创文章 42 获赞 17 访问量 1523

猜你喜欢

转载自blog.csdn.net/weixin_45502929/article/details/105461073