C++学习笔记8-操作符重载

1. 重载操作符必须具有一个类类型操作数

用于内置类型的操作符,其含义不能改变。例如,内置的整型加号操作符不能重定义:

// error: cannotredefine built-in operator for ints

int operator+(int, int);

也不能为内置数据类型重定义加号操作符。例如,不能定义接受两个数组类型操作数的operator+。

重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。

2. 优先级和结合性是固定的

操作符的优先级、结合性或操作数目不能改变。不管操作数的类型和操作符的功能定义如何,表达式

x == y +z;

总是将实参y 和z 绑定到operator+,并且将结果用作operator== 右操作数。 有四个符号(+,-, * 和&)既可作一元操作符又可作二元操作符,这些操作符有的在其中一种情况下可以重载,有的两种都可以,定义的是哪个操作符由操作数数目控制。除了函数调用操作符operator() 之外,重载操作符时使用默认实参是非法的

3. 类成员与非成员

重载一元操作符如果作为成员函数就没有(显式)形参,如果作为非成员函数就有一个形参。类似地,重载二元操作符定义为成员时有一个形参,定义为非成员函数时有两个形参。

类Sales_item 中给出了成员和非成员二元操作符的良好例子。我们知道该类有一个加号操作符。因为它有一个加号操作符,所以也应该定义一个复合赋值(+=)操作符,该操作符将一个Sales_item 对象的值加至另一个Sales_item对象。一般将算术和关系操作符定义非成员函数,而将赋值操作符定义为成员:

// member binaryoperator: left-hand operand bound to implicit this pointer

Sales_item&Sales_item::operator+=(const Sales_item&);

// nonmember binaryoperator: must declare a parameter for each operand

Sales_itemoperator+(const Sales_item&, const Sales_item&);

4. 不要重载具有内置含义的操作符

赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本,编译器就自己定义以下这些操作符。

? 合成赋值操作符(第13.2 节)进行逐个成员赋值:使用成员自己的赋值:使用成员自己的赋值操作依次对每个成员进行赋值。

? 默认情况下,取地址操作符(&)和逗号操作符(,)在类类型对象上的执行,与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址,逗号操作符从左至右计算每个表达式的值,并返回最右边操作数的值。

? 内置逻辑与(&&)和逻辑或(||)操作符使用短路求值。如果重新定义该操作符,将失去操作符的短路求值特征。

通过为给定类类型的操作数重定义操作符,可以改变这些操作符的含义。

重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义。

有时我们需要定义自己的赋值运算。这样做时,它应表现得类似于合成操作符:赋值之后,左右操作数的值应是相同的,并且操作符应返回对左操作数的引用。重载的赋值运算应在赋值的内置含义基础上进行定制,而不是完全绕开。

5. 审慎使用操作符重载

每个操作符用于内置类型都有关联的含义。例如,二元+ 与加法是完全相同的。将二元+ 对应到一个类类型的类似操作可提供方便的简写方法。例如,标准库的类型string,遵循许多程序设计语言的通用规范,使用+ 表示连接——将一个串“加”至另一个串。

当内置操作符和类型上的操作存在逻辑对应关系时,操作符重载最有用。使用重载操作符而不是创造命名操作,可以令程序更自然、更直观,而滥用操作符重载使得我们的类难以理解。

在实践中很少发生明显的操作符重载滥用。例如,不负责任的程序员可能会定义operator+ 来执行减法。更常见但仍不可取的是,改变操作符的“正常”含义以强行适应给定类型。操作符应该只用于对用户而言无二义的操作。在这里所谓有二义的操作符,就是指具有多个不同解释的操作符。

当一个重载操作符的含义不明显时,给操作取一个名字更好。对于很少用的操作,使用命名函数通常也比用操作符更好。如果不是普通操作,没有必要为简洁而使用操作符。

6. 选择成员或非成员实现

为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,程序员没有选择,操作符必须是成员;在另一些情况下,有些经验原则可指导我们做出决定。下面是一些指导原则,有助于决定将操作符设置为类成员还是普通非成员函数:

? 赋值(=)、下标([])、调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误。

? 像赋值一样,复合赋值操作符通常应定义为类的成员,与赋值不同的是,不一定非得这样做,如果定义非成员复合赋值操作符,不会出现编译错误。

? 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增、自减和解引用,通常就定义为类成员。

