普通运算符重载:
通过运算符重载,可以定义对象执行相应运算符之后的行为,比如定义两个对象相加的行为:
class Person {
public:
int age;
Person() : age(18) {}
Person operator+(const Person& p) { // 重载+运算符,格式一定要这么写
Person temp;
temp.age = this->age + p.age;
return temp;
}
};
// 写在类外也可以,都一样
Person operator+(const Person& p1, const Person& p2) {
Person temp;
temp.age = p1.age + p2.age;
return temp;
}
int main() {
Person p1, p2;
Person p3 = p1 + p2;
cout << p3.age << endl; // 输出36
return 0;
}
关系运算符重载:
通过定义==与!=运算符,来自定义两个对象的比较关系,默认的==和!=会比较二者的内存地址,很明显永远都不相等。
class Person {
public:
int id;
int age;
Person(int _id, int _age) : id(_id), age(_age) {}
bool operator==(const Person& p) {
return (id==p.id && age==p.age);
}
bool operator!=(const Person& p) {
return (id != p.id || age != p.age);
}
};
int main() {
Person p1(0, 18);
Person p2(1, 28);
cout << (p1 == p2) << endl; // 0
cout << (p1 != p2) << endl; // 1
return 0;
}
赋值运算符重载:
通过重载赋值运算符=,我们可以自定义对象的赋值行为,默认的赋值是完全拷贝过去,那我们考虑以下情况:
class Person {
public:
int* age; // 注意age是一个指针
Person(int _age) : age(new int(_age)) {} // 注意age在堆区被创建
~Person() {
delete age;
}
};
void test() {
Person p1(18);
Person p2(28);
p2 = p1;
}
int main() {
while (true) {
test();
}
}
上述的代码会造成野指针与内存泄漏两个错误!首先,在执行外赋值语句之后,test函数结束了,那p1会被首先释放,执行了析构函数,age也会被从堆区删除,而轮到p2析构时,因为它的age和p1指向的age是一样的,此时age已经被p1的析构删掉了,p2没东西可以删,所以就会造成野指针。然后p2创建时的age,也就是28,因为没人管,会一直留在堆区,随着循环进行,内存泄漏便发生了!
要解决野指针的问题,我们只需要使用一个深拷贝即可:
class Person {
public:
int* age;
Person(int _age) : age(new int(_age)) {}
~Person() {
delete age;
}
Person& operator=(Person& p) { // 重载=运算符
age = new int(*p.age); // 在堆区再创建一个age
return *this;
}
};
void test() {
Person p1(18);
Person p2(28);
p2 = p1;
}
int main() {
while (true) {
test();
}
}
嗯,此时野指针的问题被解决了,可是内存泄漏的问题依然存在,为此,我们再优化一下:
class Person {
public:
int* age;
Person(int _age) : age(new int(_age)) {}
~Person() {
delete age;
}
Person& operator=(Person& p) {
if (age != NULL) {
delete age; // 如果原来存在指向堆区的指针,那么先把堆区里面的东西删掉
}
age = new int(*p.age); // 然后在创建一个新的堆区的变量,重新让age指向它
return *this;
}
};
void test() {
Person p1(18);
Person p2(28);
p2 = p1;
}
int main() {
while (true) {
test();
}
}
左移运算符重载:
在使用cout输出的时候,我们希望直接输出对象的某些值,这个时候就需要重载左移运算符:
class Person {
public:
int id;
int age;
Person() : id(0), age(18) {}
void operator<<(ostream& cout) { // 注意传入的是ostream(output stream)的引用
cout << id << "," << age;
}
};
int main() {
Person p;
p.operator<<(cout);
p << cout;
// cout << p // 报错,没有找到对应的方法
return 0;
}
值得注意的是,上面的重载是写在类体内的,不可以实现cout << p这种常用的写法,为了实现,我们需要把它写到类体外面去,这非常特殊,需要格外记住:
class Person {
public:
int id;
int age;
Person() : id(0), age(18) {}
};
//值得注意的是,传入的是cout和p,和前面的不同,前面的传入的都是一样的两个形参
void operator<<(ostream& cout, const Person& p) {
cout << p.id << "," << p.age;
}
int main() {
Person p;
operator<<(cout, p); // 成功输出
cout << p; // 成功输出
return 0;
}
但现在还不够优雅,我们希望实现连锁调用,于是我们稍作修改:
class Person {
public:
int id;
int age;
Person() : id(0), age(18) {}
};
// 让函数返回cout的引用即可,注意不是返回p的引用,因为要连锁调用的是cout而不是p!
ostream& operator<<(ostream& cout, const Person& p) {
cout << p.id << "," << p.age << endl;
return cout;
}
int main() {
Person p;
Person p1;
operator<<(operator<<(cout, p), p1);
cout << p << p1;
cout << p << p1 << endl;
return 0;
}
自增(自减)运算符重载:
以下以自增运算符为例,自减是一样的。
首先我们实现“先++,再返回”,即“++a”的形式:
class Person {
public:
int age;
Person() : age(18) {}
// 注意此处返回的是Person&而不是Person,返回引用才能实现连锁调用!
// 如果返回一个Person对象,那么无论加多少次,最终只是加了1次!原理和之前连锁调用一致。
Person& operator++() {
++age;
return *this;
}
};
ostream& operator<<(ostream& cout, const Person& p) {
cout << p.age;
return cout;
}
int main() {
Person p;
cout << ++p << endl; // 19
cout << p << endl; // 19
cout << ++(++p) << endl; // 21
cout << p << endl; // 21
return 0;
}
接下来我们实现“先返回,再++”,即“a++”的形式。值得注意的是,C++原生不支持此类型的连锁调用,即“(a++)++”的行为是违法的,因为从原理上无法实现!
class Person {
public:
int age;
Person() : age(18) {}
// 首先注意到,为了区别++在前还是在后,此处传参的地方写了一个int,表示在后
// 然后,返回值是Person而不是Person&,如果返回的是引用,则会导致野指针!
// 因为引用指向的对象已经在函数结束的时候被销毁了!
Person operator++(int) {
Person temp = *this;
++age;
return temp;
}
};
ostream& operator<<(ostream& cout, const Person& p) {
cout << p.age;
return cout;
}
int main() {
Person p;
cout << p << endl; // 18
cout << p++ << endl; // 18
cout << p << endl; // 19
cout << (p++)++ << endl; // 19
cout << p << endl; // 20,虽然上面连锁调用了,但和前面一样,那么无论调用多少次,都只是加了一次!
return 0;
}
为了防止连锁调用,我们在修改一下函数即可:
class Person {
public:
int age;
Person() : age(18) {}
// 在函数返回值加上一个const,返回一个常量,那就杜绝了连锁调用了!
const Person operator++(int) {
Person temp = *this;
++age;
return temp;
}
};
函子(functor):
这个东西比较简单,用处不大,但很好玩:
class Adder {
public:
int sum;
Adder(): sum(0) {}
int operator()(int a, int b) { // 其实本质上就是重载了“括号”运算符
this->sum += a + b;
return a + b;
}
};
int main() {
Adder adder;
cout << adder(2,3) << endl; // 输出5
cout << adder.sum << endl; // 输出5
cout << adder(2,3) << endl; //输出5
cout << adder.sum << endl; // 输出10
return 0;
}
其实上述例子,函子只是重载了括号运算符,比较有用的是,可以记录sum了。