C++类型强制转换

类型转换

  • 隐式类型转换
  • 显式类型转换

语法

xxx_cast <类型> (表达式)

1. static_cast

  • 用法
    用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。
    主要有如下
  1. 基本数据类型转换
  2. int转换成enum
  3. 基类和子类之间指针和引用的转换
  • 上行转换,把子类的指针或引用转换成父类,这种转换是安全的(通常使用默认转换)。
  • 下行转换,把父类的指针或引用转换成子类,这种转换是不安全的,也需要程序员来保证(通常使用dynamic_cast)。

1.1 基本数据类型转换

  • int转换成char
int n = 97;
cout << n << '\t' << (char)n << '\t' << static_cast<char>(n) << endl;
  • int转换成float
int n = 1;
cout << n/2 << '\t' << (float)n/2 << '\t' << static_cast<float>(n)/2 << endl;

1.2 int转换成enum

enum Week{
   SUN,MON,TUE,WED,THU,FRI,SAT
};
Week day = 0;

编译上述代码出现如下错误:

  • g++编译错误:error: invalid conversion from ‘int’ to ‘Week’
  • clang++编译错误:error: cannot initialize a variable of type 'const Week' with an rvalue of type 'int'

把代码Week day = 0;改为Week day = static_cast<Week>(0);可以消除上面的错误。

1.3 基类和子类之间指针和引用的转换

已知存在继承关系的两个类BaseDerive

class Base{
public:
    void Print(){cout << "Base" << endl;}
};
class Derive:public Base{
public:
    void Print(){cout << "Derive" << endl;}
};
1.3.1 上行转换
// 对象
Derive d;
Base b = static_cast<Base>(d);
b.Print();

// 引用
Base& fb = static_cast<Base&>(d);
fb.Print();

// 指针
Base* pb = static_cast<Base*>(new Derive);
pb->Print();

通常使用隐式转换

// 对象
Derive d;
Base b = d;
b.Print();

// 引用
Base& fb = d;
fb.Print();

// 指针
Base* pb = new Derive;
pb->Print();
1.3.2 下行转换

这种转换不安全,通常使用dynamic_cast

1.4 指针/引用转换

void指针转换成目标类型的指针,这种转换是不安全的,也需要程序员来保证;
如下代码编译出错,因为不能把void*转换成其他具体指针。

void* pd = new Derive;
Base* pb = pd; //  error: invalid conversion from ‘void*’ to ‘Base*’
pb->Print();

Base* pb = pd;改为Base* pb = static_cast<Base*>(pd);,可以解决上述编译错误。

static_cast跟传统小括号转换方式几乎是一致的。
问题:为什么要用static_cast代替传统小括号转换方式?
(来自《C++ prime 4th》5.12.7)
标准 C++ 为了加强类型转换的可视性,引入命名的强制转换操作符,为程序员在必须使用强制转换时提供了更好的工具。例如, 非指针的 static_cast 和const_cast 要比 reinterpret_cast 更安全。结果使程序员(以及读者和操纵程序的工具)可清楚地辨别代码中每个显式的强制转换潜在的风险级别。
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。

2. const_cast

常量赋值给非常量时,会出现下面编译错误。

const int a = 10;

// 指针
const int* cp = &a;
int* p = cp;// error: invalid conversion from ‘const int*’ to ‘int*’

// 引用
const int& cf = a;
int& f = cf;// error: binding ‘const int’ to reference of type ‘int&’ discards qualifiers

const_cast主要作用是移除类型的const属性。

  1. 常量指针被转化成非常量的指针,并且仍然指向原来的对象;
  2. 常量引用被转换成非常量的引用,并且仍然指向原来的对象;
  3. const_cast一般用于修改底指针。如const char *p形式。
const int a = 10;

// 指针
const int* cp = &a;
int* p = const_cast<int*>(cp);

// 引用
const int& cf = a;
int& f = const_cast<int&>(cf);
  • 在类中的应用情景
    1.const对象想使用类中非const成员函数
    2.const成员函数中想修改某个成员变量
