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.3 指针/引用转换

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代替传统小括号转换方式?

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_cast<><>中通常只用指针或者引用类型。
  2. 基本类型常量,因为存在常量展开的情况,const_cast<>并不会改变后面的值。
const int a = 10;
cout << "a:" << a << endl;
const_cast<int&>(a) = 11; // 已经改变a所在内存的值
cout << "a:" << a << endl; // 常量展开,看不到改变
cout << "*(&a)" << *(&a) << endl; // 直接访问内存可以看到改变 
int *p = &a;
cout << "*p:" << *p << endl; // 直接访问内存可以看到改变
const成员函数中修改成员变量

通常在const成员函数中是不能修改成员变量。

const函数中所有成员变量都是const
const函数this指针const类型,所以所有成员不能被直接改变。
例如:
提供一个打印出Set/Get次数的功能

#include <iostream>
using namespace std;

class Integer{
    int n;
    int setter;
    int getter;
public:
    Integer(int n):n(n),setter(0),getter(0){}
    void Set(int n){
       ++setter;        
       this->n=n;
    }
    int Get()const {
       ++getter;
       return n; 
    }
    void PrintCount()const{
        cout << "set:"<< setter << ",get:" << getter << endl;
    }
};

int main(){
   Integer n(10);
   n.Set(2);
   cout << n.Get() << endl;
   n.Set(4);
   cout << n.Get() << endl;
   n.Set(5);
   cout << n.Get() << endl;
   n.Set(-1);
   cout << n.Get() << endl;
   n.Set(2);
   cout << n.Get() << endl;

   n.PrintCount();
}
  • 方法1
    const_cast<>()把需要修改的成员变量转成非const类型
int Get()const { 
    ++const_cast<int&>(getter);
    return n; 
 }
  • 方法2
    使用const_cast<>()this转换成非const类型,然后修改成员变量。
int Get()const {
    ++(const_cast<Integer*>(this)->getter);
    return n; 
 }
  • 方法3
    在需要修改的成员变量声明前加上关键字mutable
mutable int getter;

mutable的成员变量可以在const函数中修改。

3. dynamic_cast

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

主要用于以下三种情况:

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

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

下行转换
#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只在多态有效。

扩展
  • 如果不是多态会有什么情况?编译错误
  • 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.转换转换对象作用转换时机
1static_cast基本类型、指针、引用实现传统的小括号转化功能编译期间实现转换
2const_castconst类型的对象、指针、引用移除变量const限定编译期间实现转换
3dynamic_cast类的指针、类的引用或者void *多态父类指针/引用转化成子类指针/引用运行期间实现转换,并可以返回转换成功与否的标志/抛出异常
4reinterpret_cast指针、引用、算术类型万能强制类型转换编译期间实现转换

cout打印地址

如果要打印地址需要强制转换

#include <iostream>
using namespace std;
void f(){}

int main(){
    int n = 10;
    int* p = &n;
    const char* s = "abc";
    cout << p << " " << (void*) p << endl;
    cout << s << " " << (void*) s << endl;
    cout << f << " " << (void*) f << endl;
}

试着把上面改成C++的转换方式

参考答案:

<< " "
> << static_cast<const void*>(s) << endl;  cout << f << " " <<
> reinterpret_cast<void*>(f) << endl; ```

成员指针

C++与C语言相比多了类的语法。类中有成员变量和成员函数。这两个都是有地址的,都可放在放入指针。

1. 成员变量指针
#include <iostream>
using namespace std;

struct Point{
  int x,y,z;
  Point(int x,int y,int z):x(x),y(y),z(z){}
  int X(){return x;}
  int Y(){return y;}
  int Z(){return z;}
};
int main(){
    // 成员变量
    // 类和结构体是不占空间的,所以只有偏移,没有地址
    printf("&Point::x=%p\n",&Point::x);
    printf("&Point::y=%p\n",&Point::y);
    printf("&Point::z=%p\n",&Point::z);

    Point p(100,200,300);
    printf("&p.x=%p\n",reinterpret_cast<void*>(&p.x));
    printf("&p.t=%p\n",reinterpret_cast<void*>(&p.y));
    printf("&p.z=%p\n",reinterpret_cast<void*>(&p.z));

    // cout不能打印偏移
    cout << &Point::x << "," << &Point::y << "," << &Point::z << endl;
    // cout可以打印地址
    cout << &p.x << "," << &p.y << "," << &p.z << endl;
}

总结

  • &类名::成员变量获取成员偏移
  • &对象.成员变量获取成员的地址
2. 成员变量指针
 printf("&Point::X=%p\n",&Point::X);
    printf("&Point::Y=%p\n",&Point::Y);
    printf("&Point::Z=%p\n",&Point::Z);
    // 全是1
    cout << &Point::X << "," << &Point::Y << "," << &Point::Z << endl;
    // cout可以打印地址
    cout << reinterpret_cast<void*>(&Point::X) << "," 
         << reinterpret_cast<void*>(&Point::Y) << ","
         << reinterpret_cast<void*>(&Point::Z) << endl;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值