运算符重载1(operator overload)
运算符重载(操作符重载) : 可以为运算符增加一些新的功能.
#include <iostream>
using namespace std;
class Point {
friend Point operator+(const Point &, const Point &);
private:
int m_x;
int m_y;
public:
Point(int x, int y) : m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << ", " << m_y << ")" << endl;
}
};
Point operator+(const Point &p1, const Point &p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
int main()
{
Point p1(10, 20);
Point p2(20, 30);
Point p3 = p1 + p2;
// 本质是Point p3 = operator+(p1, p2);
p3.display();
Point p4 = p1 + p2 + p3;
// 本质是调用了两次operator+, 第一次的结果又作为参数传进去
p4.display();
return 0;
}
运算符重载2-完善
(1)为什么拷贝构造函数的参数是const Point &point 形式?
1.必须是引用, 否则报错.
class Point {
public:
Point(Point point) {
this->m_x = point.m_x;
this->m_y = point.m_y;
}
};
int main() {
Point p1(10, 20);
Point p2 = p1;
// 利用已经存在的对象创建新的对象,
// 调用拷贝构造函数, 相当于Point point = p1
// 又是利用已经存在的对象创建新的的对象, 又
// 要调用拷贝构造函数, 就又相当于
// Point point = p1, 从而陷入死循环.所以
// 必须是引用. 一旦变成引用就是地址值的赋值
// 就不存在创建对象了.
}
2.最好是const, 从而保证可以接受const对象和非const对象, 使接受参数的范围更大.
运算符重载3-更多运算符
(1)一般来说跟某个对象相关的代码, 干脆重载为成员函数.
一旦变为成员函数, 就变成了
class Point {
public:
Point operator+(const Point &point) {
return Point(this->m_x + point.m_x, this->m_y + point.m_y);
// 只接受一个参数, 因为一旦变为成员函数, 就
// 通过对象去调用了, 比如p1 + p2 本质就变成了
// p1.operator+(p2);就意味着会把p1的地址传给
// operator+函数, this就指向外面的p1.
// 对比原来来说简单了, 首先只需要接受一个参数
// 其次这个功能本来就是跟Point相关的, 所以
// 直接写在Point类里面.而且一旦写成成员函数就
// 意味着我们自己的私有成员变量可以访问.
// 不需要友元函数.
}
};
(2)存在的问题:
在重载运算符的时候, 最好要保留运算符原有的特性, 比如:
因为int a = 10, b = 20; (a + b) 这个表达式不允许被赋值(因为a + b 返回的是常量并没有被存储在内存中), 所以(p1 + p2)也应该不能赋值, 因为p1 + p2 返回的是一个临时对象, 马上就要销毁了, 所以不应该可以赋值, 所以要在operator+函数的返回值前加const声明为返回常量对象, 不允许被赋值, 但这样的话, 又会导致新的问题, 在p1 + p2 + p3 时, 相当于p1.operator+(p2).operator+(p3) 因为常量对象是不能调用非常量函数的(因为在非常量函数里面按语法的话可以修改里面对象的值, 而常量对象又不允许修改, 矛盾), 所以也应该把operator+函数声明为常量函数.最终为
const Point operator+(const Point &point) const {
}
const Point operator-(const Point &point) const {
}
(3)+=运算符
因为int a = 10, b = 20; (a += b) = 10; 是可以被赋值的, 因为a += b是a + b后的结果又赋值给a, 而a可以被赋值, 所以(p1 += p2)也应该可以赋值
所以应该返回p1对象, 而返回对象(把对象作为返回值)又会导致产生中间对象的问题, 所以应该返回引用.
class Point {
public:
Point &operator+=(const Point &point) {
this->m_x += point.m_x;
this->m_y += point.m_y;
return *this;
}
};
(4)==运算符
int a = 10, b = 20;
if (a == b) 原来是相等返回1, 不相等返回0, 所以是bool类型
bool operator==(const Point &point) const {
// 要保证常量对象可以和非常量对象比较
/* if ((m_x == point.m_x) && (m_y == point.m_y)) {
return 1;
}
else {
return 0;
} */
return (m_x == point.m_x) && (m_y == point.m_y);
}
(5)!=运算符
bool operator!=(const Point &point) const {
return (m_x != point.m_x) || (m_y != point.m_y);
}
(6)-运算符(符号)
首先-p1; 不需要传参, p1.operator-();
而且
Point p1(10, 20);
Point p3 = -p1; 时p1并没有改变, 所以应该返回一个临时的
其次int a = 10, b; (-a) = 10;不允许, 因为a并没有被赋值, 返回的是一个临时的值, 所以-p1不允许被赋值, 所以返回const
const Point operator-() const {
// 因为(-(-p1))时相当于 p1.operator-().operator-(),
// 而返回的const对象不能调用非const函数, 所以函数也要声明为const
return Point(-m_x, -m_y);
}
(7)++, --运算符
1.为了区分前置++和后置++规定
void operator++() {
// 是前置++
}
void operator++(int) {
// 是后置++
}
2.前置++可以被赋值, 后置++不可以被赋值
因为int a = 10;
int b = ++a + 5;
相当于a += 1; int b = a + 5; 也就是说++a是先让a += 1, 再把最新的a返回.所以可以被赋值.
而后置++ int a = 10;
int c = a++ + 5; 会先将a之前的值放到a++的地方等价于int c = 10 + 5; 在 a += 1; 所以a++的话并不会返回a.
3.最终实现:
前置:
Point &operator++() {
m_x++;
m_y++;
return *this;
}
后置:
而Point p2 = p1++ + Point(30, 40);
应该可以, 所以p1不能返回void而应该返回p1之前的值
所以
const Point operator++(int) {
Point old(this->m_x, this->m_y);
this->m_x++;
this->m_y++;
return old;
}
(8)<<运算符
1.因为cout << p1 << endl;
所以<<重载函数不能是成员函数, 一旦是成员函数就必须通过Point对象调用
其实cout也是对象, 它的类是ostream
ostream -> output stream
在头文件iostream中
2.因为cout << 1 << 2 << endl;
所以调用完函数后应该返回cout
3.因为(cout << 1) = cout; 返回cout后不允许被赋值, 所以const
但是返回值是const之后, 会导致cout << p1 << p2 出问题, 所以参数cout也应该const, 但是因为系统自带的const的定义是非const的, 所以参数是const会导致原来的const找不到.所以放弃, 但是发现其实不能被赋值, 因为在iostream头文件里, 将=赋值运算符重载到了private导致外面不能访问=
所以返回值不能是const, cout参数不能是const
ostream &operator<<(ostream &cout, const Point &point) {
cout << "(" << point.m_x << ", " << point.m_y;
return cout;
}
(9)cin运算符
input stream -> istream
1.要去掉后面参数的const, 因为是要输入东西去改变point对象.
istream &operator>>(istream &cin, Point &point) {
cin >> point.m_x;
cin >> point.m_y;
return cin;
}
运算符重载-调用父类的运算符重载函数
class Person {
int m_age;
public:
Person &operator=(const Person &person) {
this->m_age = person.m_age;
}
};
class Student : public Person {
int m_score;
public:
Student &operator=(const Student &student) {
Person::operator=(student);
this->m_score = student.m_score;
}
};
int main() {
Student stu;
stu.m_score = 60;
stu.m_age = 10;
Student stu1;
stu1 = stu;
}
运算符重载-仿函数(函数对象)
//int sum(int a, int b) {
// return a + b;
//}
class Sum {
int m_age;
public:
int operator()(int a, int b) {
return a + b;
}
};
int main() {
Sun sum;
cout << sum(10, 20) << endl;
// cout << sum.operator()(10, 20) << endl;
sum(20, 30);
}
仿函数和普通函数的区别是仿函数是成员函数, 意味着可以访问类里面的所有成员变量.
运算符重载注意点
1.有些运算符不可以被重载, 比如
(1)对象成员访问运算符: .
(2)域运算符: ::
(3)三目运算符: ?:
(4)sizeof
2.有些运算符只能重载为成员函数, 比如
(1)赋值运算符: =
(2)下标运算符: []
(3)函数运算符: ()
(4)指针访问成员: ->