? 对称的操作符,如算术操作符、相等操作符、关系操作符和位操作符,最好定义为普通非成员函数。

7. 赋值必须返回对*this 的引用

string 赋值操作符返回string 引用,这与内置类型的赋值一致。而且,因为赋值返回一个引用,就不需要创建和撤销结果的临时副本。返回值通常是左操作数的引用,例如,这是Sales_item 复合赋值操作符的定义:

// assumes that bothobjects refer to the same isbn

Sales_item&Sales_item::operator+=(const Sales_item& rhs)

{

units_sold +=rhs.units_sold;

revenue +=rhs.revenue;

return *this;

}

一般而言,赋值操作符与复合赋值操作符应返回操作符的引用。

8. 原型下标操作符

类定义下标操作符时,一般需要定义两个版本:一个为非const 成员并返回引用,另一个为const 成员并返回const 引用。

下面的类定义了下标操作符。为简单起见,假定Foo 所保存的数据存储在一个vector<int>: 中:

class Foo {

public:

int &operator[](const size_t);

const int&operator[] (const size_t) const;

// other interfacemembers

private:

vector<int>data;

// other member dataand private utility functions

};

下标操作符本身可能看起来像这样:

int&Foo::operator[] (const size_t index)

{

return data[index];// no range checking on index

}

const int&Foo::operator[] (const size_t index) const

{

return data[index];// no range checking on index

}

9. 转换可能引起内置操作符的二义性我们再次扩展SmallInt 类。这一次,除了到int 的转换操作符和接受int 参数的构造函数之外,将增加一个重载的加操作符:

class SmallInt {

public:

SmallInt(int = 0); //convert from int to SmallInt

// conversion to intfrom SmallInt

operator int() const{ return val; }

// arithmetic operators

friend SmallInt

operator+(constSmallInt&, const SmallInt&);

private:

std::size_t val;

};

现在,可以用这个类将两个SmallInts 对象相加,但是,如果试图进行混合模式运算,将会遇到二义性问题:

SmallInt s1, s2;

SmallInt s3 = s1 +s2; // ok: uses overloaded operator+

int i = s3 + 0; //error: ambiguous

第一个加使用接受两个SmallInt 值的+ 的重载版本。第二个加有二义性,问题在于,可以将0 转换为SmallInt 并使用+ 的SmallInt 版本,也可以将 s3 转换为int 值并使用int 值上的内置加操作符。

既为算术类型提供转换函数,又为同一类类型提供重载操作符,可能会导致重载操作符和内置操作符之间的二义性。

10. 可行的操作符函数和转换

通过为每个调用列出可行函数,可以理解这两个调用的行为。在第一个调用中,有两个可行的加操作符:

?operator+(const SmallInt&, const SmallInt&)

?The built-in operator+(int, int)

内置的operator+(int, int)。

第一个加不需要实参转换——s1和s2 与形参的类型完全匹配。使用内置加操作符对两个实参都需要转换,因此,重载操作符与两个实参匹配得较好,所以将调用它。对于第二个加运算:

int i = s3 + 0; //error: ambiguous

两个函数同样可行。在这种情况下,重载的+ 版本与第一个实参完全匹配,而内置版本与第二个实参完全匹配。第一个可行函数对左操作数而言较好,而第二个可行函数对右操作数而言较好。因为找不到最佳可行函数,所以将该调用标记为有二义性的。

C++学习笔记8-操作符重载

时间: 2024-08-25 23:26:24

C++学习笔记8-操作符重载的相关文章

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符、成员函数方式重载、友元函数方式重载

C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符.成员函数方式重载.友元函数方式重载 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 一.重载的操作符名 像任何其他函数一样,操作符重载函数有一个返回值和一个形参表.形参表必须具有操作符数目相同的形参.比如赋值时二元运算,所以该操作符函数有两个参数:第一个形参对应着左操作数,第二个形参对应右操作数. 大多数操作符可以定义为成员函数或非成员函数.当操作符为成员函数时,它的第一个操作数隐式绑定

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载、!运算符重载、赋值运算符重载 、String类([]、 +、 += 运算符重载)、&gt;&gt;和&lt;&lt;运算符重载

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载.!运算符重载.赋值运算符重载 .String类([]. +. += 运算符重载).>>和<<运算符重载 一.++/--运算符重载 1.前置++运算符重载 成员函数的方式重载,原型为: 函数类型 & operator++(); 友元函数的方式重载,原型为: friend 函数类型 & operator++(类类型 &); 2.后置++运算符重载 成员函数的方式重载,原型为:

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载、覆盖与隐藏、类型转换运算符、*运算符重载、-&gt;运算符重载