#include <iostream>

using namespace std;
//const成员函数修改const成员变量的两种方法
//1.使用const_cast
//2.成员变量前加关键字mutable
class Test
{
    int m_n;
    int m_count;
    //mutable允许成员变量在const成员函数中被改变
public:
    Test(int n):m_n(n),m_count(0){}
    void Add(int m)
    {
        m_n = m+m_n;
    }
    void Print()const
    {
        //在const函数中this指针是const,所以this指针下的成员都是const
        cout<<m_n<<endl;
        //cout<<"count= "<<++m_count<<endl;//这一句编译会报错,错误:increment of member ‘Test::m_count’ in read-only object

        cout<<"count= "<<++const_cast<int&>(m_count)<<endl;//在const成员函数中修改辅助的成员变量
    }
    void PrintCount()const {cout<<m_count<<endl;}
};
int main()
{
    //const_cast<>() :把const对象/指针/引用转成非const对象/指针/引用
    const Test t(10);
    t.Print();

    //t.Add(5);//这一句编译会报错,因为const对象只能调用const成员函数 
    //使用const_cast<>()将t转为非const对象
    const_cast<Test&>(t).Add(5);
    t.Print();
    t.PrintCount();
}

3. dynamic_cast

用于类的指针、类的引用或者void *转化。

主要用于以下三种情况:

  1. 上行转换,把子类的指针或引用转换成父类,与static_cast相同。
  2. 下行转换,把父类的指针或引用转换成子类,比static_cast安全。
  3. 交叉转换,兄弟之间指针转换,static_cast会出现编译错误。

如果时指针,进行正确的转换,获得对应的值;否则返回NULL,如果是引用,则在运行时就会抛出异常;

  • dynamic_cast使用条件
    1.存在父子关系
    2.父类中要存在多态
    3.子类指针/引用指向父类指针/引用

下行转换

#include <iostream>

using namespace std;

class Base {
public:
  void Print() { cout << "Base" << endl; }
  virtual ~Base(){}
};
class Derive : public Base {
public:
  void Print() { cout << "Derive" << endl; }
};
int main() { 
    Base * pB = new Derive;
    pB->Print();
    Derive *pD = dynamic_cast<Derive*>(pB);
    pD->Print();
}

交叉转换

#include <iostream>

using namespace std;

class Base {
public:
  void Print() { cout << "Base" << endl; }
  virtual ~Base(){}
};
class Derive1 : public Base {
public:
  void Print() { cout << "Derive1" << endl; }
};
class Derive2 : public Base {
public:
  void Print() { cout << "Derive" << endl; }
};

int main() { 
    Derive1* pD1 = new Derive1;
    pD1->Print();
    Derive2 *pD2 = dynamic_cast<Derive2*>(pD1);
    pD2->Print();
}
  • dynamic_cast使用场景
    1.如果我们想使用父类指针/引用调用子类特有的成员函数
    2.可以通过返回值来判断继承关系(假设你定义了猫,狗,猪三个纯虚函数,并且猫有1000种,狗有1000种,猪有1000种,在狗的类中,都有“吠”这个成员函数,现在你想通过代码让所有的狗叫,你不可能定义1000个狗的对象,那样太麻烦了,现在的问题是怎么做才能从3000个物种种得到你的1000只狗,这样就可以使用返回值来判断,假设Dog是所有狗的基类,vec是所有3000种动物的集合)
Dog *g = new Dog;
foreach(auto i : vec)
{
    if(dynamic_cast<i*>(g) != NULL)
        g->func();
}

由于dynamic_cast转化失败时,会返回空值,这样不是继承于Dog的类都不会调用到func()这个函数,只有Dog派生出来的类才会调用。

扩展问题

  • 如果不是多态会有什么情况?编译错误
  • dynamic_cast为什么只在多态的继承关系才有效?由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表。

4. reinterpret_cast

修改了操作数类型,重新解释了给出的对象的比特模型而没有进行二进制转换。

