热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

操作符重载浅析

什么是操作符重载?一看到重载,很容易就让人联想到成员函数重载,函数重载可以使名称相同的函数具有不同的实际功能,只要赋给这些同名函数不同的参数就可以了,操作符重载也是基于这一机制的。系统为我们提供了许多

什么是操作符重载?

一看到重载,很容易就让人联想到成员函数重载,函数重载可以使名称相同的函数具有不同的实际功能,只要赋给这些同名函数不同的参数就可以了,操作符重载也是基于这一机制的。系统为我们提供了许多操作符,比如“+”,“[ ]”等,这些操作符都有一些默认的功能,而操作符重载机制允许我们给这些操作符赋予不同的功能,并能够按照普通操作符的使用格式来使用自己定义功能的操作符(即重载的操作符) 定义之后,我们就可以按照平常使用操作符的格式来使用我们自己的重载操作符了。 操作符重载一般在类内部定义,就像成员函数一样定义,这叫做类成员重载操作符。当然也可以在类外定义,即非类成员操作符重载。 二、为什么要使用操作符重载? 举例说明,比如类String,该类有这样一个功能,可以将两个字符串连接成一个字符串,为此,我们可以给类String定义一个成员函数实现此功能,可以给该函数取一个形象的名字,比如concatenate或append,但是相比较,这两个名字都不如操作符“+=”形象直观。在这种情况下,我们就可以定义操作符“+=”的重载,来实现此功能。 也就是说,如果要定义一个函数,而这个函数的功能与操作符的功能比较类似时,这个时候我们就可以定义重载操作符,而不使用通常的成员函数定义。这里所说的操作符重载,指的是与系统定义的操作符重载,而不是说定义两个“+=”,这两个重载,这一点需要清楚。 但是这四个操作符不能用于重载::: *   ? : 三、如何声明操作符重载? 同普通函数类似,只不过它的名字包括关键字operator,以及紧随其后的一个预定义操作符。例如: String& operator+=(const String&); String& operator+=(const char*); 注意:上面的括号表示形式参数,即使操作符重载不需要参数,也应该写上一个空的“( )”,而不是将其省略,这一点其实和普通函数的声明是类似的。其实,声明的唯一区别就是名字不同而已 四、怎样使用操作符重载? 两种操作符重载:类成员操作符重载和非类成员操作符重载。 1、类成员操作符重载 已知类String中声明了两个“==”操作符重载,分别是: bool operator==(const char*) const; bool operator==(const String&) const; 其中第一个重载的操作符允许我们比较一个String类对象是否等于一个C风格字符串,第二个允许我们比较两个String类对象是否相等。 示例代码: #include int main() {        String flower;        If(flower==”lily”) //正确:调用bool operator==(const char*) const;        ……        else               if(“tulip”==flower) //错误               ……. } 关键看一下,为什么第二个重载操作符的使用是错误的? 因为:只有在左操作数是该类类型的对象时,才会考虑使用作为类成员的重载操作符。 因为这里的”tulip”不是String类型对象,所以编译器试图找到一个内置操作符,它可以有一个C风格字符串的左操作数,然而事实上并不存在这样的操作符,所以编译时产生错误。 疑问:我们可以使用String类的构造函数将一个C风格字符串,转换成一个String对象,为什么编译器不能做以上转换呢?即       if(String(“tulip”)==flower);//这样就是正确的 答:为了效率和正确性 重载操作符并不要求两个操作数的类型一定相同。可能有这样一个类Text,这个类的构造函数的参数及其成员重载操作符的参数都与String类一致,如果使编译器能够自动将C风格字符串转换成某个类型的对象,那么编译器首先会检索所有的类定义,选择能够提供正确构造函数和重载操作符的类进行转换,这无疑会增加程序的编译时间,还有就是类String和类Text均合适,编译器也不知道该将C风格字符串转换成String还是Text对象了。 对于类成员重载操作符,隐式的this指针被用作隐式的第一个参数,对于成员操作符,flower==”lily”会被编译器重写为:flower.operator==(“lily”); 2、非类成员操作符重载 为了解决上面的问题,我们可以考虑使用非类成员操作符代替类成员操作符,这样做的好处是左操作数不必非要是某个类的类型对象了,对于需要两个操作数的操作符重载,我们就可以定义两个参数了。比如: bool operator==(const String&,const String&); bool operator==(const String&,const char*); 可以看到,这两个全局重载操作符比成员操作符多了一个参数。 这样定义之后,还是上面的代码,当调用flower==”lily”时,会调用上面的bool operator==(const String&,const char*);。 然而“tulip”==flower会调用哪个操作符重载呢,我们并没有定义bool operator==(const char*,const String&);,我们是不是必须定义这样一个全局操作符重载呢?答案是否定的,因为当一个重载操作符是一个名字空间函数时,对于操作符的第一个和第二个参数,即等于操作符的左右两个操作数都会考虑转换,就像 int vi=1; double vd=2.0; vi=vi+vd; 会先将vi转换成double型,再做加法一样 这意味着,编译器将解释第二个用法如下: bool operator==(String(“tulip”),flower)。这样会增加系统转换开销。 因此,如果需要频繁比较C风格字符串和String对象,那么最好定义上面的操作符重载,如果不频繁,我们只需定义下面一个就够了: bool operator==(const String&,const String&); 分析:什么时候定义类成员操作符重载,什么时候定义非类成员操作符重载? 答:(1)如果一个重载操作符是类成员,那么只有当跟它一起使用的左操作数是该类对象时,它才会被调用,如果该操作符的左操作数必须是其他类型,那么重载操作符必须是非类成员操作符重载。 (2)C++要求,赋值(=),下标([ ]),调用(())和成员访问箭头(->)操作符必须被指定为类成员操作符,否则错误。 我们只能为类类型或枚举类型的操作数定义重载操作符,我们可以这样实现:把重载操作符声明为类的成员,或者声明为非类成员重载操作符但同时至少有一个类或者枚举类型的参数(按值传递或按引用传递)。这句话的意思,实际是说我们不能修改内置类型的操作符重载或添加操作符重载,比如下面的声明便是错误的: int operator+(int,int); 这改变了内置类型int的+操作符的功能,所以错误。 操作符重载相关知识点: 五、友元(friend) 考虑到类成员操作符重载可以访问类中的私有变量,但是非类成员重载操作符却不能很方便的访问类的私有成员,为了方便起见,我们可以通过使用友元(friend的方式,方便的访问类的私有成员。 举例: class String {        friend bool operator==(const String&,const String&);        friend bool operator==(const String&,const char*);        public:               //……..        private:               //……… } 注意:friend声明紧跟在类名之后,而不是放在publicprivateprotected中,因为友元不是授权类的成员,并且该关键字只能出现在类中。 经过上述声明之后,我们的非类成员重载操作符就可以直接方位String类的私有成员了。当然我们也可以不使用友元,而使通过该类的共有成员函数来间接访问该类的私有成员也是可以的,内联函数inline就不错,效率也不低。 由此看来,声明友元(friend)主要是为了方便高效的访问类的私有成员变量。 分析:什么时候应该使用友元: (1)       某个类不提供公有的访问私有成员的函数。 (2)       使用共有成员函数访问私有成员变量效率比较差时。 友元除了用在非成员的重载操作符外,一个名字空间函数(比如全局函数),另一个在此之前定义的类的成员函数或者一个完整的类,均可以声明为某个类的成员。 如下声明: class B; class A {        friend class B;        public:               //…….        Private:               //…… } 通过上述声明,类A的成员函数可以访问所有的类B的私有成员,同样类B的成员函数可以访问所有类A的私有成员。 六、类类型对象的隐式类型转换 我们知道,系统提供的内置类型有隐式类型转换的功能,实际上我们也可以为自己编写的类提供隐式类型转换功能。这样,当我们在使用这种类的对象时,如果需要,编译器会自动调用该类的类型转换函数,实现类型转换功能。 C++提供了这样一种机制,通过它,每个类都可以定义一组“可被应用在该类型对象上的转换” 举例: class SmallInt {        public:               SmallInt(int ival):val(ival){};               operator int(){return value;} ;//类型转换操作符SmallInt->int        private:               int value; } 在上例中,操作符int()是一个转换函数,它定义了一个用户转换,实现在类类型和转换函数中指定的类型之间的转换,本例中,目标类型是int。 经过上面的定义后,SmallInt对象便可以用在任何可以使用int的地方,例如: SmallInt si(3); si+3.1415926; 这样,首先调用SmallInt转换函数,产生int型值3,然后将3变为3.0,和3.1415926相加。 实际上,上面类中定义转换函数还有一个好处就是: 省略了为该类定义重载操作符,尤其是重载操作符参数类型多样时。 1、 转换函数 格式:operator type( ); //参数必须为空,且( )不能省略,并且无返回值类型。 说明:type表示目标类型,它可以用内置类型,类类型或typedef名取代,但不允许表示数组,转换函数必须是类的成员函数,并且不能指定返回类型和参数表。 显式的强制类型转换会导致调用转换函数,如果被转换值的类型是一个类类型,它有一个转换函数,并且该转换函数的类型是强制转换所指定的类型,则调用这个类的强制转换函数。 比如: char* tokName=static_cast(tok);//tok表示对象,它所属的类提供一个到char*的转换函数。 2、 用构造函数作为转换函数 在一个类的构造函数中,凡是只带有一个参数的构造函数,例如SmallInt的构造函数SmallInt(int),都定义了一组隐式转换,把构造函数的参数类型转换成该类类型。 注意:是将int转换成SmallInt型。和前面说的转换函数功能正好相反。 举例: void calc(SmallInt); int vi; calc(vi); 这时,编译器就会隐式调用类SmallInt的构造函数SmllInt(int),将vi转换成SmallInt对象,然后再将这个对象传递给函数calc( )。 可以这样理解: {        SmallInt temp=SmallInt(i);        Calc(temp); } 这个大括号指出temp这个临时对象的生命周期。 注意:编译器不会使用一个显式构造函数(关键字explicit标志)来执行隐式类型转换,但是却可以使用这样的构造函数来进行强制转换(即static_cast<>)。
推荐阅读
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • 本文详细解释了为什么在成功执行移动赋值操作后,对象的析构函数会被调用,并提供了代码示例和详细的分析。 ... [详细]
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 本文探讨了浮点数、字符、Kotlin类型转换以及字符串处理等关键概念。介绍了Float.MIN_VALUE表示最小正数,Float.NaN的特殊性质,以及Double.MIN_VALUE和Char类型的细节。同时,解释了Kotlin中的类型转换、字符串拼接及编译期常量的概念。 ... [详细]
  • 探讨ChatGPT在法律和版权方面的潜在风险及影响,分析其作为内容创造工具的合法性和合规性。 ... [详细]
author-avatar
涛之圣首到
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有