0%

C++中的运算符重载

为什么要对运算符进行重载

C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。

运算符重载的实质

运算符重载的实质就是函数重载或函数多态运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:operator p(argument-list)//operator 后面的’p’为要重载的运算符符号。

语法如下:

1
2
3
4
返回值类型 operator 运算符符号(参数表)
{
函数体
}

这里的 ‘operator运算符’ 相当于函数名

加法运算符的重载

第一种:通过全局函数对加号进行重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
using namespace std;

class Person
{
public:
int a;
int b;
};
//通过全局函数对 '+' 进行重载 ,以实现对类中多个变量的加法
Person operator+(Person &p1,Person &p2)
{
Person temp;
temp.a=p1.a+p2.a;
temp.b=p1.b+p2.b;
return temp;
}

int main()
{
Person p1;
p1.a=10;
p1.b=10;

Person p2;
p2.a=20;
p2.b=15;

Person p3;
//实质:p3=operator+(p1,p2),简化后如下
p3=p1+p2;
cout<<"p3.a="<<p3.a<<endl;
cout<<"p3.b="<<p3.b<<endl;
return 0;
}

第二种:通过成员函数对加号进行重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;

class Person
{
public:
int a;
int b;
//通过成员函数对 '+' 进行重载 ,以实现对类中多个变量的加法
Person operator+(Person &p)
{
Person temp;
temp.a=this->a+p.a;
temp.b=this->b+p.b;
return temp;
}
};
int main()
{
Person p1;
p1.a=10;
p1.b=10;

Person p2;
p2.a=20;
p2.b=15;

Person p3;
//实质:p3=p1.operator+(p2),简化后如下
p3=p1+p2;
cout<<"p3.a="<<p3.a<<endl;
cout<<"p3.b="<<p3.b<<endl;
return 0;
}

左移运算符的重载

利用成员函数重载左移运算符 p.operator<<(cout) 简化版本p<<cout,所以我们一般不会利用成员函数重载<<运算符,因为无法实现cout在左侧

通过全局函数重载左移运算符

void operator<<(cout,p)的本质是operator<<(cout,p)简化为cout<<p,从而实现cout在左侧

cout属于ostream类的一个对象,叫做标准输出流对象

初级代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<iostream>
using namespace std;
class Person
{
public:
int a;
int b;

};

void operator<<(ostream &cout,Person &p)//这里是引用cout,因为不能再创建已有的全局对象
{
cout<<"p1.a="<<p.a<<endl;
cout<<"p1.b="<<p.b<<endl;
}

int main()
{
Person p1;
p1.a=10;
p1.b=20;
cout<<p1;

return 0;
}

注意:以上代码cout<<p1;不能再追加endl;因为此时函数返回值为空,所以我们要加上一个返回数据类型,也就是ostream &,这里同样可以按照引用来理解。

改进后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<iostream>
using namespace std;
class Person
{
public:
int a;
int b;

};

ostream & operator<<(ostream &cout,Person &p)//这里是引用cout,因为不能再创建已有的
//同样,这里的cout也可以写成别的名称,因为引用的本身就是起别名。例如
/*
ostream & operator<<(ostream &out,Person &p)
{
out<<"p1.a="<<p.a<<endl;
out<<"p1.b="<<p.b<<endl;
return out;
}

*/
{
cout<<"p1.a="<<p.a<<endl;
cout<<"p1.b="<<p.b<<endl;
return cout;
}

int main()
{
Person p1;
p1.a=10;
p1.b=20;
cout<<p1<<endl<<"Hello world!"<<endl;//调用完之后仍然返回cout,这样后面便可以追加内容了

return 0;
}

但是通常情况下,我们类中的成员属性设置成私有化,但是私有化之后重载运算符的函数不能够正常访问类中的成员变量,而利用我们以前set get函数的方式又太麻烦了,那么我们可以用友元函数来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;
class Person
{
friend ostream & operator<<(ostream &cout,Person &p);//友元函数用法
private:
int a;
int b;
public:
void setAB(int aa,int bb)
{
a=aa;
b=bb;
}

};

ostream & operator<<(ostream &cout,Person &p)
{
cout<<"p1.a="<<p.a<<endl;
cout<<"p1.b="<<p.b<<endl;
return cout;
}

int main()
{
Person p1;
//p1.a=10; 外部无法赋值 ,所以我们不得不创建set函数
//p1.b=20;
p1.setAB(10,20);
cout<<p1<<endl<<"Hello world!"<<endl;

return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义数据类型

递增运算符的重载

用成员函数实现

*this的解释:

*表示的解引用,那么*this就很好理解了,就是指针的内容,即对象本身。return *this返回的是当前对象的克隆或者本身(若返回类型为A, 则是克隆, 若返回类型为A&, 则是本身 )

下面用的是成员函数进行的重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream &cout,MyInteger i);//友元函数,用来输出私有变量
private:
int a=0;
public:
MyInteger& operator++()//重载前置++运算符
{
a++;//先进行++操作,后返回
return *this;
}
MyInteger operator++(int)//后置++,int是占位参数,用来区分前置和后置++,且只能用int
{
//记录初始值
MyInteger temp=*this;
//实现++操作
a++;
//返回初始值
return temp;
}
};

