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

C++类型转换详解

时间:2024-11-17 17:15:52浏览次数:3  
标签:类型转换 int C++ cast 详解 类型 指针 size

        在C语言和C++有很多的类型转换,内置类型(int,char,bool等等编译器自带的)之间的转换像隐式类型转换,强制类型转换,此外还有自定义类型(自己定义的类)和内置类型的转换,自定义类型间的转换,父类和基类之间的类型转换。总之C语言和C++的类型转换复杂多样,如果你对上面的内容有所疑惑,今天我们就来一起学学看吧!( つ•̀ω•́)つ

一.内置类型之间的类型转换

1.隐式类型转换

        在c++中内置类型关系比较强的可以直接进行隐式类型转换,这里博主整理了可以直接隐式转换的类型:

  • 整形(int,long,long,size_t,char等)之间 
  • 整形和浮点数 
  • bool和整形 
  • bool和指针 

看例子:

#include<iostream>
using namespace std;
void test()
{
	//1.整型之间相互转换
	int a = 100;
	char b = a;
	size_t c = b;
	cout << a << " " << b << " " << c << endl;

	//2.整型和浮点数转换
	double a1 = 100.101;
	int b1 = a1;
	char c1 = a1;
	cout << a1 << " " << b1 << " " << c1 << endl;

	//3.bool和整型
	bool a2 = true;
	int b2 = a2;
	cout << a2 << " " << b2 << " " << endl;

	//4.bool和指针
	int* a3 = &b2;
	bool b3 = a3;
	cout << *a3 << " " << b3 << " " << endl;
}
int main()
{
	test();
}

运行结果如下:

 

 可以看出上面的类型是能自动隐式转换的,在所有类型转换的过程其实产生了临时变量如:

void test2()
{
	int a = 1;
	double b = a;
}

        上面的代码会为a生成类型为double类型的临时变量,假定为c,再执行b=c的过程,以为double b=a,类型不同,cpu是无法直接一步运算的,需要产生临时变量过渡。大多数类型转换的过程中都会产生临时变量。如果对临时变量感兴趣的话可以看博主的这篇博客讲的也很详细。临时变量和隐式转换-CSDN博客

2.强制类型转换

在c++中内置类型中关系比较弱,但还是具有一定关系的可以进行强制类型转换。有以下几种情况:

  • 指针和整形 (指针也就是描述地址的整型)
  • 不同类型的指针之间 

 如:

void test5()
{
	//指针和整型之间
	int c = 1;
	int d = (int)&c;
	cout << c << " " << d << endl;

	//指针和指针之间
	int a = 1024+100;
	char* b = (char*) &a;
	int* f = (int*)&a;
	cout << a << " " <<*b<<""<< *f << endl;



}
int main()
{
	test5();
}

 运行结果:

这里我们顺便讲讲指针,指针的类型决定了指针对于指向的内存取多少长度,如char*只能取1个字节(0-127),int* 则可以取四个字节。示意图如下:

二.内置类型和自定义类型的转换

1.内置类型转成自定义类型

        自定义类型的单参数构造函数,支持内置类型隐式转换成自定义类型。如下:

void test6()
{
	class date
	{
	private:
		int _year;
		int _month;
		int _day;
	public:
		date(int year,int month=1,int day=1)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	};

	date d1 = 1;
	string s = "111";//同理,也是通过单参数的构造函数来支持
}
int main()
{
	test6();
}

         date d1 = 1;string s = "111";这俩行代码都是通过单参数的构造函数支持类型转换,在这个过程本来也会产生临时变量,如date d1=1,是先调用单参数的构造函数用参数1生成临时变量,再用临时变量进行拷贝构造初始化d1,不过编译器会将构造和拷贝构造优化成一次构造。具体也可以看之前提到的博客哟。

2.自定义类型转换成内置类型

        自定义类型转换成内置类型,我们需要在自定义类型中通过运算符重载来完成,我们通过下面的代码来看看是这么操作的。

void test7()
{
	class A
	{
	public:
		operator int()
		{
			return _a1 + _a2;
		}
	private:
		int _a1 = 1;
		int _a2 = 2;
	};
	A aa;
	int ii1 = aa;
	int ii2 = (int)aa;
	cout << ii1 << " " << ii2 << endl;
}
int main()
{
	test7();
}

        我们要注意这种重载运算符比较特殊,没有返回值。并且隐式类型转换和强制类型转换都行。

        虽然这种转换比较少见,但是c++库中也是有使用到的,如ifstream成员函数中的operator bool(),可以将ifstream类型转成bool类型,定义如下。


        当文件内部出错,错误标志被设置时,operator bool会返回false,用例如下。

#include <iostream>     // std::cerr
#include <fstream>      // std::ifstream

