指標與參考

建電大社 [8] 

by 鹽亞倫

AKA  AaW、忘記要備課的那個講師

先從一個故事開始說起......

很久很久以前,在一個遙遠的森林裡,住著 AaW 和 Brine 兩個人。

有一天,Brine用抓到了一隻雞,他突然突發奇想

如果他拿著一隻雞腿去考數資班,是不是就可以用雞腿換到數資資格,然後他就可以

於是,他要AaW幫他寫一個交換的函數

swap(a, b) 

可以讓他拿雞腿換東西交換兩個變數的內容物

地奧選訓前半 & 雙校隊 & 電爆AaW & 在兩個社團瘋狂炸魚 & 地奧金牌 & 資奧金牌 & 保送台大資工系

所以,要怎麼做?

直接來嗎?

#include <iostream>
using namespace std;

void swap(int a, int b) {
	int temp = a;
	a = b;
	b = temp;
	return;
}

int main() {
	int a = 10;
	int b = 20;
	cout << "before swap:";
	cout << "a = " << a << ", b = " << b << endl;

	swap(a, b);

	cout << "after swap:";
	cout << "a = " << a << ", b = " << b << endl;
	// output result???
}

為什麼沒有換成功?

變數的作用域!!!

變數的作用域

簡單講,一般寫程式分成全域變數和區域變數

全域變數在整個程式中都可以被使用

區域變數只能在同一個變數中使用

例如:在main函數中宣告的韓數只能在main裡面使用

換句話說:
剛剛swap函數裡面的a和b
和主程式裡面的a和b
其實是兩個不同的變數,各自有各自的作用範圍

因此,swap裏面a, b互換並不會換到main裡面的a和b!

也就是說,swap裡面的a,實質上是把main的a存的複製並且存入一個新的記憶體空間新變數

這種函數呼叫方式稱為 call by value

記憶體

所以說,到底變數存在電腦裡是長怎麼樣?

這是一個記憶體的示意圖

其中可以看到,記憶體每個變數區塊都有一個編號,稱為記憶體位置

而值則代表變數實際存的東西

如何知道一個變數的記憶體位置呢?
可以用 取址符號 (&)
例如:要看 x 的記憶體位置,你可以

cout << &x << endl;

這是一個剛剛那份code 記憶體的示意圖

可以看到他其實有兩個區塊

main 和 swap
兩個區塊各自有兩個區域變數 a 和 b

#include <iostream>
using namespace std;

void swap(int a, int b) {
    cout << "in swap, &a = " << &a << " &b = " << &b << endl;
    
    int temp = a;
    a = b;
    b = temp;
    return;
}

int main() {
    int a = 10;
    int b = 20;

    // print the address of a and b
    cout << "in main, &a = " << &a << " &b = " << &b << endl;
    
    swap(a, b);

}

執行這段code來觀察剛剛講的東西

可以看到一樣是 cout << &a;
在swap裡面和main的結果不一樣

也就證明了它並不是同一個變數

指標

pointer

回想一下,如果我們今天在swap函數裡面,

如果我希望直接動到main裡面的變數怎麼辦?

如果我知道main函數的a和b存在記憶體的哪裡,我是不是就可以直接動它!?

把記憶體位置直接傳入函式當中!!!

But How?

指標變數

用來處理剛剛的問題的啦~

在指標變數裡面,存入另一個變數的記憶體位置!

假設我讓一個變數p記錄著x的位置:

所以這個神奇指標型態的p要怎麼用勒?

int *p = &x;

所以 p 的變數型態是

int*

代表 p 是一個整數指標變數

存入 x 的記憶體位置

換句話說,任何型態為 T 的變數 x
我都可以讓 p 為指向 x 的指標

by this :

T* p = &x;

就算 T 本身就是 int* 也可以!

===> 指向指標的指標!

int a = 10;
int *b = &a;
int **c = &b;

好,所以我知道 p 指向 x了
然後勒
要怎麼用 p 去改 x 存的東西?

