在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