int main () {
  std::ifstream is;
  is.open ("test.txt");
  if (is) {
    // read file
  }
  else {
    std::cerr << "Error opening 'test.txt'\n";
  }
  return 0;
}

三.自定义类型之间的转换

        通过构造函数就能实现自定义类型间的转换。

void test8()
{
	class A
	{
	public:
		A(int a)
		{
			_a = a;
		}
	
		int _a;
	};
	class B
	{
	public:
		B(A a)
		{
			_b = a._a;
		}
	private:
		int _b;
	};

	A a1(100);
	B b1(a1);
}
int main()
{
	test8();
}

 四.基类和父类之间的类型转换

1.派生类到基类的转换(向上转型)

class Base {};
class Derived : public Base {};
 
Derived d;
Base* bPtr = &d;  // 向上转型,不会产生临时变量
Base& bRef = d;   // 向上转型,不会产生临时变量

        关于向上转型为什么没有临时变量我们可以这么理解:在C++的继承体系中,派生类对象在内存中的布局是以基类部分为起始的。这意味着派生类对象的前部分(即基类部分)在内存中的布局与基类对象完全相同。因此,当我们将一个派生类对象的地址赋给一个基类指针时,实际上只是改变了指针的解释方式,而没有改变指针所指向的内存地址。这种转换是“安全”的,因为它没有改变对象的实际内容或内存布局,因此不需要额外的临时变量来存储转换结果。 

2.对象切片

void test10()
{
	class Base {
	public:
		int baseValue;
	};

	class Derived : public Base {
	public:
		int derivedValue;
	};

	Derived d;
	d.baseValue = 1;
	d.derivedValue = 2;

	Base b = d;  // 对象切片,会产生一个临时的 Base 对象
}

 五.关于c语言类型转换的缺陷介绍

        c语言中对于类型转换并不严格,很多时候在我们没有注意到的情况下就会发生类型转换,这可能会导致代码中出现一些错误,下面举出常见的一例:

//功能:在数组a的pos位置插入x值,其他值向后移动,size 表示数组大小
void Insert(int a[], size_t pos, int x,int size)
{
	int end = size;
	// 比较的时候end会隐式类型转换成size_t,再比较
	for (int i = end - 2; i >= pos; i--)
	{
		a[i + 1] = a[i];

	}
	a[pos] = x;
	return;

	
}

void test11()
{
	int arr[10] = { 1,2,3,4,5 };
	Insert(arr, 2, 100,10);
	
	for (auto e : arr)
	{
		cout << e << " ";
	}
}

         insert功能为在数组a的pos位置插入x值,其他值向后移动,size 表示数组大小,insert(arr,2,100,10)过程如图:

运行结果如下,结果正确

        但是如果我们运行Insert(arr,0,100,10)代码,结果会出错,报错如下:

        错误内容是数组越界,这又是这么一回事?

        其实这时因为运行 i >= pos;代码默默的发生了隐式类型转换,i是int类型,pos是size_t类,俩者进行运算时,会产生一个临时变量,将i的值取出由int类型提升为size_t类型(i本身的值没有改变),这时即使i为负数,由于临时变量是size_t类型(size_t类型会把负数看为正数),临时变量依旧大于等于0(这里和原码和补码有关系),循环不会结束,数组便会越界访问。

        此时我们只需要将i>=pos改成i>=(int)pos,让俩者成为整型即可。

//功能:在数组a的pos位置插入x值,其他值向后移动,size 表示数组大小
void Insert(int a[], size_t pos, int x,int size)
{
	int end = size;
	// 比较的时候end会隐式类型转换成size_t,再比较
	//for(int i=end-1;i>pos;i--)也能避免越界范围
	for (int i = end - 2; i >=(int) pos; i--)
	{
		a[i + 1] = a[i];
		/*cout << i;*/
		//cout << i << " ";

	}
	a[pos] = x;
	return;

	
}

void test11()
{
	int arr[10] = { 1,2,3,4,5 };
	//Insert(arr, 2, 100,10);
	Insert(arr, 0, 100, 10);
	
	for (auto e : arr)
	{
		cout << e << " ";	
    }

}

运行结果:

 

六.C++强制类型转换

        标准C++为了加强类型转换的可视性,防止类似上面悄悄的类型转换,引入了四种命名的强制类型转换操作符: static_cast、reinterpret_cast、const_cast、dynamic_cast,我们来逐个讲解。

1.static_cast

        static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,就是用于隐式类型转换。

void test12()
{
	//1.static_cast<int>
	double d = 12.34;
	int a = static_cast<int>(d);
	cout << a << endl;
}

2.reinterpret_cast

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


	//2.reinterpret_cast
	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);

        强制类型类型转换只能用reinterpret_cast,隐式类型转换只能用static_cast。

3.const_cast

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

