文章目录
- 1. C语言中的类型转换
- 1.1 隐式转换
- 1.2 显示转换
- 2. C++的类型转换
- 2.1 static_cast
- 2.2 reinterpret_cast
- 2.3 const_cast
- 2.4 dynamic_cast
- 3. 常见面试题
前言: C++给出了四种类型转换,这是做出的一些规范,为了减少隐式转换。隐式转换的问题,考虑清楚还行,就怕你考虑不清楚,然后悄咪咪的发生了隐式转换,让人头疼。所以 如果要进行类型转换,最好用 C++给出的方式,这样代码更清晰易懂。
1. C语言中的类型转换
先讲讲C语言中的类型转换,因为C++兼容C嘛。其实 要想规避 隐式转换的发生,最简单的方式 就是 将隐式转换 这一机制删除掉。但是 这样做 相当于大改,这样的事情 也就是python敢做。所以 C++还是 支持了C中的类型转换方式。
需要类型转换的情况:
- 赋值,比如 float a = 2; 2是int整型,它会发生类型转换成float 再赋值给 a(隐式)。
- 算术运算符两边的类型不同,不能比较,发生隐式转换。
- 函数传参,形参和实参类型不同。
- 函数返回值,被接收的类型与返回的类型不同,这种情况。
总共有两种 类型转换方式:
- 隐式转换
- 显示转换
1.1 隐式转换
发生隐式转换的都是类型相接近的,就是一个隐式的类型提升。
隐式转换的目的是 让两个类型 变为同一个类型,方便赋值,比较之类的事;赋值只能是同类型之间的赋值,如果是不同类型,那么就得是 发生隐式转换。
隐式转换的原则是:低的 转化 高的。
我画一幅图,帮助理解:
我写一个代码:可能 坑过初学C语言的我们
int main()
{
int a = 10;
size_t i = 0;
while (a >= i)
{
printf("%d\n", a);
a--;
}
return 0;
}
它是像要打印 10 到 0。 如果 i是一个整型int,那么这个代码是没问题的;关键 i是一个 size_t 。i和a进行比较时,a发生隐式转换为 size_t 。而且我们知道 size_t 是永远大于 0的。所以造成了死循环。
a本来减到 -1 就该停止,但是发生隐式类型转换,-1 转换为 size_t 变成了很大的整数,于是乎 永远大于0嘛。
看看结果:
还有赋值的时候,也会存在隐式转换:
float a = 1;
同类型之间才能相互赋值,因为 1是一个整型,所以先发生隐式类型转换为浮点型,再复制给a。
1.2 显示转换
这就是强制类型转换,在C语言中很常用。有很多 骚操作,都利用的这个显示转换。但是也会造成不少的bug。
就是显示的发生转换:
int a = 10;
int* p = &a;
int m = (int)p;
当然上面这种玩法比较的挫,很少这样用。
比如:Linux中thread_create()函数的最后一个参数是 void* 它可以接收任意类型的指针。然后进入 线程执行的函数里,通过强转 可以转为 自己想用的类型。
我简单的写段代码:
void run(void* v)
{
printf("%s\n", (char*)v);
}
int main()
{
run("good");
return 0;
}
这就有点意思了,通过显示类型转换 还有很多骚操作,还有那个system V方案里对 共享内存,消息队列,信号量 用同一个 结构体指针接收。这个也有意思,这是我了解最早的切片操作。
我还是给段代码,让大家 看看 大神 是如何利用 一种结构体指针 ,管理三个不同结构体:
struct con
{
int m;
};
struct A
{
struct con m;
int a;
};
struct B
{
struct con m;
int b;
};
struct C
{
struct con m;
int c;
};
int main()
{
// 定义三个结构体
struct A ma;
struct B mb;
struct C mc;
// 用同一种类型的结构体 指向它们
struct con* p1 = &ma;
struct con* p2 = &mb;
struct con* p3 = &mc;
// 如果具体要用某个结构体,可以通过强转,转回原来类型
struct A* pa=(struct A*)p1;
return 0;
}
为什么能这样操作?很明显 每个结构体 的开头都是 struct con类型的结构体。然而指针指向结构体的开头位置,所以能用 struct con类型指针 指向 那三个结构体。
有用嘛?这个东西? 肯定是有用,比如 要统一化接口。因为之前 C语言没有函数重载,也没有模板。对吧,所以 这个东西 还是 不错的。有人这里可能还不太理解,我简单给个代码:
void run(struct con* p);
这个函数 是不是就可以 接收 struct con* 类型的指针,然后 struct con* 又可以指向 A,B,C 三种类型的结构体。就相当于 可以接收不同类型的指针。然后 具体 要怎么操作,进了 函数 自己去控制,比如 具体强转 成 那种类型呀之类的。
2. C++的类型转换
C++是兼容C的,所以上面的类型转换,C++都有。但是 发现隐式转换 有点 悄咪咪的。不好被发现。要么就明着来嘛,不是还可以显示的强制类型转换嘛?但是 显示的强制类型转换在某些情况下,也不能瞎转换,有可能转换不成功。或者转换成功了,但是不安全。
于是 C++ 就 对类型转换 做出一定的规范。
C++给出四种类型转换的方式:
- static_cast、
- reinterpret_cast、
- const_cast、
- dynamic_cast
2.1 static_cast
这是用于相近类型的转换,也就是之前所有的隐式转换都可以用 staic_cast 转换。
int a = 10;
float f = static_cast<int>(a);
使用起来也很简单对吧。之前那个死循环问题,一般情况不容易被发现,但是 你如果 用这个进行转换,就很明了的 看出问题所在。
int a =10;
size_t i=0;
while(static_cast<size_t> a >= i)
{
;
}
嗯,size_t 类型永远大于 0 ,所以死循环。
2.2 reinterpret_cast
这是不相近类型的转换,这个转换 很强势。无视种族,可以随便转换,就类似于强制转换。多用于不同类型指针之间的转换。这是它的主要用途。
比如:
int add(int x, int y)
{
return x + y;
}
int main()
{
int (*p)(int, int) = &add;
int m = reinterpret_cast<int>(p); // 等价于 int m =(int)p;
return 0;
}
看到了吧,把一个函数指针,赋值给了 整型。但是这样的操作没有意义。
它主要用于 指针之间的相互转换,就是 我之前给大家演示的那个代码:
void run(void* v)
{
printf("%s\n", (char*)v);
}
int main()
{
run("good");
return 0;
}
上面是利用的强制转换嘛,如果在c++中,我们推荐使用reinterpret_cast 进行转换。
void run(void* v)
{
char* s = reinterpret_cast<char*> v;
printf("%s\n", s);
}
错误使用reinterpret_cast 会使得 程序变得不安全。
正确的使用方式:
利用reinterpret_cast 转换成可以接收的类型;再使用 reinterpret_cast 将其转换回 原来的类型。
当然 如果是 void* 接收,那么 就省略第一步。
2.3 const_cast
这是用于取出const属性的。const属性可以通过指针去除掉,用这个注意是起到了一个提示作用,表示 我要去除 某某的 const属性。
比如:
这是C语言的去除const属性方式,但是 在C++中加入了检查机制,const 变量只能赋值给const属性的指针。
const int a = 10;
int* p = &a;
*p = 2;
我们再来看c++去除const属性的方式:
const int a = 10;
int* p = const_cast<int*>(&a);
*p = 2;
cout << a << endl;
我们来看一下结果:
发现a 还是 10,这是打印结果,其实底层a 已经变为 2。打印结果还是10的原因来自于 编译器的优化,因为是const属性 ,编译器默认它不会变了。如果不想让编译器优化可以加上 关键字 volatile修饰变量。
我先通过调试 看一下 a到底变没:
很明显变了,再加上volatile修饰变量,阻止编译器优化。
volatile const int a = 10;
看打印结果:
2.4 dynamic_cast
它是用于检查 父类子类之间的赋值的。
- 父类 可以 赋值给父类;
- 子类 可以 赋值给子类;
- 父类 不可以 赋值给子类;
- 子类 可以 赋值给父类,这是切片嘛;
使用条件:
- dynamic_cast只能用于含有虚函数的类,并且只操作 类的指针
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
第一个条件表明立场:处理多态时,指针的转换,用它更安全,以及它的参数都是类指针或类地址
第二个条件 是 它的返回机制。
我举个例子:
class A
{
virtual void f() {}
public:
};
class B : public A
{
public:
int _b = 0;
};
void func(A* pa)
{
B* pb1 = dynamic_cast<B*>(pa);
if (pb1 == nullptr)
{
cout << "转换失败" << endl;
}
else
{
cout << "pb1:" << pb1 << endl;
pb1->_b++;
}
}
int main()
{
A aa;
B bb;
func(&aa);
func(&bb);
return 0;
}
看运行结果:
也就是说 dynamic_cast是有检查机制,它可以判断出 能不能转换成功。如果我们这里用
static_cast 直接就转换了,它不会做检查的。
本来 父类是不能够 赋值给子类的,但是用 static_cast ,reinterpret_cast 或者是直接强转 都是可以完成 父类 赋值给 子类。肯定这样做 是不太安全的。
A aa;
B* bb;
///
bb = (B*)&aa;
B* pb1 = static_cast<B*>(&aa);
B* pd2 = reinterpret_cast<B*>(&aa);
所以为了 规范这一点,给出了 dynamic_cast。它发现 不能赋值过去,就直接返回 0,而且也不会做 赋值操作。非常安全。
3. 常见面试题
- C++中的4中类型转化分别是:
- 说说4中类型转化的应用场景。
答案就不给了,大家自由发挥。