【C++】C++提供类型转换的机制

目录

前言:

一,static_cast

二,reinterpret_cast

三,const_cast

四,dynamic_cast


前言:

传统的不同类型转换有隐式类型转换 (类型不匹配时编译器自动进行的转换,如:int a;float b; a = b;)和强制类型转换(强制类型转换也叫显示类型转换,如:a = (int)b)。

**隐式类型转换:**基本数据类型之间的转换。内置类型之间的隐式转换系统内部自动完成,但必须是两者相近的类型,如int,char,double等。自定义类型之间的隐式转换借助的是构造函数来完成。内置类型隐式转换成自定义类型也是借助的构造函数完成。自定义类型隐式转成内置类型借助的是一个重载类型的函数,即:operator 类型。

class B
{
public:
int _b = 10;
};
class A
{
public:
A(int a = 0) :_a(a) { }
A(const B& b) :_a(b._b) { }
//重载一个隐式转换成int型的函数
operator int()
{
return _a + 1;
}
int _a;
};

int main()
{
int x = 5;
double f = 3.14;
A a;
B b;
a = x; //内置类型隐式转换成自定义类型
cout << "A: " << a._a << " " << "x: " << x << endl;
a = b; //自定义类型隐式转换成自定义类型
cout << "A: " << a._a << " " << "B: " << b._b << endl;
x = a; //自定义类型隐式转换成内置类型
cout << "x: " << x << " " << "A: " << a._a << endl;
x = f; //内置类型之间的隐式类型转换,系统内部自动实现
cout << "x: " << x << " " << "f: " << f << endl;
return 0;
}

**强制类型转换:**指名要转换的类型后系统内部强制进行机制转换,比如指针、整数和函数指针之间,隐式转换很多情况下都无法完成。这种转化机制比较强大,即便相似度较小也能完成转换, 但若是几乎没有什么相似度,那么也无法完成。一般用于特殊说明类型或针对于指针的情况

int main()
{
int a = 1;
double f = 3.14;
char c = 'a';

const char* str = "aaa";

//int* p = &f; 错误,不能隐式转换
int* p = (int*)&f;
//a = &c; 错误,不能隐式转换
a = (int)&c;
//p = a; 错误,不能隐式转换
p = (int*)a;

//a = (float)str; 错误,const char*与float基本没有相似度,无法强制转换
return 0;
}

隐式类型的转换主要是基于相似度较大的数据之间,通常不适用于指针。强制类型转换范围较广,可以实现有关指针转换的情况。但无论是哪种转换,这里面都存在不少的缺点。隐式类型转化有些情况下可能会出现数据精度丢失,可维护性和可读性差,而显式类型转换将所有情况混合在一起,代码不够清晰,难以发觉。因此C++提出了四种类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast。它们分别拥有自己的风格。


一,static_cast

static_cast类型转换运算符基本等价于以前的隐式类型转换,即主要用于基本数据类型之间的隐式类型转换,不能用于指针、整数和函数指针之间的强制转换。为了预防隐式类型转化可能出现数据丢失的情况,C++提供了关键字static_cast来明确这里正在进行隐式转换,使用于需要明确隐式转换的地方,即将隐式转换显式化表示出来。它是一个更安全的类型转换运算符。

int main()
{
double d = 12.34;
int a1 = static_cast<int>(d); //将d隐式转换成int型,然后赋值a1
cout << a1 << endl;
//上面a1的static_cast<int>(d)隐式转换与下面a2对应
int a2 = d;
cout << a2 << endl;

int* p = &a1;
//int a3 = static_cast<int>(p); 错误,相当于a3 = p;

//int* p1 = static_cast<int*>(a1); 错误,相当于int* p1 = a1;
return 0;
}


二,reinterpret_cast

reinterpret_cast操作符类似于以前强制类型转换,但又不能单纯理解为以前的强制类型转换,适用于static_cast隐式转换的它就不适用,因为它提供了更多的类型安全性。总的来说,reinterpret_cast操作符适用于隐式转化以外的强制转化,即reinterpret_cast用于指针、整数和函数指针之间的转换,而不是基本数据类型之间的隐式转换。

int main()
{
double f = 3.14;
int i = 1;
int* pi = &i;
double* pf = &f;
//static_cast与reinterpret_cast的正确使用
int a = static_cast<int>(f);
float f1 = static_cast<float>(i);
int a1 = reinterpret_cast<int>(pi); //对应int a1 = (int)pi;
int a2 = reinterpret_cast<int>(pf); //对应int a2 = (int)pf;

//下面都是错误的使用
int a = reinterpret_cast<int>(f); //错误,两数据间可以进行隐式转换了, 但 a = (int)f 正确
float f1 = reinterpret_cast<float>(i); //错误,同理,传统的 f1 = (float)i 正确
int a1 = static_cast<int>(pi); //错误,pi不能隐式转换成int型
int a2 = static_cast<int>(pf); //错误,同理
return 0;
}

reinterpret_cast也不是特别安全,因为它对应的是强制转换,这就导致它很容易被误用而引发错误或其它行为,比如子类指针强转为父类指针,这就可能导致父类指针指向子类对象出现越界访问的情况。因此,我们应特别小心使用reinterpret_cast,大多数情况下应该优先使用其它类型转换操作符,它们提供了更多的安全性。

需要适用隐式类型转换的时候要用static_cast,隐式类型不能转换的且需要强制类型转换的要用reinterpret_cast,这里的强制转换不完全等同于传统的强制类型转换,可以理解为被限制的传统强制类型转换。


