# casting、C-style casting、upcasting、downcasting ## casting( = type casting)概念 last update : 2024/11/12 基礎概念: 轉型應該用於差不多的type之間的轉換,比如都是數字的(int、float、long...)或是父類跟子類。 我們通常不會把一個整數轉型成一個map或是自己定義的class,很奇怪。c++提供的那些cast就是為了避免這種奇怪的轉型。 what:什麼是轉型 why:為何要做轉型 how:該怎麼做轉型 [理解C++中的向上转型](https://www.openwriter.com/dimzou/93/93) 轉型 = type轉換,包含顯式跟隱式轉換。 顯式 = 使用者主動做類型轉換。隱式 = 編譯器自動幫你作轉換。 做轉型的目的:想使用某些函數或operator,但當前變數的type不符合需求,因此想轉型成他們能用的type 通常做類型轉換是宣告一個類型(type)為A的變數,轉換成別的類型(type)B,作為函數的引數或是operator的運算元(包含assignment operator =)。(假設此operator只接受同type運算元) explicit casting(顯式轉型 = 使用者主動做的轉型)有: 1. c-style casting 2. static_cast 3. const_cast : 用來新增或移除(該變數的)const關鍵字 = 改變read only的特性。不改變bit pattern 4. dynamic_cast 5. reinterpret_cast : 做type轉換時,對記憶體區塊的二進位資料重新解釋 : 與指標跟引用轉型有關 轉型有三種:一般變數的轉型(不包含指標和引用)、指標的轉型、引用的轉型 這三種轉型的行為/轉型方式不太一樣,但引用的轉型跟指標的轉型幾乎一樣,都是對該變數的記憶體區域的機器碼的重新解釋 = 把該記憶體區域的機器碼當做什麼type看待 補充:花時間重新整理此內容 實際上對於casting的分法並不是分成value casting、pointer casting、reference casting(我下面的分法),而是分成上面那四種named casts(c++規定)。 要去上網看教學+看c++ primer。 c的cast跟c++的cast的對應需要花時間去釐清(要去看書) ## 一般變數的轉型 ### 一般變數的轉型 ```cpp= #include <iostream> int main() { int i = 3; double d = (double)i; // 显式转换 // double d = i; // 隐式转换 std::cout << d << std::endl; return 0; } ``` double d = (double)i; CPU做的事情 = cpu執行此statement(被編譯器編譯成)的二進位指令: 1. 根據變數i的記憶體位置往下讀取4個bytes,放到暫存器 2. 根據這四個bytes代表的整數值、以及IEEE754(浮點數標準),生出一個8個bytes的資料,並且代表的數值跟變數i一樣 3. 將這8個bytes存回d的記憶體位置往下共8個bytes。 double d = i; 的cpu的執行流程跟 double d = (double)i;一樣,區別在於顯式隱式 這與強制指標轉換(reinterpret casting)不同,因為 (double)i 是一種 值的轉換,而不是 內存 reinterpretation(對記憶體內容重新解釋)。 :::spoiler code ```cpp= #include <iostream> int main() { float f = 5.67; std::cout << f << std::endl; int i = static_cast<int>(f); //顯式類型轉換 std::cout << i << std::endl; int i2 = f; //隱式類型轉換 std::cout << i2 << std::endl; std::cout << static_cast<int>(f) << std::endl; int i3 = i2 + static_cast<int>(f); std::cout << i3 << std::endl; return 0; } ``` ::: ### 指標的轉型 recall:用指標的目的是操作/使用該指標指向的東西,比如物件或函數。就是要解引用該指標去使用它。 不同type的指標存的都是記憶體位址,區別在於解引用時,cpu對此的解讀不同。 cpu執行指標的解引用的指令時,會根據該指標的type去決定要讀取幾bytes的記憶體,並且根據type去做二進位資料的解讀。比如該指標是整數,那就根據該指標存的記憶體位置往下讀4個bytes,並且是依照2補數解讀這4bytes的二進位資料。 ::: spoiler code example ```cpp= #include <iostream> int main() { int a = 3; // int是4bytes float* ptr = (float*)&a; // float也是4bytes std::cout << ptr << " " << &a << std::endl; //&a跟ptr指向同一塊記憶體,但解讀方式不同。 std::cout << *ptr << std::endl; // cpu將a的記憶體這4bytes解讀成float = 把該區域作為float看待 double* ptr2 = (double*)&a; // double是8bytes // cpu將讀取一塊8bytes的記憶體,前4bytes是a的4bytes的記憶體,後4bytes是記憶體的另外4bytes,會讀到不知道有沒有被用到的記憶體 = 未定義行為 std::cout << *ptr2 << std::endl; return 0; } ``` ::: ### 引用的轉型 :::spoiler recall:編譯器如何編譯"宣告一個引用"這件事 ```cpp= int main() { int a = 3; int& b = a; return 0; } ``` int& b = a; 編譯器將視為a的alias,共用一樣的記憶體位置以及同一塊記憶體。 編譯後所有對b的存取都會被轉換為(被編譯器優化成)對a的存取 = 編譯後的machine code裡面的b都會被替換成a。 ::: 引用的轉型跟指標的轉型幾乎一樣,只是最後解引用而已 ::: spoiler code example ```cpp= #include <iostream> int main() { int a = 3; int& b = a; // correct // float& f = (float)a; //error:non-const ref must be l-value。因為(float)a不在記憶體內,他是暫時的執行結果。想想(float)a的組語指令 float& f = (float&)a; std::cout << f << std::endl; float* ptr = (float*)&a; std::cout << *ptr << std::endl; return 0; } ``` ::: 当你在 C++ 中对引用进行强制类型转换时,这与对指针进行类型转换类似。具体来说,强制转换引用实际上是对指针的强制转换,然后解引用。这意味着你重新解释了变量的内存地址,将其视为另一种类型。 編譯器編譯float& f = (float&)a: 1. 讀取a的記憶體位置 2. 用float的標準(IEEE754)從a的記憶體位置向下4bytes重新解讀這塊記憶體。如果是double就是下向8bytes重新解讀這塊記憶體。 3. 之後f的值就是對a的記憶體重新解讀後的值 所以這個f一樣是對a的引用,只是解讀那塊記憶體的方式不同。 ## 自定義class的物件的轉型 類也是type,所以也有類型轉換(轉型)。但只發生在有繼承關係的類才可以做轉型。畢竟不同class(非繼承關係的class)做轉型聽起來也很不合理,因此class的轉型都是討論有繼承關係的class間的轉型。 recall:子類的記憶體佈局 因為有繼承關係,所以只有class的轉型才會稱"向上"或"向下"。一般float轉int之類的就沒有向上或向下的說法。 向上轉型 = 子類的type轉換成父類的type。 向下轉型 = 父類的type轉換成子類的type。 當然向上跟向下轉型也分顯式跟隱式 比如Base* ptr = new Derived;是隱式的指標的向上轉型,將Derived\*轉型成Base* 也當然包含物件(變數)本身、指標跟參考的類型轉換:各有向上以及向下轉型 ## 下面的重點整理: 下面例子的method都不是virtual,因此指標/參考是哪個type,用的method就是該type的 1. 向上轉型: 一般物件(會有object slicing)、指標、引用的向上轉型編譯器會自動幫你做。並且不會有問題。 2. 向下轉型: 0. 不能做一般物件的向下轉型(會編譯錯誤),只能做指標跟引用的向下轉型 1. 指標的向下轉型要注意如果子類指標存取到父類沒有的方法或屬性,會有未定義行為(不會有編譯錯誤,且程式可執行),很危險。 2. 指標跟引用的向下轉型他們的行為是一樣的 ### 一般物件的轉型 ```cpp= //this is example.h #ifndef EXAMPLE_H #define EXAMPLE_H class Base { public: Base(int a1) : a1_(a1) {} void show() { std::cout << "Base class show() called\n"; } int a1_; }; class Derived : public Base { public: Derived(int a1, int a2) : Base(a1), a2_(a2) {} void show() { std::cout << "Derived class show() called\n"; } void uniqueToDerived() { std::cout << "Derived class unique function\n"; } int a2_; }; #endif ``` #### upcasting ```cpp= #include <iostream> #include "Example.h" int main() { //up casting Derived derived_obj(2, 3); Base base_obj = (Base)derived_obj; // Base base_obj = derived_obj; // 和上一行一樣 std::cout << base_obj.a1_ << std::endl; // std::cout << base_obj.a2_ << std::endl; //error // base_obj.uniqueToDerived(); //error base_obj.show(); //Base class show() called return 0; } ``` :::spoiler Base base_obj = (Base)derived_obj;解釋 1. (Base)derived_obj:複製一份Base的臨時物件(在stack),並且只複製derived_obj中Base有的屬性。(object slicing) 2. 呼叫Base的預設複製建構子,將該Base臨時物件作為參數,執行shallow copy(因為是預設的複製建構子)。 For more details, you can refer to the [C++ tutorial on object slicing](https://www.learncpp.com/cpp-tutorial/object-slicing/). ::: #### down casting cannot do down casting ```cpp= #include <iostream> #include <example.h> int main() { //cannot do down casting Base base_obj(5); // Derived derived_obj = base_obj;//error 因為有少資料 // Derived derived_obj = (Derived)base_obj;//error return 0; } ``` ### 指標的轉型 #### up casting ```cpp= #include <iostream> #include "example.h" int main() { // upcast Derived derived_obj(7, 3); Base* base_ptr = (Base*)&derived_obj; // Base* base_ptr = &derived_obj; // 和上面一樣 std::cout << base_ptr->a1_ << std::endl; // std::cout << base_ptr->a2_ << std::endl; //error base_ptr->show(); //Base class show() called // base_ptr->uniqueToDerived(); //error return 0; } ``` #### down casting ```cpp= #include <iostream> #include "example.h" int main() { // downcast Base base_obj(5); // Derived* derived_ptr = &base_obj; //error Derived* derived_ptr = (Derived*)&base_obj; //no compile error std::cout << derived_ptr->a1_ << std::endl; std::cout << derived_ptr->a2_ << std::endl; //derived_ptr->a2_:show the value of original memory value : undefined behavior derived_ptr->show(); derived_ptr->uniqueToDerived(); return 0; } ``` ### 引用的轉型 #### upcasting ```cpp= int main() { // upcast Derived derived_obj(7, 3); Base& base_ref = (Base&)derived_obj; // Base& base_ref = derived_obj; //和上面一樣 std::cout << base_ref.a1_ << std::endl; // std::cout << base_ref.a2_ << std::endl; //error base_ref.show(); // Base class show() called // base_ref.uniqueToDerived(); //error return 0; } ``` #### down casting ```cpp= int main() { // downcast Base base_obj(5); // Derived& derived_ref = base_obj; //error Derived derived_ref = (Derived&)base_obj; //no compile error std::cout << derived_ref.a1_ << std::endl; std::cout << derived_ref.a2_ << std::endl; //derived_ref.a2_:show the value of original memory value : undefined behavior derived_ref.show(); derived_ref.uniqueToDerived(); return 0; } ``` ## 問題 [Object slicing(对象切片)-CSDN博客](https://blog.csdn.net/weixin_40539125/article/details/105668760) Slicing and functions The Frankenobject
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up