指標、參考

struct

建電社展專案組 [1] 

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;

Struct

把一堆變數包成一個物品

什麼是struct呢?

假設我今天要建立乘一的資料

我可能會要記錄他的身高、體重等等

像是這樣:

int height = 150;
int weight = 90;
int IQ = 10;
int EQ = 15;
long long girlfriend;

可是假設我今天要記錄很多個人呢

假設我要記錄乘一阿蘇、和田鼠的資料

int times1_height = 150;
int times1_weight = 90;
int times1_IQ = 10;
int times1_EQ = 15;

int ivanlo_height = 200;
int ivanlo_weight =60;
int ivanlo_IQ = 180;
int ivanlo_EQ = 180;

int Repkironca_height = 170;
int Repkironca_weight = 70;
int Repkironca_IQ = 180;
int Repkironca_EQ = -200;

要記錄這麼多東西就太亂了

所以這時候struct就發明了

struct的中文稱為結構,可以讓你定義一種新型態的變數,其中包含多項資料

struct person {
    int height;
    int weight;
    int IQ;
    int EQ;
};

int main() {
    person times1;
    times1.IQ = 10;
    cout << times1.IQ << endl;
}

有了struct,你就可以把好幾個變數包成同一個

現在就來詳細的講到底struct要怎麼用吧

  1. 定義
  • 在使用struct時,一開始要先定義這一個struct
  • 要注意的是,這個定義要寫在 main函數外面
  • 宣告方式(記得加分號!):
struct 結構名字 { // 沒有小括號!加了小括號就變成函數了
    // 裡面等著放東西
}; // 要注意這裡有一個分號

// example
struct person {
    
}; 

2. 宣告變數

  • 當我定義完一個struct之後(舉例:people)
  • 我就可以宣告一個屬於此結構的變數了
  • 宣告時,直接把我的結構名當作int之類的東西使用
  • 例如:
struct person {
};

int main() {
    person AaW;
}
struct person {
    int height;
    int weight;
    int IQ;
    int EQ;
};

int main() {
    person times1;
    times1.IQ = 10;
    cout << times1.IQ << endl;
}

回到一開始那一段程式碼

可以看到在struct的定義裡面,我們還放了一些變數

這些在struct內的變數我們就稱為成員變數

所以成員變數的作用是什麼?

  • 剛剛一開始有說,struct的用途就是把多個變數包成一個
  • 也就是說,每一個類別為people的變數,都包含了 height、weight、IQ、EQ這四個變數!
struct person {
    int height;
    int weight;
    int IQ;
    int EQ;
};

int main() {
    person times1;
    times1.IQ = 10;
    cout << times1.IQ << endl;
}

要怎麼使用成員變數呢?

  • 一樣以剛剛的people為例
  • 首先,一樣都是people建立出來的多個變數,彼此的成員變數是不同的變數,可以有獨立的值
    • 簡單來說,雖然都是人,但AaW的身高怎麼會和乘一一樣矮
  • 因此,取用成員變數時,作法為:

例如:

struct people {
    int height, weight, IQ, EQ; 
    // 和宣告一般變數一樣,也可以這樣寫
};
people times1;
cout << times1.IQ << endl;
cout << people.IQ << endl;
// 錯誤!電腦哪知道你要輸出誰的IQ

X

變數名.成員變數名

更多關於struct的語法補充

  1. 自定義型別也可以有指標變數
struct people{
    int IQ, EQ;
    int burned_chicken[100];
}

people AaW;
people* idiot = &AaW;
// 完全合法
  1. 成員變數型態不能和struct一樣,但是可以是指標
  • 你沒辦法在一個people裡面再塞一個people,會無限輪迴啦
struct people{
    people girlfriend; // 不合法
    people* boyfriend; // 合法
    // 變數名稱請忽略www
}
  1. 成員變數也可以是陣列、或甚至STL

更多關於struct的語法補充

-> 運算子

假設你有一個指標times1,你想要輸出他的IQ

你應該會這樣寫

cout << (*times1).IQ << endl;

但是這樣有點麻煩,因此有了以下的用法,和上面完全一樣

cout << times1->IQ << endl;

方法函數

在struct裡面寫函數

//函式寫在struct內部
struct people {
    double height;
    double weight;
    double BMI() { //不用把自己當參數傳入
    	return this->weight / (this->height * this->height); 
        // 預設會有一個指標this指向"自己"
    }
};

int main() {
    people times1;
    times1.height = 1.5;
    times1.weight = 90;
    cout << times1.BMI() << endl; //記得呼叫時前面要加名稱
}

可以看到第六行中,在方法函數內部,可以使用this取得自己的成員變數

但...this是甚麼?_?

剛剛在BMI的程式當中,我們希望取得這個人“自己”的身高及體重等資料

這時候C++預設有一個指標this

會指向呼叫此方法的物件本身。

如果times1.BMI()在執行時this就會指向times1

this

但C++預設使用方法時,本來就有辦法取得自己的成員變數
所以在這裡this其實可以省略

也就是說...

範例

//函式寫在struct內部
struct people {
    double height;
    double weight;
    
    double BMI() { //不用把自己當參數傳進去
        return weight / (height * height); //可以省略this
    }
};

int main() {
    people times1;
    times1.height = 1.5;
    times1.weight = 90;
    cout << times1.BMI() << endl; //記得呼叫時前面要加名稱
}

