# **2018 Homework1 你所不知道的C語言**
- [為什麼要深入學習 C 語言?](https://hackmd.io/s/HJpiYaZfl)
- [x] [直播 - 開發工具和規格標準篇](https://www.youtube.com/watch?v=scLFY2CRtFo)
- [你所不知道的 C 語言: 指標篇](https://hackmd.io/s/HyBPr9WGl#)
- [x] [直播 - 指標篇 (上)](https://www.youtube.com/watch?v=G7vERppua9o&feature=youtu.be)
- [x] [直播 - 指標篇 (下)](https://www.youtube.com/watch?v=Owxols1RTAg&feature=youtu.be)
- [你所不知道的 C 語言: 函式呼叫篇](https://hackmd.io/s/SJ6hRj-zg )
- [你所不知道的 C 語言: 遞迴呼叫篇](https://hackmd.io/s/rJ8BOjGGl)
- [你所不知道的 C 語言: 前置處理器應用篇](https://hackmd.io/s/S1maxCXMl)
- [你所不知道的 C 語言: goto 和流程控制](https://hackmd.io/s/B1e2AUZeM)
- [你所不知道的 C 語言: linked list 和非連續記憶體操作](https://hackmd.io/s/SkE33UTHf)
- [你所不知道的 C 語言:技巧篇](https://hackmd.io/s/HyIdoLnjl)
- [GNU/Linux 開發工具](https://hackmd.io/c/rJKbX1pFZ)
## *為甚麼要深入學習C語言
- ### Note :
1. #### 讀規格書可大幅省去臆測 ( 拋開過去的學習習慣 !!! )
- [C99規格書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)
2. #### `&` 不要念成 and,涉及指標操作的時候,要讀為 "address of",是一種operator
- 參閱 : **C99 標準 [6.5.3.2]**
3. #### 如果你沒辦法用英文來解說 C 程式的宣告,通常表示你不理解!
- 安裝 `cdecl` 程式,可以幫你產生 C 程式的宣告 ( :+1: )
- 有時候用中文的翻譯在看解釋名詞時,會發覺跟程式功能本意有落差,所以盡量能用英文去理解就用英文去理解,否則有時候會產生一些不必要的誤會,甚至造成自己困擾。
4. #### 只用 printf 觀察資料,有問題嗎 ?
- 只用 printf() 觀察的話,永遠只看到你設定的框架 (format string) 以內的資料,但很容易就忽略資料是否合法、範圍是否正確,以及是否看對地方。
- ### 心得 :
老實說我是第一次修到需要上課前先觀看線上課程影片的,然而大學時期毫無扎實的實作經驗,讓我聽課聽得很痛苦,因為幾乎完全沒有什麼共鳴,不過想說既然大學都這樣過了,而現在念到了研究所自然就要來還債,記得第一天老師上課就強調,要誠實的面對自己,承認自己與他人的差距,所以要更加努力地趕上進度,在大學時期在修有關程式語言方面的課程時,我完全不知道規格書是什麼東西,不知道語法如何使用,就只會try and error,編譯過了後就很開心,但也沒有實際的去了解,到底為什麼編譯不過,而為什麼這樣編譯會過,直到上了老師的課,提到了規格書C99這東西,才發覺原來C語言也有使用手冊這東西 ? 好奇去打開規格書後密密麻麻的全都是英文的文字,此刻才發現又要還大學英文很差的債了 ...,簡單來說,上完老師的課後,我感覺到自己好像沒學過程式語言似的 ...,雖然說有部分原因也是因為大學太混,不過也因如此讓我更加清楚明白自己的不足,而藉此提供了一個方向讓自己去努力邁進,我知道會很辛苦,但我會盡我所能的學好學滿。
---
## *你所不知道的 C 語言: 指標篇 (上)
- ### Note :
1. C 語言的用途 : 開發 UNIX 作業系統。
2. C 語言能充分掌握硬體架構。
3. 要說 C 語言是一種程式語言,不如說它是一種思維方式,
是一種文化,而且是UNIX的文化,甚至可說是UNIX的母語。
4. C 語言永遠只有 **call-by-value** (傳值) 。
5. 在 C 語言的物件就指在執行時期,==資料==儲存的區域,可以明確表示數值的內容。
6. 注意 "scalar type" 這個術語,日後我們看到 `++`, `--`, `+=`, `-=` 等操作,都是對 scalar (純量)。
**複習 :** ++ a 和 a ++ 的不同
- **++ a :** 會先將變數的值加 1 再指派 。
```click=
int x,a=1 ;
x = ++a ;
```
**➨ 結果 a=2 , x=2**
- **a ++ :** 會先將變數的值指派再加 1 。
```click=
int x,a=1 ;
x = a++ ;
```
**➨ 結果 a=2 , x=1**
7. 純量只有大小,它們可用數目及單位來表示 ( 例如溫度=30度C )。純量遵守算數和普通的代數法則。
**注意**:純量有「單位」(可用 sizeof 操作子得知單位的「大小」),假設 ptr 是個 pointer type,對 ptr++ 來說,並不是單純 ptr = ptr + 1,而是遞增或遞移 1 個「**單位**」。
8. 沒有「雙指標」只有「指標的指標 ( a pointer of a pointer )」
9. C語言沒有真正的 陣列 / 矩陣,意思就是說,數學上不成立的 C 語言可以成立。
- ### Pionter ( 指標 )
- **指標可描述為 :** 只要我給你一個圖片網址,你可以藉由這個網址拿到這張圖片,而這個網址就是指向這個資料的「指標」。換句話說,原來資料的地址就是指標嘛 !!!
- **說明 :**
參考以下程式碼 :
```clike=
viod main() {
int a = 2 ;
int b = 4 ;
int c = 6 ;
}
```
在宣告好變數、程式開始執行後,會去記憶體中要一塊儲存空間,然後把 2 這個資料放進去記憶體中。而記憶體就像一個大櫃子,每個格子都有相對應 的地址,而這個2 的地址就是在記憶體中的某一個地方。但我們說過,記憶 體中一個格子的大小是 1 個 byte,而一個 int(整數型)的大小就占了 4個 byte,所以這邊寫的地址,是 2 這個整數所占的這一塊記憶體空間的 起始地址。從起始地址開始起算,共佔了 4 個格子,也就是 4 byte。也就是說,當我們宣告一個變數時,總共會有三個要素:
1.變數位址 (記憶體位址) 、 2.變數值 (2) 、 3.變數名稱(a)
程式會向記憶體要一塊空間來儲存變數值,所以這個儲存空間有一個起始位址。再加上這個變數的名稱(a)與變數值(2)。通常我們會把變數的位址,稱為「指向該變數的指標」。在這邊需要特別注意的是,我講的是「指標」,而不是「指標變數」,這兩個是不同的東西。
拿一開始舉的例子來說,我們可以發現,變數位址就是圖片網址,變數值就是圖片,而圖片的名稱就是變數名稱。
- **運算符號介紹 :**
1. **`&` :** **address of** ( 取得某資料位址 )
2. **`*` :** **valus of** ( 從某位址中取出某值 )
- **說明 :**
參考以下程式碼 :
```clike=
#include <stdio.h>
int main(){
int a = 2;
printf("變數a的值為: %d\n",a);
printf("變數a的值為: %d\n",&a);
printf("變數a的值為: %d\n",*&a);
//printf("變數a的值為: %d\n",*a);
return 0; }
```
**輸出的結果為 :**
變數a的值為:2
變數a的值為:某記憶體位址
變數a的值為:2
**說明 :**
由程式碼可知第7行很明顯是編譯不過的,因為value of operator是用來從某地址中取出某值,而我們定義a是一個整數,而不是位址,故編譯失敗。
且由程式碼我們可以發現,第4行跟第6行的執行結果是相同的,因為第6行是先由address of operator先取得a的資料位址,然後value of operator再由剛剛取得的資料位址中取出a的值,故輸出結果會跟第4行相同,由此現象我們也可以說「*&a」和「a」的意義是相同的。
- ### Pionter varible ( 指標變數 )
- **指標(Pionter)和指標變數(Pionter varible)的差別** :
- 指標(Pionter) : 某變數的位址
- 指標變數(Pionter varible) : 用來存放指標(某變數的位址)的變數
- 整理 :
一般來說,變數都是用來儲存一個「值」的,比如說整數型變數 int 就是存整數、字元型變數 char 就是存字元。所以這個指標變數就是用來存「地址」的變數。
- **要怎麼去宣告一個指標變數呢 ? ➜ 採用「*」這個運算符號。**
```clike=
int* pinoter ; //「*」表示這變數是個指標,pionter表示這個變數的名稱
或
int *pionter ; //「*」表示這變數是個指標,pionter表示這個變數的名稱
```
- **參考以下程式碼**
```clike=
int a = 2 ;
a = 2 ;
int *pionter ;
pinoter = &a ;
```
==" Hint " : 這邊要注意,第四行的程式碼絕對不能寫成 **pionter = a ;**
因為pionter是專門放位址的變數,故要在a面前加一個&去抓他的位址。
如此一來,我們就稱「指標變數 pointer 指向了變數 a」==
- **參考以下程式碼**
```clike=
int a = 2 ;
int *pionter = &a ;
```
會發生這件事情 :
![](https://i.imgur.com/iTOqRWT.png)
也就是說變數 a 在記憶體中對應了一塊儲存空間,而這塊儲存空間總有一個起始的地址。
所以 pointer 對應到的就是這個起始地址。在這種狀況下,就可以用 * pointer 來拿到這個變數。這裡的「 * 」,和宣告指標變數的 int* pointer 的意義不太一樣。反而是和「&」相對應的「&」代表「取出地址」、「*」代表「取出內容」。
- **討論:** 那所謂的「*pointer 取出的內容」指的到底是變數 a、還是變數 a 的值 2?
這兩個是不同的東西,變數 a 是這塊區域,2 是個值。
**Ans**:*pointer 代表的就是變數 a。所以我們可以把 *pointer 當作變數 a 來使用。
- **參考以下程式碼**
```clike=
#include <stdio.h>
int main(void){
int a = 2;
int* pointer = &a;
printf("變數 a 的值:%d\n", a);
printf("變數 a 的地址:%p\n", &a);
printf("pointer 的值:%p\n", pointer);
printf("指標變數 pointer 的位址:%p\n", &pointer);
*pointer = 100; // 因pointer = &a,故*pointer = a 的意思
printf("*pointer 的值:%d\n", *pointer);
printf("變數 a 的值:%d\n", a);
printf("指標變數 pointer 的地址:%p\n", &pointer);
return 0; }
```
**輸出結果為 :**
變數 a 的值:2
變數 a 的位址:0x7fffc32af124
pointer 的值:0x7fffc32af124
pointer 的位址:0x7fffc32af128
*pointer 的值:100
變數 a 的值:100
變數 pointer 的地址:0x7fffc32af128
**討論 :**
我們可以看到一開始的變數 a 的值被設定為 2,所以印出來也會是 2。然後用「&a」取出變數 a 的地址為「0x7ffffea4b894」。
由於 &a 的值被賦予給 pointer,所以把 pointer 印出來後同樣也是 「0x7ffffea4b894」,而pointer這個指標變數當然也需要一個記憶體位址去儲存&a這個地址,而儲存&a這個地址的記憶體位置為「0x7fffc32af128」。
因我們說過 *pointer 就是其指向的變數 a,所以在這邊我們試著把 *pointer 中的值改成 100,然後印看看原有的變數 a 會不會跟著改變,之後我們發現 2 被改成 100 了。
最後,由於存放 pointer 這個變數的地址,和變數 a 的地址不一樣,所以利用「&pointer」後,可發現地址「0x7ffffea4b898」和變數 a 的地址當然不同。
:::info
*Quastion:
想請問老師,想不太通為甚麼&pointer後的地址會改變的原因,是因為a的值變了嗎 ? a的值變了後他還是維持原本的記憶體位址嗎 ? 還是他就固定用那塊記憶體區域儲存100這個值呢 ?
懇請老師解惑,謝謝老師 ...
*更新 :
是否因為pionter這個指標變數也需要記憶體去存取位址,故&pointer的位置才會跟a的記憶體存放位置不同 ? ( 已經了解 )
:::
> 以上引用自 : [C語言 : 超好懂的指標,初學者請進~](https://kopu.chat/2017/05/15/c%E8%AA%9E%E8%A8%80-%E8%B6%85%E5%A5%BD%E6%87%82%E7%9A%84%E6%8C%87%E6%A8%99%EF%BC%8C%E5%88%9D%E5%AD%B8%E8%80%85%E8%AB%8B%E9%80%B2%EF%BD%9E/)
>
- ### Pionter to Pionter ( 指標的指標 )
- **延續以上討論 :**
如果在上例中,要儲存 pionter 這個指標變數的記憶體位址,也就是
「 0x7fffc32af128」 這個值,那麼如何作?由於 pionter 是個 int* 型態變 數,如同 int 變數必須宣告 int* 指標,所以 int* 型態變數就必須宣告 int** 型態的指標,例如:
```clike=
int **ptr2 = &pionter; //ptr2為用來儲存pionter指標變數的位址
```
注意 : 指標的指標不能翻譯為 double pionter。
- **考慮以下程式碼 :**
```clike=
#include <stdio.h>
int main(void) {
int p = 10;
int *ptr1 = &p;
int **ptr2 = &ptr1;
printf("變數p 的值:%d\n", p);
printf("變數p的記憶體位置:%p\n\n", &p);
printf("指標變數 ptr1 的值 = %p\n", ptr1);
printf("指標變數 ptr1 的記憶體位置:%p\n", &ptr1);
printf("*ptr1 = %d\n", *ptr1);
printf("指標的指標變數 ptr2 的值 = %p\n", ptr2);
printf("指標的指標變數 ptr2 的位址 = %p\n", &ptr2);
printf("*ptr2 = %p\n", *ptr2);
printf("**ptr2 = %d\n", **ptr2);
return 0; }
```
**輸出結果 :**
變數p 的值:10
變數p的記憶體位置:0x7fff9a0066fc
指標變數 ptr1 的值 = 0x7fff9a0066fc
指標變數 ptr1 的記憶體位置:0x7fff9a006700
*ptr1 = 10
指標的指標變數 ptr2 的值 = 0x7fff9a006700
指標的指標變數 ptr2 的位址 = 0x7fff9a006708
*ptr2 = 0x7fff9a0066fc
**ptr2 = 10
**討論 :**
由程式碼我們可以知道,指標變數 ptr1 儲存了P的位址,指標的指標變數ptr2 儲存了 ptr1 的位址,而我們利用 a value of ( * ) 去取值可以發現,因 ptr1 儲存了 P 的位址,故 *ptr1 就為 P 的值,同樣的,因 ptr2 儲存了 ptr1 的位址,故 *ptr2 就為 ptr1 的值,而我們在對 *ptr2 做一次 a vaule of 的運算也就是 **ptr2 後可以發現,**ptr2 就為 P 的值。
> 以上引用自 : [雙重指標](https://openhome.cc/Gossip/CGossip/DoublePointer.html)
>
- ### Struct ( 結構 )
在設計程式的過程中,經常遇到一組變數需要宣告在一起,比如說學號、姓名、性別、年齡、地址、成績等變數,全都是用來描述一個學生,有時候我們就想要把這組變數綁在一起、讓它看起來更像是一體的,使變數之間的關聯變得更直接,C 語言裡面有一個辦法能做到,叫 strutct (結構),在這組變數前面加上 struct、用大括號包起來。
- **舉個例子 :**
```clike=
struct student{ //名稱為student的結構
int id; //學號為整數型
char name[8]; //姓名為字元陣列
char sex; //性別為字元型
int age; //年齡為整數型
char addr[30]; //地址為字元陣列
float score; //成績為浮點型
}
```
現在開始,我們已經成功利用結構、宣告了一個新的資料類型「student」。在這邊必須強調的是,此處的「student」並不是一個變數,而是一種資料型態。也就是說這個 student 和 int, float, char 等資料類型一樣,是一種「用來宣告變數」的資料型態。所以說,結構 (struct) 就是一種由使用者自訂之資料型態。在這邊我們自己定義了一種資料型態叫「student」,裡面包括了 int、chat、float 等型態。
- **宣告結構變數**
宣告的方式,和我們之前宣告 int 變數和 float 變數、char 變數的方法都一樣,前面放「型別」、後面放「變數名稱」即可。
**例如 :**
```clike=
struct student Amy, John; //宣告兩個新變數Amy跟John為結 構形式
```
或者我們也可以直接把變數宣告在結構的後面
```clike=
struct student{
int id;
char name[8];
char sex;
int age;
char addr[30];
float score;
}Amy, John;
```
- **參考以下範例 :**
```clike=
#include <stdio.h>
#include <ctype.h> //引入字元測試與轉換函數標頭檔
struct student{
int id;
char name[10];
};
void new_student(struct student new_one){
new_one.id = 1000 + new_one.id;
for(int i = 0; new_one.name[i] != '\0'; i++){ // '\0' 為空字元
new_one.name[i] = toupper(new_one.name[i]); //把所有字元改為大寫
}
printf("新學生id:%d\n", new_one.id);
printf("新學生姓名:%s\n", new_one.name);
};
int main(void) {
struct student john = {291, {'j', 'o', 'h', 'n', '\0'}};
new_student(john);
printf("\n");
printf("學號:%d\n", john.id);
printf("姓名:%s\n", john.name);
}
```
**執行結果 :**
新學生id:1291
新學生姓名:JOHN
學號:291
姓名:john
**討論 :**
我們同樣利用 struct 建好一個叫 student 的型別,接下來在 main 函數中宣告一個 student 變數 john,並給他一個初始值:291, john。
接下來把 john 這個變數當成一個參數,給 new_student 這個函數。
在 new_student 函數裡面,我又宣告了一個新的參數名稱叫 new_one, 然後用 new_one 來接受傳進來的 john 的值。
接下來就利用 new_one 來調整原有 id 和 name 變數的值。最後把 new_one 的值印出來。
因為 new_student 這個函數是 void 型,所以不用回傳 main function 任何值。
最後再把原本的 john id和 name印出來,會發現和 new_one 完全不同。
由於 main 函數把 john 複製了一份,給 new_student 裡面的 new_one,所以接下來對 one 做的任何修改都不會影響到主函數 main() 裡面的 john。
因為它們已經是兩個獨立的變數了。到最後 new_one 印 new_one 的、 john 印 john 的,不會互相干擾。這個程式驗證了一個結論,把一個結構 變數當作參數、傳遞給一個函數的時候,實際上是把這個變數複製一份傳遞給這個函數。
- ### Link list ( 鏈結串列 )
-