取值符號 (*)

例如,將 p 指向的東西 +=10
並且輸出

*p += 10;

cout << *p << endl;

所以知道一開始swap要怎麼改了嗎?

傳入記憶體位置,用指標變數去接

因此宣告函數時:

void swap(int *a, int *b);

使用時:

swap(&a, &b);

這種傳入方式我們稱之為 call by address

完整code

#include <iostream>
using namespace std;

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    return;
}

int main() {
    int a = 10;
    int b = 20;
    cout << "before swap:";
    cout << "a = " << a << ", b = " << b << endl;

    swap(&a, &b);

    cout << "after swap:";
    cout << "a = " << a << ", b = " << b << endl;
}

此時的記憶體長相:

參考

reference
也有人叫他參照

不覺得剛剛用指標很煩人很累嗎

我如果只是希望,兩個變數變成完全變成同一個,連用法都一模一樣,有辦法做到嗎?

用參考!!!

宣告:型態& 名稱 = 被參考的變數;

ex.

int& refa = a;

double &rb = b; // &放靠近變數名也可以

要注意的是,這裡的&和剛剛取址符號無關

僅代表我宣告的變數是一個 參考變數

換句話說, refa、refb 的型態不是int,而是 int&

阿宣告完之後要怎麼使用?

直接用!!!

而且對refa做的變動會同步到a!

int a = 10;
int& refa = a;

cout << refa << endl; // 10

refa = 15;
cout << refa << endl; // 15
cout << a << endl;    // 15

a = 25;
cout << refa << endl; // 25
cout << a << endl;    // 25

為什麼會這樣?

熟悉的圖

假如我說 int& c = a;

c應該放哪裡?

c
(int &)

電腦會直接將c和a放在同一個記憶體空間!!!

也就是說以下兩句會有一模一樣的輸出!
cout << &a; // 輸出a的記憶體位置

cout << &c; // 輸出c的記憶體位置

回到最初的swap函數

所以要怎麼寫勒?

#include <iostream>
using namespace std;

void swap(int &a, int &b) { // 改為參考
    int temp = a;
    a = b;
    b = temp;
    return;
}

int main() {
    int a = 10;
    int b = 20;
    cout << "before swap:";
    cout << "a = " << a << ", b = " << b << endl;

    swap(a, b);

    cout << "after swap:";
    cout << "a = " << a << ", b = " << b << endl;
    // output result???
}

成功了!

void swap(int &a, int &b);

之後會常常遇到將物件以參考傳入函數當中

你開心甚至可以把vector用參考傳入函數

這種方式稱為 call by reference

由於使用參考並不需要將記憶體內東西複製,也不用增加新的空間,因此空間時間用量都很好!

所以這個東東一定要會ㄛ~

學習檢測時間!

& 和  *
這兩個符號知多少?

* &
宣告時 代表我要宣告一個
指標變數
ex. int *a = &b;
代表我要宣告一個
 參考變數
ex. int &a = b;
非宣告時 取值符號
代表我要取得指標指向的變數存的值
ex. cout << *a;
取址符號
代表我要一個變數的記憶體位置!
ex. int *a = &b;
ex. cout << &b;

測驗

  1. cout << a;
  2. cout << &a;
  3. cout << b;
  4. cout << *b;
  5. cout << &b;
  1. cout << c;
  2. cout << &c;
  3. cout << *c;
  4. cout << d;
  1. 10
  2. 0X8701
  3. 0X8701
  4. 10
  5. 0x8702
  1. cout << *d;
  2. cout << **d;
  3. cout << &d
  4. cout << &*d
  5. cout << &**d
  1. 10
  2. 0X8701
  3. CE
  4. 0x8702
  1. 0X8701
  2. 10
  3. 0x8703
  4. 0x8702
  5. 0x8701
int a = 10;
int *b = &a;
int &c = a;
int **d = &b;

下課摟

今天東西很硬QAQ
回去好好複習喔~

 

週日秋遊記得來不要忘了ㄚ~