C/C++类型转换

文章目录

C/C++类型转换

1. C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化。C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败 。具有强关联的数据之间才可以进行转换。比如整形家族(char,double等),都可以转为int。
  2. 显式类型转化:需要用户自己处理 ,但是也需要具有关联,比如不同类型的指针之间转换。
cpp 复制代码
void Test ()  
{  
    int i = 1;  
    // 隐式类型转换  
    double d = i;  
    printf("%d, %.2f\n", i, d);  

    int* p = &i;  
    // 显示的强制类型转换  
    int address = (int) p;  

    printf("%x, %d\n", p, address);  
}

缺陷:

转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换

在C++中也兼容C语言的类型转换,同时还加入了内置类型转为自定义类型,以及自定义类型转为内置类型。

2. C++中的类型间的隐式转换

内置转内置

C++中内置类型之间的隐式转换与C语言中完全相同,强相关的类型可以直接转化

内置转自定义

在C++中,我们经常使用一个内置类型去给自定义类型复制,比如使用字符串复制给string,或者

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class A {
public:
	A(int val = 0):_val(val){}

private:
	int _val;
};

class B {
public:
	B(int val = 0, string data = string()) :_val(val), _data(data) {}

private:
	int _val;
	string _data;
};
int main()
{
	string obj1;
	obj1 = "this is const char";
	A obj2;

	obj2 = 3;
	B obj3;
	obj3 = { 2, "the val is 2" };

	return 0;
}

单参数的构造函数支持隐式类型转换,在这种情况下,我们可以使用参数直接去给自定义类型对象赋值,编译器可以帮助我们将参数自己的类型转换为自定义类型,在进行赋值。其本质是构造临时对象在赋值。

以obj1为例,当使用一个字符串 "this is const char"给string 对象赋值的时候,会先使用该字符串构建出一个临时的对象,在使用该临时的对象去给obj1进行赋值拷贝,达到"转化类型"的目的。

等价于

obj1 = string("this is const char");

多参数的构造函数可以支持列表初始化,只需将多个参数用花括号{}扩起来,就可以进行隐式类型转换。本质也是使用花括号列表里的参数构造出临时对象在复制。

obj3的赋值等价于

obj3 = B( { 2, "the val is 2" } );

自定义转内置

要想将自定义类型转为内置类型,达到以下目的:

A obj1(5);

int a = obj1;

B obj2( { 2, "the val is 2" } );

int b = obj2;

可以理解为我们想要做到以下效果:

int a = (int)obj1;

int b = (int)obj2;

那么我们就可以在类内写这样的函数来支持

int operator( )

{ //reutrn ......; }

但是重载()来进行类型转换则无法与仿函数进行区分,函数调用的参数也是用()符号来使用,则C++为了实现类型转换,为其做了特殊处理,可以在operator后面带上类型,表示返回值类型,在类型后面加上括号,表示这是类型转化,则可以达到自定义转为内置类型的目的

operator int( )

{ //reutrn ......; }

代码示例:

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class A {
public:
	A(int val = 0):_val(val){}
	operator int()
	{
		return _val;
	}
private:
	int _val;
};

class B {
public:
	B(int val1 = 0,int val2 = 0, string data = string()) :_val1(val1), _val2(val2), _data(data) {}

	operator int()
	{
		//return _val1;返回val1
        //自定义返回值,只要是int即可
        //也可以根据实际需要返回想要的内容
        // 返回两数之和
        return _val1 + _val2;
	}
private:
	int _val1;
	int _val2; //加第二个成员为了演示类型转换的返回值是自定义的

	string _data;
};
int main()
{
	A obj1(3);
	int a = obj1;

	B obj2({ 2, 3, "the val is 2" });
	int b = obj2;
	
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	
	return 0;
}

运行结果:

自定义转自定义

当一个类的构造函数中重载了另一个类为参数的函数时,此时就可以发生类型转换

cpp 复制代码
class C {
public:
	C(const string& s1,const string& s2):_s1(s1),_s2(s2){}

	string gets1() const
	{
		return _s1;
	}

	string gets2() const
	{
		return _s2;
	}

private:
	string _s1;
	string _s2;

};


class D {
public:
	D(const string& str = string()):_str(str){}
    
    //使用C类对象为参数重载构造函数
	D(const C& obj):_str(obj.gets1() + obj.gets2()){} 

	string getstr() const
	{
		return _str;
	}
private:
	string _str;

};


int main()
{
	C obj1({ "one", "two" });

	D obj2;
	obj2 = obj1; //调用构造函数,传参obj1,生成临时对象后再赋值

	cout << obj2.getstr() << endl;

	return 0;
}

运行结果:

3. C++4种类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

static_cast, reinterpret_cast, const_cast, dynamic_cast

static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换

cpp 复制代码
int main()
{
    double d = 12.34;
    int a = static_cast<int>(d);
    cout<<a<<endl;
    return 0;
}
reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型

cpp 复制代码
int main()
{
    double d = 12.34;
    int a = static_cast<int>(d);
    cout << a << endl;

    // 这里使用static_cast会报错,应该使用reinterpret_cast
    //int *p = static_cast<int*>(a);
    int *p = reinterpret_cast<int*>(a);

    return 0;
}
const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值

cpp 复制代码
void Test()
{
    const int a = 2;
    int* p = const_cast<int*>(&a);
    *p = 3;
    cout << a << endl;

    int b = 3;
    cout << b << endl;
}
int main() {

    Test();

    return 0;
}

const修饰的变量叫做常变量,在栈上存储,按道理不应该被修改,但是我们可以通过指针强转去修改,如上述代码。

但是修改之后为什么值没有变?

我们先看一下内存是如何变化的:

执行前:

执行后:

可以看到,在内存里已经成功将a的值修改,即将常变量修改,那么为什么打印出来的a还是2呢?

通过反汇编,我们可以很清楚的看到,在定义的时候,a与b相同,都是在栈上定义,但是在输出的时候,a输出的时候直接将2写入寄存器edx ,在进行输出edx内容,就类似于宏定义一样,直接将a替换为2,而b的输出则是去内存里将b的数据读取到edx,在输出edx内容。

常变量在vs下,直接替换变量的值,就类似于宏定义一样,因此使用指针修改了,但是再去访问常变量a还是没有修改的内容,这也是为什么我们打印a看到的还是2的原因。

使用volatile关键字,表示变量不稳定,强制去内存里拿数据,如果使用该关键字修饰常变量a,那么打印出来将是修改后的内存数据3.

dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)

向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)

