运算符重载
1、运算符重载基本概念
我们简单的运算符,有"+", “-”, “*”, “/”,等等基本的运算,可以用两个赋予值的变量,变量可以是int,float,double类型,进行运算,但是在C++中有了对象,导致对象无法通过运算符进行运算,故引入了运算符重载。
实现复数的相加减运算示例:
#include <iostream>
using namespace std;
class Complex
{
private:
double real; //实部
double image;//虚部
public:
Complex(double r = 0, double i = 0) : real(r), image(i) {} //构造函数
void Show() const; //显示函数
static Complex Add(const Complex& z1, const Complex& z2);
};
void Complex::Show() const
{
if (image < 0) cout << real << image << "i" << endl; //虚部为负 3-5i
else if (image == 0) cout << real << endl; //虚部为0 3
else cout << real << "+" << image << "i" << endl; //虚部为正
}
Complex Complex::Add(const Complex& z1, const Complex& z2)
{
Complex z(z1.real + z2.real, z1.image + z2.image); //构造一个对象z
return z;
//等价于
//return Complex(z1.real + z2.real, z1.image + z2.image);
}
int main(int argc, char* argv[])
{
Complex z1(2, 3), z2(6, -5), z3;
z1.Show();
z2.Show();
z3 = Complex::Add(z1, z2);
z3.Show();
//Complex::Add(z1, z2).Show(); //返回的是一个对象,该对象的Show()函数,也可以这样写
system("pause");
return 0;
}
代码中引入了静态成员函数
static Complex Add(const Complex& z1, const Complex& z2);
所以调用该函数不需要指出对象,因为静态成员函数属于类,不属于对象,所以只需要指明该函数在哪个类里面
Complex::Add(z1, z2)
将以上的代码改为用运算符重载的方式去实现
重载运算符的函数原型如下:
返回值类型 operator 运算符 (形参表)
例如用“+”号重载Complex类的加法运算运算符重载函数原型可以为:
Complex operator+ (const Complex& z);
代码示例:
#include <iostream>
using namespace std;
class Complex
{
private:
double real; //实部
double image;//虚部
public:
Complex(double r = 0, double i = 0) : real(r), image(i) {} //构造函数
void Show() const; //显示函数
//写法1
/*friend Complex operator+ (const Complex& z1, const Complex& z2)
{
return Complex(z1.real + z2.real, z1.image + z2.image);
等价于
Complex Z(z1.real + z2.real, z1.image + z2.image); //直接构造,通过构造函数进行操作
return Z;
}*/
//写法2
/*Complex operator+ (const Complex& z)
{
return Complex(this->real + z.real, this->image + z.image);
等价于
Complex Z(this->real + z.real, this->image + z.image); //直接构造,通过构造函数进行操作
return Z;
}*/
//写法3
Complex operator+ (const Complex& z)
{
Complex Z;
Z.real = this->real + z.real;
Z.image = this->image + z.image;
return Z;
}
//写法3更加简单明了,容易理解
//写法1、2、3都是实现两个对象相加运算
};
void Complex::Show() const
{
if (image < 0) cout << real << image << "i" << endl; //虚部为负 3-5i
else if (image == 0) cout << real << endl; //虚部为0 3
else cout << real << "+" << image << "i" << endl;
}
int main(int argc, char* argv[])
{
Complex z1(2, 3), z2(6, -5), z3;
z1.Show();
z2.Show();
z3 = z1 + z2;
z3.Show();
system("pause");
return 0;
}
以上有3种写法
还是觉得第三种便于理解,
计算两个对象相加运算:z3 = z1 + z2;
第一种 Complex operator+ (const Complex& z)
实现两个对象相加运算,对象z1 + 对象z2,那么调用 Complex operator+ (const Complex& z)
等价于 z1.operator+(z2)
所以该函数存在一个this指针,这个this指针是指向z1的, 所以该函数就自己存在一个对象,参数只需要宁外一个对象就可以了。
私有成员是:
double real; //实部
double image;//虚部
定义一个对象z3;
z3.real = this->real + z.real;
z3.image = this->image + z.image;
return z3;
最终实现两个对象相加的运算。
第二种:friend Complex operator+ (const Complex& z1, const Complex& z2)
重载+号运算符的operator前面加了friend,变成了友元函数,由于友元函数没有this指针,不能找到对象,所以参数要传两个对象才能进行运算。
两种方式不同:
第一种 z1 + z2等价于 z1.operator+(z2)第二种 z1 + z2等价于 operator(z1, z2)
第一种是z1.operator存在this指针,指针指向的是对象z1 第二种不是指出是某个对象的运算符重载函数,没有this指针
2、运算符重载方式
c++的运算符按照参加运算的操作数个数分为单目运算符、双目运算符、三目运算符、以及不确定目数运算符。
单目运算符只能操作一个数,例如!p, -b(负号运算符)
双目运算符:2+3(加法运算符)
c++只能重载单目,双目,不确定目数运算符“()”
2.1.非友元的运算符重载函数
示例1:
#include <iostream>
using namespace std;
class Integer
{
private:
int num;
//构造
public:
Integer(int n = 0) : num(n) {}
//功能模块
public:
void Set(int n) { num = n; } //设值
int Get() const { return num; } //取值
//运算符重载
public:
//单目运算
//重载“-”
Integer operator- () const
{
Integer M1;
M1.num = -this->num;
return M1;
//等价于
//return Integer(-this->num);
}
//双目运算
//重载"+"
Integer operator+ (const Integer& a) const
{
Integer M2;
M2.num = this->num + a.num;
return M2;
//等价于
//return Integer(this->num + a.num);
/*
通过i1+i2调用该函数
等价于i1.operator+ (i2) <===> this->num + i2.num
如果该函数改为:
friend Integer operator+ (const Integer& a1, const Integer& a2) const
等价于
operator(a1, a2) <===> a1.num + a2.num
*/
}
};
int main(int argc, char* argv[])
{
//单目运算符测试
Integer i(6);
cout << i.Get() << "的负数为:";
i = -i;
cout << i.Get() << endl;
//双目运算符测试
Integer i1(1), i2(3), i3;
i3 = i1 + i2;
cout << i1.Get() << "+" << i2.Get() << "=" << i3.Get() << endl;
system("pause");
return 0;
}
以上代码不含友元,并且是类的成员函数,所以含有this指针,如果是两个对象进行运算,那么参数就可以只传一个对象,因为本身存在一个this指针,
2.2.友元函数的运算符重载
示例2
#include <iostream>
using namespace std;
class Integer
{
private:
int num;
public:
Integer(int n = 0) : num(n) {}
void Set(int n) { num = n; }
int Get() const { return num; }
//友元函数运算符重载
public:
//单目重载
friend Integer operator- (const Integer& a)
{
Integer M;
M = -a.num;
return M;
//等价于
Integer(-a.num);
}
//双目重载
friend Integer operator- (const Integer& a1, const Integer& a2)
{
Integer M;
M.num = a1.num - a2.num;
return M;
//等价于
return Integer(a1.num - a2.num);
}
};
int main(int argc, char* argv[])
{
//单目运算符 友元
Integer i(5);
cout << i.Get() << "的相反数为:";
i = -i;
cout << i.Get() << endl;
//双目运算符 友元
Integer a1(6), a2(3), a3;
a3 = a1 - a2;
cout << a1.Get() << "-" << a2.Get() << "=" << a3.Get() << endl;
system("pause");
return 0;
}
运算符函数重载,如果前面加了friend,就是友元,友元没有this指针,因此,如果需要2个对象进行运算时候,参数需要传两个,也就是说,需要几个对象进行运算就在参数传几个进行运算。
2.3.普通函数的运算符重载
示例3
#include <iostream>
using namespace std;
class Integer
{
private:
int num;
public:
Integer(int n = 0) : num(n) {}
void Set(int n) { num = n; }
int Get() const { return num; }
};
//单目重载
Integer operator- (const Integer& a)
{
return Integer(-a.Get());
}
//双目重载
Integer operator- (const Integer& a1, const Integer& a2)
{
return Integer(a1.Get() - a2.Get());
}
int main(int argc, char* argv[])
{
//单目运算符 友元
Integer i(5);
cout << i.Get() << "的相反数为:";
i = -i;
cout << i.Get() << endl;
//双目运算符 友元
Integer a1(6), a2(3), a3;
a3 = a1 - a2;
cout << a1.Get() << "-" << a2.Get() << "=" << a3.Get() << endl;
system("pause");
return 0;
}
小总结:
如果operator运算重载函数非友元函数,并且是类的成员函数,就存在this指针,那么如果是两个对象进行运算时候,那么参数只需要传一个对象的。因为本身自带一个对象
如果operator运算符重载函数是友元函数,那么知道友元函数不存在this指针,至于为什么友元函数不存在this指针呢?
例:
有一个类A和一个类B
一个函数C
如果函数C是类A的友元
同时函数C也是类B的友元
那么此时函数C的this指针到底是指向类A的对象的指针。
还是函数C的this指针指向类B的对象的指针?
所以不清楚函数C里面的this指针到底指向哪个类的对象的指针。
最终友元函数不存在this指针,不明确到底指向的是哪个类的对象的指针。
3、典型运算符重载
可以重载赋值运算符、自增1运算符、自减1运算符、下标运算符[], 重载运算符(), 重载运算符">>“和运算符”<<"
3.1.重载赋值运算符“=”
由于赋值运算符重载后实现将一个表达式的值赋值给用户自定义的对象,因此c++规定赋值运算符"="只能重载为类的成员函数,一般格式:
类名 类名::operator= (const 类名& 源对象) {
if (this != 源对象)
{
//目的对象与源对象不是同一个对象
}
return *this;
}
如果用户没有为类重载赋值运算符,编译器程序将生成一个默认赋值运算符函数。
默认赋值运算符函数吧源对象的数据成员逐个地复制到目的对象的相应数据成员。
对于一般的类,使用默认赋值运算符函数都能正常工作,但当一个类中包含指针类型的数据成员,并且通过指针在构造函数中动态分配内存空间,在析构函数进行释放动态存储空间就会出现错误。
示例错误源码:
#include <iostream>
#include <cstring>
#pragma warning(suppress : 4996)
using namespace std;
class String
{
private:
char* strValue;
public:
String(const char* s = "")
{
if (s == NULL) s = " ";
strValue = new char[strlen(s) + 1];
strcpy_s(strValue, strlen(s), s);
}
String(const String& copy)
{
strValue = new char[strlen(copy.strValue) + 1];
strcpy_s(strValue, strlen(copy.strValue), copy.strValue);
}
~String() { delete[] strValue; }
void Show() const { cout << strValue << endl; }
};
int main(int argc, char* argv[])
{
String s1("try"), s2;
s2 = s1;
s1.Show();
s2.Show();
system("pause");
return 0;
}
报错信息
这是因为,当我定义一个对象进行给对象的私有成员初始化一个字符串时候如图:
指向该字符串
如果执行
s2 = s1
当程序执行此代码时候,由于没有类的重载赋值运算符,编译器会默认赋值运算符,将s1有的东西让s2对象也有,那么如下图
对象2也指向了串str,当执行析构函数时候,先释放对象2所指向的内存,如图下:
对象2所指向的内存没有了,然后析构对象1,发现对象1所指向向的内存没有了,这时候会报以上那个错误。
为了解决这样的问题,就是单独为对象2创建存储空间,这样就能解决了。
解决错误示例代码:
#include <iostream>
#include <cstring>
using namespace std;
class String
{
private:
char* strValue;
public:
String(const char* s = " ")
{
if (s == NULL) s = "";
strValue = new char[strlen(s) + 1];
strcpy_s(strValue, strlen(s)+1, s);
}
String(const String& copy)
{
strValue = new char[strlen(copy.strValue) + 1];
strcpy_s(strValue, strlen(copy.strValue)+1, copy.strValue);
}
~String() { delete[]strValue; }
String operator= (const String& copy);
void Show() const
{
cout << strValue << endl;
}
};
String String::operator=(const String& copy)
{
if (this != ©)
{
this->strValue = new char[strlen(copy.strValue) + 1];
strcpy_s(this->strValue, strlen(copy.strValue) + 1, copy.strValue);
}
return *this;
}
int main(int agrc, char* argv[])
{
String a("try"), b;
b = a;
a.Show();
b.Show();
system("pause");
return 0;
}
解决办法就是让他们各自有各自的内存,释放自己的内存,如图:
这样有个各自的内存了,当执行析构函数的时候,先释放对象2所指向的内存,然后释放对象1所指向的内存。
这样的问题对于有些编译器不会出现上述错误,原因是在发现delete释放一个已释放的内存空间时候,不再做释放操作。
3.2.重载自增1运算符“++”和自减1运算符“–”
在C++中,自增1运算符是“++”和自减1运算符是“–”, 他们的功能是使原本变量的值自增1和自减1。
这两个运算符还有前缀后后缀的差别
前缀:
++i 和 --i
后缀
i++ 和 i–
但在C++标准中,对此做了特殊的约定,对于重载前缀使用方式,与一般单目运算符的重载方式相同
但是重构在后缀使用方式,需要在重载函数中加一个int类型的参数
示例:
类名 operator++ () //重载前缀
类名 operator++(int) //重载后缀
friend operator++(类名 &); //重载前缀
friend operator++(类名&, int) //重载后缀
代码示例:
#include <iostream>
using namespace std;
class Integer
{
private:
int num;
public:
Integer(int n = 0) : num(n) {}
void Set(int n) { num = n; }
int Get() const { return num; }
//自增运算符
public:
Integer operator++() //前缀自增 ++i
{
//写法1
Integer z;
z.num = ++this->num;
return z;
//写法2
//return Integer(++num);
}
Integer operator++ (int i) //后缀自增1
{
//写法1
Integer z;
z.num = this->num++;
return z;
//写法2
//return Integer(num++);
}
//自减运算符
public:
friend Integer operator-- (Integer& a) //前缀自减1
{
//写法1
Integer z;
z.num = --a.num;
return z;
//写法2
return Integer(--a.num);
}
};
Integer operator-- (Integer& a, int i)
{
Integer z = a;
a = Integer(a.Get() - 1);
return z;
}
int main(int argc, char* argv[])
{
Integer i, j;
i.Set(6);
j = ++i;
cout << "i = " << i.Get() << " j = " << j.Get() << endl; //前缀自增1
i.Set(6);
j = i++;
cout << "i = " << i.Get() << " j = " << j.Get() << endl; //后缀自增1
i.Set(6);
j = --i;
cout << "i = " << i.Get() << " j = " << j.Get() << endl; //前缀自减1
i.Set(6);
j = i--;
cout << "i = " << i.Get() << " j = " << j.Get() << endl; //后缀自减1
system("pause");
return 0;
}
3.3.重载下标运算符“[]”
代码示例:
#include <iostream>
using namespace std;
template <typename ElemType>
class Array
{
private:
ElemType* elem;
int size;
public:
Array(int n) : size(n)
{
elem = new int[size];
for (int i = 0; i < size; i++)
elem[i] = 2*i;
}
~Array() { delete []elem; }
ElemType operator[] (int i) ;
};
//数组下标重载
template <typename ElemType> ElemType Array<ElemType>::operator[] (int i) //这里的i是数组的下标
{
if (i < 0 || i > size)
{
cout << "当前下标i = " << i << "不存在" << endl;
return(0);
}
return elem[i];
}
int main(int argc, char* argv[])
{
Array<int> obj(3);
for (int i = 0; i < 3; i++)
cout << obj[i] << endl; //obj[i]<====>obj.operator[i]
//obj这个对象里面的私有成员elem相当于是给int类型数组
//obj对象当前下标为i时候,elem数组对应的i的下标的元素进行return
system("pause");
return 0;
}
obj[i]调用operator[] (int i)
重载运算符下标的函数
此时等价为:obj.operator[i]
其中operator[] (int i) //i表示数组的下标那么obj[i]总体的意思就是: 当前对象是obj,但这个对象里面的私有成员elem是一个数组,
obj后面的[i],就是obj对象私有成员elem数组中第i个元素
obj[i] <======> obj对象里面的数组第i个元素
3.4.重载下标运算符"[]"做左值
如果将
ElemType operator[] (int i) ;
这句改为
ElemType & operator[] (int i)
那么实际返回的是数组元素的别名,这样下标运算符”[]“的重载函数的调用出现在赋值语句的左边,使得程序更加灵活
更改后:
#include <iostream>
using namespace std;
template<class ElemType>
class Array
{
private:
ElemType* elem;
int size;
public:
Array(int s) : size(s)
{
elem = new ElemType[size];
}
~Array() { delete[]elem; }
ElemType &operator[] (int i) const;
};
template <class ElemType> ElemType &Array<ElemType>::operator[](int i) const
{
if (i < 0 || i > size)
{
cout << "元素位置错误" << endl;
exit(-1); //异常退出
}
return elem[i];
}
int main(int argc, char* argv[])
{
int a[] = { 8,6,2 };
Array<int> obj(3);
for (int i = 0; i < 3; i++)
obj[i] = a[i];
for (int i = 0; i < 3; i++)
cout << obj[i] << endl;
system("pause");
return 0;
}
此时返回的是obj对象里面的数组的第几个元素位置的变量
例如:
obj[1]
解释:返回obj对象中数组elem[1]位置的变量,也就是elem[1]存储空间,而上上面的代码,返回的是左值,返回的是一个数字,而不是一个能够存储数字的空间,而此代码就是返回第i个空间,做左值,然后将数组a[i]元素,赋予返回的obj对象中数组elem第几个空间里面去。
2.5.重载函数调用运算符"()"
格式: 返回值类型 operator()(形参表) 在c++中,由于面的声明格式中形参表的参数个数不确定,因此"()"是不确定目数运算符。
代码示例:
#include <iostream>
using namespace std;
template<class ElemType>
class Array
{
private:
ElemType* elem;
int size;
public:
Array(int n) : size(n) { elem = new ElemType[size]; }
~Array() { delete[]elem; }
ElemType& operator() (int i);
};
template <class ElemType> ElemType& Array<ElemType>::operator() (int i)
{
if (i < 0 || i > size)
{
cout << "元素位置错误" << endl;
exit(-1);
}
return elem[i];
}
int main(int argc, char* argv[])
{
int a[] = { 5,6,5 };
int n = 3;
Array<int>obj(n);
for (int i = 0; i < n; i++)
obj(i) = a[i];
for (int i = 0; i < n; i++)
cout << obj(i) << endl;
system("pause");
return 0;
}
2.6.重载输入运算符“>>” 和重载输出运算符“<<”
对于定义的对象,不能直接用“>>”和“<<”来进行输出和输入的。
如果需要输出和输入对象,必须对它们进行重载
声明格式:
friend istream& operator>> (isteam &, 类名& )
friend ostream& operator<< (osteam &, const 类名& )
istream& operator>> (isteam &, 类名& )
ostream& operator<< (osteam &, const 类名& )
输入运算符“>>”的重载函数的第一个参数的类型是istream的引用类型,第二个参数是输入操作的类的引用
输出运算符“<<”的重载函数的第一个参数的类型是ostream的引用类型,第二个参数是输出操作的类的常引用
声明">>"与“<<”的重载函数只能为友元函数和普通函数
代码示例:
#include <iostream>
using namespace std;
class Integer
{
private:
int num;
public:
Integer(int n = 0) : num(n) {}
friend istream& operator>> (istream& in, Integer& z); //输入重载
friend ostream& operator<< (ostream& out, const Integer& z); //输出重载
};
//输入重载
istream& operator>> (istream& in, Integer& z)
{
z.num = 10;
return in;
}
//输出重载
ostream& operator<< (ostream& out, const Integer& z)
{
cout << z.num << endl;
return out;
}
int main(int argc, char* argv[])
{
Integer a;
cin >> a;
cout << a << endl;
system("pause");
return 0;
}