可以看到第六行中,在方法函數內部,預設就會有辦法取得自己的成員變數

範例

//函式寫在struct外部
struct people {
    double height;
    double weight;
    double BMI(); //要寫在外面要先宣告!
};

double people::BMI() { //一樣不用把自己當參數傳進去
    return weight / (height * height);
}

int main() {
    people times1;
    times1.height = 1.5;
    times1.weight = 90;
    cout << times1.BMI() << endl; //記得呼叫時前面要加名稱
}

方法函數也可以寫在外面(先定義再實作)
這樣在寫多檔案時比較方便

傳參數ㄉ範例

//函式寫在struct內部
struct people {
    void say(string content){ //宣告時裡面加參數
        cout<<"say"<<content<<'\n';
    } 
};

int main() {
    people times1;
    times1.say("hello!"); //記得傳參數進去!
}
//say hello!

實作時間

把一堆變數包成一個物品

乘一是個非常有禮貌的孩子,和別人講話到一半時,常常會去問候對方母親、父親與祖母等親朋好友。但是,身為有品建中人,乘一突然十分好奇一個人的家人是哪些人?因此請你幫忙寫一個程式,方便他問候您的家人

題目輸入:

第一行有一個數字n,代表總共幾個人

接下來有n行,每行有三個英文單字,

依序代表他自己、媽媽以及爸爸的名字,如果沒有爸媽的話則會輸入-1

接下來有一個數字Q代表詢問幾次

再接下來Q行,每行有兩個字

第一個為詢問對象的英文名字

第二個為"MOM", "DAD", "GRANDMA", "GRANDPA"

四個字其中之一,

其中GRANDMA定義為媽媽的媽媽,GRANDPA為爸爸的爸爸

請輸出該位人物姓名,沒有的話輸出"EMPTY"

#include <bits/stdc++.h>
using namespace std;

struct person{
    string name = "_____"; // 預設要設成什麼呢?
    _______ mom; // 請用指標喔
    _______ dad;	
};


int main() {
    map <string, person*> FamilyTree;
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        string who, momName, dadName;
        cin >> who >> momName >> dadName;

        // 這邊是判斷是否為nullptr
        if (FamilyTree[who] == nullptr)     FamilyTree[who] = new person;
        if (FamilyTree[_______] == _______) FamilyTree[_______] = ___ ______; // 媽媽
        if (FamilyTree[_______] == _______) FamilyTree[_______] = ___ ______; // 爸爸
        
        FamilyTree[who]->name = who;
        if (momName != "-1") FamilyTree[who]->mom = FamilyTree[_______], FamilyTree[momName]->name = _______;
        if (dadName != "-1") FamilyTree[who]->dad = FamilyTree[_______], FamilyTree[dadName]->name = _______;
    }

    int q;
    cin >> q;
    for (int i = 0; i < q; ++i) {
        string who, family;
        cin >> who >> family;

        if (family == "MOM") {
            if (FamilyTree[who]->mom != nullptr) {
                cout << FamilyTree[who] ___________ << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "DAD") {
            if (________________________) {
                cout << FamilyTree[who] ___________ << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "GRANDMA") {
            if (FamilyTree[who]->mom != nullptr &&  FamilyTree[who]->mom->mom != nullptr){
                cout << FamilyTree[who] _______________________ << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "GRANDPA") {
            if (_____________________________________________________________){
                cout << FamilyTree[who] _______________________ << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }
    }
}

填空題,來寫寫看吧

#include <bits/stdc++.h>
using namespace std;

struct person{
    string name = "EMPTY"; // 預設要設成EMPTY,若沒媽媽或爸爸才不會出事
    person* mom;
    person* dad;    
};

int main() {
    map <string, person*> FamilyTree;
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        string who, momName, dadName;
        cin >> who >> momName >> dadName;

        if (FamilyTree[who] == nullptr)     FamilyTree[who] = new person;
        if (FamilyTree[momName] == nullptr) FamilyTree[momName] = new person;
        if (FamilyTree[dadName] == nullptr) FamilyTree[dadName] = new person;
        
        FamilyTree[who]->name = who;
        if (momName != "-1") FamilyTree[who]->mom = FamilyTree[momName], FamilyTree[momName]->name = momName;
        if (dadName != "-1") FamilyTree[who]->dad = FamilyTree[dadName], FamilyTree[dadName]->name = dadName;
    }

    int q;
    cin >> q;
    for (int i = 0; i < q; ++i) {
        string who, family;
        cin >> who >> family;

        if (family == "MOM") {
            if (FamilyTree[who]->mom != nullptr) {
                cout << FamilyTree[who]->mom->name << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "DAD") {
            if (FamilyTree[who]->dad != nullptr) {
                cout << FamilyTree[who]->dad->name << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "GRANDMA") {
            if (FamilyTree[who]->mom != nullptr &&  FamilyTree[who]->mom->mom != nullptr){
                cout << FamilyTree[who]->mom->mom->name << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }

        else if (family == "GRANDPA") {
            if (FamilyTree[who]->dad != nullptr && FamilyTree[who]->dad->dad != nullptr){
                cout << FamilyTree[who]->dad->dad->name << endl;
            } else {
                cout << "EMPTY" << endl;
            }
        }
    }
}

答案,不准偷看喔

建電社展專案組[1] 指標、參考、結構與類別

By Aaron Wu

建電社展專案組[1] 指標、參考、結構與類別

  • 193