运算符重载
运算符重载本质上也是一种函数重载,运算符重载主要用于扩展运算符的使用范围,例如,+一般用于数字的加和,类A中包含了int类型的a和b,如果我要让类A的两个对象相加表示类A中的a和b分别相加,然后返回新的对象,那么传统的+无法完成这项任务,就需要重载+运算符,代码如下:
class A
{
public:
int a, b;
explicit A(const int a, const int b): a(a), b(b){}
// 1. 直接返回一个新的拷贝,消耗性能
A operator+(A &_a) const
{
A retA(0, 0);
retA.a = this->a + _a.a;
retA.b = this->b + _a.b;
return retA;
}
// 可以直接返回引用
A& operator+(A &_a) // 2. 此时改变了类的属性值,不用const
{
this->a = this->a + _a.a;
this->b = this->b + _a.b;
return *this;
}
};
int main() {
A a(1,2);
A b(3,4);
A c = a + b;
std::cout << c.a << " " << c.b << std::endl;
return 0;
}
// 4 6
运算符重载的两种方法
类内成员函数重载
成员函数重载类型可以分为一元运算符和二元运算符
二元运算符重载
即 + - * / 等的重载,例如我们实现 在类之间实现 - ,重载其他的运算符类似
class A
{
public:
int a, b;
...
A & operator-(A &_a)
{
a = a - _a.a;
b = b - _a.b;
return *this;
}
...
};
一元运算符重载
例如 ++ -- 等,针对这个运算符,存在前置和后置的概念,对于前置 ++,需要注意是先进行+运算,再返回,因此其代码如下:
A & operator++()
{
a = a + 1;
b = b + 1;
return *this;
}
后置++与前置不同,后置++需要使用int作为函数参数占位符,以此区分前置后置,且前置++返回的是对象的本身,可以返回引用,而后置++是先返回对象,再对对象 ++,故返回的是拷贝。
class A {
public:
A(const A& a)
{
this->a = a.a;
this->b = a.b;
}
A operator++(int)
{
A r(*this); // 调用拷贝构造函数
// 如果修改成A r = *this则调用拷贝赋值运算符
a = a + 1;
b = b + 1;
return r;
}
}
友元函数重载
友元函数不是类的成员函数,但是它能够获取到类的私有属性,可以这么理解,友元函数可以看成是类的好兄弟,类的小秘密(私有属性)能够和好兄弟共享,自己的儿子(继承类)或者其他人都无法知道这个秘密。
友元函数的声明需要在类中表示,即在一个函数前加上friend关键字即可。我们通过友元函数再来实现一下上面的一元二元运算符,并且包括前置++和后置++
class A
{
private:
int a, b;
public:
explicit A(int a, int b): a(a), b(b) {}
~A(){};
friend A operator+(A &a, A &b);
friend void operator++(A &a);
friend A operator++(A &a, int); // 同样需要使用占位符表示前置和后置
int getA() {return a;}
int getB() {return b;}
};
// 类的成员函数此处重载+可以返回,可以不返回,因为成员函数可以获取到this,
// 而友元函数无法获取到this,故需要返回。
A operator+(A &a, A &b)
{
A c(0, 0);
c.a = a.a + b.a;
c.b = a.b + b.b;
return c;
}
void operator++(A &a)
{
a.a += 1;
a.b += 1;
}
A operator++(A &a, int)
{
A b(a.a, a.b);
a.a += 1;
a.b += 1;
return b;
}
int main() {
A a(1,2);
A b(3,4);
A c = a + b;
++ b;
A d = b++;
std::cout << c.getA() << " " << c.getB() << std::endl;
std::cout << b.getA() << " " << b.getB() << std::endl;
std::cout << d.getA() << " " << d.getB() << std::endl;
return 0;
}
运算符重载本质上来说也是函数调用
// 我们可以通过下面的方法调用
A c = operator+(a, b); // 效果等同于A c = a + b; 我们更习惯于写成后面这种
A d = func(a, b); // 等同于这种
一些特定的运算符的重载
=:赋值运算符重载,当类中包含动态分配的内存时,必须重载赋值运算符,否则可能出问题。默认的赋值运算符采用的是浅拷贝,即拷贝指针的值,不拷贝指针所指向的值,拷贝之后有两个指针指向同一块儿内存,当主要程序运行结束后,释放内存,当delete 一个指针后,另外一个指针所指向的内存被释放,指向内容不确定,再次delete就不合理。因此针对这种情况,必须重载赋值运算符函数以及拷贝构造函数。
class B
{
public:
explicit B(int a, int b) : a(a), b(b)
{
c = new int(5);
}
~B() // 析构函数,释放内存
{
delete c;
}
// 拷贝构造函数
B(const B & b_)
{
this->a = b_.a;
this->b = b_.b;
this->c = new int(*b_.c);
}
// 拷贝赋值运算符 ,这个是属于=前面的对象的,因此使用this可以操作它
B& operator=(const B & b_)
{
this->a = b_.a;
this->b = b_.b;
this->c = new int(*b_.c);
return *this;
}
public:
int a, b;
int * c;
};
B b(1,2,3);
B c(b); // 拷贝构造函数
B d = b; // 拷贝赋值运算符
std::cout << b.a << " " << b.b << " " << *b.c << std::endl;
[]重载,这个代表数组取下标符号,比较常用的数组,例如vector,array等都可以使用[],因此重载[]可直接取下标非常有必要,以数组为例
class Array
{
public:
explicit Array(int size): S(size)
{
arr = new int[size];
cur = -1;
}
~Array() // 有动态分配的内存就需要手动delete或者free
{
delete [] arr;
}
int& operator[](int t)
{
assert(t >=0 && t < S);
return *(arr + t);
}
void push_back(int v)
{
assert(cur + 1 < S);
++ cur;
arr[cur] = v;
}
void pop_back()
{
assert(cur > -1);
-- cur; // 只需要向前 - 1即可
}
int getSize()
{
return cur + 1;
}
private:
int * arr;
int S;
int cur;
};
()重载,即函数调用的方法
class C
{
public:
double operator()(double a, double b)
{
return a * b;
}
};
注意: = [] ()均不能通过友元函数重载
>> << ,这两个运算符在c++中作为插入运算符和提取运算符,通常在cin和cout中使用。我们可以通过重载这两个运算符用于自定义的类型
class D
{
public:
// 返回的必须是引用,以完成连续 >> 或者连续 << 的操作
explicit D(int c, int d): c(c), d(d) {};
friend std::ostream& operator<<(std::ostream& out, const D &d);
friend void operator>>(std::istream& in, D &d);
public:
int c, d;
};
// 输出系列
std::ostream& operator<<(std::ostream& out, const D &d)
{
out << "D(" << d.c << "," << d.d << ")" << std::endl;
return out;
}
// 输入系列
void operator>>(std::istream& in, D &d)
{
in >> d.c >> d.d;
std::cout << d;
}
? 为什么上述 >> << 的重载需要使用友元函数
因为上述运算符的参数有一个是这个类的对象,但是另外一个并不是这个类的对象
关系运算符的重载
成对出现,例如 > 与 <、>=与<=、==与!=
class E
{
public:
explicit E(int a, int b) : a(a), b(b) {};
// == 与!=
bool operator==(const E &e)
{
return (a + b) == (e.a + e.b);
}
bool operator!=(const E &e)
{
return !(*this == e); // 直接调用上面的==函数
}
// > 与 <
bool operator>(const E &e)
{
return (a + b) > (e.a + e.b);
}
bool operator<(const E &e)
{
return !(*this >= e);
}
// >= 与 <=
bool operator>=(const E &e)
{
return (a + b) >= (e.a + e.b);
}
bool operator<=(const E &e)
{
return !(*this > e);
}
public:
int a, b;
};