ostream& operator<<(ostream &cout,MyInteger i)
{
cout<<i.a;
return cout;
}

int main()
{
MyInteger myint;
cout<<++(++myint)<<endl;//*this也就是返回myint本身
cout<<myint<<endl;
cout<<"-----------------"<<endl;
cout<<myint++<<endl;//这里不能实现(myint++)++的操作
cout<<myint<<endl;
return 0;
}

注意:前置++返回的是引用,后置++返回的是值

用全局函数实现

前置++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream &cout,MyInteger i);
friend MyInteger operator++(MyInteger &i);
private:
int a=0;
public:
/*
MyInteger& operator++()//前置++
{
a++;
return *this;
}
MyInteger operator++(int)//后置++,int是占位参数,用来区分前置和后置++,且只能用int
{
//记录初始值
MyInteger temp=*this;
//实现++操作
a++;
//返回初始值
return temp;
}
*/
};

ostream& operator<<(ostream &cout,MyInteger i)
{
cout<<i.a;
return cout;
}

MyInteger operator++(MyInteger &i)//前置++,这里要传引用,要不无法改变实参
{
i.a=i.a+1;
return i;
}


int main()
{
MyInteger myint;
/*
cout<<"++a ------------------"<<endl;
cout<<++(++myint)<<endl;
cout<<myint<<endl;
cout<<"a++ ------------------"<<endl;
cout<<myint++<<endl;
cout<<myint<<endl;
*/
cout<<"----------------"<<endl;
++myint;
cout<<++myint;
return 0;
}

后置++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream &cout,MyInteger i);
friend MyInteger operator++(MyInteger &i,int);//友元函数
private:
int a=0;
};

ostream& operator<<(ostream &cout,MyInteger i)
{
cout<<i.a;
return cout;
}

MyInteger operator++(MyInteger &i,int)//后置++,一开始一直返回不了正确值,后来记起来,形参改变实参不会改变,所以这里的i要用引用传值,而且不要忘记加上友元函数
{
MyInteger temp=i;
i.a=i.a+1;
return temp;
}

int main()
{
MyInteger myint;
cout<<"----------------"<<endl;
cout<<myint++<<endl;
cout<<myint;
return 0;
}

人生建议:重载++运算符用成员函数进行

赋值运算符重载

什么时候用到赋值运算符重载?

答:当浅拷贝不适用时,要用赋值运算符重载来进行深拷贝,以避免堆区内存二次删除。

下面用成员函数对赋值运算符进行重载操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<iostream>
using namespace std;

class Person
{
private:
int *age;
public:
int getAge()
{
return *age;
}
Person(int a)
{
age=new int(a);//new int(a)返回的是int*,和age数据类型一致
}
~Person()
{
if(age!=NULL)
{
delete age;
age=NULL;
}

}
Person& operator=(Person &a)
{
//编译器提供的是浅拷贝
//age=a.age;

//应该先判断是否有属性在本身堆区,如果有先释放,再深拷贝
if(age!=NULL)
{
delete age;
age=NULL;
}

//深拷贝
age=new int(*a.age);

//返回类本身,以允许连续赋值,例如p1=p2=p3
return *this; //this是指向自身的指针,想返回自身就要对this进行解引用
}
};

int main()
{
Person p1(18);
Person p2(20);
Person p3(30);
cout<<p1.getAge()<<" "<<p2.getAge()<<" "<<p3.getAge()<<endl;
p3=p2=p1;
cout<<p1.getAge()<<" "<<p2.getAge()<<" "<<p3.getAge()<<endl;
return 0;
}

总结:重点就是深拷贝和浅拷贝的知识,目的是为了防止浅拷贝造成堆区内存二次释放。

关系运算符重载

成员函数重载“==”和“>”其余类似,不再赘述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<iostream>
#include<cstring>
using namespace std;

class Person
{
private:
int age;
string name;
public:
Person(string n,int a)
{
name=n;
age=a;
}

bool operator==(Person &p)//重载==
{
if(name==p.name&&age==p.age)
{
return true;
}
return false;
}
bool operator>(Person &p)//重载>
{
if(age>p.age)
{
return true;
}
return false;
}
};

int main()
{
Person p1("Tom",18);
Person p2("Tom",18);
if(p1==p2)
{
cout<<"p1==p2"<<endl;
}
else
{
cout<<"p1!=p2"<<endl;
}
Person p3("Tom",19);
if(p1>p3)
{
cout<<"p1>p3"<<endl;
}
else
{
cout<<"p1<p3"<<endl;
}
return 0;
}

函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称仿函数
  • 仿函数没有固定的用法,非常灵活
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<string>
using namespace std;

class Print
{
public:
void operator()(string a)
{
cout<<a<<endl;
}
};
void b(string n)
{
cout<<n;
}
int main()
{
Print a;
a("print");//仿函数
b("Print");//真函数
return 0;
}

不过这里比较重要的是一个匿名函数对象的概念,我们将上面的代码稍作修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<string>
using namespace std;

class Print
{
public:
void operator()(string a)
{
cout<<a<<endl;
}
};
void b(string n)
{
cout<<n<<endl;
}
int main()
{
Print a;
a("print");//仿函数
b("Print");//真函数
Print()("Print again");//匿名函数对象,用完即销毁。
return 0;
}