与学长在群里讨论到了iostream的>>操作符的返回值问题,记得曾经找资料的时候有说过>>返回的是流引用,不过可以被转换成bool,因此可以被直接用在while中作为条件。当时就记得有这么回事,也没有继续深入;今天正好借此机会好好补一补,心得记录下来,备用。
我翻看了vc6.0中的iostream及相关头文件,所有>>操作符的返回值都是basic_stream<>&(另外模板对char*和wchar_t*做了特别的定义),并没有返回bool型的;随后百度,发现了端倪:
//<xiosbase> in VC6.0 operator void *() const {return (fail() ? 0 : (void *)this); } bool operator!() const {return (fail()); }
在vc6里上面的两个函数是在class ios_base中,位于文件XIOSBASE。不怕大家笑话,本菜从未见过operator void*这样的重载,甚是不解。我只看过一本C++的教程,是谭浩强先生的《C++程序设计》,还是第一版。那本书当时我大概完整地看了有三四遍,记得书中没有讲过有关用户自定义转换的东西,也很久没有再碰过那本书了,因为借给了同学……还是自己再找点东西吧。
首先,>>操作符返回的是流引用,所以才可以实现连续>>的操作。>>和<<都是从左至右结合,就是说譬如cin>>a>>b;这样的语句,先结合cin>>a,输入a之后返回流引用(就相当于是cin),继续与后面的>>b结合,就相当于是cin>>b,再次输入b的值。
其次,ios_base还重载了void*。这种重载相当于是定义了一种转换,由当前类型转换到重载中声明的类型。由其他类型转换至当前类型很简单,依靠构造函数就能解决;而从当前类型转换至其他类型就需要靠这种重载来转换,而且这种转换可以衔接到标准转换序列中。比如上述void*的转换,返回的是一个指针,指针的本质其实就相当于一个ULONG类型的变量,进而转换至bool类型。所以,不论是while(cin)还是while(cin>>a)这样的写法,都可以得到正确的结果。
因此,用户自定义转换主要用于用户自定义类型与其他类型(既包括内置类型也包括自定义类型)之间的相互转换,可以分为两大部分:从其他类型到当前类型的转换;从当前类型到其他类型的转换。前者主要是利用构造函数实现,后者主要靠重载转换操作符来实现,而且重载函数没有参数也没有返回值。另外,这种转换之后的类型只允许进入标准转换序列。也就是说,如果在用户自定义转换之后仍需进行另一次用户自定义转换,编译器将不会进行隐式转换(也许我们用该要么勤快一点,多写一步显示转换;要么一步到位,再重载一个转换操作符)。不过这种重载也可能造成二义性,也分两种情况:
1.类型转换序列中的二义性问题
这种二义性在标准转换序列中就比较常见,比如float和int都可以经由标准序列转换为long(没有一个是精确匹配),此时就是二义的;这种情况当然可能出现在我们的用户自定义转换当中。
2.两种用户自定义转换之间的矛盾
假如有这样一个代码,class A中由构造函数提供了从B到A的隐式转换(通过构造函数);而在B中提供了由B到A的转换(通过重载转换操作符),那么在转换的时候到底应该应用哪一个呢?此问编译器不解,遂其罢工也……
解决方法是这样的:一种方法为调用B.operator A()来显示调用(有人说用A(B)也能达到目的,可以成功编译。也许是C++标准和编译器实现不同所致?),另一种方法为用explicit关键字显示声明A的构造函数,这样A的构造函数将不再参与隐式转换,也就消除了此类转换的二义性。