指標、參考
&
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; |
測驗
cout << a;
cout << &a;
cout << b;
cout << *b;
cout << &b;
cout << c;
cout << &c;
cout << *c;
cout << d;
- 10
- 0X8701
- 0X8701
- 10
- 0x8702
cout << *d;
cout << **d;
cout << &d
cout << &*d
cout << &**d
- 10
- 0X8701
- CE
- 0x8702
- 0X8701
- 10
- 0x8703
- 0x8702
- 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要怎麼用吧
- 定義
- 在使用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的語法補充
- 自定義型別也可以有指標變數
- 指標不熟的可以回去看鹽亞倫的大社簡報,之後會常常用到,要記熟用法喔
struct people{
int IQ, EQ;
int burned_chicken[100];
}
people AaW;
people* idiot = &AaW;
// 完全合法
- 成員變數型態不能和struct一樣,但是可以是指標
- 你沒辦法在一個people裡面再塞一個people,會無限輪迴啦
struct people{
people girlfriend; // 不合法
people* boyfriend; // 合法
// 變數名稱請忽略www
}
- 成員變數也可以是陣列、或甚至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] 指標、參考、結構與類別
- 205