运算符重载
编译环境 MSVC
一些不常用的运算符本文没有介绍,可以参考Microsoft的VC++手册,或者阅读C++ primer
本文中一些参数被定义为const typename&,是为了提高效率和避免误修改语义上不应该被修改的参数
- 在C++中,运算符是一种特殊的函数,支持重载
- 运算符函数有特殊的命名方式(operator),不同运算符有不同且固定的参数个数
- 有n个参数的运算符被称作n元运算符或n目运算符,常见的有一元(自运算)、二元(算术运算)、三元(?:)
- 基本的算术运算、逻辑运算、自运算(复合赋值)、指针成员访问运算(->、->*)、迭代运算、取地址运算(&)、申请空间(new、new[]、delete、delete[])都可以被重载
- 对象访问运算符(. 、.*)、类作用域访问符(:😃、三元运算符(?:)不能被重载
- 赋值运算符(=)、下标运算符([])、类型转换运算符(())、指针访问运算符(->、->*)只能被重载为类的非静态成员
- 其他所有运算符都可以被重载为类的非静态成员函数、友元函数、类外普通函数
- 运算符重载限定条件:
- 不能改变运算符优先级
- 不能改变运算符的结合顺序
- 不能改变运算符的参数个数
- 不能为运算符设置默认参数
- 不能创造新的运算符
- 不能改变运算符的原有含义(在类外重载基本算术运算符)
- 如果运算符被重载为类的函数成员,则只能是非静态成员
- 当运算符被重载为类成员函数时,实际参数会比被重载为友元或普通函数时少一个
- 成员运算符函数的一个隐藏参数为this指针,静态成员因为没有this指针所以无法作为运算符重载函数
- 普通运算符函数的参数为参与计算的对象,而类的成员运算符函数将有一个隐藏的this作为参与的对象之一
- 友元运算符函数可以直接访问类的私有成员
运算符重载函数设计
- =、[]、()、->、只能被重载为类的非静态成员函数
- 复合运算函数(自运算)+=、-=、/=、*=通常应该重载为类成员,但也可以为普通或友元函数
- 对于要改变对象状态或与自定义类型密切相关的运算符,应该被重载为成员(如上)
- 对于不需要返回左值且运算对象可互换位置的运算符函数(+、-、*、/)适合作为普通或友元函数
- 这是由于C++会在参数类型不匹配时尝试可能的强制类型转换,在作为类成员运算符函数重载时,对第二个参数的强制转换可能会导致其他运算出错
// 假设Complex类的operator+是一个成员函数(与上方定义不一致)
Complex c1,c2(1,2);
c1 = c2 + 2; // 语句正确,成员运算符函数会对第二个参数进行强制转换
// 由于2是int类型,在强制转换时匹配了构造函数Complex(int),因此被转换为了一个Complex类型
c1 = c2 + Complex(2); // 相当于该语句
// c1 = 2 + c2; // 错误语句,员运算符函数的第一个参数不会进行类型转换
c1 = Complex(2) + c2; // 正确调用
- 而普通和友元函数会对两个参数都进行转换
// 此处operator+为类外友元
Complex c1, c2(1, 2);
c1 = c2 + 2; // 语句正确
c1 = c2 + Complex(2); // 相当于该语句
c1 = 2 + c2; // 语句正确
c1 = Complex(2) + c2; // 相当于该语句
- 对于一些算术运算符(+、-、*、/),为了避免无意义语句(如:x1 + x2; 运算但没有接收返回值)改变参与运算的对象的成员值,可以定义为普通或友元函数并返回一个同类对象
Complex c1(1, 1);
c1 + 1; // 此语句不会改变c1的值,参考下方Complex类定义
Complex operator+(const Complex& x1, const Complex& x2)
{
// 没有改变this的值
return Complex(x1.real + x2.real, x1.image + x2.image);
}
- 类的成员重载运算符函数通常返回一个同类型的引用(*this)
Complex& operator+=(const Complex& x);
特殊运算符函数
自运算符++和–
- ++和–运算符有前缀( ++ a)和后缀(a ++ )两种情况,适合重载为类的成员函数
- 前缀操作可以返回一个同类对象的引用(运算后的结果),后缀可以返回一个同类对象(运算前的结果)
- C++内置的前缀运算返回引用,后缀返回值
- 由于两者只在返回值上有差异(类成员无参数,普通或友元一个参数),不进行参数上的区分会在调用函数时产生二义性错误
- C++采取在进行后缀运算时增加一个无用的形式参数(通常为int)解决该问题,该参数在函数原型和 函数定义中都不需要参数名,仅告知编译器这是一个后缀运算符
// 自增运算(前缀)
Complex& operator++()
{
this->real++;
return *this;
}
// 自增运算(后缀),int用于注明是一个后缀运算符,作为占位符
Complex operator++(int)
{
// 返回运算前的值
Complex _this(this->real, this->image);
this->real++;
return _this;
}
下标运算符[]
- C++内置下标运算符不具有下标越界检查功能,重载时可以添加下标检查
- []是一个二元运算符,第一个参数为this隐式传递,第二个为下标,下标可以是任何一种顺序存储的数据类型
- 下标运算符可以出现在赋值运算符=的左边或右边,因此时常返回引用,返回引用的函数的返回值可做左值和右值
- 下标运算符只能被重载为类的非静态成员函数
// 假设需要[]返回类中一个成员的下标
class Sets
{
public:
int& operator[](const int& index)
private:
int arr[100];
};
int& Sets::operator[](const int& index)
{
return this->arr[index];
}
int main()
{
// 此处省略声明和初始化Sets的对象s
// 由于返回一个引用,可以同时做左值和右值
s[10] = 10;
int i = s[10];
}
赋值运算符=
- 赋值运算符函数在类有指针成员时需要被定义,否则容易产生指针悬挂问题
- 赋值运算符只能被重载为类的非静态成员函数
- 在没有为类定义赋值运算符函数时,编译器会自动合成
- 在没有动态分配空间的成员的类中,可以不定义赋值运算符函数
- 在定义了赋值运算符函数、拷贝构造函数、析构函数的类中,编译器不会合成移动构造函数或移动赋值运算符函数
类型转换运算符()
- 类型转换运算符只能被重载为类的非静态成员函数
- 构造函数可以承担类型转换任务,当对一个类进行无关类型的赋值操作时,如果有参数对应无关类型的构造函数,赋值语句的右边实际上会生成一个临时对象,该对象的构造调用和无关类型相匹配的有参构造函数
// 作类型转换作用的构造函数
Complex(double r) :real(r), image(0)
{
}
int main()
{
Complex c1(1, 1);
c1 = 2; // 相当于c1 = Complex(2); 发生了隐式转换
}
- 要避免构造函数的隐式类型转换,在构造函数声明时加上explicit关键字,即显式的构造函数,默认是隐式implicit,会被用来隐式类型转换。
// 显式声明
explicit Complex(double r) :real(r), image(0)
{
}
int main()
{
Complex c1(1, 1);
c1 = 2; // 此时该语句会出错,构造函数被声明为需要显式调用
}
- 但是构造函数不能将类自己的对象转换为其他类型,此时需要类型转换运算符
- 类型转换运算符的命名格式:operator type() {…},不能在函数原型中声明返回值,必须返回一致的type类型
// 以下语句在Complex类的内部
// 向整数类型的类型转换
operator int()
{
return int(this->real);
}
// 向浮点数类型的类型转换
operator float()
{
return float(this->real);
}
// 向浮点数类型的类型转换
operator double()
{
return this->real;
}
- 一个类可以有多个类型转换运算符,但是必须注意可能引发的二义性问题
- C++的强制类型转换对于每个数据类型而言并无优先级,如把int转为double和float都是同级别的
- 假如一个类同时定义了向int、float和double和其他数值的类型转换运算符,在进行函数调用时编译器无法确定链接哪个函数
- 构造函数同样有这个问题,最好不要为一个类定义多个数值类型为参数的构造函数或类型转换函数,以及其他成员函数
void fn(long l){...}
int main()
{
Complex c(1, 1);
fn(c); // 由于参数类型是long,可以将Complex转换成int、float、double后再强制转换为long
// 编译器:那我究竟应该调用哪个转换函数?
}
函数调用运算符()
-
函数调用时和类型转换一样,使用(),但是在重载函数调用运算符定义时,需要加上参数表作区分
-
函数调用运算符只能被重载为类的非静态成员函数
-
函数调用运算符的意义是为类的对象提供一种函数调用风格的操作语句进行某些操作
-
在定义了函数调用运算符后,在对象名后加上参数表就是一个函数调用语句
-
定义格式:返回类型 operator() (agrs…) {…},参数不能有默认值
-
调用语句格式:对象名(args…);
// 函数调用运算符,模拟一个命令输入函数 Complex& operator() (const string& cmd) { if (cmd == "display") { this->display(); } else if (cmd == "reset") { this->real = 0; this->image = 0; } else { cout << "-------------------- help list --------------------" << endl; cout << " cmd display - print real and image" << endl; cout << " cmd reset - reset real and image to 0" << endl; cout << " cmd help - show method and cmds" << endl; cout << " method display - print real and image" << endl; cout << " method setImage - set image" << endl; cout << " method setReal - set real" << endl; cout << "---------------------------------------------------" << endl; } return *this; }
输入输出运算符>>和<<
- 输入运算符>>也被称为提取或析取运算符,输出运算符<<也被称为插入运算符
- 在没有定义自定义类型的输入输出运算符时,C++的默认输入输出流只支持输出内置类型
- 可以通过运算符重载实现自定义类型的流式输入和输出,是二元运算符
- 通常输入输出运算符返回输入输出流对象(istream和ostream)的引用
- 输入输出运算符函数的第一个参数是ostream或istream的引用,第二个参数是要输出对象的引用
- 因此不能被重载为类的成员函数(第一个参数必须是流对象),通常会被定义成类的友元
- 由于流式传输的特性,输入输出运算符适合重载为需要输入输出大量数据的类的友元
- 输出函数一般不改变输出对象,可以考虑把第二个参数定义为const
- 输入函数一般会改变输入对象,无需const
ostream& operator<<(ostream& os, const Complex& data)
{
os << data.real;
data.image >= 0 ? os << "+" : os << "";
os << data.image << "i" << endl;
return os;
}
istream& operator>>(istream& is, Complex& data)
{
cout << "input format: real<blank>image" << endl;
is >> data.real >> data.image;
return is;
}
int main()
{
Complex c1(1, 2);
cin >> c1;
cout << c1;
}
Complex复数类
#pragma once
#include <iostream>
using namespace std;
class Complex
{
public:
Complex() : real(0), image(0)
{
}
Complex(double r, double i) : real(r), image(i)
{
}
// 作类型转换作用的构造函数
Complex(double r) :real(r), image(0)
{
}
void display()
{
cout << this->real;
image >= 0 ? cout << "+" : cout << "";
cout << this->image << "i" << endl;
}
void setImage(const double& i)
{
this->image = i;
}
void setReal(const double& r)
{
this->real = r;
}
// 加运算
friend Complex operator+(const Complex& x1, const Complex& x2);
// 累加运算
Complex& operator+=(const Complex& x)
{
this->real = x.real + this->real;
this->image = x.image + this->image;
return *this;
}
// 减运算
friend Complex operator-(const Complex& x1, const Complex& x2);
// 取负值运算
friend Complex operator-(const Complex& x);
// 累减运算
Complex& operator-=(const Complex& x)
{
this->real = this->real - x.real;
this->image = this->image - x.image;
return *this;
}
// 乘运算
friend Complex operator*(const Complex& x1, const Complex& x2);
// 累乘运算
Complex& operator*=(const Complex& x)
{
this->real = this->real * x.real - this->image * x.image;
this->image = this->real * x.image + this->real * x.image;
return *this;
}
// 除运算
friend Complex operator/(const Complex& x1, const Complex& x2);
// 累除运算
Complex& operator/=(const Complex& x)
{
double d = 1;
d = this->real * this->real + x.image * x.image;
real = (this->real * x.real + this->image * x.image) / d;
image = (this->image * x.real - this->real * x.image) / d;
return *this;
}
// 自增运算(前缀)
Complex& operator++()
{
this->real++;
return *this;
}
// 自增运算(后缀)
Complex operator++(int)
{
Complex _this(this->real, this->image);
this->real++;
return _this;
}
// 自减运算(前缀)
Complex& operator--()
{
this->real--;
return *this;
}
// 自减运算(后缀)
Complex operator--(int)
{
Complex _this(this->real, this->image);
this->real--;
return _this;
}
// 向整数类型的类型转换
operator int()
{
return int(this->real);
}
// 向浮点数类型的类型转换
operator float()
{
return float(this->real);
}
// 向浮点数类型的类型转换
operator double()
{
return this->real;
}
// 函数调用运算符,模拟一个命令输入函数
Complex& operator() (const string& cmd)
{
if (cmd == "display")
{
this->display();
}
else if (cmd == "reset")
{
this->real = 0;
this->image = 0;
}
else
{
cout << "-------------------- help list --------------------" << endl;
cout << " cmd display - print real and image" << endl;
cout << " cmd reset - reset real and image to 0" << endl;
cout << " cmd help - show method and cmds" << endl;
cout << " method display - print real and image" << endl;
cout << " method setImage - set image" << endl;
cout << " method setReal - set real" << endl;
cout << "---------------------------------------------------" << endl;
}
return *this;
}
// 输出运算符
friend ostream& operator<<(ostream& os, const Complex& data);
// 析取运算符
friend istream& operator>>(istream& is, Complex& data);
private:
double image;
double real;
};
Complex operator+(const Complex& x1, const Complex& x2)
{
return Complex(x1.real + x2.real, x1.image + x2.image);
}
Complex operator-(const Complex& x1, const Complex& x2)
{
return Complex(x1.real - x2.real, x1.image - x2.image);
}
Complex operator-(const Complex& x)
{
return Complex(x.real * -1, x.image * -1);
}
Complex operator*(const Complex& x1, const Complex& x2)
{
return Complex(x1.real * x2.real - x1.image * x2.image, x1.real * x2.image + x2.real * x1.image);
}
Complex operator/(const Complex& x1, const Complex& x2)
{
double d = 1;
d = x2.real * x2.real + x2.image * x2.image;
return Complex((x1.real * x2.real + x1.image * x2.image) / d, (x1.image * x2.real - x1.real * x2.image) / d);
}
ostream& operator<<(ostream& os, const Complex& data)
{
os << data.real;
data.image >= 0 ? os << "+" : os << "";
os << data.image << "i" << endl;
return os;
}
// 注意:这段输入运算符的定义鲁棒性不好,格式错误会引发问题,有兴趣的读者可以尝试修改
istream& operator>>(istream& is, Complex& data)
{
cout << "input format: real<blank>image" << endl;
is >> data.real >> data.image;
return is;
}
测试用的main函数
#include <bits/stdc++.h>
#include "Complex.hpp"
using namespace std;
int main()
{
Complex c1(3, 4);
Complex c2(1, 1);
// 无意义的加运算,不会改变对象
c1 + c2
// 加运算
c1 = c1 + c2;
c1.display();
// 减运算
c1 = c1 - c2;
c1.display();
// 取负运算
c1 = -c1;
c1.display();
// 后自增运算
c1++.display();
// 前自增运算,作左值
++c1 = c2;
c1.display();
// 强制类型转换
cout << double(c1) << endl;
// 函数调用运算符
c1("display");
c1("anything");
// 输入和输出运算
cin >> c1;
cout << c1;
}