首页 > 编程语言 >C++类型转换

C++类型转换

时间:2023-10-20 17:13:01浏览次数:30  
标签:类型转换 const 常量 int C++ cast 指针

C++类型转换

1. const_cast

const_cast可以将const转换成非const,也可以将非const转换成const。需要注意的是 const_cast只能用于改变指针或者引用的底层const。

底层const和顶层const

首先要弄清楚 const修饰的到底是谁,用顶层表示指针本身是个常量(指针常量),底层表示指针所指向的对象是个常量(常量指针)。

int* const p1 = &i1;
int const* p2 = &i2;
const int i3 = 3;
int const i4 = 4;
const int* p3 = &i5;
const int* const p4 = &i3;
const int const* p5 = &i6;
const int& r = i7;

const默认是修饰它左边的符号的,如果左边没有,那么就修饰它右边的符号,比如例子中

  • 第一个 const左边是 int*,表示指针不能修改,所以是顶层const
  • 第二个修饰的 int,表示指针指向的值不能修改,所以是底层const
  • 第三个 const左边没有,修饰右边符号 int,这个非指针非引用,没有底层顶层的概念
  • 第四个修饰左边 int和第三个一样
  • 第五个左边没有,修饰右边 int,注意不是修饰 int*
  • 第六个第一个 const左边没有,修饰右边 int是底层;第二个 const修饰左边 int*是顶层
  • 第七个两个 const都是修饰 int,都是底层,这个指针是可以修改指向对象的
  • 第八个第一个 const修饰 int,是底层const;引用默认了顶层 const,因为引用一旦初始化就不能再改为其他对象的引用。

关于顶层const和底层const可以看看这篇文章C++干货系列——顶层const和底层const

const_cast只可以对指针和引用使用

const_cast针对指针和引用是语法上的要求,把一个常量变量变成非常量变量不应该是我们的目的。我们不应该为了修改一个const变量的值而去使用,例如我们有一个常量 const_a,但是我们想去修改它的值于是决定用一个常量指针指向它,然后用 const_cast让常量指针变成普通指针,修改这个变量

const int const_a = 66;
const int* const_p = &const_a;
int* p = const_cast<int*>(const_p);
*p = 88;
std::cout << *p << std::endl;//输出结果88
std::cout << const_a << std::endl;//输出结果66

理论上这个变量的内存的内容确实改变了,我们输出 *p的结果也证实了这一点。但是当我们输出 const_a的值会发现结果依然是66。难道是他们的内存地址不一样?

std::cout << &const_a << std::endl;//000000730AAFF5C4
std::cout << p << std::endl;	   //000000730AAFF5C4

事实上他们确实是同一块内存地址。真实的原因是编译器将const变量直接用66替换了。加const只是告诉编译器不能修改而不是真正地不可修改,如果程序员不注意而去修改了它会报错,现在我们利用const_cast去除了常量性,然后通过指针和引用对其进行了修改,所以通过指针打印或者引用传参的时候就能看出其内存确实变化了,但为了保护这个变量本来的const特性,所以每次我们使用const变量时,系统都将其替换成初始值66,确保了const变量还是“不可变”的。可以参考文章const_cast的小问题

这种情况是一种未定义行为,所谓未定义,是说这个语句在标准C++中没有明确的规定,由编译器来决定如何处理。很可能换一个编译器,处理结果就完全不同,我们要避免未定义行为的发生。

什么时候使用const_cast

const_cast的目的,在于某些变量原本不是const的,但由于某种特殊原因,无意间被变成了const的。例如使用了一个const引用指向了一个本来不是const的对象。结果写了一些代码之后发现它实际上需要被修改。这在平时的工作中不会遇到因为你可以直接把const引用修改成非const的,但C++中可能的情况太多,尤其考虑到很多复用的时候,有时还是会出现本不该是const的对象被const引用了这种情况。

int k=10;

const int& ref=k;

int& x=const_cast<int&>(ref);

x=11;

还要一些情况,例如我们需要在类里面重载一个const函数和非const函数。这么做的目的很明确,因为不能在一个常量对象中调用非常成员函数,这是因为函数需要传入隐式的 this指针,在默认情况下,this的类型是指向类的非常量版本的常量指针(意思是 this的值不能改变,永远指向那个对象,即“指针常量”,但是被 this指向的对象本身是可以改变的,因为是非常量版本,这里 this相当于是顶层const),而 this尽管是隐式的,它仍然需要遵循初始化规则,普通成员函数的隐式参数之一是一个底层非const指针,在默认情况下我们无法把一个底层const的 this指针转化为非const的 this指针,因此我们不能在常量对象上调用普通的成员函数。可以参考文章常量对象只能调用常成员函数 原因分析

