---
# System prepended metadata

title: C++ 函數、暫時變數、Reference
tags: [C/C++, C++]

---

---
title: 'C++ 函數、暫時變數、Reference'
disqus: kyleAlien
---

C++ 函數、暫時變數、Reference
===
真正程式設計來自，STL & BOOT C++ 函式庫的結合，C 語言相關基礎請看 [**指標 & Array & typedef**](https://hackmd.io/bzOkHGC_TDGN-RF4lK18Pw?view#%E6%8C%87%E6%A8%99-amp-Array-amp-typedef)

## OverView of Content

[TOC]

## 函數

### 函數呼叫

```c=
int a = 0;
    
void func_B(int v) {
    a = v - a;
}

void func_A() {
    func_B(10);
    printf("a value: %d\n", a);
}
```
* 當呼叫 func_A() 時，電腦會如何操作
    
    1. 儲存進入函數前的下一個的記憶體位置 ( Line 9 )

    2. Copy 引數 10 的值到堆疊

    3. 跳到 func_B(int) 的記憶體位置，並處理程序

    4. 回到先前儲存的記憶體位置 ( Line 9 )

### 函數回傳值

* 與一般 Java 語言相同

    ```cpp=
    #include <iostream>

    using namespace std;

    int sum(int a, int b) {
        return a + b;
    }

    int main() {

        int res = sum(1, 2);

        cout << res << endl;

        return 0;
    }
    ```
    
    :::warning
    * C++ 特點，不能回傳 Array 類型，可以使用 ptr 替代  

        ```cpp=
        int array[6] = {0, 1, 2, 3, 4, 5};

        int[6] returnArray() {
            return array;
        }
        ```
        > ![](https://i.imgur.com/NJHI03r.png)
    :::
    
    :::success
    * 函數回傳值
        
        回傳值複製到 CPU 的暫存器中 or 記憶體中，然後呼叫程式才會檢查此記憶體位置的數值，在賦予到 Stack 中
    :::

### 函數原型 - Prototype

* 函數原型是幫助 Compiler 編譯用，功能有 ^1.^  判斷函數入參、^2.^ 判斷函數回傳值

    ```cpp=
    // 1. 入參兩個 int 類型
    // 2. 回傳 int 類型
    int sum(int a, int b);
    ```
    
* 為何需要 函數 Prototype ?

    1. 效率，否則就須要將所檔案都遍歷並收集所有函數

    2. 函數可能不在自身檔案中，也就是說如果使用 lib 會導致找不到函數，這時使用函數原型，就可以確保該函數會被實現
        
        :::info
        * 如果每有引數 (入參) 可以使用 `(void)` 替代，如果有引數，但不確定引數長度，則可以使用 `(...)` 替代
            
            > 像是 `printf` 函數
        :::

### 函數引數
* 呼叫函數時，傳入的數值是 `實際引數 (actual argument)`，當真正進入函數時，函數接收到的參數又稱為 `形式引數`

    實際引數進入形式引數時，是經過 **複製**，**函數接收到的參數只是副本 !**

    ```cpp=
    int sum(int a, int b) {    // a、b 是形式引數
        return a + b;
    }

    int main() {

        int A = 1, B = 2;        // A、B 是實際引數
        int res = sum(A, B);        

        cout << res << endl;

        return 0;
    }
    ```
    > ![](https://i.imgur.com/O63yevU.png)


## 函數 & Array

* 當函數接收一個 Array 類型的參數時，其實 **接收到的是一個指標，並不是實際整個 Array 的複製**，所以如果改變該指標指向的內容，原來的數據也會一起更改

    > 可以使用 const 來避免資料被修改

    ```cpp=
    #include <iostream>

    using namespace std;

    void printArray(int array[], int len) {

        for(int i = 0; i < len; i++) {
            if(i == 2) {
                array[i] = 3333;
            }
            cout << array[i] << endl;        // [] 符號相對來說就是 + i 再取值
        }
    }

    int main() {

        int array[5] = {
                1, 2, 3, 4, 5
        };

        printArray(array, 5);

        return 0;
    }
    ```

    > ![](https://i.imgur.com/mTKwygr.png)

### Array & Pointer

* 在函數內，我們也可以使用指標來操控該陣列，**結果相同**；**But~ array 不可以代替指標**

    1. 接收形參為 array，使用 ptr 操控

        ```cpp=
        void printArrayByPtr(int array[], int len) {
            cout << "printArrayByPtr" << endl;

            for(int i = 0; i < len; i++) {
                if(i == 2) {
                    *(array + i) = 3333;
                }
                cout << *(array + i) << endl;
            }
        }
        ```
        > ![](https://i.imgur.com/edmtAn6.png)

        
    2. 接收形參為 ptr，使用 ptr 操控

        ```cpp=
        void printArrayByPtr_2(int* array, int len) {
            cout << "printArrayByPtr_2" << endl;

            for(int i = 0; i < len; i++) {
                if(i == 2) {
                    *(array + i) = 3333;
                }
                cout << *(array + i) << endl;
            }
        }
        ```
        > ![](https://i.imgur.com/WUuu9gU.png)

    :::info
    * Function 內參數的形參為 指標的第一個元素，但有幾個點需要注意


        1. 陣列宣告時，用陣列名稱宣告記憶體
        
            ```cpp=
            int main() {

                int array[5] = {
                        1, 2, 3, 4, 5
                };

                cout << "array: " << array << endl;

                cout << "&array[0]: "<< &array[0] << endl;
            }
            ```
            > ![](https://i.imgur.com/XFrmezh.png)

        2. 使用 sizeof 關鍵字來計算陣列大小，代表了整個陣列的大小 (`Byte 為單位`)
            
            ```cpp=
            int main() {

                int array[5] = {
                        1, 2, 3, 4, 5
                };

                cout << "sizeof(array): "<< sizeof(array) << endl;
                
                cout << "sizeof(&array): "<< sizeof(&array) << endl;
            }
            ```
            > ![](https://i.imgur.com/HivsHiB.png)

        3. **如果使用 `&` 配合 `陣列宣告時的 Symbol`，代表的是整個陣列的地址**，所以 `&陣列 + 1` 是加了整個陣列的大小
            
            ```cpp=
            int main() {

                int array[5] = {
                        1, 2, 3, 4, 5
                };

                cout << "&array: "<< &array << endl;

                cout << "array + 1: "<< array + 1 << endl;    // 指標前進一個

                cout << "&array + 1: "<< &array + 1 << endl;    // 加 20 Byte
            }
            ```
            > ![](https://i.imgur.com/wpRvsI9.png)

    :::

* 函數中陣列的大小計算：傳入函數後的陣列形參，它代表的是一個陣列的 Header 指標，不能使用 sizeof 算出整個陣列的大小，**所以陣列大小必須另外傳入**

    ```cpp=
    void printArray(int array[], int len) {
        // 只能算出指標 int* 的大小
        cout << "sizeof(array): "<< sizeof(array) << endl;    

        for(int i = 0; i < len; i++) {
            if(i == 2) {
                array[i] = 3333;
            }
            cout << array[i] << endl;
        }
    }
    ```
    > ![](https://i.imgur.com/uSIrSNM.png)

### 二維 Array & Pointer

* 二維陣列的第二個元素必須要給予值，基本型態為 `int *[<長度>]`，並且同樣可以使用指標操控

    ```cpp=
    #include <iostream>

    using namespace std;

    int sum(int array[][3], int size) {
        int res = 0;

        for(int i = 0; i < size; i++) {

            for(int j = 0; j < 3; j++) {
                res += *(*(array + j) + i);        // 使用 指標 操控
    //			res += array[i][j];                // 一般 Array 寫法
            }
        }

        return res;
    }


    int main() {

        int array[][3] = {
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9}
        };

        cout << sum(array, 3) << endl;

        return 0;
    }
    ```
    > ![](https://i.imgur.com/V0nifu0.png)


## Pointer


### Pointer & Const

這邊就不再提及 Pointer & Const 的一般情節，來看看 C++ 中 Pointer & Const 的差異之處

```cpp=
// 一般的三種情況

int value = 200;
const int *pV = &value;        // 指向值不可改
int const * pv = &value;        // 同上


int * const pv = &value;        // 指標指向不可改

const int * const pv = &value;        // 指標指向不可改、值也不可改
```

* **C++ 禁止將非 const 指標，指向 const 數值** (可以使用 `const_cast` 運算子改變)

    ```cpp=
    void invalidConst() {

        const int value = 200;
        int * pV = &value;

        cout << *pV << endl;
    }
    ```
    > ![](https://i.imgur.com/axOORgK.png)
    
    :::info
    C++ 對於 const 的檢查會比較嚴格，但這也是一個好處，避免資料被意外修改
    :::

### Pointer & Function

* 只要知道函數的原型後，就可以透過指標，指向該類型函數

    函數指標基本使用

    ```cpp=
    #include <iostream>

    using namespace std;

    int sub(int, int);        // 函數原型宣告

    int sub(int a, int b) {
        return a - b;
    }

    int main() {

	    int (*method)(int, int);

        method = sub;

        // 兩種寫法都可以
        cout << "method(10, 4): " << method(10, 4) << endl;

        cout << "*method(50, 4): " << (*method)(50, 4) << endl;

        return 0;
    }
    ```
    > ![](https://i.imgur.com/0Q1DUam.png)

    * C++ 有另外一個常用的特性，就使用區域變數的 **auto 自動推導**，auto 也可以使用在 函數指標

        ```cpp=
        int main() {
            auto method = sub;        // 自動推導類型

            cout << "method(10, 4): " << method(10, 4) << endl;

            cout << "*method(50, 4): " << (*method)(50, 4) << endl;

            return 0;
        }
        ```
        
        :::danger
        * 自動推導只能跟單一值賦予使用，不能推倒超過一個值
        :::

## C++ 特色函數

C++ 有 C 語言中沒有的特色函數類型

### 內嵌函數 - inline function

* 一般函數在呼叫時會進入靜態 Stack 中，過程如下

    1. 將當前位置 PC 紀錄到 LR 暫存器中，LR 原本的數值進入 SP 暫存器

    2. 複製函數需要的實際引數到 Stack 上

    3. 執行函數
    
    4. SP Pop 出上一個指令位置到 LR，而 LR 原本數據給予 PC 暫存器，PC 繼續執行函數位置計數


* 從一般的函數呼叫可看得出來，記憶體的 **跳轉會導致系統負擔**，而內嵌函數則不需跳躍記憶體，但 **inline 要付出更多的記憶體當代價**

    > **inline 最好使用在程式碼較少的地方** (建議 1 ~ 2 行)

* inline 是否適用，這由編譯器自己判斷，如果它覺得該 **函數過於龐大，不符合效益它就會使用記憶體轉跳**
    > sayHelloWorld() 這個 func 會**複製到每個呼叫它的地方**
    > 
    > ![](https://i.imgur.com/5GZWyJ7.png)


    ```cpp=
    #include <iostream>

    using namespace std;

    inline int sum_3(int a, int b, int c) {
        return a + b + c;
    }

    int main() {

        cout << sum_3(1, 2, 3) << endl;

        return 0;
    }
    ```
    > ![](https://i.imgur.com/nSyr5n6.png)

### Reference - 變數
:::success
關於 Reference 可以想做一個變數、對象的別名，它並 **不會產生一個新的地址**，這與指標相向，但又不全等於指標
:::

* 如果用在變數，不管是參數還是變數，都代表了別名，其地址是相同的

    ```cpp=
    #include <iostream>

    using namespace std;

    void expressRef() {
        
        int apple = 100;
        int& refApple = apple;

        cout << "apple: " << apple <<
                ", &address of apple: " << &apple << endl;

        cout << "refApple: " << refApple <<
                ", &refApple of apple: " << &refApple << endl;
    }
    ```
    > ![](https://i.imgur.com/uTLaTdV.png)

* Reference 必須在宣告時就指定其數值，不可以在另外指定，這有點類似 const 修飾 Pointer，不讓指標肆意更動

    ```cpp=
    void refInit() {
        int apple = 100;
        int& refApple = apple;

        int* const ptrApple = &apple;        // 使用 Pointer 表達類似於 ref 的方法
    }
    ```

    :::warning
    * 如果沒有初始化，則會編譯失敗

        > ![](https://i.imgur.com/9BqSEa2.png)
    :::


* Reference 又類似於自帶 `*` 解析指標符號的表示法，如果透過 ref 修改了變數，則會修改到原數值 (這應該不意外，因為它指向了源數據的地址)

    ```cpp=
    void refChangeValue() {
        int apple = 100;
        int& refApple = apple;

        refApple = 90;

        cout << "apple: " << apple << endl;
        cout << "refApple: " << refApple << endl;
    }
    ```
    > ![](https://i.imgur.com/SfzoT5a.png)

### 暫時變數、Reference、const

* C++ 有規定暫時變數的產生時機是，**++`實際引數` & `Reference` 引數不符合時++**，就會產生 **暫時變數**；以當前來講就是 函數為 const 的 Reference 時

    > 並不是每個形參都會產生暫時變數

    ```cpp=
    int refConst(const int& a) {
        return a * a;
    }

    void temporaryVal() {
        int a = 3;
        int aRes = refConst(a);            // 不產生暫時變數

        int b_ref = a;
        int bRes = refConst(b_ref);            // 不產生暫時變數

        int* c_ptr = &b_ref;
        int cRes = refConst(*c_ptr);            // 不產生暫時變數

        // ------------ 以下由於實際參數 & Reference 形參不同型態，所以都會產生暫時變數
        long d_long = 3.9L;
        int dRes = refConst(*c_ptr);

        int eRes = refConst(5.0);

        int fRes = refConst(10 + 5.0);
    }
    ```
    :::info
    * 暫時變數的釋放 ?

        由編譯器 Compiler 自動幫我們完成
        
    * 為何限制 const reference 形參 ?

        因為 const reference 形參代表其值在函數內絕對不會被修改，所以可以大膽放心使用暫時變數，否則可能造成有要修改傳入參數的函數失敗
        
        
        ```cpp=
        void swaper(int &a, int &b) {    // 收到的變成暫時變數
            int tmp = a;
            a = b;
            b = a;
        }

        int main() {
            long a = 10.0L, b = 20.0L;
            swaper(a, b);    // 由於型態不符合，導致產生暫時變數，最終交換的是暫時變數 !
            return 0;
        }
        ```
    :::
    
    
### Reference 傳遞 & 返回

1. Reference 傳遞：使用 Reference 如同指標，可以修改原來傳入的資料

    ```cpp=
    #include <iostream>

    using namespace std;

    struct ClzInfo {
        string name;
        int studentCount;
    };

    void show(const struct ClzInfo & clz) {
        cout << "Class name: " << clz.name <<
                ", student count: " << clz.studentCount << endl;
    }

    void merge(struct ClzInfo & target, struct ClzInfo & clz) {
        target.studentCount += clz.studentCount;

        clz.name = "Non";
        clz.studentCount = 0;
    }

    int main() {

        struct ClzInfo clz_a {"A Class", 10};
        struct ClzInfo clz_b {"B Class", 9};

        merge(clz_a, clz_b);

        show(clz_a);
        show(clz_b);

        return 0;
    }
    ```
    > ![](https://i.imgur.com/NWR2YV1.png)

2. 回傳 Reference：如果需要操控回傳的數據，就可以回傳 Reference，而是否需要視情況而定
    
    :::success
    回傳的 Reference 實際上就是原來數據的別名 (類似指標地址，但是帶有了 `*` 符號)
    :::
    ```cpp=
    struct ClzInfo & merge_2(struct ClzInfo & target, struct ClzInfo & clz) {
        target.studentCount += clz.studentCount;

        clz.name = "Non";
        clz.studentCount = 0;

        return target;
    }

    int main() {

        struct ClzInfo clz_a {"A Class", 10};
        struct ClzInfo clz_b {"B Class", 9};
        struct ClzInfo clz_c {"C Class", 11};

        merge_2(merge_2(clz_a, clz_c), clz_b);

        show(clz_a);
        show(clz_b);
        show(clz_c);

        return 0;
    }
    ```
    > ![](https://i.imgur.com/H9Pg2HD.png)

    :::danger
    * 避免回傳暫時變數 (local) 的指標、Reference，因為區域變數分配在 Stack 上

        ```cpp=
        struct ClzInfo & badAction_1(struct ClzInfo & clz) {
            struct ClzInfo clz_x {"X Class", 10};
            return clz_x;
        }

        struct ClzInfo& badAction_2() {
            struct ClzInfo clz_xx {"XX Class", 10};

            struct ClzInfo & res = clz_xx;

            return res;
        }
        ```
    :::
    
* 如果不希望回傳的 Reference 被修改，可以使用 const 對其修飾

    ```cpp=
    const struct ClzInfo & merge_3(struct ClzInfo & target, struct ClzInfo & clz) {
        target.studentCount += clz.studentCount;

        clz.name = "Non";
        clz.studentCount = 0;

        return target;
    }

    void test3() {
        struct ClzInfo clz_a {"A Class", 10};
        struct ClzInfo clz_b {"B Class", 9};
        struct ClzInfo clz_c {"C Class", 11};

        merge_3(merge_3(clz_a, clz_c), clz_b);    // ERR

        show(clz_a);
        show(clz_b);
        show(clz_c);
    }
    ```

    > ![](https://i.imgur.com/mrUdd7s.png)

### 預設參數

* C++ 對於函數的參數，可以有預設值，當然該預設值是可以覆蓋的；^1.^ 預設值的順序是由左至右，^2.^ 預設值不可在沒預設值之前

    ```cpp=
    void default_params_valid_1(int a, int b = 10, int c = 20);
    void default_params_valid_2(int a = 10, int b = 20, int c = 30);

    void default_params_invalid_1(int a = 10, int b, int c = 10);
    void default_params_invalid_2(int a, int b = 10, int c = b + 10);
    ```
    
    :::warning
    只有 **函數原型** 可以設置預設值，物件導向來說就是 Class 對象中的函數
    :::

    > ![](https://i.imgur.com/HnkrXIh.png)

### 函數重載 - Overloading

* 在 C 語言中，函數的名稱是唯一，就算是接收參數數量不同也不可以有相同名稱

    ```c=
    void Hello() {
    }

    void Hello(int offset) {    // Error: redefine
    }
    ```
    
* C++、Java 語言則可以進行函數重載，函數重載的判斷標準為 **函數簽名 (signature)**

    ```cpp=
    #include <iostream>
    #include <string>

    using namespace std;

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

    void Hello(int offset) {
        char res[] = "Hello";

        string s(res + offset);

        cout << s << endl;
    }

    int main() {
        Hello();

        Hello(1);

        return 0;
    }
    ```
    > ![](https://i.imgur.com/jwCjaOv.png)

    * **函數簽名 (signature)** 細節：
        
        1. 可以判斷 指標、Reference 是否是 const 參數

            ```cpp=
            // ------------------------------------ Pointer
            void Hello(const int* offset) {
                char res[] = "Hello";

                string s(res + *offset);

                cout << s << endl;
            }

            void Hello(int* offset) {
                char res[] = "Hello";

                string s(res + *offset);

                cout << s << endl;
            }


            // ------------------------------------ Reference
            void Hello(const int& offset) {
                char res[] = "Hello";

                string s(res + offset);

                cout << s << endl;
            }

            void Hello(int& offset) {
                char res[] = "Hello";

                string s(res + offset);

                cout << s << endl;
            }
            ```
            :::warning
            * Reference 重載會跟 原來沒修飾的 const 數據 衝突

                ```cpp=
                void Hello(const int& offset) {    // 兩者的簽名相同
                    char res[] = "Hello";

                    string s(res + offset);

                    cout << s << endl;
                }

                void Hello(int offset) {
                    char res[] = "Hello";

                    string s(res + offset);

                    cout << s << endl;
                }
                ```
            :::

        2. **無法判斷返回值的不同**，必須要參數值不同

            ```cpp=
            int Hello(int a, int b) {
                return a + b;
            }

            long Hello(int a, int b) {        // 無法判斷不同返回值
                return a + b;
            }
            ```            
            > ![](https://i.imgur.com/ZQYh93p.png)

### 一般函數樣板 - 隱式實體化

* 我們知道 Java 有泛型，用來簡易化抽象 (不示範嘞~ )；而 C++ 也有泛型，只是表示的方式不同，C++ 泛型的關鍵字有兩種 (任意種都可以)
    
    1. `template<typename [t]>`：推薦使用 typename

        ```cpp=
        template<typename T>
        void _swap(T &t1, T &t2) {
            T tmp = t1;
            t1 = t2;
            t2 = tmp;
        }
        ```
    
    2. `template<class [t]>`

        ```cpp=
        template<class T>
        void _swap2(T &t1, T &t2) {
            T tmp = t1;
            t1 = t2;
            t2 = tmp;
        }
        ```
        
* **隱式實體化**：由函數自動推定；呼叫時可以直接使用 泛型函數

    ```cpp=
    #include <iostream>

    using namespace std;

    template<typename T>
    void _swap(T &t1, T &t2) {
        T tmp = t1;
        t1 = t2;
        t2 = tmp;
    }

    int main() {

        int a = 10, b = 20;
        cout << "Before a: " << a << ", b: " << b << endl;
        _swap(a, b);
        cout << "After a: " << a << ", b: " << b << endl;

        return 0;
    }
    ```
    > ![](https://i.imgur.com/lIiC3ry.png)



### 特定化模板 - explicit 顯示

* 特定化 (顯示化) 函數會覆寫一般的樣板，如果有符合的限定函數呼叫，就會進入特定化函數 (請見下方範例)

    :::info
    可以當成指定某個泛型要如何操作
    :::

* 特定化 (顯示化) 泛型的 **原型宣告、實現，前置都必須有 `tamplate<>` 關鍵字**，並 **提及特定化的型態名稱 !**

    ```cpp=
    struct Hello {
        string say;
	    int offset;
    };

    // 1. 一般函數原型宣告 (非模板函數)
    void __swap(struct Hello &, struct Hello &);

    // 2. 模板函數
    template <typename T>
    void __swap(T &t, T &t);

    // 3. 特定化模板
    template <> void __swap<struct Hello>(struct Hello &, struct Hello &);
    ```

* 範例，交換 Hello 結構中的 offset，但不交換 say

    ```cpp=
    #include <iostream>

    using namespace std;

    struct Hello {
        char say[10];
        int offset;
    };

    // 一般模板 (泛型)
    template <typename T>
    void __swap(T &, T &);

    // 一般模板 (泛型) - 實現
    template <typename T>
    void __swap(T &t1, T &t2) {
        T tmp = t1;
        t1 = t2;
        t2 = tmp;
    }

    // 特定化模板 (泛型)
    template <> void __swap<struct Hello>(struct Hello &, struct Hello &);

    // 特定化模板 (泛型) -  實現
    template <> void __swap<struct Hello>(struct Hello &h1, struct Hello &h2) {
	    cout << "--------- explicit ---------" << endl;
        // 指交換 offset 成員，不交換 say 成員
        int tmp = h1.offset;

        h1.offset = h2.offset;
        h2.offset = tmp;
    }

    void show(struct Hello &h) {
        cout << h.say + h.offset << endl;
    }

    int main() {

        struct Hello h1{"Hello", 1};
        struct Hello h2{"World", 2};

        cout << "Before swap: " << endl;
        show(h1);
        show(h2);

        __swap(h1, h2);        // 交換過後 offset 數值不同

        cout << "After swap: " << endl;
        show(h1);
        show(h2);

        return 0;
    }
    ```
    > ![](https://i.imgur.com/PZAN2Ri.png)


### 實體化模板 - explicit 顯示

:::success
`實體化模板` 與 `特定化模板` 最大的不同是，template 不需要 `<>` 符號
:::

* **顯式**：由函數自動推定；呼叫時可以直接使用 泛型函數
    1. 使用時直接指定需要的模板
    
        ```cpp=
        #include <iostream>

        using namespace std;

        template <typename T>
        T _add(T a, T b) {
            return a + b;
        }

        int main() {

            int a = 10, b = 20;
            cout << _add<long>(a, b) << endl;

            return 0;
        }
        ```
        > ![](https://i.imgur.com/jbTScZC.png)

    2. 先宣告需要實體化的樣板格式，在後面使用時，對應到相對的格式，就會使用特定化樣板

        ```cpp=
        template double _add<double>(double, double);

        int main() {
            
            int c = 10, d = 20;
            cout << _add(c, d) << endl;        // 隱式實體化

            double e = 10.5, f = 20.7;        
            cout << _add(e, f) << endl;        // 顯式實體化

            return 0;
        }
        ```
        > ![](https://i.imgur.com/tGBmWY3.png)


## Appendix & FAQ

:::info
:::

###### tags: `C++` `C/C++`
