---
# System prepended metadata

title: casting、C-style casting、upcasting、downcasting

---

# 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