# C語言程式設計講義
[](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;
}
```

#### 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
*/
```