普通运算符重载:
通过运算符重载,可以定义对象执行相应运算符之后的行为,比如定义两个对象相加的行为:
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了。