void test12()
{
	
	const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a <<" "<<*p<< endl;
		
		
}
int main()
{
	test12();
}

        我们来猜猜最后结果是什么?

        答案是2 3,但是我们看监视窗口(监视窗口访问是内存实际地址的值):a和b的又是 3,3。

        这里是因为a被const 修饰,编译器认为a的值一般不会修改,直接放入寄存器,因此当我们通过p修改a地址里实际的值,再访问a其实是访问之前寄存器的值,如果想让每次对a的访问都通过访问地址,可以给a加上volatile修饰。这样结果就会为:3 3

void test12()
{
	volatile const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a << " " << *p << " "  << endl;	
		
}
int main()
{
	test12();
}

4.dynamic_cast

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

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

        我们还要注意以下几点 

  • 1. dynamic_cast只能用于父类含有虚函数的类(dynamic_cast依赖于 C++ 的运行时类型信息(RTTI)机制来确定对象的实际类型,RTTI 机制是通过在类中引入虚函数表(vtable)来实现的,只有包含虚函数的类才会有虚函数表,因此只有这样的类才能被dynamic_cast使用)
  • 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。
void fun(A* pa)
{
	// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
	
	B* pb1 = dynamic_cast<B*>(pa);
	cout << "pb1:" << pb1 << endl;
	
}
void test13()
{
	A a;
	B b;
	fun(&a);
	fun(&b);
	return;
	
}
int main()
{
	test13();
}

运行结果:

        总之就是如果基类指针指向基类,尝试用dynamic_cast转换为派生类指针,转换不成功返回0。如果基类指针指向派生类, 尝试用dynamic_cast转换为派生类指针,转换成功。

         好了,内容就讲解到这,感觉有帮助的话就点点赞,这真的很重要。

标签:类型转换,int,C++,cast,详解,类型,指针,size
From: https://blog.csdn.net/2301_76293625/article/details/143734169

相关文章

  • C++ lambda 表达式与「函数对象」(functor)
    C++lambda表达式与「函数对象」(functor)Created:2024-06-27T16:29+08:00Published:2024-11-17T17:01+08:00Categories:C-CPPfunctor(FunctionObject)首先要介绍的是functor——一个重载了operator()的类,该类的实例可以使用operator()。WhatareC++functorsand......
  • C++ 模板
    C++模板Created:2024-03-24T20:24+08:00Published:2024-11-17T16:37+08:00Categories:CPP目录偏特化类模板函数模板形参包Question我记不住模板的语法,尤其是偏特化的语法,怎么办?面试问题利用类模板和函数模板实现编译器计算斐波那契数列模板的声明和定义为什么不能分开写......
  • c++入门基础后续
    1.缺省参数缺省参数是指在声明或定义的同时给上指定的一个缺省值,在调用函数是如果没有传指定的实参那么就会用这个缺省值。缺省参数分为全缺省和半缺省。全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃......
  • C++仍要用的scanf函数介绍
    很多C++初学者刚学便是使用cin和cout,这无可厚非;但C语言中的scanf函数在一些特定情况下仍必不可少,故写给C++初学=者scanf函数的部分介绍以及具体使用情景。scanf输入数字的用法头文件:#include<cstdio> 调用格式:scanf(格式控制字符串,变量地址列表);注:格式说明和各输入......
  • CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃
    CSP/信奥赛C++语法基础刷题训练(11):洛谷P5743:猴子吃桃题目描述一只小猴买了若干个桃子。第一天他刚好吃了这些桃子的一半,又贪嘴多吃了一个;接下来的每一天它都会吃剩余的桃子的一半外加一个。第n......
  • CSP/信奥赛C++语法基础刷题训练(12):洛谷P1047:[NOIP2005 普及组] 校门外的树
    CSP/信奥赛C++语法基础刷题训练(12):洛谷P1047:[NOIP2005普及组]校门外的树题目描述某校大门外长度为lll的马路上有一排树,每两棵相邻的树之间的间隔都是......
  • 【华为OD技术面试手撕真题】84、前 K 个高频元素 | 手撕真题+思路参考+代码解析(C & C+
    文章目录一、题目......
  • C++因子个数
    目录1.题目描述2.输入输出3.样例输入 Copy样例输出 Copy1.题目描述一个自然数N的正因子个数记为F(N),例如18的所有正因子为1、2、3、6、9、18,所以F(18)=6。现在给出K,求所有满足F(N)=K的N中最小的数。2.输入从文件读入数据,第一行为K,其中0<K≤80。输出......
  • 【C++】引用
    目录引用的概念引用的特点引用定义时必须初始化引用的类型必须与被引用对象的类型相同一个变量可以设置多个引用引用只能对应一个实体引用有传递性赋值与引用的区别引用作参数引用作参数-利用引用的变量共同指向同一块内存空间交换两个变量传递单链表节点指针引......
  • GESP2023年12月认证C++四级( 第一部分选择题(11-15))
    ......