所以为了让常量对象能调用同样的函数,我们需要重载一个常量版本。

class A
{
public:
	void fun()
	{
		std::cout << "非常量版本" << std::endl;
	}
};

int main()
{
	A a;
	const A const_a;

	a.fun();
	const_a.fun();//错误,对象含有与成员函数不兼容的类型限定符

}

这样就会报错:对象含有与成员函数不兼容的类型限定符

我们需要加一个常量成员函数

class A
{
public:
	void fun()
	{
		std::cout << "非常量版本" << std::endl;
	}

	void fun() const
	{
		std::cout << "非常量版本" << std::endl;
	}
};

int main()
{
	A a;
	const A const_a;

	a.fun();
	const_a.fun();

}

如果这两个版本做的事情完全一样,那么我们并不需要写两份一样的代码,可以利用 const_cast这样做

class A
{
public:
	void fun()
	{
		std::cout << "hello" << std::endl;
	}

	void fun() const
	{
		const_cast<A*>(this)->fun();
	}
};

int main()
{
	A a;
	const A const_a;

	a.fun();
	const_a.fun();

}

参考文章C++中const_cast的作用和缘由【C++】const_cast基本用法(详细讲解)

2. dynamic_cast

dynamic_cast:动态类型转换,用于将基类的指针引用安全地转换成派生类的指针或引用(也可以向上转换),若指针转换失败返回NULL,若引用返回失败抛出bad_cast异常。

什么时候使用dynamic_cast

如果我们有一个 Eneity类,PlayerEnemy继承了这个类。我们用基类指针e1和e2分别指向Player和Enemy。现在我有了基类指针,但我想把基类的指针分别转型到它们自己的类别指针上面。但可能我并不清楚e1和e2实际指向的到底是Player类还是Enemy类。如果我做了错误的转型,我把实际指向Enemy类的e2转型成了Player类,并且调用了函数 func,最后输出的是“I am Player”。这不是我们想要的,如果我们尝试做一些Enemy没有,而Player所独有的事情,比如访问特定的成员变量,只有player有,而enemy没有,因为潜在的类型实际上是Enemy,我们的程序很可能崩溃,或者做出些我们不想做的奇怪的事情。

class Entity
{

};

class Player : public Entity
{
public:
	void func()
	{
		std::cout << "I am Player";
	}
};

class Enemy : public Entity
{
public:
	void func()
	{
		std::cout << "I am Enemy";
	}
};


int main()
{
	Entity* e1 = new Player();
	Entity* e2 = new Enemy();

	Player* p = (Player*)e2;
	p->func(); //输出“I am Player”
}

为了避免一些安全问题,我们可以使用 dynamic_castdynamic_cast是在运行时进行安全性检查;使用 dynamic_cast父类一定要有虚函数,否则编译不通过;

我们给基类加一个虚析构函数就行了。最终 dynamic_cast返回一个空指针,转型失败

class Entity
{
public:
	virtual ~Entity() {};
};

class Player : public Entity
{
public:
	void func()
	{
		std::cout << "I am Player";
	}

};

class Enemy : public Entity
{
public:
	void func()
	{
		std::cout << "I am Enemy";
	}
};


int main()
{
	Entity* e1 = new Player();
	Entity* e2 = new Enemy();

	Player* p = dynamic_cast<Player*>(e2);
	if (p)
		p->func();
	else
		std::cout << "转型失败" << std::endl;
}

dynamic_cast的原理

dynamic_cast是怎么做到能知道Player是Player,Enemy是Enemy的呢?这是因为他用到了运行时类型信息RTTI(Runtime Type Information)。RTTI的实现原理是通过在虚表中放一个额外的指针,每个新类只产生一个typeinfo实例,额外指针指向typeinfo, typeid返回对它的一个引用 。类的内存布局可以看这篇文章C++类内存布局与虚继承

我们来看上面示例的布局

1697791106360

第一个指针就是指向当前类对应的 type_info 对象。当程序在运行阶段获取类型信息时,可以通过对象指针 p 找到虚函数表指针 vfptr,再通过 vfptr 找到 type_info 对象的指针,进而取得类型信息。只要对比type_info就能知道是否能进行转型。虽然这么做会消耗资源,但也是不得已而为之。

