<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取代
但其實在之後實作許多資料結構都會需要用到指標
而且學習指標也有助於對程式運作的理解