向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)

存在上图的类模型,那么有以下类型转换:

  1. B* -> A* 赋值兼容,如下图A* ptr1 = ptr0,

  2. A* -> B* 存在两种情况:

    • B* -> A* -> B*, 由赋值兼容规则转换得到的的A *指针,其本身指向的就是B类型对象,因此在转换回B *也没有任何问题,

      对应下图B * ptr2 = (B*) ptr1.

    • A* -> B*, 如下图的 B * ptr4 = (B*)ptr3,强行转化会导致发生越界访问问题,这是不被允许的!

因此C++加入了dynamic_cast,用于多态体系中的类型转化,当有A* -> B*的时候,即父类指针转为基类指针的时候,其内部会做判断。

  • 可以转换则进行转换,即其指针本身指向的对象是派生类对象,由于赋值兼容变为基类对象的指针,会进行转换
  • 不可以转换则转换失败返回nullptr,对于引用抛出std::bad_cast异常,即其本身指向的对象是基类对象,转换会越界,因此转换失败。

注意:

  1. dynamic_cast只能用于父类含有虚函数的类
  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr,对于引用抛出std::bad_cast异常

代码示例:

cpp 复制代码
class A
{
public:
    virtual void f(){}
};

class B : public A
{};

void fun(A* pa)
{
    //使用static_cast进行转换,不做检查,与强转相同,存在风险
    B* pbl = static_cast<B*>(pa);

    // dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回nullptr
    B* pb2 = dynamic_cast<B*>(pa);

    cout << "pbl:" << pbl << endl;
    cout << "pb2:" << pb2 << endl;
}

int main() {
    A a;
    B b;
    fun(&a);
    fun(&b);
    return 0;
}

注意

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

dynamic_cast底层依赖了多态中的虚表,C++类的多态特性是运行时多态,因此该类型转化也是运行时转化,编译器在运行时进行类型识别,也被称作RTTI(Run-time Type identification),除了dynamic_cast之外,C++中还有typeid运算符和decltype来支持RTTI。

4. C++类型转换对比C语言类型转换

​ C语言中的类型转换只有隐式类型转换和强制类型转换,虽然简单,但是不直观也不安全,而C++引入了4种类型转换,在一些方面解决了C语言类型转换的不足:

  1. 隐式类型转化有些情况下可能会出现问题:比如数据精度丢失
  2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。 他们之间也有对应关系,如下所示:

转换种类 C语言 C++ 主要优点 主要缺点
隐式/显式安全转换 隐式类型转换 static_cast 明确安全转换意图,编译时检查 不能用于无关类型指针转换
重新解释转换 强制类型转换 reinterpret_cast 明确标识危险操作,代码中易识别 与C语言强制转换一样危险
常量性转换 强制类型转换 const_cast 职责单一,仅用于常量性消除 滥用会导致未定义行为
相关推荐
馨谙2 小时前
RHEL 存储堆栈完全解析:从硬件到应用的存储管理指南
服务器·开发语言·php
爪哇部落算法小助手2 小时前
爪哇周赛 Round 1
c语言·c++·算法
二川bro2 小时前
第38节:WebGL 2.0与Three.js新特性
开发语言·javascript·webgl
MediaTea3 小时前
Python 第三方库:Markdown(将文本渲染为 HTML)
开发语言·前端·python·html
Halo_tjn3 小时前
Java 基于分支和循环结构的专项实验
java·开发语言·计算机
洛_尘3 小时前
数据结构--9:反射、枚举以及lambda表达式(了解即可)
java·开发语言·数据结构
CoderBob3 小时前
【EmbeddedGUI】简易Page开发模式
c语言·图像处理·单片机
青衫码上行3 小时前
【Java Web学习 | 第12篇】JavaScript(6)DOM
java·开发语言·前端·javascript·学习
弘毅 失败的 mian3 小时前
C++、Java 还是测试开发?
java·c++·经验分享·笔记·测试开发·技术方向·就业