学习运算符重载,让运算符能做一些原来做不了的事情,方便它的使用
1.重载:重新载入,就像之前学的函数重载,对一个已有的函数赋值一个新的定义,因此同一个函数名就可以有不同的含义。
2.运算符也是可以重载的,比如cout在输出一个变量的时候,能接受不同类型的数据并输出,他就是重载了<<运算符,这个就是运算符重载
3.所以运算符重载指的是对已有的运算符重新定义新的运算规则,以适应不同的数据类型,当然重载之后之前的运算规则还是有的
1.运算符重载之后可以让运算符去适应不同的数据类型,对于基本数据类型,系统给出了运算符的操作规则,对于自定义数据类型来说,系统不知道该给出什么规则
- class student
- {
- int id;
- int age;
- char name[20];
- public:
- student(int id,int age,const char* name)
- {
- this->id=id;
- this->age=age;
- strcpy(this->name,name);
- }
- }
- student stu1(1,23,"张三");
- student stu2(2,24,"李四");
- stu1+stu2;//如果是这样相加,那么应该加的是什么呢?编译器是不知道的,所以编译器就提供了运算符重载这个机制,让用户自定义运算符的运算规则
1.关键字:operator,通过关键字来定义运算符重载(跟写个函数一样)
2.定义:
函数返回值类型 operator 要加载的运算符(参数列表)
{
函数体;
}
这里把运算符的使用,理解为调用函数,只是和平时的调用函数有一点区别
- #include
- #include
- using namespace std;
- class student
- {
- int id;
- int age;
- string name;
- public:
- student(int age)
- {
- this->age = age;
- id = 1;
- name = "sss ";
- }
- student(int id, int age, string name)
- {
- this->id = id;
- this->age = age;
- this->name = name;
- }
- void showstudent()
- {
- cout << id << "\t" << age << "\t" << name << endl;
- }
-
- //重载 + 号 双目运算符,this占一个参数
- student operator+(const student& p1)//这个函数会返回一个新的对象
- {
- //这个重载就是让两个对象相加,加的是各自的年龄
- int x = this->age + p1.age;
- student p(x);
- return p;//返回的是一个对象,会调用拷贝构造
- }
- //重载 - 号
- int operator-(int x)
- {
- return this->id - x;
- }
-
- };
- //1.操作这个运算符之后,返回值类型是什么
- int main()
- {
- student p1(0, 1, "yunfei");
- int x = p1.operator-(1);//显示调用-号重载
- cout << x << endl;
-
- student stu1(1, 23, "张三");
- student stu2(2, 24, "李四");
- student stu3 = stu1.operator+(stu2);
- student stu4 = stu1 + stu2;//隐式调用 + 号重载
- stu3.showstudent();
- system("pause");
- return 0;
- }
注意:因为我们这个运算符是在类中写的,所以是通过对象调用的,那么this指针会占一个参数,而且是第一个参数,也就是说我们重载一个运算符,是在类中,而这个运算符是个单目运算符,那么参数列表就不用写东西了,是双目运算符,那么就需要传另一个参数进来
绝大部分的运算符重载都可以参照上面这个+号重载
1.几乎所有的运算符都可以被重载,除了 . :: ()?():() sizeof()
2.运算符重载基本出现在类中和结构体中
3.运算符可以理解为函数的一个表现
1.使用成员函数来实现运算符重载时,少写一个参数,因为第一个参数就是this指针。
2.一般情况下,单目运算符重载,使用成员函数进行重载更方便(不用写参数)
3.一般情况下,双目运算符重载,使用友元函数更直观
方便实现a+b和b+a相同的效果,成员函数方式无法实现。
例如: 100 + cow; 只能通过友元函数来实现
cow +100; 友元函数和成员函数都可以实现
特殊
= () [ ] -> 不能重载为类的友元函数!!!(否则可能和C++的其他规则矛盾),只能使用成员函数形式进行重载。
如果运算符的第一个操作数要求使用隐式类型转换,则必须为友元函数(成员函数方式的第一个参数是this指针)
1.为了防止对标准类型进行运算符重载,C++规定重载运算符的操作对象至少有一个不是标准类型,而是用户自定义的类型
比如不能重载 1+2
但是可以重载 cow + 2 和 2 + cow // cow是自定义的对象
2.不能改变原运算符的语法规则, 比如不能把双目运算符重载为单目运算
3.不能改变原运算符的优先级
4.不能创建新的运算符,比如 operator**是非法的,
operator*是可行
5.不能对以下这四种运算符,使用友元函数进行重载
= 赋值运算符,()函数调用运算符,[ ]下标运算符,->通过指针访问类成员
6.不能对禁止重载的运算符进行重载
不能被重载的运算符
| 成员访问 | . |
|---|---|
| 域运算 | :: |
| 内存长度运算 | sizeof |
| 三目运算 | ? : : |
| 预处理 | # |
可以被重载的运算符
| 双目运算符 | + - * / % |
|---|---|
| 关系运算符 | == != < <= > >= |
| 逻辑运算符 | && || ! |
| 单目运算符 | +(正号) -(负号) *(指针) &(取地址) ++ -- |
| 位运算 | & | ~ ^ <<(左移) >>(右移) |
| 赋值运算符 | = += -= *= /= %= &= |= ^= <<= >>= |
| 内存分配 | new delete new[ ] delete[ ] |
| 其他 | ( ) 函数调用 -> 成员访问 [ ] 下标 , 逗号 |
1.赋值运算符的重载,一定要使用引用参数(这样可以连续赋值 a=b=c)
2.如果一个类有指针成员,而且使用了动态内存分配,那么一定要定义自己的拷贝构造函数【要使用深拷贝】,避免调用自动生成的拷贝构造函数
因为自动生成的拷贝构造函数,是浅拷贝!
1.类在已经实现且部分修改的情况下下,需要进行运算符重载,就可以通过友元的方式来进行重载
- #include
- #include
- using namespace std;
- class person
- {
- int id;
- int age;
- string name;
- public:
- person(int id, int age, string name)
- {
- this->id = id;
- this->age = age;
- this->name = name;
- }
- void showperson()
- {
- cout << id << "\t" << age << "\t" << name << endl;
- }
- //重载+号
- friend int operator+(person&p1, person&p2);
-
- };
- //形参使用的是类对象的引用,在实参传对象的时候不会调用拷贝构造
- int operator+(person&p1, person&p2)
- {
- return p1.id + p2.id;
- }
-
- int main()
- {
- person stu1(1, 23, "张三");
- person stu2(2, 24, "李四");
- int x = operator+(stu1, stu2);//显示调用
- int y = stu1 + stu2;//隐式调用
- cout << x << endl << y << endl;
- system("pause");
- return 0;
- }
1.左移右移运算符重载:
- #include
- using namespace std;
- class person
- {
- int id;
- public:
- person(int id)
- {
- this->id = id;
- }
- friend ostream& operator<<(ostream& os, person& p1);
- friend istream & operator>>(istream & in, person & p2);
- };
-
- //左移右移运算符重载,必须在类外重载,通过友元实现
- ostream& operator<<(ostream& os, person& p1)//左移运算符
- {
- os << p1.id << endl;
- return os;//返回的是一个cout,而且只能用引用
- }
- istream & operator>>(istream & in, person & p2)//右移运算符
- {
- in >> p2.id;
- return in;
- }
-
- int main()
- {
- person p1(10), p2(20);
- cin >> p1 >> p2;
- cout << p1 << endl << p2 << endl;
-
-
- system("pause");
- return 0;
- }
2.前++,后++运算符重载:
- #include
- using namespace std;
- class person
- {
- int id;
- public:
- person(int id)
- {
- this->id = id;
- }
-
- //单目运算符,只需要一个this参数
- person& operator++()//前++
- {
- this->id++;
- return *this;
- }
-
- person& operator++(int)//后++,int是一个占位符,用来区分前++和后++的
- {
- static person temp = *this;//引用不能返回局部变量,要用静态变量
- this->id++;
- return temp;
- }
- friend ostream& operator<<(ostream& os, person& p1);
- friend istream & operator>>(istream & in, person & p2);
- };
-
- //左移右移运算符重载,必须在类外重载,通过友元实现
- ostream& operator<<(ostream& os, person& p1)//左移运算符
- {
- os << p1.id << endl;
- return os;//返回的是一个cout,而且只能用引用
- }
- istream & operator>>(istream & in, person & p2)//右移运算符
- {
- in >> p2.id;
- return in;
- }
-
- int main()
- {
- person p1(10), p2(20);
-
- cout << p1;//10
- cout << p1++;//10
- cout << p1;//11
- cout << ++p1;//12
- cout << p1;//12
-
-
- system("pause");
- return 0;
- }
3.等号运算符重载:
- #include
- using namespace std;
- class person
- {
- char* name;
- public:
- //构造函数
- person(const char* name)
- {
- this->name = new char[strlen(name) + 1];
- strcpy(this->name, name);
- }
-
- person& operator=(person&p1)//用不用引用传参,要看返回的对象会不会消失
- {
- if (this->name != NULL)
- {
- delete[]this->name;
- this->name = NULL;
- }
- //申请内存
- this->name = new char[strlen(p1.name) + 1];
- //拷贝赋值
- strcpy(this->name, p1.name);
- return *this;
- }
- void show()
- {
- cout << name << endl;
- }
- ~person()//如果有申请内存,就要加上析构函数
- {
- if (name != NULL)
- {
- delete[]name;
- name = NULL;
- }
- }
- };
-
- int main()
- {
- {
- person p1("张三"), p2("李四"), p3("王五");
- p1 = p2 = p3;
- p1.show();
- p2.show();
- p3.show();
- }//加上大括号,让对象死亡,就能调用析构函数
- system("pause");
- return 0;
- }
4.智能指针和==号运算符重载:
- #include
- using namespace std;
-
- class person
- {
- int id;
- public:
- person(int id)
- {
- this->id = id;
- }
- void show()
- {
- cout << id << endl;
- }
-
- bool operator==(person& p)
- {
- return this->id == p.id;
- }
- ~person()
- {
- cout << "person的析构函数" << endl;
- }
- };
-
- class smartpointer
- {
- person* ps;//包含你要new出来的对象的类的指针
- public:
- smartpointer(person* p)
- {
- //指向相同的内存空间
- ps = p;
- }
- //重载->
- person* operator->()//传回来的是地址,不是对象,不用引用
- {
- return ps;
- }
- //重载*
- person& operator*()//返回的是对象,会调用拷贝构造,所以返回值用引用,就不会再调用拷贝构造了
- {
- return *ps;//得到一个对象,
- }
- ~smartpointer()
- {
- if (ps != NULL)
- {
- delete ps;
- ps = NULL;
- }
- }
-
- };
-
-
- int main()
- {
- {
- smartpointer p(new person(5));
- p->show();
- (*p).show();
- person p1(1), p2(3);
- cout << (p1 == p2) << endl;
- }//有三个对象,所以析构函数执行了三次
- system("pause");
- return 0;
- }
5.[]运算符重载:
- #include
- using namespace std;
- class person
- {
- char* name;
- public:
- person(const char* name)
- {
- this->name = new char[strlen(name) + 1];
- strcpy(this->name, name);
- }
-
- char& operator[](int index)
- {
- return name[index];
- }
-
- ~person()
- {
- if (name != NULL)
- {
- delete[]name;
- name = NULL;
- }
- cout << "这是析构函数" << endl;
- }
- };
- int main()
- {
- person p("asdfg");
- cout << p[3] << endl;
- system("pause");
- return 0;
- }
1.以引用返回函数值,定义函数时需要在函数名前加 &
2.用引用返回一个函数值的最大好处是:在内存中不产生被返回值的副本
3.返回值为引用的时候,返回的是一个地址,隐形指针
4.当返回值不是引用时,编译器会专门给返回值分配出一块内存的
引用作为返回值,必须遵守以下规则:
(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。