主要用于以下六种情况:

  1. 从指针类型到一个足够大的整数类型
  2. 从整数类型或者枚举类型到指针类型
  3. 从一个指向函数的指针到另一个不同类型的指向函数的指针
  4. 从一个指向对象的指针到另一个不同类型的指向对象的指针
  5. 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  6. 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

4.1 指针类型与整数类型的转化

把指针值(地址)转化成整数,把整数转化成指针值。

#include <iostream>
#include <vector>

using namespace std;

void Func(){
  cout << "Func" << endl;
}

int main() {
  // 变量指针类型与整数类型转化
  {
    int n = 100;
    int addr = reinterpret_cast<int>(&n);
    cout << "addr:" << hex << addr << dec << " "<< &n << endl;
    int* b = reinterpret_cast<int*>(addr);
    cout << "value:" << *b << endl;
  }
  // 函数指针类型与整数类型转化
  {
    int f = reinterpret_cast<int>(Func);
    typedef void (*pFunc)();
    pFunc pf = reinterpret_cast<pFunc>(f);
    pf();
  }
}

4.2 函数指针的转化

FuncNum函数指针转化成FuncAddr,使用FuncAddr的参数列表。

#include <iostream>
#include <vector>

using namespace std;

void FuncNum(int n){
  cout << "num:" << n << endl;
}
void FuncAddr(int* p) { 
  cout << "addr:" << p << endl;
}


int main() {
  int n = 10;
  FuncNum(n);
  FuncAddr(&n);
  
  typedef void (*pfNum)(int n);
  typedef void (*pfAddr)(int* n);
  pfNum pfunc = FuncNum;
  pfunc(n);
  reinterpret_cast<pfAddr>(pfunc)(&n);
}

4.3 对象指针的转化

A类对象指针转化成B类的对象指针,使用B类的成员函数。

#include <iostream>
#include <vector>

using namespace std;

class A{
public:
  void Func(){
    cout << "A" << endl;
  }
};

class B {
public:
  void Test() { cout << "B" << endl; }
};

int main() {
  A* pA = new A;
  pA->Func();
  B* pB = reinterpret_cast<B*>(pA);
  pB->Test();
}

4.4 类函数成员的转化

A类对象使用B类的成员变量。(A类与B类没有任何关系。)

#include <iostream>
#include <vector>

using namespace std;

class A{
public:
  void Func(){
    cout << "A" << endl;
  }
};

class B {
public:
  void Test() { cout << "B" << endl; }
};

int main() {
  // 对象的函数指针
  {
    A a;
    a.Func();
    typedef void (A::*Func_t)();
    Func_t pfTest = reinterpret_cast<Func_t>(&B::Test);
    (a.*pfTest)();
  }
  // 对象指针的函数指针
  {
    A *pA = new A;
    pA->Func();
    typedef void (A::*Func_t)();
    Func_t pfTest = reinterpret_cast<Func_t>(&B::Test);
    (pA->*pfTest)();
  }
}

4.5 类数据成员的转化

#include <iostream>
#include <vector>

using namespace std;

class Test{
public:
  Test(int data) : data(data) {}
private:
  int data;
};
int main() {
  Test test(0x61626364);
  char* p = reinterpret_cast<char*>(&test);
  for (int i = 0; i != sizeof(Test) / sizeof(char); i++) {
    cout << p[i] << endl;
  }
}

谨慎使用reinterpret_cast


小结

No. 转换 转换对象 作用 转换时机
1 static_cast 基本类型、指针、引用 实现传统的小括号转化功能 在编译期间实现转换
2 const_cast const类型的对象、指针、引用 移除变量const限定 在编译期间实现转换
3 dynamic_cast 类的指针、类的引用或者void * 多态父类指针/引用转化成子类指针/引用 在运行期间实现转换,并可以返回转换成功与否的标志/抛出异常
4 reinterpret_cast 指针、引用、算术类型 万能强制类型转换 在编译期间实现转换

扩展阅读

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容