C++中的运算符重载
1.概念:
C++提供了一种扩展运算符号作用范围的机制
比如: 正常C/C++的语法 加法运算只能用于整数,小数,字符
但是有了运算符重载这种机制,程序员可以实现类的对象相加(任何数据类型均可以相加)
示例代码:认识运算符重载
#include <iostream>
/*
运算符重载的基本语法规则
让两只猫对象可以相加,规定猫对象相加就是把年龄相加
运算符重载总结成公式:
返回值类型 operator运算符号(形参)
{
// code
}
总结:运算符重载本质就是个函数,这个函数有固定写法(名字必须叫做operator运算符号)
*/
class Cat {
public:
Cat(int _age) : age(_age) {}
/*
问题1:如何确定返回值类型和形参?
答案:依据运算表达式反推
int result=c1+c2; //c1+c2是两个猫对象相加,返回值类型是int,形参是Cat &cat
转换成函数调用的写法
int result=c1.operator+(c2) //函数调用的思维来写
*/
//猫类里面重载加法运算,让猫具备做加法的能力
int operator+(Cat &cat){
return this->age + cat.age;
}
private:
int age;
};
int main(int argc, char const *argv[])
{
Cat cat1(3), cat2(5);
// int result = cat1 + cat2; // 等同于 cat1.operator+(cat2);
int result = cat1.operator+(cat2);
std::cout << "cat1 + cat2 is: " << result << std::endl; // cat1 + cat2 is: 8
Cat cat3 = 8 + cat2; //
std::cout << "8 + cat2 is: " << cat3.age << std::endl;
return 0;
}
2.实现方法:
2.1 运算符重载的本质:
就是程序员自定义一个函数,通过该函数实现运算符功能的扩展
返回值 operator运算符号(参数)
{
}
2.2 运算符重载函数的特点:
2.2.1 统一叫做 operator运算符号
operator+
operator-
operator>
operator++
operator==
operator[]
2.2.2 运算符重载有两种形式
- 把运算符重载为类的成员函数(隐含着有this指针,函数的参数可以少一个)
- 把运算符重载为类的友元函数(没有this指针,函数的参数必须完整)
\quad 正常情况下,运算符重载为形式1或者形式2都是没有问题,但是有些情况(不能通过对象来调用)只能用形式2
如:
Rect r2=5+r1;
思考步骤:
第一步:我知道要重载加法,但是还不知道是重载为友元函数,还是成员函数合适,还是两种都合适
第二步:Rect r2=5.operator+(c1); //成员函数的理解,错误的,5不是Rect的对象
Rect r2=operator+(5,c1); //友元函数的理解,正确了
运算符重载思考思维:
示例代码:把运算符重载作为类的成员函数
#include <iostream>
/*
运算符重载为类的成员函数
*/
class Cat {
public:
Cat(int _age = 0) : age(_age) {}
//猫类里面重载加法运算,让猫具备做加法的能力
// int operator+(Cat &cat){
// return this->age + cat.age;
// }
// int operator+(int num){
// return this->age + num;
// }
Cat operator+(Cat &cat) {
Cat temp;
temp.age = this->age + cat.age;
return temp;// Cat(this->age + cat.age)
}
Cat operator+(int n) {
return Cat(this->age + n);
}
void show(){
std::cout<<"age: "<<this->age<<std::endl;
}
private:
int age;
};
int main(int argc, char const *argv[])
{
Cat cat1(3), cat2(5);
// 写法1:
// int result = cat1 + cat2; // 等同于 cat1.operator+(cat2);
// int result = cat1.operator+(cat2);
// std::cout << "cat1 + cat2 is: " << result << std::endl; // cat1 + cat2 is: 8
// 写法2:
// result = cat1 + 100; // 等同于 cat1.operator+(100);
// std::cout << "cat1 + 100 is: " << result << std::endl; // cat1 + 100 is: 103
// 写法3:
Cat c = cat1 + cat2; // 等同于 Cat c = cat1.operator+(cat2);
c.show(); // age: 8
// 写法4:
c = cat1 + 100; // 等同于 Cat c = cat1.operator+(100);
c.show(); // age: 103
return 0;
}
示例代码:运算符重载作为友元函数:
#include <iostream>
/*
运算符重载两种表现形式:
形式1:运算符重载作为类的成员函数
形式2:运算符重载作为类的友元函数
两者的区别:
区别1:运算符重载作为类的友元函数,无法通过对象来调用,因此代码中不可以使用this指针
区别2:运算符重载作为类的友元函数,此时没有当前对象,因此传递参数的时候,参数的个数会多一个
*/
class Cat
{
public:
Cat(int _age=0)
{
age=_age;
}
//加法重载作为猫类的成员函数
// int operator+(Cat &other)
// {
// cout<<"加法重载作为猫类的成员函数被调用了"<<endl;
// return this->age+other.age;
// }
friend int operator+(Cat &c1,Cat &c2);
private:
int age;
};
//加法重载作为猫类的友元函数
int operator+(Cat &c1,Cat &c2)
{
std::cout<<"加法重载作为猫类的友元函数被调用了"<<std::endl;
return c1.age+c2.age;
}
int main()
{
Cat c1(5);
Cat c2(7);
//更加清晰地看到,的确是成员函数
//int result=c1.operator+(c2);
//cout<<"c1+c2 is: "<<result<<endl;
//更加清晰地看到,的确是友元函数
//result=operator+(c1,c2);
//cout<<"c1+c2 is: "<<result<<endl;
//换成写法1:用人类的思维习惯再来调用
/*
这种写法会造成冲突,这种写法跟成员函数、友元函数都能匹配
因此这种写法下,成员函数或者友元函数只能留下了一个
*/
int result=c1+c2;
std::cout<<"c1+c2 is: "<<result<<std::endl;
}
/*
执行结果:
加法重载作为猫类的友元函数被调用了
c1+c2 is: 12
*/
2.2.3 运算符重载不可以改变运算符原本的语义(不是语法规定,行业潜规则)
比如: 加法运算--》心中的印象就是相加,把两个东西合并
不可以发明新的运算符号
如下运算符号不可以重载
?: :: . sizeof
3.运算符重载有两种表现形式:
- 把运算符重载作为类的成员函数
- 把运算符重载作为类的友元函数(注意写法的差异,不能使用this,不属于类的成员函数)
4.几种特殊的运算符重载
4.1 自增和自减
\quad 后置的自增,自减需要添加int类型的参数(参数必须是int类型,不可以是其他类型,为了跟前置的区分)
示例代码:重载++
#include <iostream>
using namespace std;
/*
重载++
自增运算有两种
a++
++a
*/
class Cat
{
public:
Cat(int _age=0)
{
age=_age;
}
//搞定后置的++
/*
人类的思维习惯
Cat c2=c1++;
函数调用
Cat c2=c1.operator++(); //成员函数的理解
Cat c2=operator++(c1); //友元函数的理解
*/
//error: no ‘operator++(int)’ declared for postfix ‘++’
//错误原因:后置的++添加int类型的参数为了跟前置的++区别
Cat operator++(int n)
{
cout<<"后置的++重载被调用"<<endl;
Cat temp;
//先备份当前对象的值
temp.age=this->age;
//当前对象的值自己加1
this->age+=1;
return temp;
}
Cat operator++()
{
cout<<"前置的++重载被调用"<<endl;
//当前对象的值自己加1
this->age+=1;
return *this;
}
void show()
{
cout<<"年龄: "<<age<<endl;
}
private:
int age;
};
int main()
{
Cat c1(5);
//写法1:后置的++
// Cat c2=c1++;
// c1.show(); //6
// c2.show(); //5
//前置的++
// Cat c3=++c1;
// c1.show(); //7
// c3.show(); //7
//写法2:函数调用的写法
Cat c2=c1.operator++(666);
c1.show(); //6
c2.show(); //5
Cat c3=c1.operator++();
c1.show(); //7
c3.show(); //7
}
4.2 <<(插入运算符)和>>(抽取运算符)配合cout和cin
示例代码:理解cout输出原理
#include <iostream>
using namespace std;
/*
研究cout输出的原理
*/
int main()
{
int a=456;
float b=45.6;
char c='@';
//写法1:人类的思维习惯
//cout<<a;
cout<<a<<b<<c<<endl;
//写法2:函数调用的写法
//cout.operator<<(a);
//偷懒:一次性搞定
cout.operator<<(a).operator<<(b).operator<<(c).operator<<(endl);
//分步骤写:方便理解 ostream &:返回ostream的引用,方便连续输出
ostream &ret1=cout.operator<<(a);
ostream &ret2=ret1.operator<<(b);
ostream &ret3=ret2.operator<<(c);
ret3.operator<<(endl);
}
5.重新认识string和cout以及cin
5.1 string
string str=str1+str2; //对+重载了
示例代码:string类重载的运算符
#include <iostream>
using namespace std;
/*
string类重载的运算符
*/
int main()
{
string str="hello";
string str1="nihao";
//重载了中括号,获取下标对应的字符
cout<<str[1]<<endl; //e
cout<<str.operator[](1)<<endl; //e
//重载了赋值
// str=str1;
// str.operator=(str1);
// cout<<str<<endl; //nihao
}
5.2 cout
//对<<重载,实现cout<<string的对象
cout<< //ostream重载了<<符号,实现的效果就是输出
class ostream
{
pubic:
ostream &operator<<(参数) cout<<a<<b; -->cout
{
代码;
return cout; //关键点,实现连续输出的关键点
}
};
写法一:人类的思维习惯
cout<<a<<b<<endl; //连续输出
写法二:函数调用的习惯
cout.operator<<(a).operator<<(b).operator<<(endl);
ostream重载左移运算:
示例代码:cout输出自定义的类对象
#include <iostream>
using namespace std;
/*
受到cout输出的原理启发,直接打印(输出)结构体,自定义的类对象(猫)
*/
class Cat
{
public:
Cat(string _name,float _weight,int _age)
{
name=_name;
weight=_weight;
age=_age;
}
//friend void operator<<(ostream &out,Cat &other); //缺陷:无法连续打印
friend ostream &operator<<(ostream &out,Cat &other); //可以连续打印
private:
int age;
string name;
float weight;
};
//void operator<<(ostream &out,Cat &other) //缺陷:无法连续打印
ostream &operator<<(ostream &out,Cat &other) //可以连续打印
{
cout<<"友元函数被调用"<<endl;
out<<other.name<<endl;
out<<other.weight<<endl;
out<<other.age<<endl;
return out;
}
int main()
{
Cat c1("旺财",55.8,5);
Cat c2("来福",56.8,6);
/*
人类的思维习惯
cout<<c1;
cout<<c1<<c2;
函数调用
cout.operator<<(c1) //成员函数,不正确,ostream这个类里面根本就没有这个版本
operator<<(cout,c1) //友元函数,正确
*/
//人类的思维习惯
//cout<<c1;
cout<<c1<<c2;
//函数调用
//operator<<(cout,c2);
//浓缩,不好理解
operator<<(operator<<(cout,c1),c2);
}
/*
执行结果:
友元函数被调用
旺财
55.8
5
友元函数被调用
来福
56.8
6
*/
5.3 cin
cin>> //istream重载了>>符号,实现的效果就是获取键盘输入
class istream
{
pubic:
istream &operator>>(参数)
{
代码;
return cin; //关键点,实现连续输入的关键点
}
};
写法一:人类的思维习惯
cin>>a>>b //连续输入
写法二:函数调用的习惯
cin.operator>>(a).operator>>(b)
istream重载右移运算:
示例代码:读取键盘输入的数据存放到自定义的类对象
#include <iostream>
using namespace std;
/*
受到cout输出的原理启发,我想直接打印(输出)结构体,自定义的类对象(猫)
受到cin输入的原理启发,我想直接读取键盘输入的数据存放到自定义的类对象(猫)
*/
class Cat
{
public:
friend ostream &operator<<(ostream &out,Cat &other); //可以连续打印
friend istream &operator>>(istream &in,Cat &other);
private:
int age;
string name;
float weight;
};
ostream &operator<<(ostream &out,Cat &other) //可以连续打印
{
cout<<"友元函数输出被调用"<<endl;
out<<other.name<<endl;
out<<other.weight<<endl;
out<<other.age<<endl;
return out;
}
istream &operator>>(istream &in,Cat &other) //可以连续输入
{
cout<<"友元函数输入被调用"<<endl;
in>>other.name;
in>>other.weight;
in>>other.age;
return in;
}
int main()
{
Cat c1;
Cat c2;
cout<<"请依次输入猫的姓名,体重,年龄"<<endl;
/*
人类的思维习惯
cin>>c1;
函数调用
cin.operator>>(c1) //成员函数,不正确,istream这个类里面根本就没有这个版本
operator>>(cin,c1) //友元函数,正确
*/
cin>>c1;
cin>>c2;
//或者
//cin>>c1>>c2;
cout<<c1<<c2;
}
/*
执行结果:
请依次输入猫的姓名,体重,年龄
友元函数输入被调用
xiaoh
45.6
5
友元函数输入被调用
xiaol
56.7
9
友元函数输出被调用
xiaoh
45.6
5
友元函数输出被调用
xiaol
56.7
9
*/
练习:
1.实现
Cat c3;
c3=c1+c2;
c3=c1+5;
c3=5+c1;
实现学生结构体可以做加法运算(把学生年龄相加即可)
示例代码:
#include <iostream>
using namespace std;
struct student //披着结构体的外衣 ,其实已经算是一个类
{
char name[10];
int age;
//重载加法运算,作为成员函数
int operator+(struct student &other)
{
cout<<"结构体做加法"<<endl;
return age+other.age;
}
};
int main()
{
struct student stu1={"张三",18};
struct student stu2={"李四",20};
cout<<"stu1+stu2 is: "<<stu1+stu2<<endl; // stu1+stu2 is:38
}
2.两种方式实现运算符重载
猫的对象可以用> < ==比较年龄的大小
if(c1>8)
if(8>c1)
if(c1>c2)
示例代码:
#include <iostream>
using namespace std;
class Cat
{
public:
Cat(int _age)
{
age=_age;
}
/*
if(c1>8)
if(8>c1)
if(c1>c2)
*/
bool operator>(int n)
{
if(age>n)
return true;
else
return false;
}
bool operator>(Cat &other)
{
if(age>other.age)
return true;
else
return false;
}
friend bool operator>(int n,Cat &other);
private:
int age;
}
bool operator>(int n,Cat &other)
{
if(n>other.age)
return true;
else
return false;
}
int main()
{
Cat c1(5);
Cat c2(10);
if(c1>c2)
cout<<"大于"<<endl;
else
cout<<"小于等于"<<endl;
}
3.定义矩形类,宽,高两个属性
实现:
Rect r4=9+r1+5+r2+6+r3-7; //从左到右,一步步计算
要求:分别用两种重载实现
示例代码:
#include <iostream>
using namespace std;
/*
Rect r4=9+r1+5+r2+6+r3-7;
*/
class Rect
{
public:
Rect(int _w=0,int _h=0)
{
w=_w;
h=_h;
}
Rect operator+(int n)
{
cout<<"成员版本的加法重载,带int参数"<<endl;
Rect temp;
temp.w=n+w;
temp.h=n+h;
return temp;
}
Rect operator-(int n)
{
cout<<"成员版本的减法重载,带int参数"<<endl;
Rect temp;
temp.w=w-n;
temp.h=h-n;
return temp;
}
Rect operator+(Rect &other)
{
cout<<"成员版本的加法重载,带Rect参数"<<endl;
Rect temp;
temp.w=w+other.w;
temp.h=h+other.h;
return temp;
}
void show() const
{
cout<<"宽: "<<w<<" 高: "<<h<<endl;
}
friend Rect operator+(int n,Rect &other);
private:
int w;
int h;
};
Rect operator+(int n,Rect &other)
{
cout<<"友元版本的加法重载"<<endl;
Rect temp;
temp.w=n+other.w;
temp.h=n+other.h;
return temp;
}
int main()
{
Rect r1(10,5);
Rect r2(25,17);
Rect r3(40,22);
Rect r4=9+r1+5+r2+6+r3-7;
r4.show();
}
/*
执行结果:
友元版本的加法重载
成员版本的加法重载,带int参数
成员版本的加法重载,带Rect参数
成员版本的加法重载,带int参数
成员版本的加法重载,带Rect参数
成员版本的减法重载,带int参数
宽: 88 高: 57
*/