三,const_cast

我们先来观察下面代码的问题。

int main()
{
const int a1 = 2;
int* p1 = (int*)&a1;
*p1 = 3;
cout << a1 << endl;
cout << *p1 << endl;
return 0;
}

输出窗口:

监视窗口:

这里发现输出a1与p1所对应的数据不一样,但通过调试,监视窗口数据都正常。出现这种问题源于编译器对const数据的优化。由于const型数据不会改变,这里会把a1存入寄存器中,输出打印时把数据从寄存器中取出,但我们通过指针p1将内存中a1进行改变,监视窗口是从内存中读取的数据,这也就是输出打印时a1数据不变,监视窗口a1正常改变的原因。

C++提供了关键字volatile 解决这块问题。volatile关键字告诉编译器某个变量的值可能在程序的执行过程中被外部因素修改。因此,volatile 处理变量后,数据不会往寄存器中存取,编译器总是从内存中读取实际值,而不是假设它可以在寄存器中缓存并重复使用,如:volatile const int a1 = 2,此时跟预料一样输出。

const_cast转换也是类似于强制转换,与static_cast不同的是它常用来删除变量的const属性,不适用其它相关的强制转换。专门把const属性单独拿出来就是专门提醒去掉const是有一些内存可见优化的风险,要注意是否加了volatile,即上面那种情况。

int main()
{
volatile const int a1 = 2;
int* p1 = const_cast<int*>(&a1); //与int* p1 = (int*)&a1类似
*p1 = 3;

//若没有volatile修饰,这里输出的a1是2
cout << a1 << endl;
cout << *p1 << endl;

//const_cast与reinterpret_cast对比
int a2 = 1;
//int p2 = const_cast<int>(p1); 错误,const_cast中的类型必须是指针、引用,
int p2 = reinterpret_cast<int>(p1); //属于reinterpret_cast的强制类型转换,正确
//int* p3 = const_cast<int*>(a2); 错误, const_cast只能去除const常性, 不能更改其类型
int* p3 = reinterpret_cast<int*>(a2); //正确,与上同理
return 0;
}


四,dynamic_cast

dynamic_cast主要用于继承体系,解决将一个父类对象的指针或引用转换为子类对象的指针或引用(向下转型)出现的问题。

**向上转型:**子类对象指针或引用转换成父类指针或引用(不需要转换,赋值兼容规则),即子类指针或引用赋值给符父类指针或引用。

**向下转型:**父类对象指针或引用转换成子类指针或引用(用dynamic_cast转型是安全的),即父类指针或引用赋值给子类指针或引用。

继承体系中,父类对象无论如何都不可能赋值子类对象,但父类指针或引用可以指向子类对象,这时,倘若单纯访问父类数据是没有任何问题的,但若是访问子类对象中的数据,由于父类指针或引用指向的只是包含父类这块空间的数据,没有包含子类的数据,所以这里会出现越界访问,即向下转型出现的问题。向上转型中,子类指针或引用指向更大的空间,赋值给父类指针或引用后,父类指针或引用虽然只能访问父类数据,但是这里没有出现越界访问,不存在安全隐患。总的来说,向上转型是从大空间转到小空间,可以正常进行;向下转型是从小空间转到大空间,会出现越界访问。

当发生向上转型或向下转型时,dynamic_cast操作符会进行检查,若是向下转型,他将返回空指针,转型失败;若是向上转型或其它正常情况,将正常返回,转型成功。

class A
{
public:
virtual void f() {}

int _a = 0;
};

class B : public A
{
public:
int _b = 1;
};

void fun(A* pa)
{

//若pa指向子类对象,转回子类,正常转换;若pa指向父类对象,转回子类,转换失败
B* pb = dynamic_cast<B*>(pa);
if (pb)
{
cout << pb << endl;
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}

int main()
{
A a;
B b;
A* pa = &a;
B* pb = &b;
//向上转型(子类指针赋值给父类指针)
A* ppa = dynamic_cast<A*>(pb);
if (ppa)
{
cout << "转换成功" << endl;
}
else
{
cout << "转换失败" << endl;
}

//向下转型(父类指针赋值给子类指针),ppb访问_b时出现越界访问,pa指向的空间中没有_b那块空间
B* ppb = dynamic_cast<B*>(pa);
if (ppb)
{
cout << "转换成功" << endl;
}
else
{
cout << "转换失败" << endl;
}
fun(&a);
fun(&b);
return 0;
}

**注意:**dynamic_cast只能用于父类含有虚函数的类,没有虚函数会报错,因为它是借助虚表来识别父子类。


总结:

强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。

最后要说明,static_cast、reinterpret_cast、const_cast、dynamic_cast都是编程中的规范,它们各自对类型转换的限制有助于减少因误用类型转换而导致的错误,虽然它们也存在一定的安全隐患,但用此操作符特意明确这里存在类型转换,使我们能够意识到这里在进行对应功能的类型转换,方便对其操作。

编程规范没有强制规定,但建议平常运用时养成编程规范的习惯。求职的面试过程中,常问的这块内容是:1,C++中的4种类型转化分别是哪几种? 2,说说4种类型转化的应用场景。

相关推荐
一点媛艺2 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风2 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生3 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功3 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
pianmian13 小时前
python数据结构基础(7)
数据结构·算法
闲晨3 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程3 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
UestcXiye4 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go