<style> .mkd { background-color: rgba(0, 150, 250, .2); /* .4 => 40% */ /*自行選顏色*/ /* <span class="mkd"> 213</span> * */ } </style> # 複合型別 - 指標 pointer & 參照 reference ## 複合型別 compound type 複合型別是以其他的型別定義一種型別,指標和參照就是C++的其中兩個複合型別 <!-- 我們知道宣告的組成是一個<span class="mkd"> 基礎型別(base type)</span> 後面接著一個<span class="mkd">宣告器(declarators)</span> 對於變數的宣告我們可以理解成:一條宣告語句有一個基礎型別加上一串宣告器 --> 基礎型別: ```cpp= int val; ``` 複合型別: ```cpp= int *val; ``` ## 指標 pointer 指標是<span class="mkd">指向</span>另一種型別的複合型別。 指標本身就是一個物件,可以被指定和拷貝。 一個指標也不一定要被初始化,就像其他內建型別,指標若未被初始會,就會有未定義的值。 定義一個指標的方式是寫出<span class="mkd">&d</span>這種形式的宣告,d是被宣告的名稱 ### 取用物件地址 一個指標存放另一個<span class="mkd">記憶體地址(address)</span> 我們可以用<span class="mkd">取址(address-of)運算子(&)</span> 將地址指定到指標變數中 ```cpp= int val = 23; int* ptr = nullptr; //將p定義為int*型別 ptr = &val; //p存有val的地址 cout << ptr; //輸出一串記憶體地址 例如0x6ffe14 ``` :::warning 想想下面code哪裡錯: ```cpp= int val = 23; int* p = val; ``` :::spoiler 解答 注意型別 p為int* val為int 型別不同無法編譯 ::: ### 指標狀態 指標中的值(地址)可能為下列四種狀態的其中之一: 1. 指向一個物件 ```cpp= int a = 1; int *p = &a; ``` 2. 空指標 ```cpp= int *p1 = nullptr; //c++11新增 int *p2 = NULL; int *p3 = 0; ``` 3. 無效指標 ```cpp= int *p //指向記憶體中的某個任意物件 ``` 所以如果你在這時對p操作 就會出現不可預期的錯誤 ```cpp= *p = 23; ``` ### 存取地址物件 當一個指標指向一個物件,也就是存著一個記憶體地址 我們可以用<span class="mkd">反參照(dereference)運算子(*)</span> 來存取物件 ```cpp= int val = 23; int* ptr = &val; cout << *ptr << '\n';//用*取出記憶體地址的物件 輸出23 ``` ### 其他小觀念 ```cpp= int *ptr1, *ptr2; //ptr1,ptr2都是int* int* ptr3,ptr4; //ptr3是int* ,ptr4是int(宣告器沒有複合型別) int ptr5,*ptr6; //ptr5是int ,ptr6是int* ``` ### 動態記憶體 在資料量不固定時就可以需要動態的來配置記憶體 最基本的語法: ```cpp= int *p = NULL; p = new int[3];//配置記憶體給p使p成為一個陣列 delete []p;//釋放記憶體 ``` 如果不想手寫動態記憶體,也可以用STL中的[vector](/HXOQQPLsTFWbllos6fiepA) vector ## 參照(參考) reference 此處的參照都是左值參照,右值參照有機會在介紹 平常我們初始化一個變數,初始器的值會被拷貝到創建的物件 而參照是將參照和他的初始值<span class="mkd">繫結(bind)</span>在一起,不是拷貝初始值,一初始化一個參照就會持續繫結他的初始物件,你無法將一個參照重新繫結到其他物件。所以參照也必須被初始化。 參照不是一個物件,只是已經存在的物件的<span class="mkd">別名</span> <span class="mkd">所以當我們在對參照進行運算時,實際上都是在參照所繫結的物件上進行</span> 定義一個參照的方式是寫出<span class="mkd">&d</span>這種形式的宣告器,d是被宣告的名稱 ```cpp= int val = 23; int &r = val; //r參照至val(r是val的一個名稱) r = 5; //把2指定給r所參照的物件val cout << val; //輸出5 ``` 參照是繫結的一個物件,所以當我們以參照作為初始器時,實際上使用的也是參照繫結的物件 ```cpp= int val = 23; int &r = val; int a = r; //將a初始化為和val相同的值 ``` :::warning 想想下面的code哪裡錯: ```cpp= int &r2; ``` :::spoiler 解答 一個參考必須被初始化 :::success ::: ### 參照在函式的用處 使用函式時我們知道程式傳遞引數的方式是<span class="mkd">傳值呼叫(call by value)</span> 編譯器是將要傳入函式的引數複製一份給函式用 所以在函式裡改值並不會引響到原先變數的值 ```cpp= #include<bits/stdc++.h> using namespace std; void f(int a){ a = 20; return; } int main(){ int a = 10; f(a); cout << a << '\n';//輸出10 } ``` 那如果我們想要更改原先變數的值要怎麼做呢 讓函式的變數變成參照型態 這樣函式中使用的變數就只是原先變數的一個<span class="mkd">別名</span>而已 ```cpp= #include<bits/stdc++.h> using namespace std; void f(int &a){ a = 20; return; } int main(){ int a = 10; f(a); cout << a << '\n';//輸出20 } ``` ### 其他小觀念 ```cpp= int a = 10, b = 20; int &r1 = a,r2 = b; //r1是a的參照 ,r2是int(宣告器沒有複合型別) int& r3 = a,&r4 = b; //r3、r4都是參照 ``` ## 課後問題 :::info 有些符號有多個意義,例如&和*被用作宣告的一部份,但在運算式中也作為運算子 下面的程式碼有*和&的不同用法 可以自己思考看看它們分別的功能是什麼 ```cpp= int val = 23; int &r = val; int *ptr = nullptr; ptr = &val; *ptr = i; int &r2 = *ptr; ``` :::spoiler 解答 ```cpp= int val = 23; int &r = val; //&在型別後,是宣告的一部份 int *ptr; //*在型別後,是宣告的一部份 ptr = &val; //&在運算式中為取址運算子 *ptr = i; //*在運算式中為反參考運算子 int &r2 = *ptr; //&是宣告的一部份,*是反參考運算子 ``` ::: ## 小結 雖然這樣看起還指標好像沒什麼用 好用的動態記憶體控制也可以直接用vector取代 但其實在之後實作許多資料結構都會需要用到指標 而且學習指標也有助於對程式運作的理解