一、C++运算符重载之一些运算符的重载
1.1、关于运算符的使用规则
请参考我的上一篇文章:(七)C++基础之运算符重载(一)。
1.2、双目运算符和单目运算符重载
所谓的双目运算符和单目运算符指的是他对应的参数多少,双目运算符就意味着存在两个参数,单目运算符就意味着存在一个参数。
1、双目运算符:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int real, int imaginary)
{
this->real = real;
this->imaginary = imaginary;
}
void print(void)
{
cout << this->real << "+" << this->imaginary << "i" << endl;
}
Complex operator+(Complex &c2)
{
Complex temp(0, 0);
temp.real = this->real + c2.real;
temp.imaginary = this->imaginary + c2.imaginary;
return temp;
}
private:
int real; //实数
int imaginary; //虚数
};
int main(void)
{
Complex c1(1,2);
Complex c2(1, 2);
Complex c3(0, 0);
c3 = c1 + c2;
c3.print();
return 0;
}
运算结果:
2、单目运算符左++和右++:
左++,我们一般都是这样写:
int a;
++a;
甚至可以这样:
++++++a;
关于++a,是先将a自增1,然后在将这个值处理。
所以我们想要实现左++,那么必须满足上面两种情况,其实我们通过 ++++++a; 可以推断出**++a** 返回的是自己本身,可以使用引用的方式来定义,接下来我们实现复数的左++。
代码如下:
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int real, int imaginary)
{
this->real = real;
this->imaginary = imaginary;
}
void print(void)
{
cout << this->real << "+" << this->imaginary << "i" << endl;
}
Complex operator+(Complex &c2)
{
Complex temp(0, 0);
temp.real = this->real + c2.real;
temp.imaginary = this->imaginary + c2.imaginary;
return temp;
}
Complex &operator++()
{
this->real++;
this->imaginary++;
return *this;
}
private:
int real; //实数
int imaginary; //虚数
};
int main(void)
{
Complex c1(1, 2);
Complex c2(1, 2);
Complex c3(0, 0);
c3 = c1 + c2;
c3.print();
++c3;
c3.print();
++++++c3;
c3.print();
return 0;
}
运行结果如下:
重点解释一下这个函数:
Complex &operator++()
{
this->real++;
this->imaginary++;
return *this;
}
由于他是单目运算符,所以我并没有进行传参,至于为什么要返回本身,是因为要满足 ++++++a 这种连续计算的功能,如果不返回本身,那么接下来的那个 **++**肯定无法运行。运行结果也很好的展示可以这样多次累加。
右++的写法如下:
int a = 0;
a++;
他的功能是先拿a的值去处理,处理完之后再对a进行自增1,那他能支持多次运算么?答案是不能。
在这里编译器已经进行报错了,可是另一个问题出现了,左++和右++都是使用++,那么该如何区分呢?
这时候就不得不提一下占位符,可以通过占位符的方式区分,右++需要提供一个占位符来让便一起同识别,你要重载的是右++。
代码如下:
#include <iostream>
using namespace std;
class Complex
{
public:
friend ostream &operator<<(ostream &os, const Complex &c1);
Complex(int real, int imaginary)
{
this->real = real;
this->imaginary = imaginary;
}
void print(void)
{
cout << this->real << "+" << this->imaginary << "i" << endl;
}
Complex operator+(Complex &c2)
{
Complex temp(0, 0);
temp.real = this->real + c2.real;
temp.imaginary = this->imaginary + c2.imaginary;
return temp;
}
Complex &operator++()
{
this->real++;
this->imaginary++;
return *this;
}
const Complex operator++(int)
{
Complex temp(this->real,this->imaginary);
this->real++;
this->imaginary++;
return temp;
}
private:
int real; //实数
int imaginary; //虚数
};
ostream &operator<<(ostream &os, const Complex &c1)
{
os << c1.real << "+" << c1.imaginary << "i";
return os;
}
int main(void)
{
Complex c1(1, 2);
Complex c2(1, 2);
Complex c3(0, 0);
c3 = c1 + c2;
cout << c3++ << endl;
cout << c3 << endl;
return 0;
}
运行结果:
为了能够时刻显示右++的效果,我重载了<<这个运算符的函数,下一小节我们再讲这个,现在来看一下,这样就和普通的右++功能一样了,不能多次累加了。
我来讲一下这个函数:
const Complex operator++(int)
{
Complex temp(this->real,this->imaginary);
this->real++;
this->imaginary++;
return temp;
}
增加一个const就是为了限制不能修改,也就是不能多次累加,括号里面的int就是占位符,他没有什么功能,主要是为了系统辨别右++而已,因为右++是先将值返回给语句处理,然后在则增1,所以临时定一个变量,保存自增前的值,将值返回,然后本身自增1。
1.3、<<和>>的重载
<<左移运算符在C++中重载为cout的输出,这个重载在上一小节我们实现过。我们通过如下:
cout << "hello world";
可以知道他传入的参数需要两个,第一个为cout,他的类型是 ostream,所以我们就可以实现这个左移重载函数了。
在上一小节,我们实现的是输出复数的值,如下:
ostream &operator<<(ostream &os, const Complex &c1)
{
os << c1.real << "+" << c1.imaginary << "i";
return os;
}
因为我们可以连续输出的,所以返回引用本身,可以多次输出,在第二个参数我为什么会增加一个 const ,我在这里说明一下,从安全级别低到高是可以兼容的,但是如果从安全级别高到低,那是不兼容的,而且这只是一个输出函数,我也不允许你修改我里面的值,所以加了个 const 。
可以看到,我将这个重载函数卸载类外,因为如果卸载类里面,他默认第一个参数this,你改不了,如下:
ostream &operator<<(ostream &os)
{
return os;
}
那么你在写成 cout << c3 将会报错,除非你写成 c3 << cout,但是这样和我们的使用习惯相符,所有最终将其写在类的外面的全局函数。
右移操作符>>和左移操作符一样,只是他的类型是istream,在这里我就不举例了。
1.4、等号=运算符
其实这个等号=运算符我们不陌生,在使用拷贝函数的时候我们已经运用过了,请参考之前的文章,在这里我在强调一次,等号=运算符如果里面涉及到指针,然后开辟内存的,一定要记得使用深拷贝,否则容易导致内存泄漏而使程序奔溃。
1.5、[]运算符的重载
对于[],我们可以在数组中很常见,我们自定义一个类似数组的功能,来重载[]这个运算符,代码如下:
#include <iostream>
using namespace std;
class MyArray
{
public:
MyArray(int size)
{
this->len = size;
this->p = new int[size];
if (this->p == NULL){
cout << "new init" << endl;
}
}
~MyArray()
{
if (this->p != NULL){
delete this->p;
this->p = NULL;
}
}
int &operator[](int index)
{
return this->p[index];
}
int len;
int *p;
};
int main(void)
{
MyArray array(5);
array[3] = 10;
for (int i = 0; i < 5; i++){
array[i] = i;
}
cout << "array[3]:" << array[3] << endl;
return 0;
}
运行结果:
对于该函数:
int &operator[](int index)
{
return this->p[index];
}
最终是返回对应的引用作为左值来处理。
1.6、()运算符的重载
对于()小括号的重载,我们看一段代码:
#include <iostream>
using namespace std;
//平方
class Power
{
public:
Power(int data)
{
this->my_power = data * data;
}
int get_power()
{
return this->my_power;
}
void operator()(int data)
{
this->my_power = data * data;
}
private:
int my_power;
};
int main(void)
{
Power p(10);
cout << p.get_power() << endl;
p(20);
cout << p.get_power() << endl;
return 0;
}
运行结果:
对于这个函数,第一个()代表重载的运算符,第二个是填入形参列表的。
void operator()(int data)
{
this->my_power = data * data;
}
最终调用的 p(10); 是运行重载运算符的函数,而不是构造函数,其中如果使用 对象名() 将一个对象作为一个普通函数使用,称这种对象是仿函数或者伪函数。
1.7、&& 和 || 运算符的重载
对于这两个运算符,我们是不建议重载,因为他不会造成 短路 现象,何为 短路 现象,在&&该运算符下,如果第一个条件不成立,第二个判断条件将不会运行,还有||,如果第一个条件成立,第二个条件也不会运行,这和我们预期的结果不同,所以不建议使用。
我们来看一段代码:
#include <iostream>
using namespace std;
//平方
class Test
{
public:
Test(bool b)
{
this->a = b;
}
bool operator&&(Test &another)
{
if ((this->a == true) && (another.a == true)){
return true;
}
else{
return false;
}
}
Test &operator+(Test &another)
{
cout << "执行Test &operator+(Test &another)" << endl;
return *this;
}
private:
bool a;
};
int main(void)
{
Test t1(false);
Test t2(true);
if (t1 && (t1+t2))
return 0;
}
运行结果:
我们看到,当我初始化第一个条件参数为false时候,在&&运算中,还是会执行第二个判断条件,也就是和我们使用 && 平时的习惯不一致,和我们的预期不一样,所以不建议使用。
||的运算符和&&的运算符是一样的道理,在这里我就不举例了。