博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++运算符重载
阅读量:5775 次
发布时间:2019-06-18

本文共 8228 字,大约阅读时间需要 27 分钟。

简介

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

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
#include
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 << '\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;}

 

 

 

重载 ++   --  运算符

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

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

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

 

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

 

/*studnet.h*/#ifndef _STUDENT_H__#define _STUDENT_H__#include
#include
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 << '\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; /***/ 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
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];}

 

 

类型转换运算符

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

格式:   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;}

 

 

 

转载地址:http://vrhux.baihongyu.com/

你可能感兴趣的文章
CTOR有助于BCH石墨烯技术更上一层楼
查看>>
被遗忘的CSS
查看>>
Webpack中的sourcemap以及如何在生产和开发环境中合理的设置sourcemap的类型
查看>>
做完小程序项目、老板给我加了6k薪资~
查看>>
java工程师linux命令,这篇文章就够了
查看>>
关于React生命周期的学习
查看>>
webpack雪碧图生成
查看>>
搭建智能合约开发环境Remix IDE及使用
查看>>
Spring Cloud构建微服务架构—服务消费基础
查看>>
RAC实践采坑指北
查看>>
runtime运行时 isa指针 SEL方法选择器 IMP函数指针 Method方法 runtime消息机制 runtime的使用...
查看>>
LeetCode36.有效的数独 JavaScript
查看>>
Scrapy基本用法
查看>>
PAT A1030 动态规划
查看>>
自制一个 elasticsearch-spring-boot-starter
查看>>
软件开发学习的5大技巧,你知道吗?
查看>>
java入门第二季--封装--什么是java中的封装
查看>>
【人物志】美团前端通道主席洪磊:一位产品出身、爱焊电路板的工程师
查看>>
一份关于数据科学家应该具备的技能清单
查看>>
机器学习实战_一个完整的程序(一)
查看>>