C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】

重载操作符与转换

--转换与类类型【下】

四、重载确定和类的实参

在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数。因此,应该在函数确定期间考虑类转换操作符。函数重载确定由三步组成:

1)确定候选函数集合:这些是与被调用函数同名的函数。

2)选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数。选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参

3)选择最佳匹配的函数。为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类。对于类类型的实参和形参,可能的转换的集合包括类类型转换

1、转换操作符之后的标准转换

如果重载集中两个函数可以用同一转换函数匹配,则使用在转换之后或之前的标准转换序列的等级来确定哪个函数具有最佳匹配。

否则,如果可以使用不同转换操作,则认为这两个转换是一样好的匹配,不管可能需要或不需要的标准转换的等级如何。

注意:只有两个转换序列使用同一转换操作时,才用类类型转换之后的标准转换序列作为选择标准。

2、多个转换和重载确定

class SmallInt
{
public:
    operator int() const
    {
        return val;
    }

    operator double() const
    {
        return val;
    }
    //...

private:
    std::size_t val;
};

    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous

在这个例子中,可以使用operatorint 转换si并调用接受int参数的compute版本,或者,可以使用operatordouble 转换si并调用compute(double)。

编译器将不会试图区别两个不同的类类型转换。具体而言,即使一个调用需要在类类型转换之后跟一个标准转换,而另一个是完全匹配,编译器仍会将该调用标记为错误。即:

    void compute(int);
    //void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous,此处仍然错误

3、显式强制转换消除二义性

    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(static_cast<int>(si));  //OK
    compute(static_cast<double>(si));   //OK

4、标准转换和构造函数

class SmallInt
{
public:
    SmallInt(int = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);
    /*
    *:它既可以将 Integral 转换为 int 并调用 manip 的第一个版本,
    *也可以表示 SmallInt 转换为 int 并调用 manip 的第二个版本。
    */
    manip(10);  //Error

即使其中一个类定义了实参需要标准转换的构造函数,这个函数调用也可能具有二义性。例如,如果SmallInt定义了一个构造函数,接受short而不是 int参数,函数调用manip(10)将在使用构造函数之前需要一个从int到 short的标准转换。在函数调用的重载版本中进行选择时,一个调用需要标准转换而另一个不需要,这一事实不是实质性,编译器不会更喜欢直接构造函数,调用仍具有二义性。如:

class SmallInt
{
public:
    //将int该为short
    SmallInt(short = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);

    //此处调用仍具有二义性
    manip(10);  //Error

5、显式构造函数调用消除二义性

    manip(SmallInt(10));    //OK
    manip(Integral(10));    //OK

【警告!】

在调用重载函数时,需要使用构造函数或强制类型转换实参,这是设计拙劣的表现!

//P463 习题14.44
class LongDouble
{
public:
    operator double () const
    {
        cout << "double!" << endl;
        return val;
    }
    operator float () const
    {
        cout << "float!" << endl;
        return val;
    }

private:
    double val;
};

int main()
{
    LongDouble ldObj;
    int ex1 = ldObj;    //Error
    float ex2 = ldObj;  //OK : float
}

//习题14.45
class LongDouble
{
public:
    LongDouble(double);

private:
    double val;
};

void calc(int temp)
{
    cout << "int" << endl;
}
void calc(LongDouble temp)
{
    cout << "LongDouble" << endl;
}

int main()
{
    double dval;
    //将会调用void calc(int):因为标准转换要优于类类型转换
    calc(dval);
}

五、重载、转换和操作符

重载操作符就是重载函数。使用与确定重载函数调用一样的过程来确定将哪个操作符(内置的还是类类型的)应用于给定表达式。

    ClassX sc;
    int iobj = sc + 3;

有四种可能性:

1)有一个重载的加操作符与ClassX和 int相匹配。

2)存在转换,将sc和/或int值转换为定义了+的类型。如果是这样,该表达式将先使用转换,接着应用适当的加操作符。

3)因为既定义了转换操作符又定义了+的重载版本,该表达式具有二义性。

4)因为既没有转换又没有重载的+可以使用,该表达式非法。

1、重载确定和操作符

成员函数和非成员函数都是可能的,这一事实改变了选择候选函数集的方式!

操作符的重载确定遵循常见的三步过程(老生常谈了:-D):

1)选择候选函数

2)选择可行函数,包括识别每个实参的潜在转换序列

3)选择最佳匹配的函数

2、操作符的候选函数

一般而言,候选函数集由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。对于操作符用在表达式中的情况,候选函数包括操作符的内置版本以及该操作符的普通非成员版本。另外,如果左操作符具有类类型,而且该类定义了该操作符的重载版本,则候选集将包含操作符的重载版本。

一般而言,函数调用的候选集只包括成员函数或非成员函数,不会两者都包括。而确定操作符的使用时,操作符的非成员和成员版本可能都是候选者

确定指定函数的调用时,与操作符的使用相反,调用本身确定所考虑的名字的作用域。如果是通过类类型的对象(或通过这种对象的引用或指针)的调用,则只需考虑该类的成员函数。具有同一名字的成员函数和非成员函数不会相互重载。使用重载操作符是时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本都必须考虑。

【警告:转换和操作符】

正确设计类的重载操作符、转换构造函数和转换函数需要多加小心。尤其是,如果类既定义转换操作符又定义了重载操作符,容易产生二义性。下面几条经验规则会有所帮助:

1)不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数,不要再为类Bar定义到类型Foo的转换操作符

2)避免到内置算术类型的转换。具体而言,如果定义了到算术类型的转换,则

