zoukankan      html  css  js  c++  java
  • C++运算符重载

    简介

    重载运算符实质是编写一个执行相应操作的函数,当运算符被使用时,编译器就会调用相应的函数去完成操作。重载的运算符函数,都有个特殊的函数名:

    operator 运算符,operator为关键字,表明这个函数是运算符函数。

    如      加法运算符时的函数名:        operator+

             乘法运算符时的函数名:        operator*

    C++支持运算符重载,可以让类的设计者自定义 当运算符操作在对象上时,发生的逻辑,这使得类被封装得更加完美。

    如下是一个简单的例子

    class Font
    {
    private:
        std::string _name;
        std::size_t _size;
    public:
        Font(const std::string& name, std::size_t size):_name(name),_size(size){}
    
        Font& operator ++()   //增加字体对象 的大小(运算符重载)
        {
            ++_size;
            return *this;
        }
    
    //    Font& bigger()      //增加字体对象 的大小(普通函数)
    //    {
    //        ++_size;
    //        return *this;
    //    }
    
        bool operator <(std::size_t size) const  //比较字体的大小(运算符重载)
        {
            return _size < size;
        }
    
    //    bool small_than(std::size_t size)const  //比较字体的大小(普通函数)
    //    {
    //        return _size < size;
    //    }
    
    };
    
    
    
    int main(int argc, char *argv[])
    {
    
        Font font("Microsoft YaHei",20);
    
        if(font<20)
            ++font;
    
        return 0;
    }

    可以发现,使用运算符重载,写出来的代码干净清晰。但是对类的设计者要求就比较高,这需要熟练掌握运算符重载。

    重载的运算符的注意点

     重载运算符有一些限制,我们不能打破,否则适得其反。

    1、不能创建新的运算符,只能重载C++内置的运算符,见下表。

    与比较相关,最好成对重载,或者全部重载。返回bool,或者int > < >= <= == !=    

    与赋值相关,重载的函数要返回当前对象的 非const引用

    = += -= /= *= %=

    &=

    |=

    ^=

    >>=

    <<=

    需要区分前后缀,前缀,返回 非const引用,后缀,返回const对象值 ++ --            
    只能重载为对象的成员的运算符(还有=) [] () ->          
    逻辑相关,不建议重载. && ||          
    加和减的 一元版本,前缀。很少使用 + -            
    二元常规运算符,返回运算生成的临时const对象作为结果返回 + - * / %      
    位运算相关 & | ~ ^ >> <<    
    其他运算符 -> new new[] delete delete[]    

    2、运算符重载只改变逻辑,不会改变优先级。重载后的运算符的优先级和内置运算符的优先级一样。

    3、重载的运算符,无论是一元还是二元运算符,必须至少有一个操作数是本类(当前正在编写的类)对象

         比如: std::string 类重载了 + 运算符, 可以使用 strObj + "abc"  ,但是 "abc"+"def"  是不对的。这里的运算符+ 将2个地址相加,所以出错。

    3、不要改变运算符原有的意义,这样使用起来会很别扭。有些运算符是不建议重载的。如  &    和   *   的 一  元  版  本,他们对于所有的数据对象都有固有的操作语义:取地址 和 解地址。不要打破这个根深蒂固的操作符的语义,所以不建议重载。

    类成员函数,还是类外的辅助函数?

    下面一元运算符,他们必须重载为对象的成员函数。原因是:这些运算符只有一个操作数,且这个操作数就是本类对象。

    (什么,你说赋值运算符是2个操作数?NONONO,被赋值的那个对象还没完全诞生呢!)

    [  ]  下标运算符 ,一般用于容器或者序列。

    ( )   函数调用运算符 ,一般用于自定义函数类对象。这些对象是可调用的。

        赋值运算符,大多数类都会实现。

    ->   通过指针访问对象的成员的运算符。一般用于 自定义指 针 类对象。

     除此之外,其它的运算符既可以定义为成员函数,也可以定义为类外的辅助函数(全局函数),该如何选择呢?

    一般来说,如+   -    *   /  %  这样的二元运算符,不会改变对象,则一般定义为 类外的辅助函数。而 ++  -- 操作会改变对象状态,就定义为成员函数。

    如果是类外的辅助函数,一般会声明为类的友元函数: friend ,便于访问类的成员。

    对于二元运算符,它有2个操作数。如果重载为成员函数,则第一个操作数作为当前对象隐式传递。即:  a @ b   实质是    a.operator@(b)

    当一个二元运算符 定义为类的辅助函数时,必须指明2个操作数参数。即:   a @ b    实质是    operator@(a,b)

    运算符重载代码例子

    重载 >>   << 运算符,让对象更方便的输入 , 输出

    /*studnet.h*/
    #ifndef _STUDENT_H__
    #define _STUDENT_H__
    #include<string>
    #include<iostream>
    class Student
    {
    private: std::string _name; int _age; double _score; public: Student(); Student(const std::string &name, const int &age, const double&score);

         /*一般将非成员运算符重载 声明为类的友元,这样方便访问对象的数据*/
        friend std::ostream& operator<<(std::ostream& out, const Student& stu);
        friend std::istream& operator>>(std::istream& in, Student& stu);
    
    };
    
    std::ostream& operator<<(std::ostream& out, const Student& stu);
    std::istream& operator>>(std::ostream& in,        Student& stu);
    
    /*studnet.cpp*/
    #include"student.h"
    
    Student::Student():_name(""),_score(0),_age(0)
    {
    }
     
    Student::Student(const std::string &name, const int &age, const double&score) 
                                :_name(name), _age(age), _score(score)
    {
        
    }
    
    
    //参数 out不能修饰为const,因为通过out流对象 输出 stu时,就是更改out流状态的过程。
    //参数stu被输出,不会改变对象状态,修饰为const最好
    //返回out本身,以便连续输出
    std::ostream& operator<<(std::ostream& out, const Student& stu)
    {
        out << "age:" << stu._age << '
    '
            << "name:" << stu._name << '
    '
            << "score:" << stu._score;
    
        return out;
    
    }
    
    std::istream& operator>>(std::istream& in, Student& stu)
    {
        in >> stu._name >> stu._age >> stu._score;
    
        return in;
    
    }

    重载 ++   --  运算符

    以 ++ 运算符为例,-- 运算与之符同理。

    ++  有前缀和后缀版本。当仅仅使用 ++ 的副作用,使操作对象自增1时,++a 和 a++都可以达到相同的效果,但是优选使用 ++ a,为什么,请往后看。

    ++ a   整个表达式的值是 a +1 之后的值, a++ 整个表达式的值是 a原本的值,这是二者表明上的区别。

    下面将一个Student类对象重载 ++ 运算符,表示增加对象的_age属性。

    /*studnet.h*/
    #ifndef _STUDENT_H__
    #define _STUDENT_H__
    #include<string>
    #include<iostream>
    class Student { private: std::string _name; int _age; double _score; public: Student(); Student(const std::string &name, const int &age, const double&score); Student& operator++(); //前缀版本 Student operator++(int); //后缀版本

        friend std::ostream& operator<<(std::ostream& out, const Student& stu);

    }; std::ostream& operator<<(std::ostream& out, const Student& stu);
    /*studnet.cpp*/
    #include"student.h"
    Student::Student():_name(""),_score(0),_age(0)
    {
    }
    Student::Student(const std::string &name, const int &age, const double&score) 
    :_name(name), _age(age), _score(score)
    {

    }

    /*使用一个int参数类型占位符来区别 前缀 和后缀版本,它只用来占位,区分,并无它用*/

    /*后缀版本需要临时保存对象增1前的状态,以便返回,这就是我为什么说优先使用前缀版本的缘故了*/
     //前缀,返回值是增1后的 值,返回的是当前对象 
    Student
    & Student::operator++()
    {
    ++_age;
    return *this;
    }

    //后缀,返回的值当前对象增1 前 的值。 
    //由于返回的是局部对象,所以函数的返回类型不能是引用类型。 
    const Student Student::
    operator++(int)
    {
    Student re = *this;
    ++_age;
    return re;
    }

    std::ostream
    & operator<<(std::ostream& out, const Student& stu)
    {
    out << "age:" << stu._age << ' '
    << "name:" << stu._name << ' '
    << "score:" << stu._score;
    return out;
    }

    赋值运算符

    默认,编译器会帮我们提供一个默认的赋值算 符 函 数 ,其默认的行为是:

    对对象字段做如下操作:

      字段是class类型,struct,则调用字段的赋值运算符。

      字段是基本类型则直接赋值。

      字段 是数组,则一 一 执行数组元素的赋值运算符,复制到另一个数组中。

    很多时候这样并不能正确的执行我们需要的效果。所以需要自定义。

    固定格式形如:Student & operator=(cosnt Student& other);

    注意点:

    1、如果赋值参数是同类对象,则应该有防止自赋值代码,以提高函数效率。

    2、所有的赋值运算符,组合赋值运算符都应该返回当前对的引用。

    3、由于赋值是原有数据的覆盖,所以应在赋值数据前,做必要的清理工作,如delete原对象申请的内存。

        总结就是4个步骤:  

        ①如果参数是同类对象,则要防止自赋值

        ②清理当前对象原有的资源

        ③ 一 一拷贝数据

        ④返回当前对象引用

    下面是一个简单的 存储int类型元素的Stack 的赋值运算符。

    复制代码
        Stack& Stack::operator = (const Stack& that)
        {
            if (&that == this)     //防止自赋值
                return *this;
    
            delete[] _innerArr;   //清理源有内存
    
            _len = that._len;
            _innerArr = new int[len]; //分配新内存
            for (std::size_t i = 0; i < _len; ++i)
            {
                _innerArr[i] = that._innerArr[i];
            }
    
    
            return *this;   //返回当前对象
        
        }
    复制代码

    注意重载二元运算符的对称性

    下面,为Student重载 + 运算符,表示返回一个_age 加上 某个参数后的 新 的Student类对象。

    /*student.h(部分)*/
    class
    Student { //... public: //... //返回一个student镀锡 的_age 加上 add岁后的新的student对象 Student operator+(int add) const; };
    /*student.cpp中的实现(部分)*/
    Student Student::operator+(int add) const { Student re = *this; re._age += add; return re; }
    /*main.cpp*/
    int
    main() { Student s("Bob", 19, 90.0); cout << s+3 << endl; //OK cout << 3+s<< endl; //error 匹配不到相应的运算符,因为我们没有考虑到前操作数是int 的版本 system("pause"); return 0; }

    巧妙的补救

    /*student.h(部分)*/
    class
    Student {
    //...
    public: Student operator+(int add) const;
    /***/
    friend Student operator+(int add, const Student& stu);
    }; Student
    operator+(int add, const Student& stu); //通过全局辅助函数来完成另一个重载。
    /*student.cpp中的实现(部分)*/
    Student Student::operator+(int add) const { Student re = *this; re._age += add; return re; } Student operator+(int add, const Student& stu) { return stu + 3; }

    对容器类型重载 索引运算符 [ ]

    一些容器(Java中叫集合)类型,很多时候需要获取容器中的第 xx个元素,这个时候重载 下标运算符[ ] 再合适不过了。

    注意:由于[ ] 实质是函数调用,意味着 [ index] 索引可以是任何类型。当然不要乱用。

            如果[ index] 索引的的值是整型的,最好使用 无符号类型   std::size_t 。

            请考虑重载2和个版本:分别用于容器本身是 常量 / 非常量 的情况。当容器本身就是常量时,[]运算符取得的元素是const类型,这样避免了修改容器中的元素。

    #ifndef _MSTRING_H__
    #define _MSTRING_H__
    
    #include<cstring>
    
    class MString
    {
    
    public:
        MString(const char* cs);
        ~MString();
    
        const char& operator[](std::size_t index) const;
        char& operator[](std::size_t index);
    
    private:
        char*  pstr;
        size_t len;
    };
    
    #endif
    #include"mstring.h"
    
    
    MString::MString(const char* cs)
    {
        len = std::strlen(cs);
        pstr = new char[len + 1];
        std::strcpy(pstr,cs);
    pstr[len] = '';
    } MString::~MString() { delete[] pstr; }
    /*用于读容器中的元素,则元素是不应该被修改的,容器也不应该被修改*/
    const char& MString::operator[](std::size_t index) const { //if (index >= len || index < 0) //注意越界检查,这里没写出来了 return pstr[index]; }

    //用于给容器中的元素写入新的值
    char& MString::operator[](std::size_t index) { //if (index >= len || index < 0) //注意越界检查,这里没写出来了 return pstr[index]; }

    类型转换运算符

    除了可以自定义运算符的逻辑,还可以自定义类型转化时的逻辑,他们可以发生在 显示的或者隐式的类型转换时。严格说,这不属于运算符重载,但语法很相似,所以我一并写出来了。

    格式:   operator TypeName() const

    要求

    1、必须是成员函数

    2、没有返回类型,没有参数。

    3、类型转化不会改变对象状态,所以定义的转化函数应该是const 函数。虽然它不是必须的。

    玩过Arduino 的朋友都知道Serial类,代表了开发板的串口对象,我们将串口对象用于条件时,是判断它是否成功开启。下面来模拟一个。

    class Serial
    {
    
    private:
        bool _is_opened;
    
    public:
        Serial():_is_opened(false) {}
    
        operator bool() const   //定义对象转化为bool 时的操作逻辑
        {
            return _is_opened;
        }
    
    };
    
    int main(int argc, char *argv[])
    {
        Serial serial;
        if(serial)      //隐式的类型转化
        {
            //deal with serial
        }
        return 0;
    
    }
    
    
    
  • 相关阅读:
    Windows python 鼠标键盘监控及控制
    python 执行adb shell 命令
    python Windows提示框
    判断function属于函数或方法
    生成不同时间下的LOG
    pyqt5 QCalendar类中常用的方法
    python字符串大小写转换
    configparser 模块的基本方法
    QGridLayout类中常用的方法
    Day049--jQuery的文档操作和事件介绍
  • 原文地址:https://www.cnblogs.com/lulipro/p/5986389.html
Copyright © 2011-2022 走看看