cpp

C++ 教程 | C++ 类型转换

Posted by Aiden on January 24, 2022

C++强制类型转换:

在C++语言中新增了四个关键字 static_cast, const_cast, reinterpret_castdynamic_cast.

这四个关键字都是用于强制类型转换的。

新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。

C++中风格是 static_cast<type>(content)

C++ 风格的强制转换其他的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码, 就能立即知道一个强制转换的目的。

static_cast

在 C++ 语言中 static_cast 用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据。

int a{10};
int b{5};
double c = static_cast<double>(a)/ static_cast<double>(b);

用法: static_cast<类型说明符>(变量或表达式)

  1. 用于类层次结构中基类派生类之间指针或引用的转换:
    • 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
    • 进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
  2. 用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证:
    • 把空指针转换成目标类型的空指针
    • 把任何类型的表达式转换为void类型

注意

  1. static_cast 不能转换掉expression的 constvolitale 或者 __unaligned 属性。
  2. 如果涉及到类的话, static_cast 只能在有相互联系的类型中进行相互转换,不一定包含虚函数。

const_cast

const 限定符通常被用来限定变量,用于表示该变量的值不能被修改。

const_cast 用于强制去掉这种不能被修改的常数特性,但需要特别注意的是 const_cast 不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用

用法:const_cast<type_id>(expression)

  1. 该运算符用来修改类型的constvolatile属性。除了constvolatile修饰之外,type_id和expression的类型是一样的。
  2. 常量指针被转化成非常量指针,并且仍然指向原来的对象;
  3. 常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

一个错误的例子

const int a = 10;
const int * p = &a; 

*p = 20; //compile error 
int b = const_cast<int>(a);  //compile error
  1. 第一个编译错误是*p因为具有常量性,其值是不能被修改的;
  2. 另一处错误是const_cast强制转换对象必须为指针或引用,而例3中为一个变量,这是不允许的!

const_cast 使用案例

const  int  a = 10;
const  int  * p = &a;
int *q;
q = const_cast<int  *>(p);
*q = 20;     //fine

### result : 
# a  = 10, &a = 002CFAF4   
# *p = 20, p  = 002CFAF4
# *q = 20, q  = 002CFAF4 

查看运行结果,问题来了,pq都是指向a变量的,指向地址相同,而且经过调试发现002CFAF4地址内的值确实由10被修改成了20,这是怎么一回事呢?为什么a的值打印出来还是10呢?

其实这是一件好事,我们要庆幸a变量最终的值没有变成20!变量a一开始就被声明为一个常量变量,不管后面的程序怎么处理,它就是一个常量,就是不会变化的。

试想一下如果这个变量a最终变成了20会有什么后果呢?对于这些简短的程序而言,如果最后a变成了20,我们会一眼看出是q指针修改了,但是一旦一个项目工程非常庞大的时候,在程序某个地方出现了一个q这样的指针,它可以修改常量a,这是一件很可怕的事情的,可以说是一个程序的漏洞,毕竟将变量a声明为常量就是不希望修改它,如果后面能修改,这就太恐怖了。

上面的我们称*q=20语句为未定义行为语句,所谓的未定义行为是指在标准的C++规范中并没有明确规定这种语句的具体行为,该语句的具体行为由编译器来自行决定如何处理。对于这种未定义行为的语句我们应该尽量予以避免!

可以看出我们是不想修改变量a的值的,既然如此,定义一个 const_cast 关键字强制去掉指针的常量性到底有什么用呢?我们接着来看下面的例子。

#include<iostream>
using namespace std;
 
const int* Search(const int* a,  int n,  int val);
 
int  main()
{
     int  a[10] = {0,1,2,3,4,5,6,7,8,9};
     int  val = 5;
     int  *p;
     p =  const_cast<int *>(Search(a, 10, val));
     if (p == NULL)
         cout<< "Not found the val in array a" <<endl;
     else
         cout<< "hvae found the val in array a and the val = " <<*p<<endl;
     return  0;
}
 
const int * Search(const int * a,  int n,  int val)
{
     int i;
     for (i=0; i<n; i++)
     {
         if (a[i] == val)
             return  &a[i];
     }
     return NULL;
}
  1. 用于在a数组中寻找val值,如果找到了就返回该值的地址,如果没有找到则返回NULL。
  2. 函数Search返回值是const指针,当我们在a数组中找到了val值的时候,我们会返回val的地址,最关键的是a数组在main函数中并不是const,因此即使我们去掉返回值的常量性有可能会造成a数组被修改,但是这也依然是安全的。

reinterpret_cast

reinterpret_cast主要有三种强制转换用途:改变指针引用的类型、将指针引用转换为一个足够长度的整形、将整型转换为指针引用类型。

用法:reinterpret_cast<type_id>(expression)

  1. type_id 必须是一个指针引用算术类型函数指针或者成员指针
  2. 它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
  3. 在使用 reinterpret_cast 强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!
int *a = new int;
double *d = reinterpret_cast<double *>(a);

dynamic_cast

用法:dynamic_cast<type_id>(expression)

  1. 其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
  2. 不能用于内置的基本数据类型的强制转换。
  3. dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。
  4. 使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过。
  5. 在类的转换时,在类层次间进行上行转换时,dynamic_caststatic_cast 的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class Base{
public:
  void m(){
    std::cout << "m" << std::endl;
  }
};

class Derived : public Base{
public:
  void f(){
    std::cout << "f" << std::endl;
  }
};

int main(int argc, char*argv[])
{
  Derived *d = static_cast<Derived *>(new Base());  // 存在风险

  d->f();

  return 0;
}
  1. 对于指针类型,如果转换失败了,则返回0;
  2. 对于引用类型,如果失败了,则抛出std::bad_cast异常,因为不允许存在空的引用。
Base *d = dynamic_cast<Base *>(new Derived());

if(d) {
// 转换成功
}else{
// 转换失败
}