RTTI不仅仅有 dynamic_cast,要更详细了解可以看视频明解 C++ Primer:19.2运行时类型识别RTTI,讲的是C++Primer730页的内容。

3. static_cast

static_cast: 隐式类型转换,可以实现C++中内置基本数据类型之间的相互转换,enum、struct、 int、char、float等,能进行类层次间的向上类型转换和向下类型转换(向下不安全,因为没有进行动态类型检查)。它不能进行无关类型(如非基类和子类)指针之间的转换,也不能作用包含底层const的对象;

4. reinterpret_cast

reinterpret_cast:reinterpret是重新解释的意思,此标识符的意思即为将数据的二进制形式重新解释,但是不改变其值,有着和C风格的强制转换同样的能力。它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。不到万不得已绝对不用(比较不安全)

标签:类型转换,const,常量,int,C++,cast,指针
From: https://www.cnblogs.com/dogwealth/p/17777545.html

相关文章

  • error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visua
    error:MicrosoftVisualC++14.0isrequired.Getitwith"BuildToolsforVisualStudio":https://visualstudio.microsoft.com/downloads/ 一、背景说明在编译安装达梦数据库的Python驱动dmPython时,执行编译安装命令如下:pythonsetup.pyinstall 报错信息......
  • 安装编译工具 Microsoft Visual C++ Build Tools
    安装编译工具MicrosoftVisualC++BuildTools 一、下载VS2019下载地址如下:https://gitee.com/ivy258/vc2019-code-2022/tree/master/bag  或者从如下百度网盘中下载: 二、安装VS2019 ......
  • c++ 基本语法_1
    //1.主函数里面的各个含义意思#include<iostream>              #include表示预处理,引入一个iostream的库。<>里面表示的是一个头文件,每一个都有其功能intmain()                      #main表......
  • C++基本语法:
    C++基本语法:C++程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。对象:对象具有状态和行为。例如:一只猫的状态(颜色、名称、品种、行为、摇动、叫唤、吃),对象是类的实例。类:类可以定义为描述对象行为(或者状态)的模版(或者蓝图)。方法:从基本上说,一个方法表示一种行为。......
  • 3——of C++枚举
    C++枚举类型总结一下C++里面的枚举类就是为了方便字符表示数据的一种方式吧,java枚举类也差不多默认情况下,一个枚举变量没赋值的话则编译器赋为0,后面一次加1枚举类型默认为int类型,也可以赋值其他数字类型枚举的取值范围就不太纠结,感觉没啥用;枚举和switch配合,很棒enumexampl......
  • 目录:C++primer plus
    1:链接2:链接3:链接4:链接5:链接6:链接7:链接8:链接9:链接0:链接1:链接2:链接3:链接4:链接5:链接6:链接7:链接8:链接9:链接0:链接1:链接2:链接3:链接4:链接5:链接6:链接7:链接8:链接9:链接0:链接1:链接2:链接3:链接4:链接5:链接6:链接7:链接8:链接9:链接0:链接1:链接2:链接3:链接4......
  • C++ 中的 for 循环条件拆解
    引子CSP-J真题中,for循环后面括号内的几个表达式组形式特别……for循环的格式for(表达式一;表达式二;表达式三){循环体;}示例代码:循环输出1-100for(inti=1;i<=100;i++){cout<<i<<"";}for循环的执行顺序①表达式一(只执行一次)②表达式二③循环体④表......
  • C++零基础教程(函数重载)
    (文章目录)前言本篇文章来讲解函数重载,函数重载在C++中是非常重要的一个概念。一、概念讲解C++中的函数重载是指在同一个作用域中定义多个具有相同名称但参数列表不同的函数。函数重载允许使用相同的函数名来表示执行类似但具有不同参数类型或参数数量的操作。这样做可以提高......
  • LM拟合 C++
    未完成#include<iostream>#include<vector>#include<array>#include<ctime>#include<random>usingnamespacestd;voidCalc_J_fx(vector<array<double,2>>&data, double&k, vector<double......
  • C++
    1.什么是引用?简单来说就是给一个变量起别名。(本质是一个指针常量)当编译器遇到int&a=b;时,自动转成int*consta=&b,然后遇到a时编译成*a用法:数据类型&引用名=变量名inta=10;int&b=a;//b与a代表同一个数据注意①引用必须要初始化。int&b;//错误②引用一旦初始化后,就不能再更改......