Try   HackMD

複合型別 - 指標 pointer & 參照 reference

複合型別 compound type

複合型別是以其他的型別定義一種型別,指標和參照就是C++的其中兩個複合型別

基礎型別:

int val;

複合型別:

int *val;

指標 pointer

指標是指向另一種型別的複合型別。
指標本身就是一個物件,可以被指定和拷貝。
一個指標也不一定要被初始化,就像其他內建型別,指標若未被初始會,就會有未定義的值。
定義一個指標的方式是寫出&d這種形式的宣告,d是被宣告的名稱

取用物件地址

一個指標存放另一個記憶體地址(address)
我們可以用取址(address-of)運算子(&) 將地址指定到指標變數中

int val = 23; int* ptr = nullptr; //將p定義為int*型別 ptr = &val; //p存有val的地址 cout << ptr; //輸出一串記憶體地址 例如0x6ffe14

想想下面code哪裡錯:

int val = 23; int* p = val;
解答

注意型別 p為int* val為int 型別不同無法編譯

指標狀態

指標中的值(地址)可能為下列四種狀態的其中之一:

  1. 指向一個物件
int a = 1; int *p = &a;
  1. 空指標
int *p1 = nullptr; //c++11新增 int *p2 = NULL; int *p3 = 0;
  1. 無效指標
int *p //指向記憶體中的某個任意物件

所以如果你在這時對p操作
就會出現不可預期的錯誤

*p = 23;

存取地址物件

當一個指標指向一個物件,也就是存著一個記憶體地址
我們可以用反參照(dereference)運算子(*) 來存取物件

int val = 23; int* ptr = &val; cout << *ptr << '\n';//用*取出記憶體地址的物件 輸出23

其他小觀念

int *ptr1, *ptr2; //ptr1,ptr2都是int* int* ptr3,ptr4; //ptr3是int* ,ptr4是int(宣告器沒有複合型別) int ptr5,*ptr6; //ptr5是int ,ptr6是int*

動態記憶體

在資料量不固定時就可以需要動態的來配置記憶體
最基本的語法:

int *p = NULL; p = new int[3];//配置記憶體給p使p成為一個陣列 delete []p;//釋放記憶體

如果不想手寫動態記憶體,也可以用STL中的vector
vector

參照(參考) reference

此處的參照都是左值參照,右值參照有機會在介紹

平常我們初始化一個變數,初始器的值會被拷貝到創建的物件
而參照是將參照和他的初始值繫結(bind)在一起,不是拷貝初始值,一初始化一個參照就會持續繫結他的初始物件,你無法將一個參照重新繫結到其他物件。所以參照也必須被初始化。
參照不是一個物件,只是已經存在的物件的別名
所以當我們在對參照進行運算時,實際上都是在參照所繫結的物件上進行
定義一個參照的方式是寫出&d這種形式的宣告器,d是被宣告的名稱

int val = 23; int &r = val; //r參照至val(r是val的一個名稱) r = 5; //把2指定給r所參照的物件val cout << val; //輸出5

參照是繫結的一個物件,所以當我們以參照作為初始器時,實際上使用的也是參照繫結的物件

int val = 23; int &r = val; int a = r; //將a初始化為和val相同的值

想想下面的code哪裡錯:

int &r2;
解答

一個參考必須被初始化

參照在函式的用處

使用函式時我們知道程式傳遞引數的方式是傳值呼叫(call by value)
編譯器是將要傳入函式的引數複製一份給函式用
所以在函式裡改值並不會引響到原先變數的值

#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 }

那如果我們想要更改原先變數的值要怎麼做呢
讓函式的變數變成參照型態
這樣函式中使用的變數就只是原先變數的一個別名而已

#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 }

其他小觀念

int a = 10, b = 20; int &r1 = a,r2 = b; //r1是a的參照 ,r2是int(宣告器沒有複合型別) int& r3 = a,&r4 = b; //r3、r4都是參照

課後問題

有些符號有多個意義,例如&和被用作宣告的一部份,但在運算式中也作為運算子
下面的程式碼有
和&的不同用法 可以自己思考看看它們分別的功能是什麼

int val = 23; int &r = val; int *ptr = nullptr; ptr = &val; *ptr = i; int &r2 = *ptr;
解答
int val = 23; int &r = val; //&在型別後,是宣告的一部份 int *ptr; //*在型別後,是宣告的一部份 ptr = &val; //&在運算式中為取址運算子 *ptr = i; //*在運算式中為反參考運算子 int &r2 = *ptr; //&是宣告的一部份,*是反參考運算子

小結

雖然這樣看起還指標好像沒什麼用
好用的動態記憶體控制也可以直接用vector取代
但其實在之後實作許多資料結構都會需要用到指標
而且學習指標也有助於對程式運作的理解