a)不要定义接受算术类型的操作符的重载版本。如果用户需要使用这些操作符,转换操作符将转换你所定义的类型的对象,然后可以使用内置操作符。

b)不要定义转换到一个以上算术类型的转换。让标准转换提供到其他算术类型的转换

最简单的规则是:对于那些“明显正确”的,应避免定义转换函数并限制非显式构造函数

3、转换可能引起内置操作符的二义性

class SmallInt
{
public:
    SmallInt(int = 0);
    operator int() const
    {
        return val;
    }

    friend SmallInt operator+(const SmallInt &,const SmallInt &);
private:
    std::size_t val;
};

int main()
{
    SmallInt s1,s2;

    //使用接受两个 SmallInt 值的 + 的重载版本
    SmallInt s3 = s1 + s2;  //OK

    //以将 0 转换为 SmallInt 并使用 + 的 SmallInt 版本
    //也可以将 s3 转换为 int 值并使用 int 值上的内置加操作符。
    int i = s3 + 0; //Error
}

【小心地雷】

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

4、可行的操作符和转换

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

    SmallInt operator+(const SmallInt &,const SmallInt &);
    内置的 operator+(int, int)

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

对于第二个加运算:

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

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

//P466 习题14.46 最后的一条语句会调用哪个operator+
class Complex
{
public:
    Complex(double);
};

class LongDouble
{
    friend LongDouble operator+(LongDouble &,int);  //1

public:
    LongDouble(int);
    operator double ();
    LongDouble operator+(const Complex &);      //2
    //..
};
LongDouble operator+(const LongDouble &,double);    //3

int main()
{
    LongDouble ld(16.08);
    double res = ld + 15.05;    //调用3,为什么呢?
}

C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】,布布扣,bubuko.com

时间: 2024-10-08 10:34:22

C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】的相关文章

C++ Primer 学习笔记_58_重载操作符与转换 --重载操作符的定义

重载操作符与转换 --重载操作符的定义 引言: 明智地使用操作符重载可以使类类型的使用像内置类型一样直观! 重载操作符的定义 重载操作符是具有特殊名称的函数:保留字operator后接定义的操作符符号.如: Sales_item operator+(const Sales_item &,const Sales_item &); 除了函数调用操作符之外,重载操作符的形参数目(包括成员函数的隐式this指针)与操作符的操作数数目相同.函数调用操作符可以接受任意数目的操作数. 1.重载的操作符名

C++ Primer 学习笔记_59_重载操作符与转换 --输入/输出、算术/关系操作符

重载操作符与转换 --输入/输出.算术/关系操作符 支持I/O操作的类所提供的I/O操作接口,一般应该与标准库iostream为内置类型定义的接口相同,因此,许多类都需要重载输入和输出操作符. 一.输出操作符<<的重载 为了与IO标准库一致,操作符应接受ostream&作为第一个形参,对类类型const对象的引用作为第二个形参,并返回ostream形参的引用! ostream &operator<<(ostream &os,const ClassType &

C++ Primer 学习笔记_63_重载操作符与转换 --转换与类类型【上】

重载操作符与转换 --转换与类类型[上] 引言: 在前面我们提到过:可以用一个实参调用的非explicit构造函数定义一个隐式转换.当提供了实参类型的对象需要一个类类型的对象时,编译器将使用该转换.于是:这种构造函数定义了到类类型的转换. 除了定义到类类型的转换之外,还可以定义从类类型到其他类型的转换.即:我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象.和其他转换一样,编译器将自动应用这个转换. 一.转换为什么有用? 定义一个SmallInt的类,该类实现安全小整数,这个

C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符

重载操作符与转换 --赋值.下标.成员訪问操作符 一.赋值操作符 类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用.假设未定义这个操作符,则编译器将合成它.类赋值操作符必须是类的成员,以便编译器能够知道是否须要合成一个.并且还能够为一个类定义很多附加的赋值操作符,这些赋值操作符会由于右操作数的不同而构成重载!如string类型: string car("Volks"); car = "Studebaker"

C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符

重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪问检查: class CheckedPtr { public: //这个类没有默认构造函数,必须提供指向数组的指针. /**构造函数的參数是两个指针:一个指向数组的開始,还有一个指向数组的末端. *构造函数用这两个指针初始化 beg 和 end *并将 curr 初始化为指向第一个元素 */ Che

C++ Primer 学习笔记_62_重载操作符与转换 --调用操作符和函数对象

重载操作符与转换 --调用操作符和函数对象 引言: 能够为类类型的对象重载函数调用操作符:一般为表示操作的类重载调用操作符! struct absInt { int operator() (int val) { return val > 0 ? val : -val; } }; 通过为类类型的对象提供一个实參表而使用调用操作符,所用的方式看起来系那个一个函数调用: absInt absObj; int i = -1; cout << absObj(i) << endl; 虽然

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_63_重载运算符和转换 --转换和类类型【上】

重载运算符和转换 --转换与类类型[上] 引言: 在前面我们提到过:能够用一个实參调用的非explicit构造函数定义一个隐式转换.当提供了实參类型的对象须要一个类类型的对象时.编译器将使用该转换. 于是:这样的构造函数定义了到类类型的转换. 除了定义到类类型的转换之外,还能够定义从类类型到其它类型的转换.即:我们能够定义转换操作符,给定类类型的对象.该操作符将产生其它类型的对象.和其它转换一样,编译器将自己主动应用这个转换. 一.转换为什么实用? 定义一个SmallInt的类,该类实现安全小整