C++ 运算符重载详解
2021/10/14 17:16:22
本文主要是介绍C++ 运算符重载详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1. 运算符重载简介
所谓重载,就是赋予新的含义。函数重载(Function Overloading)可以让一个函数名有多种功能,在不同情况下进行不同的操作。同样运算符重载(Operator Overloading)可以让同一个运算符可以有不同的功能。
- 可以对 int、float、string 等不同类型数据进行操作
<< 既是位移运算符,又可以配合 cout 向控制台输出数据
也可以自定义运算符重载:
class Complex { public: Complex(); Complex(double real, double imag); Complex operator+(const Complex &a) const; void display() const; private: double m_real; double m_imag; }; // ... // 实现运算符重载 Complex Complex::operator+(const Complex &A) const{ Complex B; B.m_real = this->m_real + A.m_real; B.m_imag = this -> m_imag + A.m_imag; return B; // return Complex(this->m_real + A.m_real, this->m_imag + A.m_imag); } int main(){ Complex c1(4.3, 5.8); Complex c2(2.7, 3.7); Complex c3; c3 = c1 + c2; // 运算符重载 c3.display(); return 0; }
运算结果 7 + 9.5i
运算符重载其实就是定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数,它本质上是函数重载。
c3 = c1 + c2;
实际上通过调用成员函数 operator+(),会转换为下面的形式:
c3 = c1.operator+(c2);
全局范围内重载运算符
运算符重载函数不仅可以作为类的成员函数,还可以作为全局函数。
复数加法运算通过全局范围内重载 + 实现:
class Complex{ // ... friend complex operator+(const complex &A, const complex &B); }; // 全局函数 Complex operator+(const Complex &A, const Complex &B) { return Complex(A.m_real + B.m_real, A.m_imag + B.m_imag); // 访问了Complex 的 private 成员变量,需要声明为友元函数 }
2. 运算符重载时要遵循的规则
-
能够重载的运算符
+ - * / % ^ & | ~ ! = < > += = = /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- , -> - > () [] new new[] delete delete[]
长度运算符 sizeof、条件运算符: ?、成员选择符. 和域解析运算符::不能被重载。
-
重载不能改变运算符的优先级和结合律
-
重载不会改变运算符的用法,即操作数个数、位置都不会改变
-
运算符重载函数不能有默认的参数,因为这改变了运算符操作数个数
-
运算符重载函数既可作为类成员函数,也可为全局函数,注意全局函数如何要访问类对象的私有成员,需要声明为类的友元
-
箭头运算符->、下标运算符[]、函数调用运算符()、赋值运算符 =,只能以成员函数的形式重载。
3. 重载运算符实现形式的选择
重载运算符可以通过成员函数和全局函数(友元)来实现
转换构造函数
Complex c1(25, 25); Complex c2 = c1 + 15.6; Complex c3 = 28.23 + c1; // 要以全局函数实现重载
这几行代码都可以顺利运行,说明 Complex 对象可以和 double 类型对象相加。其实,编译器在检测到 Complex 和 double(小数默认为 double 类型)相加时,会先尝试将 double 转换为 Complex,或者反过来将 Complex 转换为 double(只有类型相同的数据才能进行 + 运算),如果都转换失
败,或者都转换成功(产生了二义性),才报错。实际上,上述两行加法代码会转换为:
Complex c2 = operator+(c1,Complex(15.6)); Complex c3 = operator+(Complex(28.23),c1);
Complex(double real)
在作为普通构造函数的同时,还能将 double 类型转换为 Complex 类型,集合了“构造函数”和“类型转换”的功能,所以被称为「转换构造函数」。换句话说,转换构造函数用来将其它类型(可以是 bool、int、double等基本类型,也可以是数组、指针、结构体、类等构造类型)转换为当前类类型。
以全局函数形式重载
以全局函数的形式重载了 +、-、*、/、==、!=,这样做是为了保证这些运算符能够被对称的处理。
如果将 operator+定义为成员函数,根据“+ 运算符具有左结合性”这条原则,Complex c2 = c1 + 15.6;
会被转换为下面的形式:
Complex c2 = c1.operator+(Complex(15.6));
但是对于 Complex c3 = 28.23 + c1
,编译器会尝试转换为不同形式:
Complex c3 = (28.23).operator+(c1);
显然这是错误的。
以成员函数的形式重载
以成员函数的形式重载了 +=、-=、 *=、/=。
运算符重载的初衷是给类添加新的功能,方便类的运算,它作为类的成员函数是理所应当的, 是首选的。不过类的成员函数不能对称处理数据,运算符的第一个运算对象不会出现类型转换(要类的对象才能调用类的成员函数)。
C++ 规定,箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载。
4. 重载 >> 和 <<(输入输出运算符)详解
C++ 标准库已对 左移运算符 << 和 >> 右移运算符进行了承载,如果我们定义新的类型需要输入输出运算符去处理,需要再进行重载。
重载运算符 >>
以全局函数的形式重载>>,使它能够读入两个 double 类型的数据,并分别赋值给复数的实部和虚部:
istream & operator>>(istream &in, Complex &A) // 友元 { in >> A.m_real >> A.m_image; return in; }
之所以返回 istream 类 对象的引用,是为了能够连续读取复数,让代码书写更加漂亮,例如:
Complex c1,c2; cin >> c1 >> c2;
如果不返回引用,需要一个一个读取:
cin >> c1; cin >> c2;
实际上,上述 >> 运算符会被转换成如下形式:
operator<<(cin, c);
重载运算符 <<
ostream & operator<<(ostream &out, complex &A){ out << A.m_real <<" + "<< A.m_imag <<" i "; }
5. 重载 [] 下标运算符
下标运算符 [] 必须以成员函数的形式进行重载。
int& Array::operator[](int i){ return m_p[i]; } const int & Array::operator[](int i) const{ return m_p[i]; }
当 Array 是 const 对象时,如果没有提供 const 版本的 operator[],会报错。
6. 重载 ++ 和 -- 自增自减运算符
class Stopwatch{ // ... 秒表类 public: Stopwatch operator++(); // ++i,前置形式 Stopwatch operator++(int); // i++,后置形式 Stopwatch run(); // 运行 private: int m_min; // 分钟 int m_sec; // 秒钟 }; Stopwatch Stopwatch::run(){ ++m_sec; if(m_sec == 60){ m_min++; m_sec = 0; } return *this; } Stopwatch Stopwatch::operator++(){ return run(); } Stopwatch Stopwatch::operator++(int n){ Stopwatch s = *this; // i++, 先返回对象,再对象自增,所以需要将对象保存 run(); return s; }
函数中参数 n 是没有任何意义的,它的存在只是为了区分是前置形式还是后置形式。
7. 重载 new 和 delete 运算符
内存管理运算符 new、new[]、delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数。一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载。
// 成员函数 void * className::operator new( size_t size ){ //TODO: } void className::operator delete( void *ptr){ //TODO: } // 全局函数 void * operator new( size_t size ){ //TODO: } void operator delete( void *ptr){ //TODO: }
在重载 new 或 new[] 时,无论是作为成员函数还是作为全局函数,它的第一个参数必须是 size_t 类型。size_t 表示的是要分配空间的大小,对于 new[] 的重载函数而言,size_t 则表示所需要分配的所有空间的总和。
size_t 在头文件
当然,重载函数也可以有其他参数,但都必须有默认值,并且第一个参数的类型必须是 size_t。
如果类中没有定义 new 和 delete 的重载函数,那么会自动调用内建的 new 和 delete 运算符。
8. 重载()(强制类型转换运算符)
类型强制转换运算符是单目运算符,也可以被重载,但只能重载为成员函数,不能重载为全局函数。经过适当 重载后,(类型名)对象这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名(),即变成对运算符函数的调用。
class Complex{ public: // ... operator double() { return real; } private: double m_real; double m_imag; }; int main() { Complex c(1.2,3.4); cout << (double)c << endl; // 1.2 double n = 2 + c; // 等价于 double n = 2 + c.operator double() }
9. 运算符重载总结
注意事项:
- 重载后运算符的含义应该符合原有用法习惯。例如重载 + 运算符,完成的功能就应该类似于做加法,在重载的+ 运算符中做减法是不合适的。此外,重载应尽量保留运算符原有的特性。
- C++ 规定,运算符重载不改变运算符的优先级。
- 以下运算符不能被重载:.、.*、::、? :、sizeof。
- 重载运算符()、[]、->、或者赋值运算符=时,只能将它们重载为成员函数,不能重载为全局函数。
-
运算符的实质是将运算符重载为一个函数,使用运算符的表达式就会被解释为对重载函数的调用。
-
运算符可以重载为全局函数。此时函数的参数个数就是运算符的操作数个数,运算符的操作数就成为函数的实参。(友元)
-
运算符也可以重载为成员函数。此时函数的参数个数就是运算符的操作数个数减一,运算符的操作数有一个成为函数作用的对象,其余的成为函数的实参。
-
必要时需要重载赋值运算符=,以避免两个对象内部的指针指向同一片存储空间。
-
<<和>>是在 iostream 中被重载,才成为所谓的“流插入运算符”和“流提取运算符”的。
-
类型的名字可以作为强制类型转换运算符,也可以被重载为类的成员函数。它能使得对象被自动转换为某种类型。
-
自增、自减运算符各有两种重载方式,用于区别前置用法和后置用法。
-
运算符重载不改变运算符的优先级。重载运算符时,应该尽量保留运算符原本的特性。
这篇关于C++ 运算符重载详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-27Rocket消息队列资料:新手入门指南
- 2024-11-27rocket消息队资料详解与入门指南
- 2024-11-27RocketMQ底层原理资料详解入门教程
- 2024-11-27RocketMQ项目开发资料:新手入门教程
- 2024-11-27RocketMQ项目开发资料详解
- 2024-11-27RocketMQ消息中间件资料入门教程
- 2024-11-27初学者指南:深入了解RocketMQ源码资料
- 2024-11-27Rocket消息队列学习入门指南
- 2024-11-26Rocket消息中间件教程:新手入门详解
- 2024-11-26RocketMQ项目开发教程:新手入门指南