C++ Primer 学习笔记_28_操作符重载与转换(3)--成员函数的重载.覆盖与隐藏.类型转换运算符.*运算符重载.->运算符重载 一.成员函数的重载.覆盖与隐藏 对于类层次的同名成员函数来说,有三种关系:重载.覆盖和隐藏,理清3种关系,有助于写出高质量的代码. 1.成员函数的重载 重载的概念相对简单,只有在同一类定义中的同名成员函数才存在重载关系,主要特点时函数的参数类型和数目有所不同:但不能出现函数参数的个数和类型均相同,仅仅依靠返回值类型不同来区分的函数,这和普通函数的重载是完全一致

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳.operator new 和 operator delete 实现一个简单内存泄漏跟踪器 一.转换构造函数 可以用单个实参来调用的构造函数定义从形参类型到该类型的一个隐式转换.如下: class Integral { public: Integral (int = 0); //转换构造函数 private: int real; }; Integral A = 1; //调用转换构造函数将1转换为In

C++ Primer Plus学习笔记之运算符重载

C++ Primer Plus学习笔记之运算符重载 1,成员函数和友元函数选择的建议 下面我们先看两个例子: 成员函数重载 #include<iostream> using namespace std; class Complex { public: Complex(double r=0,double i=0) { re=r; im=i; } Complex operator+(const Complex& obj); Complex operator!(); void Display

iOS: 学习笔记, Swift操作符定义

Swift操作符可以自行定义, 只需要加上简单的标志符即可. @infix 中置运算. 如+,-,*,/运算 @prefix 前置运算. 如- @postfix 后置运算. a++, a-- @assignment 赋值运算. +=, -=, --a, ++a // // main.swift // SwiftBasic // // Created by yao_yu on 14-7-27. // Copyright (c) 2014年 yao_yu. All rights reserved.

Java学习笔记之方法重载,动态方法调度和抽象类

一.方法重载 如果子类中的方法与它的超类中的方法有相同的方法名,则称子类中的方法重载超类中的方法,特别是当超类和子类中的方法名和参数类型都相同时,在子类中调用该方法时,超类中的方法会被隐藏.考虑下面程序: 1 class A 2 { 3 int i, j; 4 A(int a, int b) 5 { 6 i = a; 7 j = b; 8 } 9 10 // display i and j 11 void show() 12 { 13 System.out.println("i and j: &

C++学习笔记之运算符重载

一.运算符重载基本知识 在前面的一篇博文 C++学习笔记之模板(1)——从函数重载到函数模板 中,介绍了函数重载的概念,定义及用法,函数重载(也被称之为函数多态)就是使用户能够定义多个名称相同但特征标(参数列表)不同的函数,目的是在对不同类型的参数执行相同的操作时只用一个同名的函数. 运算符重载,就是使同一个运算符在面临不同类型的数据时作出不同的操作(函数重载是操作相同),就是让同一个运算符有多重功能.实际上我们经常用的许多运算符已被重载,例如,将*用于地址,将得到存储在这个地址中的值:但将它用

Java学习笔记之方法重载

被重载的方法必须具有不同的参数列表.不能基于不同修饰符或返回值类型来重载方法. package welcome; public class TestMethodOverloading { public static void main(String[] args) { System.out.println("The maximum between 3 and 4 is " + max(3, 4)); // 调用max(int, int)方法 System.out.println(&qu

PKU C++程序设计实习 学习笔记4 运算符重载

第四章 运算符重载 4.1 运算符重载的基本概念 1. 运算符 2. 自定义数据类型与运算符重载 C++提供了数据抽象的手段:用户自己定义数据类型 -- 类 ? 调用类的成员函数->操作它的对象 类的成员函数->操作对象时,很不方便 ? 在数学上,两个复数可以直接进行+/-等运算 Vs. 在C++中,直接将+或-用于复数是不允许的 3. 运算符重载 对抽象数据类型也能够直接使用C++提供的运算符 ? 程序更简洁 ? 代码更容易理解 运算符重载 ? 对已有的运算符赋予多重的含义 ? 使同一运算符