静态(编译时)多态——运算符重载
- 思考:用“+”、“-”能够实现复数的加减运算吗?
实现复数加减运算的方法 ——重载“+”、“-”运算符
//双目运算符重载实例
#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;
}
运算符重载为非成员函数
有些运算符不能重载为成员函数,例如二元运算符的左操作数不是对象,或者是不能由我们重载运算符的对象
重载为非成员函数时
函数的形参代表依自左至右次序排列的各操作数。
-
参数个数=原操作数个数(后置++、–除外。会多一个标识符,且标识符可以匿名)
-
至少应该有一个自定义类型的参数,否则无意义。
-
友元:
- 双目运算符重载为非成员函数时,重载为两个参数的友元。通常参数为
const&
型,可以减少copy的开销。 - 如果在运算符的重载函数中需要操作新类对象的私有成员,可以将此函数声明为该类的友元。
- 如果在运算符重载函数中需要对已有类成员函数进行重载,可以将该函数定义为新类的友元函数,特别注意
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
关键字。
补充:
- 派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。。