12.11 自动类型转换(Automatic type conversion)
#include <iostream>
using namespace std;
class One {
public:
int i;
One() { i=1; cout << "One" << i<< endl;}
};
class Two {
int i;
public:
Two(const One& a) { i=2;cout << "Two" << i << a.i << endl;}
};
void f(Two) {}
int main() {
One one;
f(one); // Wants a Two, has a One
}
输出结果
One1
Two21
可以看到Two的构造函数被调用,传入的参数是一个One对象。也就是自动类型转换发生了。发现当前类型不是所需要类型的时候,编译器试图寻找可以生成需要类型的方法。在上面的例子中需要Two类型,但是传入的是One,编译器发现Two的有一个只有一个参数构造函数,其参数类型为One于是,类型转换发生了。
要求:
1)构造函数有一个参数,且为被转换对象所属类型
2)构造函数除了被转换对象所属类型作为其中一个参数外,还有其他参数,且都提供了默认值
3)不能建立基于构造函数的用户定义类型到内建类型的自动转换,只能通过操作符重载这一方式
阻止基于构造函数的自动类型转换
方法是在构造函数前面加上关键字:explicit
class One {
public:
One() {}
};
class Two {
public:
explicit Two(const One&) {}
};
void f(Two) {}
int main() {
One one;
//! f(one); // No auto conversion allowed
f(Two(one)); // OK -- user performs conversion
}
通过操作符重载来实现类型自动转换(Operator conversion)
class Three {
int i;
public:
Three(int ii = 0, int = 0) : i(ii) {}
};
class Four {
int x;
public:
Four(int xx) : x(xx) {}
operator Three() const { return Three(x); }
};
void g(Three) {}
int main() {
Four four(1);
g(four);
g(1); // Calls Three(1,0)
}
要点:
1)作为类的成员函数
2)形式:operator 目标类名()
3)在使用的时候,传入一个Four对象,但是需要的是Three对象,而Four类又有一个可用于类型转换的操作符重载函数,这个时候转换发生
Reflexivity
通过一个例子来说明成员函数方式和全局函数方式的差别
class Number {
int i;
public:
Number(int ii = 0) : i(ii) {}
const Number
operator+(const Number& n) const {
return Number(i + n.i);
}
friend const Number
operator-(const Number&, const Number&);
};
const Number
operator-(const Number& n1,
const Number& n2) {
return Number(n1.i - n2.i);
}
int main() {
Number a(47), b(11);
a + b; // OK
a + 1; // 2nd arg converted to
//! 1 + a; // Wrong! 1st arg not
a - b; // OK
a - 1; // 2nd arg converted to
1 - a; // 1st arg converted to
}
成员函数方式要求左值必须是确定的类型,不能通过自动转换方式得到。所以1+a处编译错误。而全局函数方式则没有这个限制1-a处,1就自动转成了需要的类型
有些时候利用类型转换的特性可以带来编码的便利
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;
class Stringc {
string s;
public:
Stringc(const string& str = "") : s(str) {}
int strcmp(const Stringc& S) const {
return ::strcmp(s.c_str(), S.s.c_str());
}
};
int main() {
Stringc s1("hello"), s2("there");
s1.strcmp(s2);
}
利用类型转换特性加以改造
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;
class Stringc {
string s;
public:
Stringc(const string& str = "") : s(str) {}
operator const char*() const {
return s.c_str();
}
};
int main() {
Stringc s1("hello"), s2("there");
strcmp(s1, s2); // Standard C function
strspn(s1, s2); // Any string function!
}
自动类型转换的陷阱
自动类型转换要注意一些问题:如一个类型到另外一个类型有1种以上的转换路径;或者是和重载函数一同使用时要转换的目标类型模糊等
class Orange; // Class declaration
class Apple {
public:
operator Orange() const; // Convert Apple to Orange
};
class Orange {
public:
Orange(Apple); // Convert Apple to Orange
};
void f(Orange) {}
int main() {
Apple a;
//! f(a); // Error: ambiguous conversion
} ///:~
问题:有两种Apple->Orange的方式
解决办法1:
class Orange {};
class Pear {};
class Apple {
public:
operator Orange() const;
operator Pear() const;
};
void eat(Orange);
void eat(Pear);
int main() {
Apple c;
//! eat(c);
// Error: Apple -> Orange or Apple -> Pear ???
}
解决办法2:可以提供转换函数
class Fi {};
class Fee {
public:
Fee(int) {}
Fee(const Fi&) {}
};
class Fo {
int i;
public:
Fo(int x = 0) : i(x) {}
operator Fee() const { return Fee(i); }
};
int main() {
Fo fo;
Fee fee = fo;
}
发生了如下事情:
1)Fo的操作符重载函数调用,返回一个Fee对象
2)Fee没有定义copy构造函数,采用默认的copy构造函数