本文主要介绍C++面向对象编程中的多态的手段之一运算符重载,讲清运算符重载的本质,以及通过代码实现一些常用的运算符重载。
目录
1 运算符重载的本质
2 运算符重载格式
3 运算符重载分类
3.1 重载为类的成员函数
3.1.1 双目运算符重载
3.1.2 单目运算符重载
3.2 重载为类的全局友元函数
3.3 两种重载方式对比
4 运算符重载规则
5 一些运算符的重载
5.1 输入输出运算符的重载
5.2 关系运算符重载
5.3 赋值运算符的重载
5.4 类型转换运算符重载
1 运算符重载的本质
运算符重载(operator overload)是对已有的常规运算符赋予多重含义,使用相同的运算符,通过提供操作不同数据类型实现不同的行为。由于运算符的本质是C++封装的类的成员函数,因此运算符重载的本质是函数重载。
比如 对于运算符+,常规支持一般的int等数据类型,我们可以重载让其两个对象相加,赋予新的含义。
2 运算符重载格式
返回类型 operator 运算符(参数列表)
{
函数体;
}
operator 为关键字,运算符重载只能重载C++允许的运算符。
3 运算符重载分类
3.1 重载为类的成员函数
根据操作数的多少会分为单目运算符的重载和双目运算符的重载。双目运算符重载为类的成员函数,则它有两个操作数:左操作数是对象本身的数据,右操作数则通过运算符重载函数的参数表来传递。
3.1.1 双目运算符重载
调用格式为:
左操作数.运算符重载函数(右操作数)
以下代码是运算符 - + * << 的重载:
#include <iostream>
#include <fstream>
class Complex
{
private:
double real;
double imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// 运算符+重载
Complex operator+(const Complex &other) const
{
double newReal = real + other.real;
double newImag = imag + other.imag;
return Complex(newReal, newImag);
}
// 运算符-重载
Complex operator - (const Complex & other) const
{
return Complex((real-other.real),(imag-other.imag));
}
// 运算符*重载
Complex operator * (const Complex & other) const
{
return Complex((real * other.real),(imag * other.imag));
}
// 运算符<<重载,用于输出对象
friend std::ostream & operator << (std::ostream &os, const Complex &c)
{
os << "(" << c.real << ", " << c.imag << ")";
return os;
}
};
int main()
{
Complex a(1.0, 2.0);
Complex b(3.0, 4.0);
Complex c = a + b; // 使用运算符+进行复杂数相加
std::cout << "a+b: " << c << std::endl;
Complex d = a - b;
std::cout << "a-b: " << d << std::endl;
Complex f = a - b;
std::cout << "a*b: " << f << std::endl;
return 0;
}
运行结果:
3.1.2 单目运算符重载
如果是单目运算符重载为类的成员函数,则要分为前置(++i)和后置(i++)运算符。
如果是前置运算符,则它的操作数是函数调用者,函数没有参数。
调用格式为:
操作数.运算符重载函数()
以下代码是运算符++i和i++的重载:
#include <iostream>
#include <fstream>
class Complex
{
private:
double real;
double imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// ++i
Complex operator ++ ()
{
++(this->real);
++(this->imag);
return *this;
}
// i++
Complex operator ++ (int)
{
Complex cpx = *this;
(this->real)++;
(this->imag)++;
return cpx;
}
// 运算符<<重载,用于输出对象
friend std::ostream & operator << (std::ostream &os, const Complex &c)
{
os << "(" << c.real << ", " << c.imag << ")";
return os;
}
};
int main()
{
Complex a(1.0, 2.0);
Complex b(3.0, 4.0);
std::cout << "*************单目运算符重载i++*************" << std::endl;
std::cout << "a: " << a << " a++: " << a++ << std::endl;
std::cout << "a: " << a << " (a++): " << (a++) << std::endl;
std::cout << "*************单目运算符重载++i*************" << std::endl;
std::cout << "a: " << a << " ++a: " << ++a << std::endl;
std::cout << "a: " << a << " (++a): " << (++a) << std::endl;
return 0;
}
运行结果:
实现前置“++”时,数据成员进行自增运算,然后返回当前对象(即this指针所指 向的对象)。实现后置“++”时,创建了一个临时对象来保存当前对象的值,然后再将当前对象自增,最后返回的是保存了初始值的临时对象。
前置单目运算符和后置单目运算符的最主要区别是函数的形参,后置单目运 算符带一个int型形参,但它只起区分作用。
3.2 重载为类的全局友元函数
相比与重载为类的成员函数,重载为友元函数,只需要在其前面加friend`。
重载函数格式:
friend 返回类型 operator 运算符(参数列表)
{
函数体;
}
调用格式
operator 运算符(参数1,参数2)
以下是运算符-的友元函数的重载:
#include <iostream>
#include <fstream>
class Complex
{
private:
double real;
double imag;
public:
Complex(double r, double i) : real(r), imag(i) {}
// 运算符<<重载,用于输出对象
friend std::ostream & operator << (std::ostream &os, const Complex &c);
// 友元函数 运算符重载-
friend Complex operator - (const Complex & a, const Complex & b)
{
return Complex((a.real-b.real),(a.imag-b.imag));
}
};
std::ostream & operator<<(std::ostream &os, const Complex &c)
{
os << "(" << c.real << ", " << c.imag << ")";
return os;
}
int main()
{
{
Complex a(1.0, 2.0);
Complex b(3.0, 4.0);
std::cout << "*************全局函数为友元函数****************" << std::endl;
Complex d = a - b;
std::cout << "a-b: " << d << std::endl;
}
return 0;
}
运行结果:
运算符重载为类的友元函数时,由于没有隐含的this指针,因此,操作数的个数没有变化,所有的操作数都必须通过函数形参进行传递,函数的参数与操作数 自左自右保持一一对应,这是由友元函数的性质决定的。
3.3 两种重载方式对比
一般运算符重载既可以为类的成员函数,也可以为全局友元函数。
一般原则:
1,一般,单目运算符最好重载为类的成员函数,双目运算符最好重载为类的友元函数。
2,若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。
3,若运算符的操作数(尤其是第一个操作数)可能有隐式类型转换,则只能选用友元函 数。
4,具有对称性的运算符可能转换任意一端的运算对象,如:算术(a+b和b+a)、关系运算 符(a>b和b<a)等,通常重载为友元函数。
5,有4个运算符必须重载为类的成员函数:赋值=、下标[ ]、调用( )、成员访问->。
4 运算符重载规则
1,C++中不允许用户定义新的运算符,只能对已有的运算符进行重载,
2,重载后的运算符的优先级、结合性也应该保持不变,也不能改变其操作个数和语 法结构。
3,重载后的含义,与操作基本数据类型的运算含义应类似,如加法运算符“+”,重 载后也应完成数据的相加操作。
4,有5个运算符不可重载:类关系运算符“:”、成员指针运算符“*”、作用域运算 符“::”、sizeof运算符、三目运算符“?:”
5,运算符重载函数不能有默认参数,否则就改变了运算符操作数的个数,是错误的。
6,运算符重载函数既可以作为类的成员函数,也可以作为类的友元函数(全局函数)。
作为全局函数时,一般都需要在类中将该函数声明为友元函数。因为该函数大部分情况下都需要使用类的private成员。作为类的成员函数时,二元运算符的参数只有一个, 一元运算符不需要参数(参数没有任何意义,只是为了区分是前置还是后置形式:带 一个整型参数为后置形式)。因为有个参数(左操作数)是隐含的(隐式访问this指针 所指向的当前对象)。作为全局函数时,二元操作符需要两个参数,一元操作符需要一 个参数,而且其中必须有一个参数是对象,好让编译器区分这是程序员自定义的运算符, 防止程序员修改用于内置类型的运算符的性质。
5 一些运算符的重载
5.1 输入输出运算符的重载
friend istream& operator>>(istream& is, Person& p); // 输入运算符重载
friend ostream& operator<<(ostream& os, const Person& p); // 输出运算符重载
代码如下:
#include <iostream>
using namespace std;
class Person {
public:
Person() : name(""), age(0) {}
friend istream& operator>>(istream& is, Person& p);
friend ostream& operator<<(ostream& os, const Person& p);
private:
string name;
int age;
};
istream& operator>>(istream& is, Person& p) {
cout << "Enter name: ";
is >> p.name;
cout << "Enter age: ";
is >> p.age;
return is;
}
ostream& operator<<(ostream& os, const Person& p) {
os << "Name: " << p.name << ", Age: " << p.age;
return os;
}
int main() {
Person person;
cin >> person; // 从控制台读取数据
cout << person << endl; // 输出:Name: John, Age: 25
return 0;
}
运行结果:
5.2 关系运算符重载
关系运算符的重载通常用于实现自定义的数据类型。例如,我们可以为一个类定义一个大于、小于和等于运算符的重载函数。
(1)关系运算符都要成对的重载。如:重载了“<”运算符,就要重载“>” 运算符,反之亦然。
(2)“==”运算符应该具有传递性。如:a==b,b==c,则a==c成立。
(3)当成对出现运算符重载时,可以把一个运算符的工作委托给另一个运算 符。如:重载“!=”运算符是在“==”运算符的基础上,重载“<”运算 符 由重载过的“>”运算符来实现。
代码如下:
#include <iostream>
using namespace std;
class Person {
public:
Person(const string& n, int a) : name(n), age(a) {}
friend bool operator>(const Person& p1, const Person& p2);
friend bool operator<(const Person& p1, const Person& p2);
friend bool operator==(const Person& p1, const Person& p2);
private:
string name;
int age;
};
bool operator>(const Person& p1, const Person& p2) {
return p1.age > p2.age;
}
bool operator<(const Person& p1, const Person& p2) {
return p1.age < p2.age;
}
bool operator==(const Person& p1, const Person& p2) {
return p1.age == p2.age;
}
int main() {
Person person1("John", 25);
Person person2("Jane", 30);
cout << "person1 > person2: " << (person1 > person2) << endl; // 输出:person1 > person2: 1
cout << "person1 < person2: " << (person1 < person2) << endl; // 输出:person1 < person2: 1
cout << "person1 == person2: " << (person1 == person2) << endl; // 输出:person1 == person2: 0
return 0;
}
运行结果:
5.3 赋值运算符的重载
若类中没有显式定义赋值运算符的重载函数,编译器将自动提供一个默认的重载函 数,完成数据成员的“浅拷贝”。若类中的数据成员上需要附加额 外的信息,比如类中存在指针成员,指针成员指向新开辟的空间,此时需要重载赋值运算符,以实现“深拷贝”。
注意:赋值运算符只能被重载为类的成员函数。
赋值运算符的重载可以实现函数的深拷贝,深拷贝也可以通过深拷贝构造函数实现。
代码实现:
#include <iostream>
using namespace std;
class A
{
private:
int x, y;
public:
A(int x1 = 0, int y1 = 0)
{
x = x1;
y = y1;
}
A & operator = (const A & other)
{
this->x = other.x;
this->y = other.y;
return *this;
}
void show()
{
cout << "x=" << x << "," << "y=" << y << endl;
}
};
int main()
{
A a1(66, 2);
A a2(97,3);
a1 = a2;
a1.show();
return 0;
}
运行结果:
5.4 类型转换运算符重载
C++的一般基本的数据类型的强制转换为static_cast<data_type>(expression)
重载类型转换运算符的格式:
operator 类型名()
{
return static_cast<类型名>(expression)
};
注意:
(1)通过类型转换函数可以将自定义类型对象转换为基本类型数据。
(2)类型转换函数被重载的是类型名。在函数名前不能指定函数类型,函数也没 有参数。返回值类型是由函数名中指定的类型名来确定的(如果类型名为double, 则将类型数据转换为double类型返回)。
(3)因为函数转换的主体是本类的对象,类型转换运算符重载函数只能作为类的 成员函数。
代码如下:
#include <iostream>
using namespace std;
class A
{
private:
int x, y;
public:
A(int x1 = 0, int y1 = 0)
{
x = x1;
y = y1;
}
operator char()
{
return static_cast<char>(x);
}
void show()
{
cout << "x=" << x << "," << "y=" << y << endl;
}
};
int main()
{
A a1(66, 2);
a1.show();
char ch = a1;
cout << ch << endl;
return 0;
}
运行结果: