隐式转换:
基本类型的隐式转换:
当函数参数类型非精确匹配,但是可以转换的时候发生
如:
void func1(double x){cout << x << endl;
}void func2(char c){cout << c << endl;
}int main(){func1(2);//int隐式转换为doublefunc2(3);//int隐式转换为charreturn 0;
}
自定义类型的隐式转换:
函数参数列表接受类类型变量,传入的单个参数不是类对象,但可以通过这个参数调用类对应的构造函数,那么编译器就会将其隐式转换为类对象
如:
class A{
private:int a;
public:A(int x):a{x}{}
};
void func(A a){cout << "func" << endl;
}int main(){func(1);//隐式将int转换为A对象A obj = 1;//同上return 0;
}
多参数构造函数:
调用多参数构造函数时,将所有参数用用{}括起来,表示是一个初始化列表,但这并不属于自定义类型隐式转换,而是隐式构造
class A{
private:int a;
public:A(int x):a{x}{}
};class B{
private:A obj;int b;
public:B(A a,int x):obj{a},b{x}{}
};void func(B b){cout << "func" << endl;
}
int main(){func({A{1}, 2});//使用{A,int}隐式构造B对象func({1,2});//仍能运行,隐式将1转换为A对象,然后用{A,int}隐式构造B对象return 0;
}
值得注意:
自定义的类型转换不能连续发生两次以上,否则会报错
例如:
class A{
private:string str;
public:A(const string& s):str{s}{}
};void func(A a){cout << "func"<<endl;
}int main(){func("Hello");//报错,因为需要将const char*隐式转换为string,string转换为const string,//再将string隐式转换为A//需要发生两次自定义类型隐式转换func(string{"Hello"});//编译通过,只需要string转换为const string,再将string隐式转换为A,//只发生一次自定义类型隐式转换return 0;
}
禁止任何隐式调用的关键字:explicit
在类构造函数前加上explicit关键字,则该构造函数无法再被隐式转换或隐式构造
从而防止某些错误的函数调用,比如想传入int却被隐式转换为了类对象
class A{
public:explicit A(int x){cout << x << endl;}explicit A(int x,int y){cout << x << y << endl;}
};A func(int a,int b){return {a, b};//禁止{int,int}到A的隐式构造
}A func(int x){return A{x};//合法return A{x, x};//合法
}int main(){A a = 1;//禁止int到A的隐式转换return 0;
}
好习惯:
将所有接受单个参数(包括多个参数,但只有一个参数没有缺省值的)的构造函数都限定为explicit
拷贝构造和移动构造不应该限定为explicit
显式转换:
初始化列表式转换:
使用type{var}的方式进行转换,如int{'c'}
这种转换只允许小范围变量向大范围变量转换,不允许反向,如
int x{3.5};//不被允许截断
long long y{3};
cout<<double{y}<<endl;//不允许被截断
C风格转换:
使用类似(int),或者int()的方式来转换类型,底层是组合调用了C++的各种cast函数来进行转换,因此不推荐使用这种转换
在这种转换中,尝试的cast调用顺序:
const_cast
static_cast
static_cast+const_cast
reinterpret_cast
reinterpret_cast+const_cast
const_cast转换:
实际极少使用
使用方式:
const_cast<to_type>(var)
作用:
给变量添加/去除const属性
如果该变量本身就是const变量,则无法真正地去除const属性
例如:
int main(){const int x = 2;const int *p = &x;int *p2 = const_cast<int *>(p);*p2 = 3;cout << x << endl;cout << *p2 << endl;return 0;
}
输出:
2
3
这里我们试图去除p所指向的的变量的const属性,并通过p2来修改其值,但在打印x时输出的值仍为2,这是因为编译器在编译的时候知道x为const变量,因此将cout<<x直接优化成了cout<<2,从而保证了其不会被修改。
而我们尝试使用p2去修改x的值,这属于未定义行为,需避免。因此,对于本身为const属性的变量,我们不应该使用const_cast来去除其const属性,否则可能会导致未定义行为
const_cast用于去除本来不是const变量的const属性
如:
void func(const int*p){int *p2 = const_cast<int *>(p);//用来去除被函数传参过程中隐式添加的const属性*p2 = 6;
}int main(){int x = 3;const int *p = const_cast<const int *>(&x);//人为为其添加const属性,使其无法被修改//在经过了一段时间的应用后,现在又想修改x的值了int *p2 = const_cast<int *>(p);*p2 = 5;cout << *p << endl;func(&x);cout << *p << endl;return 0;
}
static_cast转换:
使用方式:
static_cast<to_type>(var)
作用:
1.进行基本类型之间的转换(允许截断,告诉编译器是有意为之的截断)
相比之下,列表初始化转化不允许截断,因此更推荐使用static_cast进行转换,当然程序员要负起截断的责任,告诉大家这是有意为之的截断,而不是自己大意导致的截断
int x=static_cast<int>(3.14);
2.进行安全性检查,不允许无关类型指针互转
int main(){double x = 3.2;int *p2 = static_cast<int *>(&x);//不合法return 0;
}
3.可以将void*指针转为任意类型指针
无安全性检查,需要保证指向的类型正确(下例为不正确用法)
int main(){double x = 3.2;int *p2 = static_cast<int *>(static_cast<void*>(&x));//通过double*转void*,再从void*转到int*,跳过了安全性检查return 0;
}
4.将派生类指针/引用/对象转换为基类指针/引用/对象(向上转换)
class Base{
};
class Derived:public Base{
};
int main(){Base *p_b = static_cast<Base *>(new Derived);Derived d;Base &ref_b = static_cast<Base &>(d);Base obj_b = static_cast<Base>(d);//导致对象切片
}
5.将基类指针/引用转换为派生类指针/引用(向下转换,不推荐)
无安全性检查,需要保证基类指针指向要转换的派生类或者其派生类
若基类派生类拥有虚函数,应优先使用dynamic_cast(下文讲解)
Base* p_b2=new Derived;
Derived* p_d=static_cast<Derived*>(p_b2);//合法
6.显式调用自定义构造函数/类型转换函数进行转换
class MyInt {
public:explicit MyInt(int x) : value(x) {}operator int() const { return value; } // 自定义的转换运算符
private:int value;
};MyInt mi = static_cast<MyInt>(42); // 调用构造函数
int x = static_cast<int>(mi); // 调用 operator int()
dynamic_cast转换:
使用方式:
dynamic_cast<Derived_ptr/Derived_ref>(Base_ptr/Base_ref);
作用:
仅能对拥有虚函数的基类/派生类使用,且不能是protected/private继承关系
基类为虚基类时,在某些情况会导致dynamic_cast失败
对基类的指针/引用进行安全检查的向下转换(转换为派生类指针/引用)
也可进行向上转换(不推荐,应使用static_cast)
返回值:
若转换成功,即Base_ptr/Base_ref实际指向的对象是Derived对象或者是其派生类对象,则返回指向该对象的Derived_ptr/Derived_ref
若转换失败,即Base_ptr/Base_ref实际指向的是别的东西,则返回nullptr
值得注意的是:
dynamic_cast依赖于RTTI(run-time type information)来确定指针指向对象的实际类型从而进行转换,因此,没有虚函数,或者关闭了RTTI优化,都会导致对象的RTTI信息丢失,从而导致dynamic_cast失败
reinterpret_cast转换:
该转换无安全性检查,直接重新解释对象的二进制比特模式
高安全风险,极少使用,不推荐
使用方式:
reinterpret_cast<to_type>(var);
作用:
1.任意类型之间的指针互转
2.指针与整数互转
3.函数指针与数据指针互转
4.任意数据类型互转
路径不确定导致的转换二义性:
1.非虚继承的菱形继承的向上转换:
此时将指向D对象的D指针向上转换为A指针时,会出现二义性错误,因为编译器不知道指向哪个A对象,这种情况下,只能一层一层地向上转换,直到产生二义性的路径消失,方可一次性转换到A
2.虚继承导致的菱形继承的向下转换:
此时将指向E对象的A指针向下转换为B指针时,会出现二义性错误,因为编译器不知道指向哪个B对象,而且虚继承导致指针偏移无法计算(虚继承机制以后的文章再讲解),这种情况下,只能先转成E指针,再向上转换,直到二义性路径消失
3.同层交叉cast转换:
可以直接将指向E对象的D指针用dynamic_cast转换为B指针
只需要满足B和D没有共同的虚基类即可(有的话,会导致指针偏移无法计算,从而dynamic_cast失败),有的话,使用第二点的方法