• 运算符重载


    学习运算符重载,让运算符能做一些原来做不了的事情,方便它的使用

    一、运算符重载的概念

    1.什么是运算符重载

    1.重载:重新载入,就像之前学的函数重载,对一个已有的函数赋值一个新的定义,因此同一个函数名就可以有不同的含义。

    2.运算符也是可以重载的,比如cout在输出一个变量的时候,能接受不同类型的数据并输出,他就是重载了<<运算符,这个就是运算符重载

    3.所以运算符重载指的是对已有的运算符重新定义新的运算规则,以适应不同的数据类型,当然重载之后之前的运算规则还是有的

    2.为什么要进行运算符重载

    1.运算符重载之后可以让运算符去适应不同的数据类型,对于基本数据类型,系统给出了运算符的操作规则,对于自定义数据类型来说,系统不知道该给出什么规则

    1. class student
    2. {
    3.    int id;
    4.    int age;
    5.    char name[20];
    6.    public:
    7.    student(int id,int age,const char* name)
    8.   {
    9.        this->id=id;
    10.        this->age=age;
    11.        strcpy(this->name,name);
    12.   }
    13. }
    14. student stu1(1,23,"张三");
    15. student stu2(2,24,"李四");
    16. stu1+stu2;//如果是这样相加,那么应该加的是什么呢?编译器是不知道的,所以编译器就提供了运算符重载这个机制,让用户自定义运算符的运算规则

    二、运算符重载

    1.定义

    1.关键字:operator,通过关键字来定义运算符重载(跟写个函数一样)

    2.定义:

    函数返回值类型 operator 要加载的运算符(参数列表)

    {

    函数体;

    }

    这里把运算符的使用,理解为调用函数,只是和平时的调用函数有一点区别

    1. #include
    2. #include
    3. using namespace std;
    4. class student
    5. {
    6. int id;
    7. int age;
    8. string name;
    9. public:
    10. student(int age)
    11. {
    12. this->age = age;
    13. id = 1;
    14. name = "sss ";
    15. }
    16. student(int id, int age, string name)
    17. {
    18. this->id = id;
    19. this->age = age;
    20. this->name = name;
    21. }
    22. void showstudent()
    23. {
    24. cout << id << "\t" << age << "\t" << name << endl;
    25. }
    26. //重载 + 号 双目运算符,this占一个参数
    27. student operator+(const student& p1)//这个函数会返回一个新的对象
    28. {
    29. //这个重载就是让两个对象相加,加的是各自的年龄
    30. int x = this->age + p1.age;
    31. student p(x);
    32. return p;//返回的是一个对象,会调用拷贝构造
    33. }
    34. //重载 - 号
    35. int operator-(int x)
    36. {
    37. return this->id - x;
    38. }
    39. };
    40. //1.操作这个运算符之后,返回值类型是什么
    41. int main()
    42. {
    43. student p1(0, 1, "yunfei");
    44. int x = p1.operator-(1);//显示调用-号重载
    45. cout << x << endl;
    46. student stu1(1, 23, "张三");
    47. student stu2(2, 24, "李四");
    48. student stu3 = stu1.operator+(stu2);
    49. student stu4 = stu1 + stu2;//隐式调用 + 号重载
    50. stu3.showstudent();
    51. system("pause");
    52. return 0;
    53. }

    注意:因为我们这个运算符是在类中写的,所以是通过对象调用的,那么this指针会占一个参数,而且是第一个参数,也就是说我们重载一个运算符,是在类中,而这个运算符是个单目运算符,那么参数列表就不用写东西了,是双目运算符,那么就需要传另一个参数进来

    绝大部分的运算符重载都可以参照上面这个+号重载

    2.特点

    1.几乎所有的运算符都可以被重载,除了 . :: ()?():() sizeof()

    2.运算符重载基本出现在类中和结构体中

    3.运算符可以理解为函数的一个表现

    3.注意事项

    1.使用成员函数来实现运算符重载时,少写一个参数,因为第一个参数就是this指针。

    2.一般情况下,单目运算符重载,使用成员函数进行重载更方便(不用写参数)

    3.一般情况下,双目运算符重载,使用友元函数更直观

    方便实现a+b和b+a相同的效果,成员函数方式无法实现。

    例如: 100 + cow; 只能通过友元函数来实现

    cow +100; 友元函数和成员函数都可以实现

    特殊

    = () [ ] -> 不能重载为类的友元函数!!!(否则可能和C++的其他规则矛盾),只能使用成员函数形式进行重载。

    如果运算符的第一个操作数要求使用隐式类型转换,则必须为友元函数(成员函数方式的第一个参数是this指针)

    4.规则

    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.如果一个类有指针成员,而且使用了动态内存分配,那么一定要定义自己的拷贝构造函数【要使用深拷贝】,避免调用自动生成的拷贝构造函数

    因为自动生成的拷贝构造函数,是浅拷贝!

    5.使用友元函数,实现运算符重载

    1.类在已经实现且部分修改的情况下下,需要进行运算符重载,就可以通过友元的方式来进行重载

    1. #include
    2. #include
    3. using namespace std;
    4. class person
    5. {
    6. int id;
    7. int age;
    8. string name;
    9. public:
    10. person(int id, int age, string name)
    11. {
    12. this->id = id;
    13. this->age = age;
    14. this->name = name;
    15. }
    16. void showperson()
    17. {
    18. cout << id << "\t" << age << "\t" << name << endl;
    19. }
    20.    //重载+号
    21. friend int operator+(person&p1, person&p2);
    22. };
    23. //形参使用的是类对象的引用,在实参传对象的时候不会调用拷贝构造
    24. int operator+(person&p1, person&p2)
    25. {
    26. return p1.id + p2.id;
    27. }
    28. int main()
    29. {
    30. person stu1(1, 23, "张三");
    31. person stu2(2, 24, "李四");
    32. int x = operator+(stu1, stu2);//显示调用
    33. int y = stu1 + stu2;//隐式调用
    34. cout << x << endl << y << endl;
    35. system("pause");
    36. return 0;
    37. }

    6.其他运算符重载

    1.左移右移运算符重载:

    1. #include
    2. using namespace std;
    3. class person
    4. {
    5. int id;
    6. public:
    7. person(int id)
    8. {
    9. this->id = id;
    10. }
    11. friend ostream& operator<<(ostream& os, person& p1);
    12. friend istream & operator>>(istream & in, person & p2);
    13. };
    14. //左移右移运算符重载,必须在类外重载,通过友元实现
    15. ostream& operator<<(ostream& os, person& p1)//左移运算符
    16. {
    17. os << p1.id << endl;
    18. return os;//返回的是一个cout,而且只能用引用
    19. }
    20. istream & operator>>(istream & in, person & p2)//右移运算符
    21. {
    22. in >> p2.id;
    23. return in;
    24. }
    25. int main()
    26. {
    27. person p1(10), p2(20);
    28. cin >> p1 >> p2;
    29. cout << p1 << endl << p2 << endl;
    30. system("pause");
    31. return 0;
    32. }

    2.前++,后++运算符重载:

    1. #include
    2. using namespace std;
    3. class person
    4. {
    5. int id;
    6. public:
    7. person(int id)
    8. {
    9. this->id = id;
    10. }
    11. //单目运算符,只需要一个this参数
    12. person& operator++()//前++
    13. {
    14. this->id++;
    15. return *this;
    16. }
    17. person& operator++(int)//后++,int是一个占位符,用来区分前++和后++的
    18. {
    19. static person temp = *this;//引用不能返回局部变量,要用静态变量
    20. this->id++;
    21. return temp;
    22. }
    23. friend ostream& operator<<(ostream& os, person& p1);
    24. friend istream & operator>>(istream & in, person & p2);
    25. };
    26. //左移右移运算符重载,必须在类外重载,通过友元实现
    27. ostream& operator<<(ostream& os, person& p1)//左移运算符
    28. {
    29. os << p1.id << endl;
    30. return os;//返回的是一个cout,而且只能用引用
    31. }
    32. istream & operator>>(istream & in, person & p2)//右移运算符
    33. {
    34. in >> p2.id;
    35. return in;
    36. }
    37. int main()
    38. {
    39. person p1(10), p2(20);
    40. cout << p1;//10
    41. cout << p1++;//10
    42. cout << p1;//11
    43. cout << ++p1;//12
    44. cout << p1;//12
    45. system("pause");
    46. return 0;
    47. }

    3.等号运算符重载:

    1. #include
    2. using namespace std;
    3. class person
    4. {
    5. char* name;
    6. public:
    7.    //构造函数
    8. person(const char* name)
    9. {
    10. this->name = new char[strlen(name) + 1];
    11. strcpy(this->name, name);
    12. }
    13.    
    14. person& operator=(person&p1)//用不用引用传参,要看返回的对象会不会消失
    15. {
    16. if (this->name != NULL)
    17. {
    18. delete[]this->name;
    19. this->name = NULL;
    20. }
    21.        //申请内存
    22. this->name = new char[strlen(p1.name) + 1];
    23.        //拷贝赋值
    24. strcpy(this->name, p1.name);
    25. return *this;
    26. }
    27. void show()
    28. {
    29. cout << name << endl;
    30. }
    31. ~person()//如果有申请内存,就要加上析构函数
    32. {
    33. if (name != NULL)
    34. {
    35. delete[]name;
    36. name = NULL;
    37. }
    38. }
    39. };
    40. int main()
    41. {
    42. {
    43. person p1("张三"), p2("李四"), p3("王五");
    44. p1 = p2 = p3;
    45. p1.show();
    46. p2.show();
    47. p3.show();
    48. }//加上大括号,让对象死亡,就能调用析构函数
    49. system("pause");
    50. return 0;
    51. }

    4.智能指针和==号运算符重载:

    1. #include
    2. using namespace std;
    3. class person
    4. {
    5. int id;
    6. public:
    7. person(int id)
    8. {
    9. this->id = id;
    10. }
    11. void show()
    12. {
    13. cout << id << endl;
    14. }
    15.    
    16. bool operator==(person& p)
    17. {
    18. return this->id == p.id;
    19. }
    20. ~person()
    21. {
    22. cout << "person的析构函数" << endl;
    23. }
    24. };
    25. class smartpointer
    26. {
    27. person* ps;//包含你要new出来的对象的类的指针
    28. public:
    29. smartpointer(person* p)
    30. {
    31.        //指向相同的内存空间
    32. ps = p;
    33. }
    34. //重载->
    35. person* operator->()//传回来的是地址,不是对象,不用引用
    36. {
    37. return ps;
    38. }
    39. //重载*
    40. person& operator*()//返回的是对象,会调用拷贝构造,所以返回值用引用,就不会再调用拷贝构造了
    41. {
    42. return *ps;//得到一个对象,
    43. }
    44. ~smartpointer()
    45. {
    46. if (ps != NULL)
    47. {
    48. delete ps;
    49. ps = NULL;
    50. }
    51. }
    52. };
    53. int main()
    54. {
    55. {
    56. smartpointer p(new person(5));
    57. p->show();
    58. (*p).show();
    59. person p1(1), p2(3);
    60. cout << (p1 == p2) << endl;
    61. }//有三个对象,所以析构函数执行了三次
    62. system("pause");
    63. return 0;
    64. }

    5.[]运算符重载:

    1. #include
    2. using namespace std;
    3. class person
    4. {
    5. char* name;
    6. public:
    7. person(const char* name)
    8. {
    9. this->name = new char[strlen(name) + 1];
    10. strcpy(this->name, name);
    11. }
    12.    
    13. char& operator[](int index)
    14. {
    15. return name[index];
    16. }
    17.    
    18. ~person()
    19. {
    20. if (name != NULL)
    21. {
    22. delete[]name;
    23. name = NULL;
    24. }
    25. cout << "这是析构函数" << endl;
    26. }
    27. };
    28. int main()
    29. {
    30. person p("asdfg");
    31. cout << p[3] << endl;
    32. system("pause");
    33. return 0;
    34. }

    三、引用作为函数返回值

    1.以引用返回函数值,定义函数时需要在函数名前加 &

    2.用引用返回一个函数值的最大好处是:在内存中不产生被返回值的副本

    3.返回值为引用的时候,返回的是一个地址,隐形指针

    4.当返回值不是引用时,编译器会专门给返回值分配出一块内存的

    引用作为返回值,必须遵守以下规则:

    (1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

    (2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

    (3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

  • 相关阅读:
    springboot(12):@ControllerAdvice和@RestControllerAdvice注解使用
    【C++报错】c++实例类的时候提示已声明所在行数,所属文件不可访问 的解决办法
    Abbkine 细胞侵袭分析试剂盒,简单方便,快速检测
    day29-JQuery02
    HTTPS是什么,详解它的加密过程
    【Flink】第二节 windows下运行
    Keras深度学习实战(11)——可视化神经网络中间层输出
    机器学习实战:基于sklearn的工业蒸汽量预测
    51单片机汽车胎压大气气压测量仪仿真设计_数码管显示(代码+仿真+设计报告+讲解)
    Verilog中关于reg [3:0] a [7:0] 和 reg [3:0] [7:0] a的区别
  • 原文地址:https://blog.csdn.net/m0_52559870/article/details/126190318