Summary
www.cplusplus.com/doc/tutoria…
Implicit conversion 隐式转换
将值赋值到兼容类型时,会自动执行隐式转换。例如:
short a=2000;
int b;
b=a;
在这里,不需要任何显式运算符就可以将a从shortpromote到int。隐式转换其实还挺复杂,在此不深入展开。
Implicit conversions with classes 类的隐式转换
在类的世界中,隐式转换可以通过三个成员函数来控制:
- 单参数构造函数: 允许从特定类型进行隐式转换以初始化对象。
- 赋值运算符: 允许在赋值时从特定类型进行隐式转换。
- 类型转换运算符: 允许隐式转换为特定类型。
// implicit conversion of classes:
#include <iostream>
using namespace std;
class A {};
class B {
public:
// conversion from A (constructor): 单参数构造函数
B (const A& x) {}
// conversion from A (assignment): 赋值运算符
B& operator= (const A& x) {return *this;}
// conversion to A (type-cast operator) 类型转换运算符
operator A() {return A();}
};
int main ()
{
A foo;
B bar = foo; // calls constructor
bar = foo; // calls assignment
foo = bar; // calls type-cast operator
return 0;
}
Keyword explicit 显式关键字
在函数调用中,C++ 允许对每个参数进行一次隐式转换。这对于类来说可能有些问题,因为它并不总是预期的。explicit关键字可以防止隐式转换。
// explicit:
#include <iostream>
using namespace std;
class A {};
class B {
public:
explicit B (const A& x) {}
B& operator= (const A& x) {return *this;}
operator A() {return A();}
};
void fn (B x) {}
int main ()
{
A foo;
B bar (foo);
bar = foo;
foo = bar;
// fn (foo); // not allowed for explicit ctor.
fn (bar);
return 0;
}
Type casting 类型转换
C++ 是一种强类型语言。许多转换,特别是那些暗示对值有不同解释的转换,需要显式转换,在 C++ 中称为type-casting。泛型类型转换有两种主要语法:functional notation和c-like cast notation
double x = 10.3;
int y;
y = int (x); // functional notation
y = (int) x; // c-like cast notation
这些类型转换的通用形式的功能足以满足基本数据类型的大多数需求。但是,这些运算符可以不加选择地应用于类和指向类的指针,这可能导致代码 - 虽然在语法上正确 - 但可能会导致运行时错误。例如,下面的代码编译没有错误:
// class type-casting
#include <iostream>
using namespace std;
class Dummy {
double i,j;
};
class Addition {
int x,y;
public:
Addition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};
int main () {
Dummy d;
Addition * padd;
padd = (Addition*) &d;
cout << padd->result();
return 0;
}
该程序声明了一个指向 的指针Addition,但随后它使用显式类型转换为它分配了对另一个不相关类型的对象的引用。
不受限制的显式类型转换允许将任何指针转换为任何其他指针类型,而与它们指向的类型无关。对 member的后续调用result将产生运行时错误或其他一些意外结果。
因此c++引入了四种具体的显示转换符
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression)
const_cast <new_type> (expression)
dynamic_cast
dynamic_cast 只能够用在指向类的指针或者引用上(或者void*)。这种转换的目的是确保目标指针类型所指向的是一个有效且完整的对象。
同隐式转换一样,这种转换允许upcast(从派生类向基类的转换)。
但dynamic_cast 也能downcast(从基类向派生类的转换)当且仅当转过去的指针所指向的目标对象有效且完整。例如:
// dynamic_cast
#include <iostream>
#include <exception>
using namespace std;
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
int main () {
try {
Base * pba = new Derived;
Base * pbb = new Base;
Derived * pd;
pd = dynamic_cast<Derived*>(pba);
if (pd==0) cout << "Null pointer on first type-cast.\n";
pd = dynamic_cast<Derived*>(pbb);
if (pd==0) cout << "Null pointer on second type-cast.\n";
} catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}
输出:
Null pointer on second type-cast.
显然第二次pd所指向的对象并不完整。
当发生如上情况时,会返回空指针来表示转换失败。倘若是无法完成的引用转换,则会抛出bad_cast 异常。
此外 dynamic_cast 也能够完成空指针在任意指针类型上的转换,以及任意指针类型向void*的转换。
注意:在上面例子中的dynamic_cast 需要用到RTTI(Run-Time Type Identification);在一些支持此特性但是默认是关闭的编译器上需要额外打开。
static_cast
static_cast能够完成指向相关类的指针上的转换。upcast和downcast都能够支持,但不同的是,并不会有运行时的检查来确保转换到目标类型上的指针所指向的对象有效且完整。因此,这就完全依赖程序员来确保转换的安全性。但反过来说,这也不会带来额外检查的开销。例如
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
虽然b指向的并不是一个完整的类以及后续在使用b时可能导致runtime error,这却是一段合法的代码。
同时,static_cast能够完成所有隐式类型转换以及它们的相反转换,更具体点:
- void*转向任何指针类型;假如该指针是从某指针类型上转到void *,现在再次转到相同的指针类型上去的话,保证转换后的指针本身的值不变。
- 整数,浮点以及枚举类型向枚举类型的转换。
此外,还能够完成:
- 显式调用仅有一个参数的构造函数或者是转换操作符(conversion operator)
- 转换到右值引用
- 枚举类的值向整数或浮点的转换
- 任意类型向void的转换,目标值在被求值之后舍弃。
reinterpret_cast
reinterpret_cast能够完成任意指针类型向任意指针类型的转换,即使它们毫无关联。该转换的操作结果是出现一份完全相同的二进制复制品,既不会有指向内容的检查,也不会有指针本身类型的检查。
reinterpret_cast也能够完成指针向整数的转换。不过该整数的类型取决于平台。唯一的保证是该整数的类型能够容纳下指针的值以及能够再次被转换成一个有效的指针。
基本上reinterpret_cast能做但static_cast不能做的转换大多都是一些基于重新解释二进制的底层操作,因此会导致代码限定于特定的平台进而导致差移植性。例如:
class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);
这个例子将A指向的二进制块当做B来重新解释。虽然可以编译,但是没有什么用处。A与B是完全没有关联的类而且看起来也不兼容。同时,对B进行解引用操作并不安全。
const_cast
const_cast可以用来设置或者移除指针所指向对象的const。例如,要把一个const指针传入一个接受非const指针的函数里:
// const_cast
#include <iostream>
using namespace std;
void print (char * str)
{
cout << str << '\n';
}
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
return 0;
}
输出:
sample text
注意,在移除const之后假如真的向目标进行写操作将导致UB。
typeid
typeid用来检查表达式的类型。
typeid (expression)
这个操作会返回一个定义在里面的const对象,这个对象可以同其他用typeid获取的const对象进行==或者!=操作,也可以用过.name()来获取一个表示类型名或者是类名的以空字符结尾的字符串。例如:
// typeid
#include <iostream>
#include <typeinfo>
using namespace std;
int main () {
int * a,b;
a=0; b=0;
if (typeid(a) != typeid(b))
{
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}
输出:
a and b are of different types:
a is: int *
b is: int
当作用在类上时,要用到RTTI来维护动态对象的类型信息。假如参数表达式的类型是多态类,结果将会是派生得最完整的类,例如:
// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;
class Base { virtual void f(){} };
class Derived : public Base {};
int main () {
try {
Base* a = new Base;
Base* b = new Derived;
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
cout << "*a is: " << typeid(*a).name() << '\n';
cout << "*b is: " << typeid(*b).name() << '\n';
} catch (exception& e) { cout << "Exception: " << e.what() << '\n'; }
return 0;
}
输出:
a is: class Base *
b is: class Base *
*a is: class Base
*b is: class Derived
注意,当作用在指针上时,仅会考虑其本身类型。当作用在类上时,才会产生出动态类型,即结果是派生得最完整的类。倘若传入的是解引用的空指针,则会抛出bad_typeid异常。
此外,name()的实现依赖于编译器及其使用的库,也许并不一定是单纯的类型名。