5 C++运算符重载
文章目录
5.1 定义和使用
运算符重载:
- 运算符重载其实就是定义一个函数,在函数体内实现想要的功能,当用到该运算符时,编译器会自动调用这个函数。也就是说,运算符重载是通过函数实现的,它本质上是函数重载。
- 作用:同一个运算符实现不同功能,+对不同的操作类型进行相加
1. 成员函数,运算符重载
-
单目运算符作为类成员函数重载时没有型参(除了后置自增(自减)有一个整型参数:
-
双目运算符作为类成员函数重载时只有一个型参,作为运算符的右操作数,其左操作数就是本对象自己,也就是this。
2.友元函数,运算符重载(全局范围)
-
友元函数(友元函数则是指某些虽然不是类成员却能够访问类的所有成员的函数)进行重载,那么它就不存在this指针了,所以需要定义两个参数来运算(对于双目运算符),
-
而友元函数的实现可以在外面定义,但必须在类内部声明。
声明为友元函数的好处:
-
和普通函数重载相比,它能够访问非公有成员。
-
将双目运算符重载为友元函数,这样就可以使用交换律。
成员函数内部,第一个参数必须是当前对象,但是全局函数中,两个参数度可以是对象(因为可以进行类型的隐式转换)
弊端:
- 友元可以像类成员一样访问类的成员和函数,但是使用不慎会造成破坏类的封装性。
运算符重载的格式为:
返回值类型 operator 运算符名称 (形参表列){
//TODO:
}
1.普通成员函数重载
#include <iostream>
using namespace std;
class complex{
public:
complex();
complex(double real, double imag);
public:
//声明运算符重载
complex operator+(const complex &A) const;
void display() const;
private:
double m_real; //实部
double m_imag; //虚部
};
complex::complex(): m_real(0.0), m_imag(0.0){ }
complex::complex(double real, double imag): m_real(real), m_imag(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;
}
//同理,使用匿名对象
complex complex::operator+(const complex &A)const{
return complex(this->m_real + A.m_real, this->m_imag + A.m_imag);
}
本质:当执行c3 = c1 + c2;
语句时,编译器检测到+
号左边(+
号具有左结合性,所以先检测左边)是一个 complex 对象,就会调用成员函数operator+()
c3 = c1.operator+(c2);
2.非成员函数重载:使用友元函数(全局范围)
//在全局范围内重载+
complex operator+(const complex &A, const complex &B){
complex C;
C.m_real = A.m_real + B.m_real;
C.m_imag = A.m_imag + B.m_imag;
return C;
}
当执行c3 = c1 + c2;
语句时,编译器检测到+
号两边都是 complex 对象,就会转换为类似下面的函数调用:
c3 = operator+(c1, c2);
5.2 重载的注意事项
- 只能重载已有的运算符,并且有的运算符能重载,有的运算符不能重载
- 重载不能改变运算符的优先级和结合性还有用法习惯
- 运算符重载函数不能有默认的参数
- 重载的运算符不能改变运算符原操作数个数
- 箭头运算符
->
、下标运算符[ ]
、函数调用运算符( )
、赋值运算符=
只能以成员函数的形式重载。
5.2.1 能够重载的运算符
1.能够重载
+ - * / % ^ & | ~ ! = < > += -= = /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ – , -> -> () [] new new[] delete delete[]
2.不能重载
-
长度运算符
sizeof
-
条件运算符
: ?
-
成员选择符
.
-
域解析运算符
::
不能被重载。
5.2.2 重载不能改变运算符的优先级和结合性
c4 = c1 + c2 * c3;
//等价于
c4 = c1 + ( c2 * c3 );
//乘法的优先级仍然高于加法,并且它们仍然是二元运算符。
5.2.3运算符重载函数不能有默认的参数
- 运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的
5.2.4 运算符重载函数既可以作为类的成员函数,也可以作为全局函数
1.运算符重载函数作为类的成员函数时
- 二元运算符的参数只有一个,一元运算符不需要参数。之所以少一个参数,是因为这个参数是隐含的。缺少的就是默认的this参数
2.将运算符重载函数作为全局函数时
- 二元操作符就需要两个参数,一元操作符需要一个参数,而且其中必须有一个参数是对象,好让编译器区分这是程序员自定义的运算符,防止程序员修改用于内置类型的运算符的性质。
注意事项:
一般都需要在类中将该函数声明为友元函数。
5.2.5 只能以成员函数重载的运算符
- 箭头运算符
->
、下标运算符[ ]
、函数调用运算符( )
、赋值运算符=
只能以成员函数的形式重载。
5.3 C++重载属性运算符
- 四则运算符(+、-、、/、+=、-=、=、/=)
- 关系运算符(>、<、<=、>=、==、!=)
复数改写
#pragma once
#ifndef _Complex_
#define _Complex_
#include<iostream>
#include<cmath>
using namespace std;
class Complex
{
public:
Complex(double real=0.0, double imag=0.0) :m_real(real), m_imag(imag) {}
~Complex(){}
//全局函数重载运算符
friend Complex operator+(const Complex &com1, const Complex &com2);
friend Complex operator-(const Complex &com1, const Complex &com2);
friend Complex operator*(const Complex &com1, const Complex &com2);
friend Complex operator/(const Complex &com1, const Complex &com2);
friend bool operator==(const Complex &com1, const Complex &com2);
friend bool operator!=(const Complex &com1, const Complex &com2);
//成员函数形式重载
Complex& operator+=(const Complex &com1);
Complex& operator-=(const Complex &com1);
Complex& operator*=(const Complex &com1);
Complex& operator/=(const Complex &com1);
//成员函数
double getReal()const {return this->m_real;}
double getImag()const { return this->m_imag; }
private:
double m_real;
double m_imag;
};
#endif // !_Complex_
#include "Complex.h"
//重载+运算符
Complex operator+(const Complex &com1, const Complex &com2)
{
return Complex(com1.m_real + com2.m_real, com1.m_imag + com1.m_imag);
}
//重载-运算符
Complex operator-(const Complex &com1, const Complex &com2)
{
return Complex(com1.m_real - com2.m_real, com1.m_imag - com1.m_imag);
}
//重载*运算符 (a+bi) * (c+di) = (ac-bd) + (bc+ad)i
Complex operator*(const Complex &com1, const Complex &com2)
{
Complex c;
c.m_real = com1.m_real*com2.m_real - com1.m_imag*com1.m_imag;
c.m_imag = com1.m_imag*com2.m_real + com1.m_real*com2.m_imag;
return c;
}
//重载/运算符 (a+bi) / (c+di) = [(ac+bd) / (c²+d²)] + [(bc-ad) / (c²+d²)]i
Complex operator/(const Complex &com1, const Complex &com2)
{
Complex c;
c.m_real = (com1.m_real*com2.m_real + com1.m_imag*com1.m_imag)/(pow(com2.m_imag,2)+pow(com2.m_real,2));
c.m_imag = (com1.m_imag*com2.m_real - com1.m_real*com2.m_imag) / (pow(com2.m_imag, 2) + pow(com2.m_real, 2));
return c;
}
//重载== 符号
bool operator==(const Complex &com1, const Complex &com2)
{
return (com1.m_real == com2.m_real) && (com1.m_imag == com2.m_imag);
}
//重载!=符号
bool operator!=(const Complex &com1, const Complex &com2)
{
return !((com1.m_real == com2.m_real) && (com1.m_imag == com2.m_imag));
}
//重载+=符号
Complex& Complex::operator+=(const Complex &com1)
{
this->m_real += com1.m_real;
this->m_imag += com1.m_imag;
return *this;
}
//重载-=符号
Complex& Complex::operator-=(const Complex &com1)
{
this->m_real -= com1.m_real;
this->m_imag -= com1.m_imag;
return *this;
}
//重载*=符号
Complex& Complex::operator*=(const Complex &com1)
{
this->m_real = this->m_real*com1.m_real - this->m_imag*com1.m_imag;
this->m_imag = this->m_imag*com1.m_real - this->m_real*com1.m_imag;
return *this;
}
//重载/=符号
Complex& Complex::operator/=(const Complex &com1)
{
this->m_real = (this->m_real*com1.m_real + this->m_imag*com1.m_imag) / (pow(com1.m_real, 2) + pow(com1.m_imag, 2));
this->m_imag = (this->m_imag*com1.m_real - this->m_real*com1.m_imag) / (pow(com1.m_real, 2) + pow(com1.m_imag, 2));
return *this;
}
5.3 成员函数和全局函数的选择
全局函数的好处
//全局
friend Complex operator+(const Complex &c1, const Complex &c2);
//成员,记得加=号,“+=”
Complex & operator+=(const Complex &c);
全局函数:
- b = a + 1.1,会转换为b = operator+(a, 1.1),因为存在转换构造函数,并且第一个参数为 double,那么编译器就会将 1.1 转换为一个匿名 complex 对象complex(1.1)
b = 2.2 + a
;,也是一样的道理,被转换为b = operator+(2.2, a)
;这样的形式,然后又调用转换构造函数将 2.2 转换为一个匿名 complex 对象complex(2.2)
成员函数:
- 对于
b = a + 1.1
;,被转换为b = a.operator+(1.1)
;,1.1 也是被转换为complex(1.1); - 对于
b = 2.2 + a
;,被转换为b = (2.2).operator+(a)
;,这很显然是不正确
的,进而编译报错;
总结:
- 声明为全局函数有助于体现交换率, 运算符的操作数能够
被对称的处理
,小数(double)在 + 左边和右边都是正确的
函数的选择标准:
- 双目运算符,将它重载为友元运算符比重载为成员运算符便于使用
- 单目运算符,则选择成员运算符较好
- 体现交换率:如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载必须用友元函数
- 对于运算符“=、()、[ ]、->” 只能作为成员函数。
- 对于运算符“+=、-=、/=、*=、&=、!=、~=、%=、<<=、>>=”,建议重载为成员函数。
- 对于其他运算符,建议重载为友元函数。
5.4 重载输入输出运算符>>,<<
核心:
-
重载输入输出运算符必须使用友元函数的形式
因为第一个参数时istream和ostream对象,如果使用成员函数,必须修改标准库的文件。
5.4.1 重载输入运算符>>
//重载输入运算符
istream& operator>>(iostream &in, Complex &com1)
{
in >> com1.m_real >> com1.m_imag;
return in;
}
//重载输出操作符
ostream& operator<<(ostream &out, Complex &com1)
{
out << com1.m_real << "+" << com1.m_imag << "+";
return out;
}
cout <<"c1 and c2"<< c1 << c2 << endl;
返回引用原因
- c++中输入输出cin和cout对象只有一份,不可以被多次初始化
- 返回引用后能作为左值
- 希望能连续重载如例子。链式法则。多次连续重载。
- 提高效率
5.5 重载()和[]和=和-> 只能作为成员函数重载
1 重载 []
C++规定,下标运算符[ ]
必须以成员函数的形式进行重载
申明一:可访问,可修改
返回值类型 & operator[ ] (参数);
申明二:只访问,不修改。(为了适应const对象)
const 返回值类型 & operator[ ] (参数) const;
提供const函数原因:
- 通过 const 对象只能调用 const 成员函数,如果不提供第二种形式,那么将无法访问 const 对象的任何元素。
#include <iostream>
using namespace std;
class Array{
public:
Array(int length = 0);
~Array();
public:
int & operator[](int i);
const int & operator[](int i) const;
public:
int length() const { return m_length; }
void display() const;
private:
int m_length; //数组长度
int *m_p; //指向数组内存的指针
};
Array::Array(int length): m_length(length){
if(length == 0){
m_p = NULL;
}else{
m_p = new int[length];
}
}
Array::~Array(){
delete[] m_p;
}
int& Array::operator[](int i){
return m_p[i];
}
const int & Array::operator[](int i) const{
return m_p[i];
}
void Array::display() const{
for(int i = 0; i < m_length; i++){
if(i == m_length - 1){
cout<<m_p[i]<<endl;
}else{
cout<<m_p[i]<<", ";
}
}
}
int main(){
int n;
cin>>n;
Array A(n);
for(int i = 0, len = A.length(); i < len; i++){
A[i] = i * 5;
}
A.display();
const Array B(n);
cout<<B[n-1]<<endl; //访问最后一个元素
return 0;
}
2 重载()运算符,类型强制转换运算符
类型强制转换运算符是单目运算符,只能重载为成员函数,不能重载为全局函数。
- 经过适当重载后,
(类型名)对象
这个对对象进行强制类型转换的表达式就等价于对象.operator 类型名()
,
#include <iostream>
using namespace std;
class Complex
{
double real, imag;
public:
Complex(double r = 0, double i = 0) :real(r), imag(i) {};
operator double() { return real; } //重载强制类型转换运算符 double
};
int main()
{
Complex c(1.2, 3.4);
cout << (double)c << endl; //输出 1.2
double n = 2 + c; //等价于 double n = 2 + c. operator double()
cout << n; //输出 3.2
}
-
强制转换的运算符不需要返回值的类型,因为返回类型的值是已知的。
-
重载强制类型转换符后,编译器会自动在需要类型转换的地方判断是否能够隐式转换。
如例子中,double n = 2 + c;编译器在+号后面会自动判断是否有double类型的转换函数。
5.6 C++重载++和-- 详解,自增和自减
一般用于成员函数的重载
格式:
- 前置: stopwatch operator++(); //++i,前置形式
- 后置:stopwatch operator++(int); //i++,后置形式
#include <iostream>
#include <iomanip>
using namespace std;
//秒表类
class stopwatch{
public:
stopwatch(): m_min(0), m_sec(0){ }
public:
void setzero(){ m_min = 0; m_sec = 0; }
stopwatch run(); // 运行
stopwatch operator++(); //++i,前置形式
stopwatch operator++(int); //i++,后置形式
friend ostream & operator<<( ostream &, const stopwatch &);
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;
run();
return s;
}
注意:
- 前置重载++,使用无参的重载
- 后置重载,使用占位参数int,其功能主要是区别前置重载。
5.7 C++重载new和delete
内存管理运算符
- new、new[]、delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数。
一般般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载。
1 new运算符的重载
以成员函数重载
void * className::operator new( size_t size ){
//TODO:
}
以全局函数重载
void * operator new( size_t size ){
//TODO:
}
size_t
-
size_t 在头文件 中被定义为
typedef unsigned int size_t;
,也就是无符号整型。 -
在重载 new 或 new[] 时,无论是作为成员函数还是作为全局函数,它的第一个参数必须是 size_t 类型。
-
size_t 表示的是要分配空间的大小,对于 new[] 的重载函数而言,size_t 则表示所需要分配的所有空间的总和。
void *
:
- 返回空指针类型,指向已经分配的空间
2 delete 运算符的重载
以成员函数重载
void className::operator delete( void *ptr){
//TODO:
}
以全局函数重载
void operator delete( void *ptr){
//TODO:
}
ptr为需要删除的指针。