运算符重载
说明:
- 运算符重载就是给运算符一些重新定义其使用方式,让我们原本只能内置数据类型类型使用的符号可以让自定义类型去使用该符号。
- 比如 +号,在内置类型中就是数据加减的意思,但是在stirng类中的意思确实字符串的拼接,string中的+就是+号运算符重载的结果。
- 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
- 语法:
函数返回类型 operator重载符号 (参数列表){ }
- operator是关键字,必须写才代是运算符重载函数。
注意:
加号+运算符重载
加号运算符重载就是让加号可以在自定义的数据类型中使用。
问题引出:
int a = 10;
int b = 10;
int c = a+b ;
请问上诉代码 c的值 等于多少?
答:c = 10;是的,没错。
在看这段代码
class Add
{
public:
int a;
int b;
};
int main ( )
{ //创建了一个Add类的对象
Add add1;
//给对象的成员变量赋值
add1.a = 10;
add1.b = 10;
//创建了一个Add类的对象
Add add2;
//给对象的成员变量赋值
add2.a = 10;
add2.b = 10;
Add add3 = add1 + add2;
return 0;
}
请问上诉代码的 c 的值等于多少?
答:上诉代码得不到正确结果。会发生错误。j
哎,是不是有点意思,两段代码都是加法的操作,第一段有结果,第二段却没有。仔细观察你发现,第一段的代码 a b c 的类型都是内置的数据类型,而第二段的代码add1 add2 add3都是自定义的数据类型,在c++中,是没有给自定义的数据类型提供运算符的使用的,只能自己写,即我们所说的加号运算符重载。
如何解决上诉第二段代码问题?我们先不引入加号运算符重载,用平时的函数方式去思考以下。
答
- 可以在Add类中设计一个成员函数 AddNums,实现两个数相加;
- 可以设计一个全局函数 AddNums, 实现两个数相加;
设计成员函数运算符重载
下面我以 1. 可以在Add类中设计一个成员函数 AddNums,实现两个数相加;设计全局函数的供你们参考和对比与成员函数的不同点。我会总结两者的不同点,但本质一样的。
class Add
{
public:
int a;
int b;
//成员函数
Add AddNums(Add & add)
{
Add temp;
temp.a = this->a + add.a;
temp.b = this->b +add.b;
return temp;
}
};
int main ( )
{ //创建了一个Add类的对象
Add add1;
//给对象的成员变量赋值
add1.a = 10;
add1.b = 10;
//创建了一个Add类的对象
Add add2;
//给对象的成员变量赋值
add2.a = 10;
add2.b = 10;
// 调用成员函数
Add add3;
add3 = add1.AddNums(add2); //这就实现了两个数相加
return 0;
}
哇,视乎一切都可以理解。在c++中就是运算符重载也是这样的过程。但是,在c++中人家会把 AddNums 的函数名字换成 operator+ 的名字变成下面的样子。
//c++加号运算符重载之后的样子
Add operator+ (Add & add)
{
Add temp;
temp.a = this->a + add.a;
temp.b = this->b +add.b;
return temp;
}
而调用时候也由 add1.AddNums(add2) 变成 add1.operator+(add2); 到这里人都傻了,这样做不是更加麻烦了吗?把名字换成了operator+ 那么复杂的名字,还不如我的 AddNums 直接美观。对的,我也是这么认为,可是,一般这种傻里傻气的操作不是你干滴,编译器专门做这种累活,我们在调用时候 这么调用就可以 add1 + add2; 啊,到这里,你会不会感叹它的简介和人性化,这不是和我们平时看到的内置加号的使用方式一样吗?对的就是一样,不得不感叹,c++真牛。
最终版本:
class Add
{
public:
int a;
int b;
//c++加号运算符重载之后的样子
Add operator+ (Add & add)
{ // 里面的具体实现,要符合加号的常规思维,是加就加哈。
Add temp;
temp.a = this->a + add.a;
temp.b = this->b +add.b;
return temp;
}
};
int main ( )
{ //创建了一个Add类的对象
Add add1;
//给对象的成员变量赋值
add1.a = 10;
add1.b = 10;
//创建了一个Add类的对象
Add add2;
//给对象的成员变量赋值
add2.a = 10;
add2.b = 10;
Add add3;
//等价于 add3 = add1.operator+ (add2);
add3 =add1 + add2;
return 0;
}
重点思考以下它的调用语句:add3 =add1 + add2; 本质是 add1.operator+(add2);add1对象去调用函数,传参传入add2,这就是成员函数作为运算符重载时候的调用方式。看图一 一 对应关系;
我们思考写写重载函数的时候,也是从需求出发,先思考调用时候怎么写,然后再反向设计重载函数。这个是我的一个写重载函数的小技巧
设计全局函数做运算符重载
Add operator+ (Add & add1,Add& add2)
{ // 里面的具体实现,要符合加号的常规思维,是加就加哈。
Add temp;
temp.a = add1.a + add2.a;
temp.b = add1.b +add2.b;
return temp;
}
//调用
//本质:add3 = operator+(add1,add2);
add3 = add1 + add2;
调用方式也是 add1 + add2;这里全局函数比成员函数多了一个形参数,是因为设计为全局函数了,调用时候的本质为:operator+(add1,add2);是给全局函数传参的。但是实际写的调用实质:add3 = add1 + add2;
左移<<运算符重载
左移运算符重载就是对 << 符号重载;
注意:不要用成员函数去重载左移运算符,不符合日常使用逻辑。
为什么呢?看一段代码:
class Add
{
public:
//有参构造构造函数
Add (int a,int b):m_a(a),m_b(b) { }
// << 运算符重载,成员函数重载方式
//调用时候就是:
//add<<cout;
void operator<< (ostream& cout)
{
cout << add <<endl;
}
private:
int m_a;
int m_b;
};
int main ( )
{ //创建了一个Add类的对象
Add add;
//调用时候就是:
add<<cout;
return 0;
}
你看调用的方式能一样吗?和我们日常使用的 cout << add;的逻辑不一样,虽然 add << cout;也可以运行,可是没必要这样设计。所以不建议 成员函数运算符重载 <<;
全局函数重载运算符 <<:
思考过程: 我们以上面的代码为例子:我们是为了调用时候这样写 cout << add;本质为:operator<<(cout,add); 所以我们设计时候就是写出框架和实现;
void operator<< (ostream& cout,Add& add)
{
//实现
cout << "a:" <<add.m_a << "b:" <<add.m_b;
}
这里我们注意 cout的类型是库函数里面的类型:ostream;
然后我们又想写:cout << add <<“Hello”; 或者 cout<<add<<endl; 你发现上诉代码不行了,因为上诉代码的返回类型是 void,也就是说我们写的 cout<<add;的返回类型就是void,你说一个void的类型还能继续 :void << "Hello"吗?不行把,那就修修改:
ostream& operator<< (ostream& cout,Add& add)
{
//实现
cout << "a:" <<add.m_a << "b:" <<add.m_b ;
return cout;
}
这样就可以实现:cout << add <<“Hello”;cout<<add<<endl; 并且后面可以无限追加 << ,这就类似一种链式编程思想。
完整代码:
class Add
{
friend ostream& operator<< (ostream& cout,Add& add);
public:
//有参构造构造函数
Add (int a,int b):m_a(a),m_b(b) { }
private:
int m_a;
int m_b;
};
ostream& operator<< (ostream& cout,Add& add)
{
//实现
cout << "a:" <<add.m_a << "b:" <<add.m_b ;
return cout;
}
int main ( )
{ //创建了一个Add类的对象
Add add(10,10);
//调用时候就是:
cout << add <<endl;
return 0;
}
递增++运算符重载
class MyInteger {
// 左移运算符重载
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++ 重载
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++ 重载
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
//测试:前置递增
test01();
//测试:后置递增
test02();
system("pause");
return 0;
}
总结:
成员函数来设计递增运算符重载的时候:
前置递增运算符的返回类型为引用类型;
后置递增为值返回类型;
后置递增的函数参数列表有一个int占位参数,用来区分 前置和后置递增的重载函数。
赋值 = 运算符重载
同样,赋值运算符,也是实现自定义的数据类型赋值。
特别注意在堆区开辟空间的成员变量,防止浅拷贝的问题;
class Add
{
public:
int* m_a;
public:
//有参构造
//堆区开辟空间存放宿舍
Add(int add)
{
m_a = new int(add);
}
~Add()
{
if (m_a != NULL)
{
delete m_a;
m_a = NULL;
}
}
public:
// = 运算符重载
//返回类型为 Add& 也是为了支持 链式赋值
Add& operator= (Add& add)
{ //先对 调用该函数的初始值先给置空
if (this->m_a != NULL)
{
delete this->m_a;
this->m_a = NULL;
}
this->m_a = new int(*add.m_a);
return *this;
}
};
void text01()
{
Add add1(10);
Add add2(20);
// = 运算符重载的调用
add2 = add1;
Add add3(30);
// = 运算符重载的调用,链式方式,也是设计 Add& 返回类型的原因
add2 = add1 = add3;
}
函数()运算符重载
就是对()进行重载;
class MyPrint
{
public:
//重载()
void operator()(string name)
{
cout << name << endl;
}
};
//打印函数
void MyPrint(string name)
{
cout << name << endl;
}
void test01()
{
MyPrint myFunc;
//重载的()操作符
//及其类似 函数的调用形式 由于这个原因也称为仿函数
myFunc("hello world");
// 函数调用
myPrint("hello world");
}
// 第二个例子 ,这说明函数重载很灵活,调用时候很灵活
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
//匿名对象调用
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
总结:
对象(实参)
这是重载()的调用方式;
类型()
等价于对象
我们把它称为 匿名对象;
总结:
其实运算符重载的本质就是给 自定义的类型的对象可以实现运算符直接的运用;
还有许多我没写在这篇文章里,你们就触类旁通,可以根据自己的需求去实现自
己的运算符重载哦。
在c++中,不支持运算符重载:
(1). (成员访问运算符)
——————————————————
(2).* (成员指针访问运算符)
——————————————————
(3)∷(域运算符)
——————————————————
(4)sizeof(长度运算符)
——————————————————
(5)?: (条件运算符)
——————————————————
感谢阅读,希望能帮助到你。