1.运算符重载
(1)运算符重载规则
C++几乎可以重载全部运算符,而且只能够重载C++中已经有的。
不能重载的运算符:. .* :: ?:
重载后运算符的优先级和结合性都不会改变
运算符重载是针对新类型数据的实际要求,对原有运算符进行适当的该在。
例如:
使复述类的对象可以用“+”运算符实现加法;
是时钟类对象可以用“++”运算符实现时间增加1秒。
重载为类的非静态成员函数;
重载为非成员函数。
(2)双目运算符重载为成员函数
重载为类成员的运算符函数定义形式:
函数类型 operator 运算符(形参)
{
......
}
参数个数=原操作数-1 (后置++、--除外)
双目运算符重载规则
如果要重载B为类成员函数,使之能够实现表达式oprd1 B oprd 2,其中oprd1为A类对象,则B应被重载为A类的成员函数,形参类应该是oprd2所属的类型。
经重载后,表达式oprd1 B oprd2 相当于 oprd1.operator B(oprd2)
例8-1 复数类加减法运算重载为成员函数
要求:
将+、-运算重载为复数类的成员函数。
规则:
实部和虚部分别相加减。
操作数:
两个操作数都是复数类的对象。
#include "iostream"
using namespace std;
class Complex {
public:
Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {}
//运算符+重载成员函数
Complex operator + (const Complex& c2) const;
//运算符-重载成员函数
Complex operator-(const Complex& c2)const;
void display() const;//输出复数
private:
double real;//复数实部
double imag;//复数虚部
};
Complex Complex::operator+(const Complex& c2)const {
//创建一个临时无名对象作为返回值
return Complex(real + c2.real, imag + c2.imag);
}
Complex Complex::operator-(const Complex& c2)const {
//创建一个临时无名对象作为返回值
return Complex(real - c2.real, imag - c2.imag);
}
void Complex::display() const {
cout << "(" << real << "," << imag << ")" << endl;
}
int main(){
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1="; c1.display();
cout << "c2="; c2.display();
c3 = c1 - c2;//使用重载运算符完成复数减法
cout << "c3=c1-c2="; c3.display();
c3 = c1 + c2;
cout << "c3=c1+c2="; c3.display();
return 0;
}
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)
(3)单目运算符重载为成员函数
前置单目运算符重载规则
如果重载U为类成员函数,使之能够实现表达式 U oprd,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。
经重载后,表达式U oprd 相当于 oprd.operator U()
后置单目运算符 ++ 和 --重载规则
如果要重载 ++ 或 --为类成员函数,使之能够实现表达式oprd++ 或 oprd-- ,其中oprd为A类对象,则++ 或 --应被重载为A类的成员函数,且具有一个int类型形参。
经重载后,表达式 oprd ++ 相当于 oprd.operator ++(0)
例:8-2重载前置++ 和后置++ 为时钟类成员函数
前置单目运算符,重载函数没有形参
后置++运算符,重载函数需要有一个int形参
操作数是时钟类的对象
实现时间增加1秒钟
//例题8-2 重置前置++ 和后置++ 为时钟类成员函数
#include "iostream"
using namespace std;
class Clock {//时钟类定义
public:
Clock(int hour = 0, int minute = 0, int second = 0);
void showTime() const;
//前置单目运算符重载
Clock& operator ++();
//后置单目运算符重载
Clock operator ++(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 = (hour + 1) % 24;
}
}
return *this;
}
Clock Clock::operator ++(int) {
//注意形参表中的整形参数
Clock old = *this;
++(*this);//调用前置“++”运算符
return old;
}
int main()
{
Clock myClock(23, 59, 59);
cout << "first time output:";
myClock.showTime();
cout << "Show myClock++:";
(myClock++).showTime();
cout << "Show ++myClock:";
(++myClock).showTime();
return 0;
}
运行结果:
first time output : 23 : 59 : 59
Show myClock++ : 23 : 59 : 59
Show ++myClock : 0 : 0 : 1
(4)运算符重载为非成员函数
运算符重载为非成员函数的规则
函数的形参代表依次从左至右次序排列的个操作数。
重载为非成员函数时
参数个数= 原操作数个数(后置++ 、除外)
至少应该有一个自定义类型的参数。
后置单目运算符++ 和–的重载函数,形参列表中只要怎嫁一个int,但不必写形参名。
如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
运算符重载为非成员函数的规则
双目运算符B重载后,表达式oprd1 B oprd2 等同于operator B(oprd1,oprd2)
前置运算符B重载后,表达式B oprd等同于operator B(oprd)
后置单目运算符++ 和–重载后,表达式oprd B等同于operaror B(oprd ,0)
例8-3:重载Complex的加减法和“<<”运算度为非成员函数
#include"iostream"
using namespace std;
class Complex {
public:
Complex(double r = 0.0, double i = 0.0): real(r), imag(i){}
friend Complex operator +(const Complex& c1, const Complex& c2);
friend Complex operator -(const Complex& c1, const Complex& c2);
friend ostream & operator << (ostream & out, const Complex & c);
private:
double real;//复数实部
double imag;//复数虚部
};
Complex operator +(const Complex& c1, const Complex& c2) {
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
Complex operator -(const Complex& c1, const Complex& c2) {
return Complex(c1.real - c2.real, c1.imag - c2.imag);
}
ostream& operator <<(ostream& out, const Complex& c) {
out << "(" << c.real << "," << c.imag << ")";
return out;
}
int main()
{
Complex c1(5, 4), c2(2, 10), c3;
cout << "c1=" << c1 << endl;
cout << "c2=" << c2 << endl;
c3 = c1 - c2;//使用重载运算符完成复数减法
cout << "c3 = c1 - c2=" << c3 << endl;
c3 = c1 + c2;//使用重载运算符完成复数加法
cout << "c3 = c1 + c2=" << c3 << endl;
return 0;
}
运行结果:
c1 = (5, 4)
c2 = (2, 10)
c3 = c1 - c2 = (3, -6)
c3 = c1 + c2 = (7, 14)
2.虚函数
初识虚函数
- 用virtual关键字说明的函数
- 虚函数时实现运行时多态性基础
- C++中的虚函数是动态绑定的函数
- 虚函数必须是非静态的成员杉树,虚函数经过派生之后,就可以实现运行过程中的多态。
例8-4:通过虚函数实现运行时多态
#include"iostream"
using namespace std;
class Base1 {//基类Base1定义
public:
virtual void display() const; //虚函数
};
void Base1::display()const {
cout << "Base1::display()" << endl;
}
class Base2 :public Base1 {//公有派生类Base2定义
public:
virtual void display() const;
};
void Base2::display()const {
cout << "Base2::display()" << endl;
}
class Derived :public Base2 {//公有派生类Derived定义
public:
virtual void display() const;
};
void Derived::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;
}
运行结果:
Base1::display()
Base2::display()
Derived::display()
什么函数可以是虚函数?
- 一般成员函数可以是虚函数
- 构造杉树不能是虚函数
- 析构函数可以是虚函数
一般虚函数成员
-
虚函数的声明
virtual 函数类型 函数名 (形参表);
-
虚函数声明只能出现在类定义中的函数原型声明中,而不能再成员函数实现的时候。
-
在派生类中可以对基类中的成员函数进行覆盖。
-
虚函数一般不声明为内敛函数,因为对虚函数的调用需要动态绑定,而对内敛函数处理事静态的。
虚析构函数
例8-5:虚析构函数举例
#include"iostream"
using namespace std;
class Base {
public:
virtual ~Base(); //不是虚函数
};
Base::~Base() {
cout << "Base destructor" << endl;
}
class Derived :public Base {
public:
Derived();
virtual ~Derived();//不是虚函数
private:
int* p;
};
Derived::Derived() {
p = new int(0);
}
Derived::~Derived() {
cout << "Derived desturactor" << endl;
delete p;
}
void fun(Base* b) {
delete b;//静态绑定,只会调用~Base
}
int main() {
Base* b = new Derived();
fun(b);
return 0;
}
运行结果:
Derived desturactor
Base destructor
虚表与动态绑定
虚表
- 每个多态类有一个虚表(virtual table)
- 虚表中有当前的各个虚函数的入口地址
- 每个对象有一个指向当前类的虚表的指针(虚指针vptr)
动态绑定的实现
- 构造函数中对象的虚指针赋值
- 通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址
- 通过该入口地址调用虚函数
3.抽象类
纯虚函数
-
纯虚函数是一个在基类中声明的虚函数,在基类中没有定义具体的操作内容,要求各派生类根据实际需要定义的版本。
纯虚函数的声明格式为:
virtual 函数类型 函数名(参数表) = 0;
抽象类的语法
-
带有纯虚函数的类称为抽象类:
class 类名 { virtual 类型 函数名(参数表)= 0; //其他成员...... }
抽象类作用
- 将有关数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
- 都与暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现
注意
- 抽象只能作为基类来使用。
- 不能定义抽象类的对象。
例8-6:抽象类举例
#include"iostream"
using namespace std;
class Base1 {
public:
virtual void display() const = 0;// 纯虚函数
};
class Base2 :public Base1 {
public :
virtual void display() const;//覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::dispaly()" << endl;
}
class Derived :public Base2 {
public:
virtual void display() const;//覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::diaplay()" << endl;
}
void fun(Base1* ptr) {
ptr->display();
}
int main() {
Base2 base2;
Derived derived;
fun(&base2);
fun(&derived);
return 0;
}
运行结果:
Base2::dispaly()
Derived::diaplay()
4.OVERRIDE与FINAL
override
- 多态行为的基础:基类声明虚函数,派生类声明一个函数覆盖该虚函数;
- 覆盖要求:函数签名(signature)完全一致。
- 函数签名包括:函数名 参数列表 const
c++11引入显示式函数覆盖,在编译期而非运行期捕获错误信息。在虚函数显示重载运用,编辑器会检查基类是否存在以虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会报错误
4.OVERRIDE与FINAL
override
- 多态行为的基础:基类声明虚函数,派生类声明一个函数覆盖该虚函数;
- 覆盖要求:函数签名(signature)完全一致。
- 函数签名包括:函数名 参数列表 const
c++11引入显示式函数覆盖,在编译期而非运行期捕获错误信息。在虚函数显示重载
运用,编辑器会检查基类是否存在以虚拟函数,与派生类中带有声明override的虚拟
函数,有相同的函数签名(signature);若不存在,则会报错误