首页 > 代码库 > C++运算符重载

C++运算符重载

 

重载运算符实质是编写一个执行相应操作的函数,当运算符被使用时,实质是函数被调用,这是编译器完成的转换工作。

重载的运算符函数,都有个特殊的函数名:operator【运算符】。operator为关键字。

如重载加法运算符:       operator+

        乘法运算符:        operator*

C++支持运算符重载,使得类被封装得更加完美,但是这也增加了其复杂性。正因为如此,Java没有支持运算符重载。

有趣的是,当我在使用Java的时候,并没有觉得Java不支持运算符重载有多么不方便,但当我使用C++的时候,又觉得支持运算符重载多么的酷。显然,我们常说的语言信仰,只不过是被语言本身洗脑 了   - -!

废话完了。开始。

 

 

可以重载的运算符

备注
与比较相关,最好成对重载,或者全部重载 > < >= <= == !=    

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

,和内置类型的赋值保持一致的效果

= += -= /= *= %=

&=

|=

^=

>>=

<<=

需要区分前后缀 ++ --            
只能重载为对象的成员的运算符(还有=) [] () ->          
逻辑相关,不建议重载,因为他们不满足短路求值 && ||          
加和减的 一元版本,前缀。很少使用 + -            
二元常规运算符 + - * / %      
位运算相关,很少使用 & | ~ ^ >> <<    
其他运算符 ->* new new[] delete delete[]    

 

 

 

 

一些规定

  • 不能创建新的运算符,只能重载C++内置的运算符,上表中的。
  • 重载的运算符不会改变运算符原有的优先级。
  • 重载的运算符可以是对象的成员函数,也可以是类外的辅助函数,这种情况下他们一般定义为类的友元,以便操作对象。
  • 重载的运算符,无论是一元还是二元运算符,必须至少有一个操作数是本类对象。 
    • 比如: string类重载了 + 运算符, 可以使用 strObj + "12"  ,但是"1234"+"56"  是不对的。因为字符串字面值作为表达式时,是这个字符串字面值在内存中的地址,确切说是第一个字符的地址。这里的运算符+ 将2个地址相加,所以出错。

 

 

一些建议:1、不要改变运算符原有的意义。

              2、不要重载的运算符  :

&    和   *   的 一  元  版  本,他们对于所有的数据对象都有固有的操作语义:取地址 和 解地址。

||    和   &&   无法满足内置的短路求值性质。因为他们实质是函数,只要出现了调用,就一定会被执行。

      ,  逗号运算符,有固定的语义。这个基本不会用到。

        3、不要为了获得微弱的使用方便性,而不假思索的重载运算符,有时候使用一个有意义名称的普通函数会更加安全,合适。

 

 

二元运算符  既可以定义为类的辅助函数(全局函数),也可以定义为对象的成员函数。

当一个二元运算符 @ 定义为对象的成员函数时: a @ b   实质是    a.operator@(b)

当一个二元运算符 @ 定义为类的辅助函数时 :  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.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 << \n
        << "name:" << stu._name << \n
        << "score:" << stu._score;

    return out;

}

std::istream& operator>>(std::istream& in, Student& stu)
{
    in >> stu._name >> stu._age >> stu._score;

    return in;

}
int main()
{
    Student s;
    cin >> s;
    cout << s << endl;    

    return 0;
}

 

 

 

重载 ++   --  运算符

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

++  有前缀和后缀版本。当仅仅使用 ++ 的副作用,使操作对象自增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.0;
    _age = 0;
}
    

Student::Student(const std::string &name, const int &age, const double&score) 
                            :_name(name), _age(age), _score(score)
{
    
}


/*使用一个int参数类型占位符来区别 前缀 和后缀版本,它只用来占位,区分,并无它用*/
/*后缀版本需要临时保存对象增1前的状态,以便返回,这就是我为什么说优先使用前缀版本的缘故了*/


Student& Student::operator++()    //前缀,返回值是增1后的 值,返回的是当前对象
{
    ++_age;
    return *this;
}

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


std::ostream& operator<<(std::ostream& out, const Student& stu)
{
    out << "age:" << stu._age << \n
        << "name:" << stu._name << \n
        << "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; }; 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] = ‘\0‘;
} 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]; }

 

 

 

 感悟:用C++有限的语法优特性提供和内置类型一致的使用体验,这就要求类的设计者深厚的C++功底。

 

 

 

/***************************************************/

 欢迎转载,请注明出处:www.cnblogs.com/lulipro

 为了获得更好的阅读体验,请访问原博客地址。

 代码钢琴家

/***************************************************/

 

C++运算符重载