# C語言程式設計講義 [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fhackmd.io%2FlJLg-L32TOGtPYmZD1c4cw&count_bg=%233DC8A9&title_bg=%230F0F0F&icon=googlesheets.svg&icon_color=%23F7EEEE&title=%E4%BB%8A%E6%97%A5%E7%80%8F%E8%A6%BD%E6%AC%A1%E6%95%B8%2F%E7%B8%BD%E7%80%8F%E8%A6%BD%E6%AC%A1%E6%95%B8&edge_flat=false)](https://hits.seeyoufarm.com) ### 1. 程式概念 #### 1.1 程式語言 程式語言就像是「指令」,透過我們的設計來讓電腦執行我們想要做的事情。 不過,電腦沒有聰明到能直接讀懂C語言,所以我們必須通過**編譯器**(1.2)來讓它讀懂並運行。 #### 1.2 編譯器 因為電腦沒辦法直接看懂程式語言,所以我們需要透過編譯器轉換成機器語言讓電腦能理解。 那我們常用到的C語言編譯器是**gcc**(GNU Compiler Collection): ``` file.c(原檔案) -> (use gcc) -> 機器看得懂的語言 ``` 以上這行白話一點就是:file.c檔案利用gcc編譯成機器能理解的語言。 ``` vim test.c //創建test.c檔案,並輸入程式 gcc test.c -o test //利用gcc編譯 ./test //執行test ``` --- ### 2. 你的第一個程式 - hello world #### 2.1 簡介 ```cpp= #include <stdio.h> int main() { printf("hello world\n"); } ``` 上面這段程式碼或標題,想必大家應該都不陌生。 是的,在步入每一種語言的第一步,大多數的老師都會利用這個當作範例教學。 那這段程式碼的功能呢,就是能**印出** *hello world*,並**換行**。 程式語言是這樣的,套一句劉邦鋒老師說的話,程式語言就是由很多個「片語」組成,每個片語都有它自己的功能。 那麼我們就先一步一步來剖析整個程式的架構吧! #### 2.2 include及標頭檔 第一行的 ```#include <stdio.h>``` 是每一個C語言程式必備的開頭。 那我們來了解一下名詞及功用吧! ```#include``` : 會將系統程式庫的「標頭檔」引入你的程式中,有點類似把你所需要的功能引進。 ```<stdio.h>```: 即為「標頭檔」,將「標準輸入輸出(scanf, printf)」此項功能引進,讓程式運作。 所以當編譯器遇到printf()函數時,如果沒有找到 **<stdio.h>** 這個標頭檔,就會發生編譯錯誤。 那其他常用的標頭檔還有: ```<math.h>``` : 提供用於常用高級數學運算的運算函數。 e.g.: pow(), sqrt(), sin(), ...。 ```<string.h>```:用於字串處理。e.g.: strchr(), memcpy(), ...。 #### 2.3 程式基礎框架 ```=cpp int main() { } ``` **main**的意思是「主函式」,代表最一開始的函式會從這裡開始運行。 {}(大括號)在C語言裡非常重要,以這個最基礎的框架來說明,上大括號({)是代表一個函式的開始,下大括號(})是代表一個函式的結束,也就是說,**main函式只會執行在大括號內的程式碼。** #### 2.4 結尾 因為main函式的資料型態是 **int** 所以需要有一行return與之匹配,那**return 0** 同時也代表退出程式。 --- ### 3. 變數及運算 #### 3.1 變數(variable) 1. 變數是用來**存放資料**的。 2. 使用變數前必須先**宣告變數**。 3. 當我們在**宣告**變數前要先了解宣告變數的資訊。 **1.型態是什麼?** 當我們在存取不同型態的資料,宣告的方式也會不同,e.g.: * int -> 整數 * double/float -> 浮點數(小數)...精準度不同 * char -> 字元(字母) * bool -> 布林變數(true / false) ...等等,還有許多範圍不同的型態,e.g.: * **2.變數名稱** 名稱是可以隨意取的(只要符合變數命名規定就好),不過如果以後再開發大型專案,每個團隊對於變數名稱的規則不太一樣,主要是方便維護、看的舒適。 **3.位址 (取自 劉邦鋒老師 由片語學習C程式語言)** 一個變數是存放在電腦的記憶體裡,而電腦的記憶體是由位址來 存取,所以一個變數就有一個相對應的記憶體位址。 **4.實例** ```=cpp int number = 2; //宣告一個整數型態的變數number,並賦予值2 char name = 'k'; //宣告一個字元型態的變數name,並賦予值'k' ``` 注意: * 宣告字元必須使用 ' ' '。 * 每行結束分號是必備的。 #### 3.2 輸出 當我們利用程式運算完後,想要將結果輸出,就要利用 **printf** 函式。 ```cpp= int i; //宣告一個整數i printf("%d", i); //輸出一整數i ``` 雙引號"代表字串,而我們變數的位置可以在雙引號裡面調整: ```cpp= int i = 20; printf("I am %d years old.", i); printf("I have %d girlfriends", i); /*輸出 I am 20 years old. I have 20 girlfriends. */ ``` 那 **%d** 這個區塊就是來判別我們要輸出什麼資料型態。 以下我來舉幾個特別常見的例子: * %d : 顯示整數(**十進位**,d是decimal的意思)。 * %o : 顯示整數(**八進位**,o是octal的意思)。 * %x : 顯示整數(**十六進位**,x是heximal的意思)。 * %c : 顯示**字元**。 * %f : 顯示**浮點數**(小數) ...等等,若有需要之後會提到一些比較特別的資料型態以及進位運算。 細節部分 : 1. **%.2f**(指定輸出至小數點二位)、**%3d**(總共輸出至整數第三位,不足的地方會用空白補齊)...。 2. 若有多個「參數」,則用逗號隔開。 3. **\n** 是用來讓輸出「換行」的。 實例: ```cpp= #include <stdio.h> int main() { int number = 10; char name = 'k'; float pi = 3.14; printf("%d\n", number); //整數 printf("%o\n", number); //八進位 printf("%x\n", number); //十六進位 printf("%c\n", name); //字元 printf("%f\n", pi); //浮點數 printf("%d %f", number, pi); //有兩個參數 return 0; } /*輸出: 10 12 a k 3.14 10 3.14 */ ``` #### 3.3 輸入 當我們要輸入數字或字元進去程式內運算時,我們會利用 **scanf** 函式。 至於用法基本上都跟輸出一樣,不過在後面參數的前面需要加上 **&**,為什麼呢?之後指標的章節會解釋(如果有上到)。 不過可以偷偷說一下,**&** 代表的是變數的位址。 ```c= int i; scanf("%d", &i); //輸入一個資料型態為整數的i ``` 這樣我們在編譯後執行就能從終端機輸入i的值,並把它輸出。 如果我們要輸入多個數的話,我們可以這麼做。 ```c= int i, j, k; //是的,可以一次宣告多個變數,只要用逗號隔開。 scanf("%d %d %d", &i, &j, &k); ``` #### 3.4 運算 基本五則運算 : 加(+)、減(-)、乘(*)、除(/)、餘(%) 餘(%)可能對大家是生面孔,正如其名,%是用來取餘數的。 a%b 代表的是 a 除以 b 後得到的餘數。 ```c= #include<stdio.h> int main() { int a = 3, b = 6, c = 2; printf("%d\n", a+b); //加 printf("%d\n", b-a); //減 printf("%d\n", a*b); //乘 printf("%d\n", b/a); //除 printf("%d\n", a%c); //餘 } /*輸出 9 3 18 2 1 */ ``` 多舉幾個例子好了。 ``` 16%3 = 1 32%3 = 2 15%4 = 3 ``` 那搭配上面所講的資料型態,我們可以發現幾件事: ```c= int i = 15, j = 2; double i_2 = 15.0, j_2 = 2.0; printf("%d", i/j); printf("%f", i_2/j_2); /*輸出: 7 7.500000 */ ``` 溫度轉換 ```c= #include <stdio.h> int main() { float f, c; scanf("%f", &c); f = c*(9.0/5.0)+32; printf("%.2f", f); return 0; } ``` 在C語言中,整數運算過程會無條件捨去小數點以下的部分,所以7.5的0.5會被捨棄。 不過當我們用浮點數下去進行運算,我們就可以順利獲得我們所需要的小數(浮點數)。 接下來要提到的是遞增(+)、遞減(--)運算子,簡而言之,他們就是讓運算子能夠「+1或-1」,舉例來說: ```c= int a_1 = 1, a_2 = 1; a_1++; a_2--; printf("%d %d", a_1, a_2); /*輸出: 2 0 */ ``` 會發現結果是 a_1+1, a_2-1 ,所以我們可以大概分析成 : ``` a_1++ = a_1 + 1; a_2-- = a_2 - 1; ``` 不過**遞增/遞減運算子**只能放在變數後面嗎?不!其實也是可以放在前面的,舉例說明 : ```c= int a_1 = 1, a_2 = 1; ++a_1; --a_2; printf("%d %d", a_1, a_2); /*輸出: 2 0 ``` 其實你會發現**遞增/遞減運算子**放在前後結果一模一樣,那他們相同嗎?No!,我們來比較一下: ```c= int a = 7, b = 7; printf("%d\n", ++a); printf("%d", b++); /*輸出: 8 7 */ ``` 嗯?怎麼這裡就不一樣了,不是應該要輸出8和8嗎?其實不同的點在於「先後」順序。 * 第二行的執行順序是:(1)a=a+1 (2)printf。 * 第三行的執行順序是:(1)printf (2)b=b+1。 所以我們能利用拆解順序的方式來了解。 白話一點的講法就是: * a++ -> 會先執行整個敘述後再將a的值加1。 * ++a -> 先把a的值加1,再執行整個敘述。 那接下來要講**指定運算子**,很簡單,我們直接看範例。 ```c= #include<stdio.h> int main() { int a = 8, b = 8, c = 8, d = 8, e = 8; a += 3; //其實就是 a = a + 3; b -= 3; // b = b - 3; c *= 3; // c = c * 3; d /= 3; // d = d / 3; e %= 3; // e = e % 3; printf("%d %d %d %d %d", a, b, c, d, e); /*輸出 11 5 24 2 2 } ``` #### 3.s 兩數交換 假設我們目前有兩個數 a 和 b,並想要它們兩個之間的數交換,那我們應該要怎麼做呢? 直觀的做法 : ```c= int a = 2, b = 3; a = b; b = a; printf("%d %d", a, b); /*輸出 3 3 ``` 你會發現兩個變數並沒有因此交換,因為第二行表示**a被指派b的值**,此時a的值從2變成3,第三行則是**b被指派a的值**,此時b的值從3變成3,所以會輸出3 3。 不過其實很簡單,我們利用**另外一個變數**先儲存a的值,再讓a被指派b的值,之後再讓b被指派**另外一個變數(同時也是a)** 的值就可以了。 ```c= int a = 2, b = 3, tmp; //tmp也就是另外一個變數 tmp = a; //tmp先儲存a的值 a = b; //a被指派b的值 b = tmp; //b被指派tmp的值(也就是a的值) printf("%d %d", a, b); /*輸出 3 2 */ ``` --- ### 4.選擇結構 #### 4.1 if架構 ```c= if(特定條件1) { 程式區塊... } else if(特定條件2) { 程式區塊... } else { 程式區塊... } ``` 白話一點,也就是**滿足特定條件1才會執行if大括號內的程式區塊,若沒有滿足特定條件1而有滿足特定條件2的話則執行else if大括號內的程式區塊,兩個特定條件都不滿足則執行else的。** #### 4.2 關係運算子 當我們在做**選擇判斷**時,會需要關係運算子來幫忙。 那其實關係運算子就跟我們數學的關係運算子只有一點點差別 : 大於(>)、小於(<)、大於等於(>=)、小於等於(<=)、等於(==)、 不等於(!=)。 沒錯,就是**等於跟不等於**最特別,不過在C語言裡面,就是如此。 #### 4.3 邏輯運算子 有**且(AND / &)、或(OR / |)、非(NOT / !)**。 如果你想要a條件和b條件同時滿足再執行程式區塊的話,可以寫成這樣 : ```c= if(a && b){ 程式區塊... } ``` 如果你想要a條件和b條件只要有一個滿足就執行程式區塊的話,可以寫成這樣 : ```c= if(a || b){ 程式區塊... } ``` #### 4.4 巢狀if 白話來說,就是**if裡面再包if**。 ```c= if(特定條件1){ if(特定條件2){ 程式區塊1... } else{ 程式區塊2... } } ``` * 滿足特定條件1也**滿足**特定條件2,會執行程式區塊1。 * 滿足特定條件1但**不滿足**特定條件2,執行程式區塊2。 #### 4.5 switch switch的架構: ```c= switch(變數名稱或運算式) { case 符合數字或字元: 程式區塊1; break; case 符合數字或字元: 程式區塊2; break; default: 程式區塊3; break; } ``` 直接以例子來看,架設今天有一個整數型態的變數叫**grade**,如果**grade**等於1就輸出A,等於2就輸出B,其他數字就輸出C。 ```c= #include<stdio.h> int main() { int grade; scanf("%d", &grade); switch(grade) { case 1: printf("A"); break; case 2: printf("B"); break; default: printf("C"); break; } return 0; } ``` --- ### 5.迴圈 到目前為止,我們都是重複地做**單一的動作**,那如果我們想多做幾次該怎麼做呢?多寫幾遍嗎? 其實C語言裡面有很方便的**迴圈結構**可以用,它能重複性的執行程式內容,來簡化整個程式。 #### 5.1 for迴圈 for迴圈的架構 : ```c= for(初始化動作;執行條件;遞增/減動作){ 程式內容; } ``` 首先我們進行**初始化動作**。 再來每次跑迴圈之前,都會確認一次執行條件,**若符合,則持續進行迴圈動作,若不符合,則跳出迴圈。** 通常使用for迴圈,都是基於我們知道使用次數,例如,當我們想輸出五次 *hello world*。 用已知所學寫法: ```c= printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); printf("hello world\n"); ``` 貌似還能完成我們所要求的任務,那如果是請你輸出100次 *hello world* 呢?有耐心複製貼上的話其實也是可以完成,不過我們寫程式就是要要求簡潔有力,這時候我們就可以利用我們的 **for迴圈** 。 ```C= for(int i=0;i<100;i++){ printf("hello world\n"); } /*輸出 hello world*100 */ ``` 第一段,我們宣告i為**計數器**,並賦予它0的值,這裡要注意,它是在迴圈內被宣告的,所以出了迴圈外後i便不能被使用。 第二段,**在i<100這個條件成立時,迴圈會持續執行,那當條件不成立(ex:i=100)時,便會跳出迴圈。** 第三段,每當迴圈執行一次,i便會加1,**i++** 就像我們前面所介紹的一樣。 那問題來了,每次i的值都會變嗎?以例子來看好了: ```c= for(int i=0;i<3;i++){ printf("%d ", i); } /*輸出 0 1 2 */ ``` 會發現其實 i 的值是有跟著變動的,而且正如我們所寫的 **i++** 一樣,那一定每次只能加1嗎?No!其實可以自己設定的,能加也能減,不過一定都會呈現**遞增或遞減**。 ```c= for(int i=0;i<5;i+=2){ printf("%d ", i); } printf("\n"); //換行 for(int j=4;j>=0;j--){ printf("%d ", j); } /*輸出 0 2 4 4 3 2 1 0 */ ``` 範例: 輸入一個正整數n,並輸出1~n的總和。 ```c= #include<stdio.h> int main() { int n, sum = 0; //n是輸入的值,sum是總和(須先歸零) scanf("%d", &n); //輸入n for(int i=1;i<=n;i++){ sum = sum + i; } printf("%d\n", sum); } /* 輸入: 5 輸出: 15 */ ``` #### 5.2 while迴圈 while迴圈的基本架構: ```c= while(條件){ 程式內容; } ``` **先測試條件,再執行敘述** 意思是只要條件成立,就會一直執行敘述。 所以 while 迴圈的使用時機為:達成某個條件時就一直做某件事情的時候。 範例: 輸出1~10。 ```c= int i=1; //宣告i是整數1 while(i<=10){ printf("%d ", i); i++; } /*輸出 1 2 3 4 5 6 7 8 9 10 */ ``` 那如果你想要設立當某個條件成立時就停止時,可以利用**break**指令,而相對的,如果你要讓他繼續運行的話,就利用**continue**指令。 範例: 從1數到10,如果遇到8就跳出迴圈。 ```c= #include<stdio.h> int main() { int i=1; while(i<=10) { printf("%d ", i); if(i == 8) break; i++; } return 0; } /*輸出 1 2 3 4 5 6 7 8 */ ``` #### 5.3 do-while **會先執行一次迴圈的敘述部分,再測試條件並決定是否繼 續執行迴圈,所以迴圈的敘述部分至少會執行一次。** while架構: ```c= do{ 程式內容; }while(條件) ``` 範例: 計算一個正整數的位數。 ```cpp= #include<iostream> int main() { int digits = 0, n; printf("輸入一個正整數:"); scanf("%d", &n); do { n /= 10; digits++; } while (n>0); printf("這個整數是 %d 位數\n", digits); return 0; } /* 輸入: 230 輸出: 這個整數是 3 位數 */ ``` 如果我們單純使用 while(n>0) 的迴圈的話,那輸入 0 會顯示 0 位數,但答案應該是 1 位數,這時候使用 do-while 迴圈就可以解決這個問題了。 #### 5.4 雙重迴圈 if能夠有if包if,那for迴圈也能包for迴圈變雙重迴圈,那這裡的重點就是**最裡面的迴圈最先執行。** 用一個範例來說明: ```c= for(int i=1;i<=2;i++){ for(int j=1;j<=2;j++){ printf("%d %d", i, j); printf("\n"); } } /*輸出: 1 1 i=1,j=1 1 2 i=1,j=2 2 1 i=2,j=1 2 2 i=2,j=2 */ ``` 至於**三層迴圈**、**四層迴圈**...也跟雙重迴圈是一樣的道理。 --- ### 6.陣列 #### 6.1 一維陣列 變數只能儲存一個值,那當我們想儲存很多**相同型態且用途相近**的資料時怎麼辦呢? 這時候我們就可以利用**陣列**。 簡單的介紹一下,陣列是一種資料結構,可以儲存相同資料型態的變數。如此一來,我們就不需要宣告一堆變數名稱,只要有一個陣列就可以搞定。 宣告: ```c= int num[10]; //宣告一個整數型態的陣列,叫做num,大小是10。 ``` 由上述可知,在變數名稱後面我們會利用 **[]** ,來宣告陣列的大小 文言一點,便是: ``` 資料型態 陣列名稱[大小]; ``` 那型態其實也跟宣告變數一樣。 陣列的值跟變數一樣都是可以先宣告的,例如: ```c= int num[5] = {1, 2, 3, 4, 5}; char c[5] = {'a', 'b', 'c', 'd', 'e'}; int special_array[3] = {0}; //這個是比較特別的宣告方法,能把裡面的元素都宣告為0 ``` 那我們要先了解資料存入陣列後他們的位置,其實你可以把陣列想成分了很多格的箱子,而每隔都有編號,而編號就是 **name[n]** 裡面的 **n**,而且n是從0開始數的,所以一個大小只有10的陣列,n是0~9,例如: ```c= #include<stdio.h> int main(){ int number[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} printf("%d %d %d", number[0], number[5], number[10]); } /*輸出: 1 6 亂數 */ ``` 我們來看number[n] : * n = 0 -> number[0] = 1 * n = 5 -> number[5] = 6 * n = 10 -> number[10] = 亂數 因為當n=10時已是第11個數,但我們的大小只有10,所以並不知道那裡放了些什麼數(指標會更深入講解),因此在宣告時要小心! 那輸入也跟輸入變數一樣,利用**scanf**函數即可。 範例: 輸入10個數,並利用陣列儲存起來。 ```c= int number[10]; for(int i=0;i<10;i++){ //i:0~9 scanf("%d", &number[i]); //輸入編號0~9的值 } ``` 範例: 輸入五科成績,並輸出平均。 ```c= #include<stdio.h> int main(){ float grade[5], sum = 0.0, avg; //五科成績分別存在grade[0~4], sum是總和, avg是平均 for(int i=0;i<5;i++){ scanf("%f", &grade[i]);//輸入五科成績 sum = sum + grade[i]; } avg = sum / 5; printf("%f\n", avg); } /*輸出 30 20 30 10 50 28.000000 */ ``` #### 6.2 二維陣列 如果要大量儲存同一種型態、而且彼此又有密切關係的「表格式」資料,例如數學中的矩陣,這時候就能將其宣告並設定為「二維陣列」。 可以把二維陣列想像成一個平面的**表格或棋盤**,用來儲存資料。 宣告範例: ``` 資料型態 陣列名稱[大小][大小] ``` 例如新增一個 2x3 大小的二維陣列: ```c= int number[2][3]; char ch[2][3]; ``` 賦予值我們可以這樣寫: ```c= int num[2][3] = {{1, 2, 3}, {4, 5, 6}}; char ch[2][3] = {{'a', 'b', 'c'}, {'d', 'e', 'f'}}; int special_array[2][3] = {0}; ``` 如果還是不太理解,我們直接把他拆開來看: ```c= int num[2][3] = {{1, 2, 3}, {4, 5, 6}}; for(int i=0;i<3;i++){ printf("%d ", num[0][i]); printf("\n"); for(int j=0;j<3;j++){ printf("%d ", num[1][j]); } return 0 ; } /*輸出: 1 2 3 4 5 6 */ ``` 可以發現其實我們可以用**行跟列**的方式來理解。 --- ### 7.函式 C 語言中,可以把重複的程式提出來寫成函式(function),而需要的時候直接呼叫這個函式就好,也就是模組化的概念。 什麼叫模組化?簡單來說,就是把特定功能分出來當成一個模組,需要的時候只要呼叫這個模組就好;而需要修正的時候也只要修正模組即可。 架構: ```c= 型態 函式名稱 ``` 例如,我們可以把**輸出hello world**寫成一個函式,格式如下: ```c= #include<stdio.h> void printhello(){ printf("hello world"); } int main() { printhello(); return 0; } /*輸出: hello world */ ``` 第一,**函式要寫在主函式(main)外**,第二,**最好寫在主函式下方**,第三,**函式可以呼叫很多次**。 那其實函式也有分型態,甚麼意思呢? 我們先來看函式的架構: ```c= 資料型態 函式名稱(參數) { 程式內容 } ``` 那代表函式也有所謂的"資料型態",那其實也只是分**是否有回傳值**。 * 有回傳值 : int, bool, ...。 * 沒回傳值 : void 。 那有回傳的資料型態我們要怎麼判斷呢? 由**回傳值**判斷,若回傳值是一個整數,那函式的資料型態就會是**int**,如果是布林值,則資料型態就會是**bool**...依此類推,那回傳我們會利用**return**。 那沒回傳值只是單純要跑函是內的程式呢?我們就會利用上面出現過的**void**來宣告資料型態。 我們舉一個例子,今天我們寫一個a+b的函式,也就是傳入a與b,並回傳a+b。 ```c= #include<stdio.h> int add(int a, int b) { return a+b; } int main() { int a, b, c; scanf("%d %d", &a, &b); c = add(a, b); printf("%d", c); return 0; } /*輸入= 1 2 輸出= 3 */ ``` 是的,我們必須再使用一個變數去接收函式所回傳的值,也就是我們的**c**。 如果函式中參數名字與主函式不同也沒關係,不過在函式內運算的話還是得利用函式所宣告的參數。 小提醒 : **return**後,後面的程式都不會執行喔! --- ### 8. 指標 指標對於C語言來說非常重要,它可以直接利用記憶體映射的方式直接控制硬體,所以這就是為什麼C語言對於硬體那麼重要的原因。 #### 8.1 位址 我們曾經說過,我們宣告一個變數時,程式會向記憶體要一塊空間來儲存變數值,所以這個儲存空間有一個起始位址。 通常我們會把變數的位址,稱為「指向該變數的指標」。 如果想知道變數的記憶體位址,可以使用 **&** 取址運算子(Address-of operator),例如 : ```c= #include<stdio.h> int main() { int n = 2; printf("n的變數值 = %d\n", n); printf("n的變數位址 = %p\n", &n); return 0; } /*輸出: n的變數值 = 2 n的變數位址 = 0x7fff3d13399c */ ``` 解釋一下名詞 : * 變數名稱 -> n * 變數值 -> 2 * 變數位址 -> 0x7fff3d13399c 也就是說從0x7fff3d13399c開始的下面四個byte都是**n**被分配到的記憶體空間,儲存了2這個值。 直接存取變數會對分配到的空間作存取,指標(Pointer)是一種資料型態,儲存記憶體位址,那如果要宣告指標變數,是使用以下的語法: ```c= 資料型態 *變數名稱 ``` ```c= int *n; //n可以儲存位址 char *c; float *f; ``` *的意思是表示這個變數是指標,後面的名稱依然是變數名稱。 我們可以使用 **&** 運算子取出變數的位址值並指定給指標 : ```c= #include <stdio.h> int main() { int n = 2; int* pointer = &n; printf("變數 n 的值:%d\n", n); printf("變數 n 的地址:%p\n", &n); printf("pointer 的值:%p\n", pointer); printf("\n"); //換行 *pointer = 100; printf("*pointer 的值:%d\n", *pointer); printf("變數 n 的值:%d\n", n); printf("變數 pointer 的地址:%p\n", &pointer); return 0; } /*輸出: 變數 n 的值:2 變數 n 的地址:0x7ffc450e57dc pointer 的值:0x7ffc450e57dc *pointer 的值:100 變數 n 的值:100 變數 pointer 的地址:0x7ffc450e57e0 */ ``` 我們可以發現一開始的n值被指派為2,n的變數位址是0x7ffc450e57dc。 由於 &n 的值被指派給 pointer,所以 pointer 印出來後同樣也是「0x7fff551b49c8」(變數位址)。 由於我們說過 *pointer 就是其指向的變數 n,所以在這邊我們試著把 *pointer 中的值改成 100,然後印看看原有的變數 n 會不會跟著改變。 發現竟然會跟這改變,太神奇了!2 被改成 100 了! 最後,由於存放 pointer 這個變數的地址,和變數 n 的地址不一樣,所以利用「&pointer」後,可發現地址「0x7fff551b49c0」和變數 n 的地址果然不一樣。 #### 8.2 指標與陣列 其實在宣告陣列後,使用到時都是從**陣列開頭**開始的,例如我們宣告一個陣列叫**arr**,那**arr**與 **&arr[0]** 的位址都會是一樣的。 ```c= #include<stdio.h> int main() { int arr[10]; printf("arr : %p\n", arr); printf("arr[0] : %p", &arr[0]); return 0; } /*輸出: arr : 0x7fff5936a1a0 arr[0] : 0x7fff5936a1a0 */ ``` 那其實這裡也有一個重點 : 陣列的[]裡面的數字其實就是陣列位址的位移量。例如 :  ```c= #include <stdio.h> int main() { int arr[3] = {0}; int *p = arr; for(int i = 0; i < 3; i++) { printf("&arr[%d]: %p ", i ,&arr[i]); printf("p + %d: %p\n", i, p + i); } return 0; } /*輸出: &arr[0]: 0x7ffefb3576fc p + 0: 0x7ffefb3576fc &arr[1]: 0x7ffefb357700 p + 1: 0x7ffefb357700 &arr[2]: 0x7ffefb357704 p + 2: 0x7ffefb357704 */ ``` 再舉個簡單的例子: 如果p指向arr[i],則 **p-j** 指向 **arr[i-j]** 那指標其實也能比大小,是依據**記憶體位置**。 ```c= int arr[10]; p = &arr[5]; q = &arr[1]; ``` 則 **p<=q** 的值會是0, **p>=q** 的值會是1。 Ex: ```c= #include<stdio.h> int main() { int a[3] = {2, 3, 4}, sum = 0, *p; for(p=&a[0];p<&a[3];p++) { sum = sum + *p; } printf("%d\n", sum); return 0; } /*輸出: 9 */ ``` 額外 : * *p++ or *(p++) : 取得 *p,將 p+1。 * (*p)++ : 取得 *p,將 *p+1 也是一大重點。 ```c= #include<stdio.h> int main()( { int a[3] = {2, 3, 4}, *p = a, b, c; b = *p++; c = *++p; printf("%d %d", b, c); } /*輸出: 2 4 */ ``` 那我們要如何使用陣列像一個指標呢? 假設我們先宣告一個陣列 : ```c= int arr[10] = {0}; ``` 那我們使用a像個指標 : ```c= *arr = 7; *(arr + 1) = 8; ``` ```c= printf("%d %d", a[0], a[1]); /*輸出: 7 8 */ ``` * 所以 **a+i** 跟 **&a[i]** 一樣。 * *(a+i) 跟 **a[i]** 一樣。 那我們剛剛上面寫的式子可以簡化成 : ```c= for(p=&a[0];p<&a[n];p++) { sum = sum + *p; } for(p=a;p<a+n;p++) { sum = sum + *p; } ``` 寫個練習題吧! 題目 : 輸入五個數字,請輸出它的相反(利用指標陣列) 範例輸入 : 1 2 3 4 5 範例輸出 : 5 4 3 2 1 --- ### 9.遞迴 一個函數在它的函數內使用它自身稱為遞迴使用,這種函數稱為遞迴函數。C語言允許函數的遞迴使用,執行遞迴函數可以反復使用自己,每使用一次就進入新的一層。 Ex1 : 給定a,b兩正整數變數,找出最大公因數。 定義:(a,b)為正整數a與b的最大公因數。 輾轉相除法原理:若a、b、q、r均為正整數,且b≠0,若a=bq+r,則(a,b)=(b,r) ```c= #include <stdio.h> int gcd(int, int); int main(void) { int m, n; scanf("%d %d", &m, &n); printf("GCD: %d\n", gcd(m, n)); return 0; } int gcd(int m, int n) { if(n == 0) { return m; } else { return gcd(n, m % n); } } ``` Ex2 : 給定正整數n,請輸出n! ```c= #include<stdio.h> int f(int n); int main() { int n, ans; scanf("%d", &n); ans = f(n); printf("%d\n", ans); return 0; } int f(int n) { if(n == 0) { return 1; } else if(n >= 1) { return n*f(n-1); } } ``` 個人覺得,遞迴這種東西,拿紙筆出來先從低層數的開始理解,就會很好懂了。 所以說呢,遞迴是需要大量練習才能理解的東西,如果你有興趣繼續進修C語言以及你的邏輯思維,可以試著去網路上找看看**遞迴經典題型**。 --- ### 10. 結構 #### 10.1 基本結構 架構 : ```c= struct 架構名稱{ int ...; char ...; }; ``` 宣告架構 : ```c= struct 架構名稱 變數名稱; ``` Ex : 架設我們要宣告一個架構裡面含有學號(id)跟英文名字(name) ```c= #include<stdio.h> struct student { int id; char name[10]; }; int main() { struct student summerclass = {123, {'k','e','v','i','n', '\0'}}; printf("%d\n", summerclass.id); printf("%s\n", summerclass.name); return 0; } ``` ![](https://i.imgur.com/zpQPCf6.jpg) #### 8.2 指標結構 ```c= #include<stdio.h> struct student { int id; char name[10]; }; int main() { struct student summerclass = {123, {'k','e','v','i','n', '\0'}}; struct student *p = &summerclass; printf("%d\n", p->id); printf("%s\n", p->name); return 0; } /*輸出: 123 kevin ``` 「->」,表示 **p** 這個指標指向的結構變數的內容。 那我們也可以把結構當成函式的參數 : ```c= #include<stdio.h> struct student { int id; char name[10]; }; void add(struct student *p) { p->id = p->id + 100; }; int main() { struct student summerclass = {123, {'k','e','v','i','n', '\0'}}; add(&summerclass); printf("%d\n", summerclass.id); printf("%s\n", summerclass.name); return 0; } /*輸出: 223 kevin */ ``` 把結構變數的位址傳給**add函式**,並在裡面做運算。 那反正都是對位址做運算,就不用在額外傳什麼參數進去了。 --- ### 11.位元運算 參考網站 : [https://openhome.cc/Gossip/CGossip/LogicalBitwise.html](https://) 在我們的邏輯運算子當中,其實 **&(AND)**,**|(OR)**,**^(XOR)**,也能用來進行位元運算。 ```c= #include <stdio.h> int main() { printf("AND運算:"); printf("0 AND 0 %d\n", 0 & 0); printf("0 AND 1 %d\n", 0 & 1); printf("1 AND 0 %d\n", 1 & 0); printf("1 AND 1 %d\n\n", 1 & 1); printf("OR運算:"); printf("0 OR 0 %d\n", 0 | 0); printf("0 OR 1 %d\n", 0 | 1); printf("1 OR 0 %d\n", 1 | 0); printf("1 OR 1 %d\n\n", 1 | 1); printf("XOR運算:"); printf("0 XOR 0 %d\n", 0 ^ 0); printf("0 XOR 1 %d\n", 0 ^ 1); printf("1 XOR 0 %d\n", 1 ^ 0); printf("1 XOR 1 %d\n\n", 1 ^ 1); printf("NOT運算 : "); printf("NOT 0 %d\n", !0); printf("NOT 1 %d\n", !1); return 0; } /*輸出: AND運算:0 AND 0 0 0 AND 1 0 1 AND 0 0 1 AND 1 1 OR運算:0 OR 0 0 0 OR 1 1 1 OR 0 1 1 OR 1 1 XOR運算:0 XOR 0 0 0 XOR 1 1 1 XOR 0 1 1 XOR 1 0 NOT運算 : NOT 0 1 NOT 1 0 */ ``` 位元在運算時,需依資料型態所佔的記憶體長度而定,例如在使用 int 型態的 0 作運算時,要考慮的 是 32 個位元,而不是只有 8 個位元,因為 int 佔有4個位元組。 ```c= #include<stdio.h> int main() { printf("%d", 34&3); return 0; } /*輸出: 2 */ ``` 在位元運算上,C 還有左移(<<)與右移(>>)兩個運算子,左移運算子會將所有的位元往左移指定的位數,左邊被擠出去的位元會被丟棄,而右邊會補上 0;右移運算則是相反,會將所有 的位元往右移指定的位數,右邊被擠出去的位元會被丟棄,至於左邊位元補 0 或補 1 則不一定。 ```c= #include <stdio.h> int main() { int num = 1; printf("2 的 0 次:%d\n", num); num = num << 1; printf("2 的 1 次:%d\n", num); num = num << 1; printf("2 的 2 次:%d\n", num); num = num << 1; printf("2 的 3 次:%d\n", num); return 0; } /*輸出: 2 的 0 次:1 2 的 1 次:2 2 的 2 次:4 2 的 3 次:8 */ ```