文章目錄
- 前言
- 一、C語言中的類型轉換
- 二、為什么C++會有四種類型轉換?
- 內置類型 -> 自定義類型
- 自定義類型 -> 內置類型
- 自定義類型 -> 自定義類型
- 隱式類型轉換的坑
- 三、C++強制類型轉換
- static_cast
- reinterpret_cast
- const_cast
- dynamic_cast
- 四、RTTI
- 總結
前言
??Hello,學完這個,再看看 IO 流,我們的C++學習之路可能真的要結束了~
一、C語言中的類型轉換
??在學習C++之前,不妨我們先看看C的類型轉換的機制:
??在C語言中,如果賦值運算符左右兩側類型不同,或者形參與實參類型不匹配,或者返回值類型與接收返回值類型不一致時,就需要發生類型轉化
??C語言中總共有兩種形式的類型轉換:隱式類型轉換(整型之間 整型浮點數之間) 和 顯式類型轉換(指針之間 整型指針之間)
沒有關聯的類型不支持轉換
- 隱式類型轉化:編譯器在編譯階段自動進行,能轉就轉,不能轉就編譯失敗。
- 顯式類型轉化:需要用戶自己處理。
#include<iostream>
using namespace std;int main()
{int i = 1;// 隱式類型轉換double d = i;printf("%d, %.2f\n", i, d);int* p = &i;// 顯示的強制類型轉換int address = (int)p;printf("%p, %d\n", p, address);// “類型強制轉換”: 無法從“int *”轉換為“double”// double x = (double)p;// 沒有關聯的類型不支持轉換return 0;
}
二、為什么C++會有四種類型轉換?
??C風格的轉換格式很簡單,但是有不少缺點的:
- 隱式類型轉化有些情況下可能會出問題:比如數據精度丟失
- 顯式類型轉換將所有情況混合在一起,代碼不夠清晰
??C++除了兼容C的轉換用法之外還有三種轉換方式:內置類型 -> 自定義類型 ;自定義類型 -> 內置類型 ; 自定義類型 -> 自定義類型
內置類型 -> 自定義類型
??內置類型轉換為自定義類型通過 隱式類型轉換 + 對應的構造函數 就能夠實現
class A
{
public:A(int a1):_a1(a1){}A(int a1, int a2):_a1(a1),_a2(a2){}
private:int _a1 = 1;int _a2 = 1;
};int main()
{// 內置類型 -> 自定義類型A aa1 = 1;// 單參數隱式類型轉換A aa2 = { 1,2 };// 多參數隱式類型轉換,使用{}// A aa3 = { 1,2,3 };// 沒有實現3個參數的構造函數或者initializer_list,就不能隱式類型轉換return 0;
}
自定義類型 -> 內置類型
??自定義類型轉化為內置類型,直接寫不支持,需要寫重載函數
class A
{
public:A(int a1):_a1(a1){}// 自定義類 -> 內置類型 重載函數operator int(){return _a1 + _a2;}
private:int _a1 = 1;int _a2 = 1;
};int main()
{// 內置類型 -> 自定義類型A aa1 = 1;// 自定義類型 -> 內置類型 直接寫不支持,需要重載int x = aa1;cout << x << endl;return 0;
}
自定義類型 -> 自定義類型
??自定義類型轉換為自定義類型,直接寫不支持,需要加拷貝構造
class A
{
public:A(int a1):_a1(a1){}// 成員變量私有,類外不能獲取,const對象需要加constint get() const{return _a1;}
private:int _a1 = 1;int _a2 = 1;
};class B
{
public:B(const A& aa):_b1(aa.get()){}
private:int _b1;
};int main()
{// 內置類型 -> 自定義類型A aa1 = 1;// 自定義類型 -> 自定義類型 直接寫不支持,需要加拷貝構造B bb = aa1;return 0;
}
隱式類型轉換的坑
// 隱式類型轉換的坑
void Insert(size_t pos)
{int end = 10;while (end >= pos){cout << end << endl;--end;}
}int main()
{Insert(5);// 插入0就有坑,-1轉換為無符號整數為最大值Insert(0);return 0;
}
三、C++強制類型轉換
??標準C++為了加強類型轉換的可視性,引入了四種命名的強制類型轉換操作符
static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast
??static_cast用于非多態類型的轉換(靜態轉換),編譯器隱式執行的任何類型轉換都可用static_cast
一樣的,但它不能用于兩個不相關的類型進行轉換
int main()
{int i = 1;// 隱式類型轉換 : static_castdouble d = static_cast<double>(i);cout << d << endl;// 強制類型轉換int* p = &i;//int address = static_cast<int>(p);//printf("%p %d\n", p, address);return 0;
}
class A
{
public:A(int a1):_a1(a1){}
private:int _a1 = 1;int _a2 = 1;
};int main()
{// 隱式類型轉換,使用staticA aa1 = 1;A aa2 = static_cast<int>(1);return 0;
}
reinterpret_cast
??reinterpret_cast操作符通常為操作數的位模式提供較低層次的重新解釋,用于將一種類型轉換為另一種不同的類型(強制類型轉換)
int main()
{int i = 1;// 強制類型轉換 : reinterpret_castint* p = &i;int address = reinterpret_cast<int>(p);printf("%p %d\n", p, address);return 0;
}
const_cast
??const_cast最常用的用途就是刪除變量的const屬性,方便賦值
int main()
{const int i = 1;// 存放到寄存器中int* ptr = (int*)&i;(*ptr)++;cout << *ptr << endl;cout << i << endl;return 0;
}
??我們能夠看到一個很奇怪的現象,我們將 i 地址處的數據++,該地址處的值修改了,但是 i 的值卻沒有修改,這是為什么呢?
實際是在C++程序中,使用 const 修飾的變量是存放在寄存器中的,最終打印該值是直接打印寄存器存儲的值
dynamic_cast
??dynamic_cast用于將一個父類對象的指針/引用轉換為子類對象的指針或引用(動態轉換)
向上轉型:子類對象指針/引用->父類指針/引用(不需要轉換,賦值兼容規則)
向下轉型:父類對象指針/引用->子類指針/引用(用dynamic_cast轉型是安全的)
- dynamic_cast只能用于父類含有虛函數的類 。
- dynamic_cast會先檢查是否能轉換成功,能成功則轉換,不能則返回0。
class A
{
public:virtual void f(){}int _a1 = 1;
};class B : public A
{
public:int _b1 = 1;
};void func(A* pa)
{// 不差別轉換,存在風險// 傳A對象的地址,沒有_b1成員,修改_b1成員則程序崩潰B* pb = (B*)pa;cout << pb << endl;pb->_b1++;
}int main()
{A a;B b;func(&a);func(&b);return 0;
}
class A
{
public:virtual void f(){}int _a1 = 1;
};class B : public A
{
public:int _b1 = 1;
};// 使用dynamic_cast,父類必須有虛函數
void func(A* pa)
{// pa指向a對象,轉換成功// pa指向b對象,轉換失敗,返回nullptrB* pb = dynamic_cast<B*>(pa);if (pb){cout << pb << endl;pb->_b1++;}else{cout << "轉換失敗" << endl;}
}int main()
{A a;B b;func(&a);func(&b);return 0;
}
四、RTTI
RTTI:Run-time Type identi?cation的簡稱,即:運行時類型識別。
C++通過以下方式來支持RTTI:
??1. typeid運算符
??2. dynamic_cast運算符
??3. decltype
總結
??蕪湖,馬上就要迎來最后一節了!