C
From YuntechWiki
(→字串的指標陣列) |
(→堆疊) |
||
Line 1,763: | Line 1,763: | ||
從堆疊刪除一個值稱為跳出堆疊(popping the stack),而新增一個值到堆疊中則稱為推進堆疊(pushing it onto the stack)。 | 從堆疊刪除一個值稱為跳出堆疊(popping the stack),而新增一個值到堆疊中則稱為推進堆疊(pushing it onto the stack)。 | ||
<code c> | <code c> | ||
+ | #define SIZE 10 | ||
+ | |||
int main(void) { | int main(void) { | ||
- | + | int i, top = -1, arr[SIZE]; | |
- | int i, top = -1, arr[ | + | |
printf("push %d\n", 9); | printf("push %d\n", 9); | ||
- | push(arr, 9, &top, | + | push(arr, 9, &top, SIZE); |
printf("push %d\n", 21); | printf("push %d\n", 21); | ||
- | push(arr, 21, &top, | + | push(arr, 21, &top, SIZE); |
printf("pop %d\n", pop(arr, &top)); | printf("pop %d\n", pop(arr, &top)); | ||
printf("push %d\n", 3); | printf("push %d\n", 3); | ||
- | push(arr, 3, &top, | + | push(arr, 3, &top, SIZE); |
printf("arr[] = "); | printf("arr[] = "); | ||
for (i = 0; i <= top; i++) { | for (i = 0; i <= top; i++) { |
Revision as of 05:51, 12 May 2022
Contents |
C程式的結構
以下是一個將攝氏溫度轉換成華氏溫度的程式, 假設它的檔案名稱是:CToF.c:
#include <stdio.h> /* include 是 preprocessor directive 的一種
stdio.h 是一個有 printf, scanf 定義的檔案
C的編譯器(compiler)會將stdio.h的內容放進
(include)到這個程式檔之後,才進行編譯 */
/* CToF.c --> preprocessor directive --> C compiler --> CToF.exe (可執行檔) */
int main(void) { /* main 是程式執行的起始點, 它沒有傳入的參數(void),
它傳回給作業系統的值是整數,而傳回 0 代表程式正常的結束 */
double c, f; /* 宣告攝氏(c)與華氏(f)兩個型態是 double 的變數 */
printf("請輸入攝氏溫度: ");
scanf("%lf", &c); /* &c的值是 c 的地址, %lf代表以double的型態讀取 c 的值 */
/* 將攝氏轉換成華氏 */
f = c * (9 / 5) + 32;
printf("華氏溫度:%f\n", f); /* %f 代表以 double 的型態輸出 f */
return (0); /* 傳回 0 代表程式正常的結束 */
}
一個C程式的結構如下:
前端處理指示碼(preprocessor directives)
全域變數宣告(可有可無)
函式宣告(function declaration)
int main(void) {
變數宣告(variable declaration)
可執行的式子(executable statement)
}
函式定義(function definition)
以上的程式,也可以改寫如下。
#include <stdio.h> /* include 是 preprocessor directive 的一種
stdio.h 是一個有 printf, scanf 定義的檔案 */
#define RATIO 9/5 /* 定義常數 */
double convert(double c); /* 讓編譯器可以知道 convert 的輸出入型態,
因為在main中便會用到 */
int main(void) {
double c, f; /* 宣告攝氏(c)與華氏(f)兩個型態是 double 的全域變數 */
printf("請輸入攝氏溫度: ");
scanf("%lf", &c); /* &c的值是 c 的地址, %lf代表以double的型態讀取 c 的值 */
f = convert(c); /* 將 c 傳入 convert 並傳回轉換的結果,然後存入 f */
printf("華氏溫度:%f\n", f); /* %f 代表以 double 的型態輸出 f */
return (0); /* 傳回 0 代表程式正常的結束 */
}
double convert(double c) {
return c * RATIO + 32;
}
以上的程式,還可以改寫如下。
#include <stdio.h> /* include 是 preprocessor directive 的一種
stdio.h 是一個有 printf, scanf 定義的檔案 */
#define RATIO 9/5
double convert(double c) {
return c * RATIO + 32;
}
int main(void) {
double c, f; /* 宣告攝氏(c)與華氏(f)兩個型態是 double 的全域變數 */
printf("請輸入攝氏溫度: ");
scanf("%lf", &c); /* &c的值是 c 的地址, %lf代表以double的型態讀取 c 的值 */
f = convert(c); /* 將 c 傳入 convert 並傳回轉換的結果,然後存入 f */
printf("華氏溫度:%f\n", f); /* %f 代表以 double 的型態輸出 f */
return (0); /* 傳回 0 代表程式正常的結束 */
}
資料、變數與輸出
變數
以下的程式碼宣告了一個命名為 a,型態是整數(int),初始值為 3 的變數。
int a = 3;
由上例可知,一個變數有:
- 一個名字
- 一個型態
- 一個資料值
- 以及儲存這個資料值的記憶體空間
共四個部份。
變數使用前要先進行宣告 (declaration) ,宣告的主要目的是告訴編譯器這個變數屬於哪一種資料型態,好讓編譯器預先替該變數保留足夠的記憶體空間。 正確的變數宣告方式是由「資料型態」加上「變數名稱」與分號所構成。此外,宣告時也可以直接加上初始值。
資料型態 變數名稱1, 變數名稱2, ... 變數名稱n;
data_type variable_list;
資料型態 變數名稱 = 初始值;
data_type variable_list = initial_value;
例如以下宣告:
int a; /* 宣告變數 a,暫時未設初始值 */
int b = 12; /* 宣告變數 b,初始值為12 */
以下是兩種不同的宣告方式,但意義相同的宣告:
double rate,time; /* 宣告變數rate和time,資料型態皆為double */
int age; /* 宣告變數age,資料型態皆為int */
變數名稱由程式設計者自行定義,但考慮到程式可讀性和容易除錯的因素,最好將名稱定義為較為具體且有意義的名字,例如總和取名為「sum」,薪資取名為「salary」等。至於變數的命名,還必須遵守以下的規則:
- 變數名稱開頭不可以為數字。
- 變數名稱區分大小寫字母;例如 Ave與AVE會視為兩個不同的變數。
- 變數中間不可以有空白。
- 不可以使用C語言中的保留字;例如goto return default…等。
- 不可使用特殊字元;例如-,*$@...等。
保留字:
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
const | float | short | unsigned |
continue | for | signed | void |
default | goto | sizeof | volatile |
do | if | static | while |
註解:
C語言使用這對符號 /* 這裡是註解 */ 為程式碼提供註解
資料型態
C的基本資料型態包括整數、浮點數、字元資料型態三種。不同的資料型態所佔空間大小不同,所儲存的數值範圍也不相同,如下表:
型別 | 資料型態 | 容量 | 數值範圍 |
---|---|---|---|
字元 | char | 1 byte | -128~127 |
字元 | unsigned char | 1 byte | 0~255 |
整數 | short | 2 byte | -32,768~32,767 |
整數 | int | 4 byte | -2,147,483,648~2,147,483,647 |
整數 | long | 4 byte | -2,147,483,648~2,147,483,647 |
整數 | unsigned short | 2 byte | 0~65,535 |
整數 | unsigned int | 4 byte | 0~4,294,967,295 |
整數 | unsigned long | 4 byte | 0~4,294,967,295 |
浮點數 | float | 4 byte | -3.4*10^-38~3.4*10^38 |
浮點數 | double | 8 byte | -1.7*10^-308~1.7*10^308 |
- 資料型態前加unsigned,表示沒有負號,亦即都是正數,可以用於整數和字元型態。
輸出
printf()函數是將程式欲輸出的內容顯示到螢幕或檔案,它除了可以直接將文字輸出外還可以輸出指定格式的變數或數值。
printf("欲輸出於螢幕的內容"); /*直接將文字輸出*/
直接將文字輸出,例如以下:
printf("請輸入一個數值:");
另一種方式是透過格式指定碼(format_string),輸出變數或數值,例如:
int num_1=21;
printf(“num_1的數值為: %d”, num_1);
輸入
scanf()函數可以讓使用者輸入資料並儲存在變數中,用法跟printf類似,例如:
int num_2;
scanf(“%d”,&num_2);
- 其中的 %d 指定輸入的值需為整數,&num_2 則為存放使用者輸入值的變數的地址,也就是 num_2 是儲存輸入值的變數,& 則為得到變數的地址的運算符號。
以下列出幾個常用的格式指定碼:
格式指定碼 | 說明 | 資料型態 | 使用函式 |
---|---|---|---|
%c | 以字元方式輸出/輸入 | char | printf/scanf |
%d | 以整數輸出/輸入 | int | printf/scanf |
%f | 以浮點數方式輸出 | double | printf |
%lf | 以浮點數方式輸入 | double | scanf |
%s | 以字串方式輸出/輸入 | char | printf/scanf |
請特別注意以下的情形:
- printf 與 scanf 使用在浮點數時,格式不同,一個是 %f, 另一個是 %lf。
- 使用 %c 讀入一字元前,若需要略過空白、換行等 white space characters,那麼你必須在 %c 前加入一個空白字元。例如:
char a;
scanf(" %c", &a);
argc 與 argv
假設以下程式的程式檔名稱是 test.c:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf( "%s %s %s: %d\n", argv[0], argv[1], argv[2], argc );
return 0;
}
那麼編譯這個程式會在視窗系統會產生可執行檔test.exe,之後再在「命令提示元」輸入「test abc def」,那麼執行結果會是:
test abc def 2
代表argv[0]的值是test, argv[1]的值是abc, argv[2]的值是def。而argc的值是2。因此argc與argv可以讓執行程式時傳入字串。
算式
一個數學式子y=a+1,包含三個重要元素-運算元(operand),運算子和運算式(expersion)。
- 運算元:可為變數(如:y和a)或常數(如:1)。
- 運算子:具有特殊意義的符號(如:=和+)。
- 運算式:由運算元和運算子組成的式子(如:y=a+1)。
運算子
算術運算子
算術運算子(arithmetic operators):是用來處理一般數學運算。
運算子 |
功能 |
範例 |
+ |
加 |
a + b |
- |
減 |
a - b |
* |
乘 |
a * b |
/ |
除 |
a / b |
% |
取餘數 |
a % b |
++ |
遞增 |
a++ |
++a |
||
-- |
遞減 |
a-- |
--a |
int main ()
{
int i = 3;
i++; // 先回傳 3 再加 1, i 的值是 4
int j = 3;
++j; // 先加 1 再回傳 4, j 的值是 4
printf("i = %d\n", i); // i = 4
printf("j = %d\n", j); // j = 4
}
int main ()
{
int i = 3;
int x = 0;
x = i++; // 先回傳 3 再加 1, i 的值是 4, x 的值是 3
int j = 3;
int y = 0;
y = ++j; // 先加 1 再回傳 4, j 的值是 4, y 的值也是 4
printf("x = %d\n", x); // x = 3
printf("y = %d\n", y); // y = 4
}
指定運算子
指定運算子(assignment operators):是表示將等於符號(=)右邊的值,指定給左邊的變數。
運算子 |
範例 |
說明 |
= |
a = b |
將b的值存放到變數a中 |
+= |
a += b |
相當於a=a+b |
-= |
a -= b |
相當於a=a-b |
*= |
a *= b |
相當於a=a*b |
/= |
a /= b |
相當於a=a/b |
%= |
a %= b |
相當於a=a%b |
關係運算子
關係運算子(relational operators):是用來判斷運算式的T(true)/F(false)值。
運算子 |
功能 |
範例 |
< |
小於 |
a<b |
<= |
小於或等於 |
a<=b |
> |
大於 |
a > b |
>= |
大於或等於 |
a>=b |
== |
等於 |
a==b |
!= |
不等於 |
a != b |
邏輯運算子
邏輯運算子(logical operators):是用來結合兩個條件。
運算子 |
功能 |
範例 |
說明 |
! |
NOT |
!a |
將運算式的結果變成相反值。 |
&& |
AND |
a && b |
當&&運算子(AND)兩邊的運算式皆為真(true)時,其執行結果才為真。 |
|| |
OR |
A || b |
當||運算子(OR)兩邊的運算式,其中一邊為真(true)時,執行結果就為真。 |
位元運算子
位元運算子(relational operators):運算對象為位元(bit)。
運算子 |
功能 |
範例 |
說明 |
~ |
NOT |
~a |
將位元值換成否定值,即為0變1,1變0。 |
& |
AND |
a&b |
兩個位元皆為1時,結果才為1,其餘皆為0。 |
| |
OR |
a|b |
兩個位元皆為0時,結果才為0,其餘皆為1。 |
^ |
XOR |
a^b |
兩個位元皆為0或1時,結果才為0,其餘皆為1。 |
<< |
左移 |
a<<n |
向左移動n個位元。 |
>> |
右移 |
a>>n |
向右移動n個位元。 |
條件運算子
條件運算子(relational operators):作條件判斷使用。是由?和:兩個運算子組成,因為此運算子作用於三個運算元,故稱三元運算子。
運算子 |
功能 |
範例 |
說明 |
?: |
條件判斷 |
a>b?True:False |
若符合條件時,執行True的敘述,否則執行False的敘述。 |
sizeof 運算子
sizeof 運算子(sizeof operators):取得變數的位元組大小。
運算子 |
功能 |
範例 |
說明 |
sizeof(var) |
位元組大小 |
sizeof(a) |
假設 a = 8, 那麼整數 a 所佔用的位元組大小,在32位元的電腦裡,所佔用的記憶體空間為 4 個位元組,所以是 4。 |
- sizeof也可以用來取得整數陣列的長度。
例如sizeof(arr[0])可以得到陣列中一個儲存值的大小的數值,而這個數值乘以陣列的長度,便可以得到整個陣列佔記憶體的大小。
函式
是完成某項功能的片段程式。
C 語言的程式,是由main()函式開始執行,不過如果C程式只使用一個main()函數,在程式愈寫愈大時,便會降低程式的可讀性和結構上規劃的困難。 由上而下的設計(top_down design),是將一個原始問題,拆成數個相關的子問題。這種方式可以幫助程式設計師設計整個程式。
庫存函數
是指C語言內已經定義了一些常用的函數,在使用某一函數時,必須先利用#include引用此函數的函式庫,才能在程式中使用此函數。
常用數學函數
數學運算的函式庫有兩個:
math.h
函式庫 |
資料型態 |
函數名稱 |
用途 |
math.h |
double |
double ceil(double x) |
回傳大於等於X的最小整數值 |
double |
double cos(double x) |
計算餘弦值 |
|
double |
double exp(double x) |
計算指數e |
|
double |
double fabs(double x) |
取浮點數的絕對值 |
|
double |
double floor(double x) |
回傳小於等於X的最小整數值 |
|
double |
double log(double x) |
計算對數值 |
|
double |
double log10(double x) |
計算以10為底的對數值 |
|
double |
double pow(double x,double y) |
計算次方值 |
|
double |
sin(double x) |
計算正弦值 |
|
double |
double sqrt(double x) |
計算平方根值 |
|
double |
tan(double x) |
計算正切數值 |
stdlib.h
函式庫 |
資料型態 |
函數名稱 |
用途 |
stdlib.h |
int |
int abs(int x) |
取整數的絕對值 |
double |
double max |
傳回兩數值中的較大值 |
|
double |
double min |
傳回兩數值中的較小值 |
|
int |
int rand(void) |
產生一個虛擬隨機亂數 |
|
void |
void randomize(void) |
初始化亂數產生器 |
常用字元測試
ctype.h
函式庫 |
資料型態 |
函數名稱 |
用途 |
ctype.h |
int |
int isalnum(int x) |
測試字元是否為英文或數字 |
int |
int isalpha(int x) |
判斷字元是否為英文字母 |
|
int |
int isascii(int x) |
測試字元是否為ASCII 碼字元 |
|
int |
int isdigit(int x) |
判斷字元是否為數字 |
|
int |
int isgraphis(int x) |
測試字元是否為可打印字元 |
|
int |
int islower(int x) |
判斷字元是否為小寫的英文字母 |
|
int |
int isprint(int x) |
測試字元是否為可打印字元 |
|
int |
int isspace(int x) |
判斷字元是否為空白字元 |
|
int |
int ispunct(int x) |
測試字元是否為標點符號或特殊符號 |
|
int |
int isupper(int x) |
測試參數是否為大寫英文字母 |
|
int |
int isxdigit(int x) |
測試字元是否為16進制數字 |
|
int |
int tolower(int x) |
將大寫的英文字母轉換成小寫的英文字母 |
|
int |
int toupper(int x) |
將小寫的英文字母轉換成大寫的英文字母 |
常用字串轉換篇
string.h
函式庫 |
資料型態 |
函數名稱 |
用途 |
string.h |
int |
int strcasecmp (char *s1,char *s2); |
忽略大小寫比較字串 |
char |
char strcat (char *s1,char *s2) |
在字串s1之後連線上s2 |
|
char |
char strchr (const char *s,int c) |
找出參數s字符串中第一個出現的參數c地址 |
|
char |
char strcmp(const char *s1,const char *s2) |
基於字典順序比較兩個字串大小,如果字串比較結果一樣,便會傳回 0 |
|
int |
int strcoll( char *s1, char *s2) |
基於當前區域設定的字元順序比較兩個字串 |
|
char |
char *strcpy(char *s1, char *s2); |
會將參數s2字符串拷貝至參數s1所指的地址。 |
|
size_t |
size_t strcspn (char *s1, char *s2); |
從字串s1的起始處開始,尋找第一個不出現在s2中的字元,返回其位置索引值 |
|
char |
char * strdup(char *s);/p></td> | <p>將字串的內容複製到一段新分配的記憶體空間 | |
size_t |
size_t strlen (char *s); |
返回一個字串的長度 |
|
int |
int strncasecmp(char *s1, char *s2,size_t n); |
比較參數s1和s2字符串前n個字符,比較時會自動忽略大小寫的差異 |
|
char |
char * strncat(char *s1, char *s2,size_t n); |
在字串s1之後連線上s2,最多增加n個字元 |
|
char |
char * strncpy(char *s1, char *s2,size_t n); |
將一個字串從一個位置複製到另一個位置,最多複製n個位元組 |
記憶體的分配與釋放
stdlib.h
函式庫 |
資料型態 |
函數名稱 |
用途 |
stdlib.h |
void |
void calloc(size_t n,size_t size) |
記憶體空間的分配,且初始化為零 |
void |
void free(void *ptr) |
系統釋放動態分配的記憶體 |
|
void |
void malloc(size_t size) |
記憶體空間的分配,其內容不初始化 |
- size_t 表示無符號整數類型。
自訂函數
是使用者依照需求來設計的函數。可以分成無引數函式和有引數函式,基本的架構如下:
函數的宣告:
函數型態 函數名稱 (參數列);
例如下:
void square (void); /*無引數函式,無傳入參數。*/ int square (int num); /*有引數函式,有傳入參數。*/
函式的呼叫:
函數名稱 (參數列);
例如下:
square(); /*無引數函式,無傳入參數。*/
ans = square(num); /*有引數函式,有傳入參數。*/
函數的定義:
函數型態 函數名稱 (參數列) { 程式敘述 return 回傳值; /* 也可以不寫,例如:只使用 printf 印出結果時 */ }
無引數函式,無傳入參數。例如:
void square(void); int main(void) { square(); return 0; } void square(void) { int num,total; printf("計算某一整數的平方:\n"); scanf("%d", &num); total=num*num; printf("%d 的平方是 %d\n", total); }
有引數函式,有傳入參數。例如:
int square(int num); int main(void) { int num, ans; printf("計算某一整數的平方:\n"); scanf("%d", &num); ans = square(num); printf("%d 的平方是 %d\n", num, ans); return 0; } int square(int num) { int total; total=num*num; return total; }
- return在自訂函數中,可以將計算給果total傳回給main主程式。而main中的return 0;則是表示將控制從程式轉移給作業系統,亦指執行結果無誤。
條件式
條件
是一種運算式,其值為假用0表式,或真用1表示。
程式是逐行逐字的執行,是所謂的單一流程。但是有時我們所做的事情,會因為不同的條件,而產生不同的做法與結果,也就是選擇流程。利用條件來決定要執行或是略過某段程式碼的判斷準則。
以關係運算子的例子如下:
x=-5,y=7,power=1024,max=1024,item=1.5,min=-999.0,mom='M',num=999,count=999
運算子 |
條件 |
意義 |
值 |
< |
power<max |
power小於max |
0(假) |
<= |
x<=0 |
x小於或等於0 |
1(真) |
> |
item>min |
item大於min |
1(真) |
>= |
x>=y |
x大於或等於y |
0(假) |
== |
mom=='M' |
mom等於'M' |
1(真) |
!= |
num!=count |
num不等於count |
0(假) |
以邏輯運算子的例子如下:
x=3,y=4,z=2。
運算子 |
條件 |
意義 |
值 |
! |
!(x<y) |
x<y為真,非真就是假 |
0(假) |
&& |
x>z&&y>z |
x大於z,y大於z |
1(真) |
|| |
x==1||x==3 |
x等於1或x等於3 |
1(真) |
條件式
在C語言中提供了三種條件控制:if、if...else及switch等等。
if
「如果...就...」的意思。
if(條件運算式) { 程式敘述; }
例如以下:
int score = 78; if (score >= 60) { printf(“您通過了!”); }
if…else
「如果...就...,否則就...」的意思。
if(條件運算式) { 程式敘述; } else { 程式敘述; }
例如以下:
int num=20; if (num >= 0) { printf(“%d大於或等於 0,是正整數”,num); //條件成立時執行 } else { printf(“%d小於 0,是負數”,num); //條件不成立時執行 }
簡單式的if...else
也就是條件運算子,若符合條件時,執行True的敘述,否則執行False的敘述。
a>b?True:False
例子如下:
int num1=56, num2=12, large;
num1 > num2 ? (large = num1) : (large = num2);
printf("%d較大\n", large);
if…else…if
「如果...就...,否則如果...就...」的意思。
if(條件運算式) { 程式敘述; } else if (條件運算式) { 程式敘述; } . . . else { 程式敘述; }
例如以下:
int num=-15; if (num>0) { printf(“%d是一正數\n”,num); } else if (num<0) { printf(“%d是一負數\n”,num); } else { printf(“%d是0\n”,num); }
巢狀if
「如果...就...,否則如果...就...」的意思。
if(條件運算式) { 程式敘述; if (條件運算式) { 程式敘述; } } else { 程式敘述; if (條件運算式) { 程式敘述; } }
例如以下:
int score=78; if (score >= 60) { if (score >= 80 && score <= 100) { printf("優良!\n"); } else { printf("良好!\n");} } else { printf("糟糕!\n"); }
switch
「選擇」適合的敘述來執行的意思。
switch(變數) { case 數值1: 程式敘述; break; case 數值2: 程式敘述; break; . . . default: //default可省略 程式敘述; }
例如以下:
int grade = 1; switch(grade){ case 1: printf(“我是大一新生”); break; case 2: printf(“我是大二學生”); break; case 3: printf(“我是大三學生”); break; case 4: printf(“我是大四學生”); break; default: print(“沒有找到此代碼”); }
迴圈
迴圈
是用來進行進行重複性的工作,重復執行某一段程式敘述,直到條件判斷不成立才會跳出迴圈。
程式存在選擇性與重複性的特點,來完成各種繁雜、重複、枯燥的計算。選擇性就是在上一章所討論的條件式,而重複性的功能就是迴圈。C語言提供for、while以及do-while三種迴圈方式。
while
while迴圈(前測試):預先設定條件運算式,先檢查條件運算式是否為真。若為真,執行一次迴圈內的程式敘述,然後回到條件運算式再檢查。
直到條件運算式不成立才跳出迴圈。
while (條件運算式) { 程式敘述; }
例如:
int sum = 0; int i = 1; While (i <= 100) { sum = sum + i; /*可用設定運算子的寫法 sum += i*/ i++; /*遞增、遞減運算子的寫法,原式為 i=i+1*/ }
do-while
do-while迴圈(後測試)::先執行一次程式敘述後,再判斷條件運算式是否為真。若為真,再回到前面執行程式敘述,重複直到條件運算式不成立才跳出迴圈。
*後測試會比前測試多執行一次。
do { 程式敘述; } while (條件運算式);
例如:
int sum = 0; int i = 1; do { sum = sum + i; i++; } while (i <= 100);
for
for迴圈(常用於有確定重複次數):利用一個變數累加或遞減來控制迴圈,等到該變數達到條件運算式設定的標準時,就會跳出迴圈。
- 包含三個部份,起始值:設定變數的初值。條件運算式:若為真,執行迴圈內的程式敘述;若為假,結束迴圈。更新變數:更新變數的值。
for (起始值 ; 條件運算式 ; 變數更新) { 程式敘述; }
例如:
int sum = 0; int i; for (i = 0; i <= 100; i++){ sum = sum + i; }
巢狀for
巢狀for迴圈(常用於有確定重複次數):在迴圈敘述中,又出迴圈敘述。
for(起始值 ; 條件運算式 ; 更新變數) { for(起始值 ; 條件運算式 ; 更新變數) { 程式敘述; } }
例如:
int sum = 0; int i,j; for (i = 1; i <= 3; i++){ | 結果: for (j = 1; j <= i; j++){ | 1 printf("%d",j); | 12 } | 123 printf("\n"); | } |
break與continue
break:讓程式跳出「整個」迴圈,繼續執行迴圈之後的程式。
int i, num, total=0; | 輸入10 個整數做累加,當輸入的值是負時,結束迴圈
printf("輸入10 個整數做累加,當輸入的值是負時,結束迴圈\n");| 請輸入第1個整數:1
for(i=1; i<=10; i++) | 請輸入第2個整數:2
{ | 請輸入第3個整數:3
printf("請輸入第%2d個整數:",i); | 請輸入第4個整數:4
scanf("%d",&num); | 請輸入第5個整數:-1
if ( num >= 0) | total=10
total = total + num; |
else |
break; |
} |
printf("total=%d\n", total); |
continue :讓程式跳出「這一輪」迴圈,繼續執行迴圈。
int i, num, total=0; | 輸入10 個整數做累加
printf("輸入10 個整數做累加\n"); | 請輸入第1個整數:1
for(i=1; i<=10; i++) | 請輸入第2個整數:2
{ | 請輸入第3個整數:3
printf("請輸入第%2d個整數:",i); | 請輸入第4個整數:4
scanf("%d",&num); | 請輸入第5個整數:-1
if ( num >= 0) | 請輸入第6個整數:6
total = total + num; | 請輸入第7個整數:7
else | 請輸入第8個整數:8
continue; | 請輸入第9個整數:9
} | 請輸入第10個整數:10
printf("total=%d\n", total); | total=50
指標
指標
記憶體的內容為另一個記憶體的位址。
除了透過變數的名稱之外,C語言還提供了另一種存取記憶體中儲存值的方式,這種方式不必使用變數的名稱,便能存取到記憶體中的儲存值,這種方式就是利用「指標」來達成。
指標變數可看成一種特殊的變數,用來存取變數在記憶體中的位址。當我們宣告一個變數時,編譯器便會配置一塊足夠儲存變數的記憶體空間給它。每個記憶體空間均有獨一無二的編號,這編號稱為記憶體的「位址」,程式就是利用記憶體的位址來存取記憶體中的內容。
宣告語法如下:
資料型態 *指標變數名稱;
例如,nump指向num:
int *nump, num;
nump = # /*讓指標變數nump指向變數num的位址*/
- 取得變數在記憶體的位址使用&位址運算子
指標和非指標變數的比較
int num=100;
int *nump;
nump=#
printf("num記憶體的位址為%p\n",&num);=>0028FF44 /*變數num的直接值,變數自已的位址*/
printf("num變數的內容為%d\n",num);=>100 /*變數num的直接值,變數自已的內容*/
printf("nump指標的位址%p\n",&nump);=>0028FF40 /*指標nump的直接值,指標自已的位址*/
printf("nump指標的內容%p\n",nump);=>0028FF44 /*指標nump的直接值,指向含100之位址*/
printf("nump所指向位址的內容%d\n",*nump);=>100 /*指標nump的間接值,指向位址的內容*/
傳值呼叫與傳址呼叫
傳值呼叫(call by value) 觀看程式執行的步驟
void swap(int a,int b); | int main(void) | 結果: { | 交換前 x=5,y=10 int x=5,y=10; | printf("交換前 x=%d,y=%d\n", x,y); | 交換後 x=5,y=10 swap(x,y); | printf("交換後 x=%d,y=%d\n", x,y); | system("pause"); | return 0; | } | void swap(int a,int b) | { | int temp; | temp = a; | a = b; | b = temp; | } |
由此程式的執行結果可知,x 與 y 的值在呼叫 swap 之後,仍然沒有改變。因此傳值呼叫只能傳入值。
傳址呼叫(call by address or reference) 觀看程式執行的步驟
void swap(int *a,int *b); | int main(void) | 結果: { | 交換前 x=5,y=10 int x=5,y=10; | printf("交換前 x=%d,y=%d\n", x,y); | 交換後 x=10,y=5 swap(&x,&y); | printf("交換後 x=%d,y=%d\n", x,y); | system("pause"); | return 0; | } | void swap(int *a,int *b) | { | int temp; | temp = *a; | *a = *b; | *b = temp; | } |
由此程式的執行結果可知,x 與 y 的值在呼叫 swap 之後被改變了。因此傳址呼叫也可以用來當作程序的輸出參數。
簡單資料型態
C的標準型態(int, double, char等)及使用者自定的列舉型態都屬於簡單資料型態(simple data type)。
程式在執行某些運算時,不當的變數型態可能會導致算術低載或溢位(arithmetic underflow or overflow).
運算式的資料型態
運算的結果與運算元的形態有關。
混合型態設定(mixed-type assignment):是指在設定敘述中,等號運算子兩邊的資料型態不同。
int m = 3, n = 2, b;
double p = 2.0, x, y, a;
x = m / p;
printf("x %.2f \n", x); => 1.50
/* m 的值是 int 3,p 的值是 double 2.0, m 會自動轉為 double,
而 x 的值是 double 1.50 */
y = m / n;
printf("y %.2f \n", y); => 1.00
/* m 和 n 是 int, 相除之後在存到 y 之前轉成 double,
因此 y 的值是 1.00 */
a = 9 * 0.5;
printf("a %.2f \n", a); => 4.50
b = 9 * 0.5;
printf("b %d \n", b); => 4
/* 將 4.5 設給 int 的變數 b,使小數部份不見 */
型別轉換(type cast):將想要轉換之型態以引號括起來並放在運算式前,而將運算式轉換成一種不同型態。
int all_student = 4,
all_score = 290,
rounded_x;
double average, average_x;
計算所有學生的平均分數
average = (double)all_score / (double)all_student;
/* 利用型別轉換(type cast), 將 int 轉成 double */
printf("average為 %.2f \n", average); => 72.5
範例:四捨五入
average_x = average - (int)average;
if (average_x >= 0.5)
rounded_x = (int)(average + 0.5);
/* 將 double 轉成 int */
printf("average 的四捨五入為 %d\n", rounded_x); => 73
數值型態的表示及轉換
不同數字型態的運算式
int k = 5;
double z, x = 1.5;
z = k + x; /* k 的形態是 int,但是在運算前自動轉為 double */
printf("z 為 %.2f\n", z); => 6.5
將型態 int 的算式指定(assign)給 double 的變數
int k = 5, m = 4;
double z;
z = k / m; /* 運算式先完成,再轉換為 double */
printf("z 為 %.2f\n", z); => 1.00
z = (double)k / (double)m; /* 運算前將 int 轉成 double */
printf("z 為 %.2f\n", z); => 1.50
z = (double)(k / m); /* 先計算括號內的 k / m 再轉為 double,結果失去小數 */
printf("z 為 %.2f\n", z); => 1.00
將 double 的算式指定給 int 的變數
int n;
double x = 1.5, y = 2.1;
n = x * y; /* 混合型態設定,運算式先完成,再將結果轉換為int,小數部份不見了 */
printf("n 為%d\n", n); => 3
函式庫中函式執行結果的轉換
int k = 5, sqrt_k;
sqrt_k = sqrt(k);
/* sqrt 函式的資料型態是 double,因此 k 會轉換至 double。
sqrt(k) 的值為 2.236068 */
printf("m 的開根號是 %d\n", sqrt_k);=>2
/* sqrt_k 是 int,因此印出 2 */
char型態的表示及轉換
將char型態轉成int型態
char start_char = 'A', end_char = 'Z';
int char_code;
for (char_code = (int)start_char; char_code <= (int)end_char; char_code = char_code+1) {
printf(" %c ", (char)char_code);
printf("%d ", (int)char_code);
printf("\n");
}
結果:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
陣列
陣列
陣列(array)是一群資料型態相同之數值的組合,它們在記憶體中佔有一塊連續的記憶體空間。
要存取陣列中的資料時,需要使用索引值(index)尋找出資料在陣列的位置。通常陣列的使用可分為一維陣列、二維陣列與多維陣列等等,其基本的運作原理都相同。
一維陣列的語法宣告如下:
未初值化,資料型態 陣列名稱[陣列大小];
element-type name[size];
int Score[5];
初值化,資料型態 陣列名稱[陣列大小] = {初始值1, 初始值2,...};
element-type name [size] = {initialization list};
int Score[5] = {68, 75, 85, 90, 100};
二維陣列的語法宣告如下:
資料型態 陣列名稱[列數][行數];
element-type name[Row-size][Col-size];;
int A[3][4];
陣列初值化
在宣告陣列的同時,將數值指定給陣列,設定陣列的初始值。
一維陣列
配置相同於陣列容量個數的初始值:宣告陣列int Score[5]時,表示該陣列最多可儲存5個整數型別的變數值。
int Score[5] = {68, 75, 85, 90, 100};
初始化陣列為0:設定的初始值個數少於陣列大小,不足的部份會自動補上0。
int Score[5] = {0};
以初始值個數決定陣列容量:不註明陣列大小的方式宣告,也就是使用空的[]。編譯器會自動以初值個數決定陣列大小為3。
int Score[] = {68, 75, 85};
二維陣列
任先列後行的順序填入初始值:二維陣列需以先列後行的順序,填入初始值。
方式一:
int A[3][4] = {65, 88, 51, 97,
98, 72, 67, 45,
45, 79, 12, 32};
方式二:
int A[3][4] = {{65, 88, 51, 97}, {98, 72, 67, 45}, {45, 79, 12, 32}};
以初始值個數決定二維陣列大小:就跟一維陣列相同,二維陣列可以用空的[]讓初始值決定其大小。但只有列數可以使用[],因為要先知道每一列的大小,編繹器才知何時將資料填入下一行。
int A[ ][4] = {65, 88, 51, 97,
98, 72, 67, 45,
45, 79, 12, 32};
/*錯誤,不知何時該換列*/
int A[3][ ] = {65, 88, 51, 97, 98, 72, 67, 45.......}
陣列索引
陣列索引是指陣列名稱之後,以[]包住的值或算式,例如:num[0]。可以用這個方式指定要計算的陣列元素,而陣列元素是指陣列的一個資料項。
int num1[ ] = {1, 2, 3, 4, 5, 6}; | 原本num1: 指定計算num2:
int num2[ ] = {1, 2, 3, 4, 5, 6}; /* 元素個數隨初值個數而定 */ | num[0]=1 num[0]=1
int i, j= 3; | num[1]=2 num[1]=2
| num[2]=3 num[2]=3
for (i = 0; i < 6; i++) | num[3]=4 num[3]=5
printf("num[%d]=%d\n", i, num1[i]); | num[4]=5 num[4]=2
printf("\n\n"); | num[5]=6 num[5]=6
num2[j] = num2[j] + 1; /*指定要計算的陣列元素num2[3]=4+1*/
num2[j + 1] = num2[2 * j - 5]; /*指定要計算的陣列元素num2[4]=num2[1]*/
for (i = 0; i < 6; i++)
printf("num[%d]=%d\n", i, num2[i]);
- 程式執行時,通常希望由陣列元素0開始循序處理陣列元素。可以用for迴圈容易的完成存取動作。
陣列元素作為函式參數
陣列函式定義
資料型別 函式名稱(資料型別 陣列名稱[])
datatype name(datatype arrary-name[]) {
...
}
例子如下:
int maxnum (int list[]) {
int i, max;
max = list[0];
for (i = 0; i < 5; i++) {
if (list[i] > max) {
max= list[i];
}
}
return(max);
}
陣列的函式呼叫
找出最大值
int maxnum (int list[]);
int main(void) {
int list[5];
int i, large, large2;
printf("請輸入5個正整數:");
for (i = 0; i < 5; i++) {
scanf("%d", &list[i]);
printf("list[%d] = %d\n", i, list[i]);
}
large = maxnum(list); /* 使用陣列的名字當做參數 */
printf("陣列最大值為:%d\n", large);
large2 = maxnum(&list[0]); /* maxnum(list) 也可以寫成 maxnum(&list[0]),
可見list就是這個陣列的第0個位置的地址 */
printf("陣列最大值為:%d\n", large2);
}
int maxnum (int list[]) { /* 陣列的函式參數, 也可以寫成 int maxnum (int *list) */
int i, max;
max = list[0];
for (i = 0; i < 5; i++) {
if (list[i] > max) {
max = list[i];
}
}
return(max);
}
- large=maxnum(list)和large2=maxnum(&list[0])執行結果完全相同,這類的呼叫方式很容易會誤會只輸出list[0]而已。
堆疊
1.是一種有順序性的資料結構。
2.資料的新增和刪除都從堆疊的最頂端輸入與輸出。
3.具有後進先出(Last In First Out:LIFO)的特性。
從堆疊刪除一個值稱為跳出堆疊(popping the stack),而新增一個值到堆疊中則稱為推進堆疊(pushing it onto the stack)。
#define SIZE 10
int main(void) {
int i, top = -1, arr[SIZE];
printf("push %d\n", 9);
push(arr, 9, &top, SIZE);
printf("push %d\n", 21);
push(arr, 21, &top, SIZE);
printf("pop %d\n", pop(arr, &top));
printf("push %d\n", 3);
push(arr, 3, &top, SIZE);
printf("arr[] = ");
for (i = 0; i <= top; i++) {
printf("%d ", arr[i]);
}
printf("\n");
system("pause");
return 0;
}
void push(int stack[], int item, int *top, int maxsize ){
if (*top < maxsize - 1) {
++(*top);
stack[*top] = item;
} else {
printf("堆疊已經滿了\n");
}
}
int pop(int stack[], int *top){
int item;
if (*top >= 0) {
item = stack[*top];
--(*top);
} else {
printf("堆疊已經是空的\n");
}
return(item);
}
選擇排序
1.首先找出陣列(0~n-1)中最小元素的位置,然後將最小元素與array[0]元素交換。
2.之後再從1~n-1的陣列中找出最小元素的位置,與arrary[1]元素交換位置,依此類推。
int main(int argc, char **argv) {
int a[10] = {9,0,7};
int i;
printf("排序前");
for (i = 0; i < 3; i++) {
printf(" %d", a[i]);
}
printf("\n");
select_sort(a, 3);
printf("排序後");
for (i = 0; i < 3; i++) {
printf(" %d", a[i]);
}
printf("\n");
system("pause");
}
int get_min_range(int list[], int first, int last) {
int i, min;
min = first;
for (i = first; i < last; i++) {
if (list[i] < list[min]) {
min = i;
}
}
return min;
}
void select_sort(int list[], int n) {
int fill,
temp,
index_min;
for (fill = 0; fill < n; ++fill) {
/* 找出未排序的陣列中最小元素的位置*/
index_min = get_min_range(list, fill, n);
/* 交換fill和index_min元素 */
if (fill != index_min) {
temp = list[index_min];
list[index_min] = list[fill];
list[fill] = temp;
}
}
}
字串
c語言裡並沒有「字串」的資料型態,但可以由字元陣列來組成字串。
字元與字串的區別
字元:是以單引號(')所包圍任一字母、符號或數字。
字串:則是以雙引號(")包圍起來,而且編譯器會在字串結束處,加上一個結束字元\0。
字元
字元輸入/輸出
輸入:函式庫有一個getchar函式可用在標準輸入源(standard in 或 stdin,通常是鍵盤)中讀取下一個字元,此輸入源和scanf所用的一樣。
- 差別在於,getchar不透過傳遞變數的位址來儲存輸入字元,傳回一字元作為結果。
字元輸入函式 |
說明 |
getchar() |
要求輸入字元,須鍵入「Enter」表示輸入結束。 |
scanf與getchar例子如下:
char ch; | char ch; | 結果: int i; | int i; | 輸入資料為:xyz printf("輸入資料為:"); | printf("輸入資料為:"); | #1的輸出資料為:x for(i=1;i<=3;i++) | for(i=1;i<=3;i++) | #2的輸出資料為:y { | { | #3的輸出資料為:z scanf(" %c", &ch); | ch=getchar(); | printf("%d 的輸出資料為: %c\n\n", i, ch); | printf("%d 的輸出資料為: %c\n\n", i, ch); | } | } |
輸出:專門來處理字元的函式是putchar(),一次只能輸出一個字元就和print相同。
字元輸入函式 |
說明 |
putchar(var) |
輸出字元變數的值。 |
printf與putchar例子如下:
char ch; | char ch; | 結果: int i; | int i; | 輸入資料為:xyz printf("輸入資料為:"); | for(i=1;i<=3;i++) | { | { | 輸出資料為: scanf(" %c", &ch); | scanf(" %c", &ch); | x printf("%d 的輸出資料為: %c\n\n", i, ch); | printf("#%d 的輸出資料為:",i); | y | putchar(ch); | z | printf("\n"); | } | } |
字元庫存函數
除了單純輸入/輸出字元外,我們還可以針對字元作比對和轉換等處理。
函式 |
說明 |
isalpha(var) |
判斷字元是否為英文字母 |
isdigit(var) |
判斷字元是否為數字 |
islower(var) |
判斷字元是否為小寫的英文字母 |
isupper(var) |
判斷字元是否為大寫的英文字母 |
tolower(var) |
將大寫的英文字母轉換成小寫的英文字母 |
toupper(var) |
將小寫的英文字母轉換成大寫的英文字母 |
字串
宣告字元陣列的格式如下:
一維陣列
char 字元陣列名稱[陣列大小] = 字串內容;
char name [size] = Content;
char str[12]="Danny Laine"; /*可預先設定初始值*/
二維陣列
資料型態 陣列名稱[列數][行數];
char name [Row-size] [Col-size] = Content;
char str[3][10]={"Danny","Ellie","Lucy"}; /*可預先設定初始值*/
上面敘述中,雖然Danny Laine只有11個字元(含空白),但編譯器會自動在字串結尾加上字串結束字元「\0」,因此必須多留一個位置給字串結束符號存放(如圖所示)。
字串的輸入/輸出
使用printf和scanf的輸入輸出
一維陣列 二維陣列 char str[10]; | char str[3][10]; /*char str[3][10]={"Danny","Ellie","Lucy"};*/ printf("請輸入英文全名:"); | int i; scanf("%s", str); | for(i=0;i<3;i++){ printf("字串為: %s\n", str); | printf("請輸入第%d個英文名字:",i+1); | scanf(" %s",str[i]); | printf("字串 %d 為 %s\n", i+1, str[i]); | } | 結果: | 結果: 請輸入英文全名:Danny Laine | 請輸入第1個英文名字:Danny 字串為:Danny | 字串1為Danny | 請輸入第2個英文名字:Ellie | 字串2為Ellie | 請輸入第3個英文名字:Lucy | 字串3為Lucy
- 字串輸入不需要使用&,因為陣列名稱str就表示陣列的第一個元素str[0]的位址,也就是&str[0]。
- 當scanf遇到空白字元,就會結束讀入的動作,我們可以用gets函式來解決這個問題。(空白字元包括:空白鍵、tab和Enter鍵)
使用字串函式的輸入輸出
函式 |
說明 |
gets(var) |
要求輸入字串,將輸入字串全部讀取直到'\n'。 |
puts(var) |
用來輸出字串,輸出完畢會自動跳行。 |
一維陣列 二維陣列 char str[10]; | char str[3][10]; /*char str[3][10]={"Danny","Ellie","Lucy"};*/ printf("請輸入英文全名:"); | int i; gets(str); | for(i=0;i<3;i++){ printf("字串為:"); | printf("請輸入第%d個英文名字:",i+1); puts(str); | gets(str[i]); | printf("字串為:"); | puts(str[i]); | } | 結果: | 結果: 請輸入英文全名:Danny Laine | 請輸入第1個英文名字:Danny 字串為:Danny Laine | 字串1為Danny | 請輸入第2個英文名字:Ellie | 字串2為Ellie | 請輸入第3個英文名字:Lucy | 字串3為Lucy
字串的庫存函數
除了單純輸入/輸出字元外,我們還可以針對字串計算長度、比對、複制和串接等處理。
函式 |
說明 |
strlen(字串名稱) |
計算字串長度(不包含'\0') |
strcpy(目的陣列名稱,來源陣列名稱) |
將字串拷貝到另一個字串 |
strncpy(目的陣列名稱,來源陣列名稱,複製字元數) |
將字串前N個字元拷貝到務一個字串 |
strcat目的陣列名稱,來源陣列名稱) |
將字串附加另一字串後面 |
strncat目的陣列名稱,來源陣列名稱,串接字元數) |
將字串的前N個字元附加在另一字串後面 |
strcmp(字串1,字串2) |
比較兩個字串的大小 |
strncmp(字串1,字串2,比對字元數) |
比較兩字串的前N個字元的大小 |
字串的指標陣列
宣告指標陣列的格式如下:
char *字元陣列名稱[陣列大小];
char *name [size];
char *str[3]={"Danny","Ellie","Lucy"}; /*可預先設定初始值*/
- char str[3][10]={"Danny","Ellie","Lucy"};,二維陣列可得到與指標陣列相同的答案,差別在於指標陣列可以彈性的配置空間,可減少節省空間。二維陣列會浪費較多的空間且沒有彈性。
例子如下:
char *str[4] = {"I", "am", "a", "student"}; | 結果:
int i; | 位址:00403000 內容:I
for(i=0;i<4;i++){ | 位址:00403002 內容:am
printf("位址:%p 內容:%s\n", str[i], str[i]); | 位址:00403005 內容:a
} | 位址:00403007 內容:student
陣列與指標陣列的差別:
陣列 指標陣列
char list_original[3][10]={"rose","daisy","tulip"}; | char list_original[3][10]={"rose","daisy","tulip"};
char temp[3]; | char *list[3],*temp;
int i,j,min,index_min; | int i,j,min,index_min;
min=0; | min=0;
for (i=0;i<3;i++) { | for (i=0;i<3;i++) {
printf("原本字串內容:%s\n",list_original[i]); | list[i]=list_original[i];
if (strcmp(list_original[i], list_original[min])<0){ | printf("原本字串內容:%s\n",list_original[i]);
min=i; | if (strcmp(list[i], list[min])<0){
} | min=i;
} | }
for (j=0;j<3;j++) { | }
index_min=min; | for (j=0;j<3;j++) {
if (j != index_min) { | index_min=min;
strcpy(temp, list_original[index_min]); | if (j != index_min) {
strcpy(list_original[index_min], list_original[j]); | temp = list[index_min];
strcpy(list_original[j], temp); | list[index_min]=list[j];
} | list[j]=temp;
} | }
printf("\n"); | }
for (i=0;i<3;i++) { | printf("\n");
printf("最小值存於索引0後:%s\n",list_original[i]);} | for (i=0;i<3;i++) {
| printf("最小值存於索引0後:%s\n",list[i]);}
指標陣列的好處:
1.指標陣列會比陣列複製整個字串所需的空間少。
2.排序時,只複製指標而不是整個字串,執行時間會比較快。
3.排序後,原來陣列資料的順序會保留下來。
遞迴
函式除了可以呼叫其他函式,也可以呼叫自己,呼叫自己本身的函式被稱為遞迴函式。
遞迴程式的特色
- 含有終止條件(termination condition);
- 含有遞迴呼叫,而遞迴呼叫所傳入的資料值一定會趨近終止條件;
- 答案的產生需要經過累計(例如:累加或累乘)的過程。
遞迴演算法,通常包含一個if敘述:
if (終止條件) 終止條件成立時的傳回值 else 含有累計及遞迴呼叫的程式碼
輸入一個數字,計算這個數字與6相乘的結果:
int main(void) {
int i, m = 6, n, ans;
printf("請輸入與6相乘的數字:");
scanf("%d", &n);
for (i = 1; i <= n; i++) {
printf("m = %d,n = %d\n", m, i);
printf("%d*%d=%d\n\n", m, i, multiply(m, i));
}
}
int multiply(int m, int n) {
if (n == 1)
return(m);
else
return(m + multiply(m, n - 1));
}
計算3的階層:
int main(void)
{
int i;
for(i = 3; i >= 1; i--) {
printf(" %d ! = %ld\n", i, factorial(i));
}
system("pause");
}
int factorial(int n)
{
if(n == 0)
return(1);
else
return(n * factorial(n-1));
}
含有陣列以及字串參數的遞迴函式
找出字串的大寫字母:
char *find_caps(char *caps, char *str);
int main(void)
{
char str[50], caps[50];
printf("請輸入字串:");
gets(str);
find_caps(caps, str);
printf("大寫的字母為:%s", caps);
system("pause");
return 0;
}
char *find_caps(char *caps, char *str)
{
char restcaps[50];
if (str[0] == '\0')
caps[0] = '\0';
else
if (isupper(str[0]))
sprintf(caps, "%c%s", str[0], find_caps(restcaps, &str[1]));
else
find_caps(caps, &str[1]);
return (caps);
}
- sprintf() 將格式化字串傳送到陣列。如上題,將 str[0] 中的字母以 %c 字元格式,和 find_caps(restcaps, &str[1]) 中其它部分的大寫字母以 %s 字串格式合併後,存入 caps 陣列中。
古典案例的遞迴
河內塔:有三根杆子A,B,C。A杆上有N個(N>1)穿孔圓盤,盤的尺寸由下到上依序變小。請按下列規則將所有圓盤移至C杆:
- 每次只能移動一個圓盤;
- 大盤不能疊在小盤上面。
請印出移動的次序。 提示:可將圓盤臨時置於B杆,也可將從A杆移出的圓盤重新移回A杆,但都必須遵循上述兩條規則。
演算法:
1) 先指定「起始柱」和「目標柱」,剩下的那個柱就是「暫存柱」,而你的盤數為 n 2) 先把 n-1 個盤子全部移到 「暫存柱」 3) 再把 第 n 個盤子移動到 「目標柱」 4) 最後,把放在「暫存柱」的 n - 1 個盤子全部移到「目標柱」
void tower(int disk_no,char start,char temp,char end);
int main(void)
{
int disk_no;
printf("\n有幾個圓盤 ? ");
scanf("%d", &disk_no);
printf("\n搬移的步驟如下 : \n");
tower(disk_no, 'A', 'B', 'C');
system("pause");
}
void tower(int disk_no,char start,char temp,char end)
{
if( disk_no > 0 ) {
tower ( disk_no-1 , start , end , temp );
printf("將 %d 號圓盤從 %c 柱搬到 %c 柱。\n" , disk_no, start , end ); /* 顯示移動狀況 */
tower ( disk_no-1 , temp , start , end);
}
結構
是由多個相同或不同型態的資料所組成的集合體,也可叫做記錄(record),而結構中的各個資料可視為記錄內的欄位(field)。
自定結構型態
在使用結構之前必須先必宣告結構的內容,才能定義結構變數,這樣系統才能配置適當的記憶體給它。
結構的定義及宣告方式有三種方式:
第一種方式如下:
struct struct_name struct 結構名稱
{ {
type_1 id_list_1 資料型態 成員名稱1;
type_2 id_list_2 資料型態 成員名稱2;
type_3 id_list_3 資料型態 成員名稱3;
... ...
type_n id_list_n 資料型態 成員名稱n;
} var_name_1,var_name_2...,var_name_n; } 結構變數名稱1,結構變數名稱2,...,結構變數名稱n;
例子如下:
struct student
{
char name[10];
char sex;
int score;
} peter, john;
- 這種方式是直接在宣告結構型態後的右大括弧},定義結構變數。
第二種方式如下:
struct struct_name struct 結構名稱
{ {
type_1 id_list_1 資料型態 成員名稱1;
type_2 id_list_2 資料型態 成員名稱2;
type_3 id_list_3 資料型態 成員名稱3;
... ...
type_n id_list_n 資料型態 成員名稱n;
}; };
struct struct_name var_name_1,var_name_2...,var_name_n; struct 結構名稱 結構變數名稱1,結構變數名稱2,...,結構變數名稱n;
例子如下:
struct student
{
char name[10];
char sex;
int score;
};
struct student peter, john;
- 這種方式是先宣告結構型態,再以"struct 結構名稱"為其資料型態,定義結構變數。
第三種方式如下:
typedef struct typedef 資料型態
{ {
type_1 id_list_1 資料型態 成員名稱1;
type_2 id_list_2 資料型態 成員名稱2;
type_3 id_list_3 資料型態 成員名稱3;
... ...
type_n id_list_n 資料型態 成員名稱n;
} struct_name; } 結構名稱;
struct_name var_name_1,var_name_2...,var_name_n; 結構名稱 結構變數名稱1,結構變數名稱2,...,結構變數名稱n;
例子如下:
typedef struct {
char name[10];
char sex;
int score;
} student;
student peter, john;
初始值的設定
除了在程式執行時輸入資料外,也可以在宣告結構的同時,設定結構變數的初始值。
第一種方式:
struct student
{
char name[10];
char sex;
int score;
} ss1 = {"peter", 'M', 80}, ss2 = {"lucy", 'F', 75};
第二種方式:
struct student
{
char name[10];
char sex;
int score;
};
struct student ss1 = {"peter", 'M', 80}, ss2 = {"lucy", 'F', 75};
第三種方式:
typedef struct {
char name[10];
char sex;
int score;
} student;
student ss1 = {"peter", 'M', 80}, ss2 = {"lucy", 'F', 75};
資料的輸入與輸出
存取一般成員的數值,語法如下:
輸入
scanf("format string", &結構變數名稱.成員名稱);
scanf("%d", &tri.length);
輸出
printf("format string", 結構變數名稱.成員名稱);
printf("%d", tri.length);
例子如下:
int main(void)
{
struct triangle
{
int length, /* 三角形的長 */
int height; /* 三角形的高 */
double area; /* 三角形的面積 */
};
struct triangle tri;
printf("請輸入三角形的長: ");
scanf("%d", &tri.length); /* 以tri.length存取結構成員length */
printf("請輸入三角形的高: ");
scanf("%d", &tri.height); /* 以tri.height存取結構成員height */
tri.area = tri.length * tri.height / 2.0; /* 以tri.area存取結構成員area */
printf("三角形的面積為: %.2f\n", tri.area);
}
存取陣列成員的數值,語法如下:
輸入
scanf("format string", &結構變數名稱.成員名稱[var]);
scanf("%d", &ss1.grade[0]);
輸出
printf("format string", 結構變數名稱.成員名稱[var]);
printf("%d", ss1.grade[i]);
例子如下:
int main(void)
{
struct student
{
char SID[7]; /* 儲存學生學號的字串陣列 */
char name[10]; /* 儲存學生名字的字串陣列 */
int grade[1]; /* 儲存成績的陣列 */
};
int i;
struct student ss1;
printf("請輸入學號\n");
scanf("%s", ss1.SID);
printf("請輸入學生姓名\n");
scanf("%s", ss1.name);
printf("請輸入學生成績\n");
scanf("%d",&ss1.grade[0]);
printf(" 學號 姓名 成績\n");
printf("%6s%6s",ss1.SID,ss1.name);
for (i=0;i<1;i++)
printf(" %d",ss1.grade[i]);
printf("\n");
}
平行陣列與結構陣列
平行陣列 結構陣列
int id[10]; struct student
double grade[10]; {
int id;
double grade;
};
struct student classes[10];
- 陣列id與grade是平行陣列,因為相同索引值的陣列元素均屬同一學生(如:id[0]與grade[0])。
平行陣列,例子如下:
int main(void)
{
int id[10]={10301,10302,10303,10304,10305};
int grade[10]={88,82,76,91,61};
int i;
printf(" 學生名單如下 \n");
printf(" ------------\n\n");
printf("學號 分數\n");
for(i = 0; i < 3; i++)
printf("%d %d\n", id[i],score[i]);
}
結構陣列,宣告語法如下:
struct 結構名稱 陣列名稱[陣列大小];
struct struct_name array_name[size];
例子如下:
struct student
{
int id;
int grade;
};
struct student classes[5];
存取資料時需用以下方式:
printf("%d",結構名稱[var].成員名稱);
printf("%d",classes[i].grade);
例子如下:
int main(void)
{
struct student/* 利用結構陣列存取資料 */
{
int id;
int grade;
};
struct student classes[5] = { /* 定義結構陣列,設定其初始值 */
{10301, 88},
{10302, 82},
{10303, 76},
{10304, 91},
{10305, 61}};
int i;
printf(" 學生名單如下 \n");
printf(" ------------\n\n");
printf("學號 分數\n");
for(i = 0; i < 5; i++)/* 使用for迴圈將資料印出 */
} printf("%d %d\n", classes[i].id,classes[i].grade);
動態資料結構
一種儲存於記憶體中的結構,在程式執行的過程中可以增大或縮小。
動態配置記憶體
在c語言中指標也可以用來儲存一塊由程式設計師使用內建的 malloc 函數所配置的記憶體空間的地址。
指標的宣告如下:
struct airplane
{
char name[10];
double weight;
double speed,
range;
};
int *nump;
char *charp;
struct airplane *airplanep;
- nump是一個指向int的指標,charp是一個指向char的指標,而airplanep是一個指向struct airplane的指標;
- 而*nump的形態是int,*charp的形態是char,*airplanep的形態是struct airplane。
協助配置記憶空間的函數有:
函數名稱 |
用途 |
malloc() |
用來動態的配置記憶空間 |
sizeof() |
用來正確計算出所需配置的記憶體大小 |
nump = (int*)malloc(sizeof(int));
charp = (char*)malloc(sizeof(char));
*nump = 307;
*charp = 'Q';
airplanep = (struct airplane*)malloc(sizeof(struct airplane));
- malloc(sizeof(struct airplane))是要求系統配置struct airplane型態的記憶體空間,經過(struct airplane*)轉型為結構指標的型態後,傳回此結構的地址(指標)。
- 由於malloc的回傳值只是個指標,並不是指向某特定型態的指標,所以必需將malloc()配置的記憶空間的地址,轉換為struct airplane*型態,才能指定給airplanep儲存。
存取動態配置的結構成員:
將資料輸入,並透過指標儲存於結構中成員的寫法範例:
scanf("%s", (*airplanep).name); // 因為 name 是字元陣列,已經是個指標,所以不用 &
scanf("%lf", &(*airplanep).speed);
而建議的寫法是:
scanf("%s", airplanep->name); // 因為 name 是字元陣列,已經是個指標,所以不用 &
scanf("%lf", &airplanep-speed);
透過指標輸出結構中成員的寫法:
printf("format string", 指標變數->結構成員);
printf("\n飛機名稱: %s\n", airplanep->name);
例子如下:
int main(void)
{
struct airplane
{
char name[10];
double weight;
double speed,
range;
};
struct airplane *airplanep;
airplanep = (struct airplane*)malloc(sizeof(struct airplane));
printf("\n飛機名稱:");
scanf("%s", airplanep->name); /*Boeing 747*/
printf("\n飛機重量:");
scanf("%lf", &airplanep->weight);/*180,000磅*/
printf("\n飛機速度:" );
scanf("%lf", &(*airplanep).speed);/*0.75馬赫(500英里或805公里)*/
printf("\n飛機航程:");
scanf("%lf", &airplanep->range);/*7,260海里(8,350英里或13,450公里)*/
printf("\n飛機名稱: %s\n", airplanep->name);
printf("\n飛機重量: %.0f\n", airplanep->weight);
printf("\n飛機速度: %.2f\n", airplanep->speed);
printf("\n飛機航程: %.4f\n", airplanep->range);
}
- 新配置的記憶體區塊是由稱為堆積(heap)的記憶區中配置出來的。
鏈結串列
鏈結串列(linked list)是一連串相連的節點。
結構與指標成員
要動態建立鏈結串列,必須使用指標成員。因為無法預知串列中會有多少個元素,所以在需要時候可以動態的建立節點,然後以指標成員指向下一個節點。
宣告語法如下:
struct student
{
char initial[4];
int grade;
struct student *linkp;
};
- struct node*型態的成員linkp指向另一個相同型態的節點。
節點設定初始值
struct student *newp1,*newp2,*newp3;
newp1=(struct student*)malloc(sizeof(struct student));
strcpy(newp1->initial,"STL");
newp1->grade=98;
newp2=(struct student*)malloc(sizeof(struct student));
strcpy(newp2->initial,"MLP");
newp2->grade=67;
/*將newp2的指標值複製至newp3*/
newp3=newp2;
連接節點
int main(void) {
struct student {
char initial[4];
int grade;
struct student *linkp;
};
struct student *newp1,*newp2,*newp3;
// 節點newp1
newp1 = (struct student*)malloc(sizeof(struct student));
strcpy(newp1->initial, "STL");
newp1->grade = 98;
// 另一種寫法是 (*newp1).grade = 98;
// 節點newp2
newp2 = (struct student*)malloc(sizeof(struct student));
strcpy(newp2->initial, "MLP");
newp2->grade = 67;
newp1->linkp = newp2; /*連結至newp2用來儲存另一個節點的位址。*/
// 節點newp2->linkp
newp2->linkp = (struct student*)malloc(sizeof(struct student)); /*動態建立一個newp2->linkp的節點*/
strcpy(newp2->linkp->initial, "HJP"); /*newp2->linkp的節點的initial欄位資料為HJP*/
newp2->linkp->grade = 73; /*newp2->linkp的節點的grade欄位資料為73*/
newp2->linkp->linkp = NULL; /*newp2->linkp的節點沒有下一個節點設為NULL*/
// 節點newp3
newp3 = newp2;
printf("\nnewp1->initial:%s\n", newp1->initial);
printf("newp1->grade:%d\n", newp1->grade);
printf("\nnewp2->initial:%s\n", newp2->initial);
printf("newp2->grade:%d\n", newp2->grade);
printf("\nnewp3->initial:%s\n", newp3->initial);
printf("newp3->grade:%d\n", newp3->grade);
printf("\nnewp1->linkp->initial:%s\n", newp1->linkp->initial);
printf("newp1->linkp->grade:%d\n", newp1->linkp->grade);
printf("\nnewp2->linkp->initial:%s\n", newp2->linkp->initial);
printf("newp2->linkp->grade:%d\n", newp2->linkp->grade);
}
以鏈結串列表示堆疊
以下程式中堆疊資料的新增與刪除都從堆疊的頂端(top)這個唯一的出入口。具有「後進先出」(last-in,first-out:LIFO)的特性。
#include <stdio.h>
#define null 0
void create_stack(void);
void free_stack(void);
int empty(void);
void push(char stack_data);
struct node { /* 定義一個單向鏈結節點結構 */
char data; /* data:用來存放整數資料 */
struct node *link; /* link:指標,指向下一個節點 */
};
struct node *top;
int main(void) {
int i;
char data[4] = {'2', '+', 'C', '/'};
create_stack();
printf("自堆疊頂端放入資料:\n");
for (i = 0; i < 4; i++) {
printf(" step %d:", i + 1);
push(data[i]);
}
}
/* 產生一個空堆疊,它只有 top 節點 */
void create_stack(void) {
top = (struct node *) malloc(sizeof(struct node)); /*動態要求一個節點的空間*/
top->link = null; /*設定項端節點楓欄位是空的*/
}
新增,將資料(stack_data)放入堆疊:
void push(char stack_data)
{
struct node *newp;
newp = (struct node *) malloc(sizeof(struct node));
newp->data = stack_data; /*設定新節點的資料內容是stack_data*/
newp->link = top->link; /*節點指標newp指向top節點所連結到的節點*/
top->link = newp; /*將節點指標top連結到新的節點*/
}
刪除,刪除堆疊頂端的資料:
char pop(void)
{
char stack_data;
struct node *x;
x = top->link; /*暫時的節點指標x指向堆疊的第一個節點*/
top->link = x->link; /*將頂端節點指標top指向堆疊的第二個節點*/
stack_data = x->data; /*將暫時節點指標x指向堆疊資料取出*/
free(x); /*釋放節點空間*/
return(stack_data);
}
- free()函式是用來釋放記憶體空間。當程式執行過程中不需要某個鏈結串列時,可以用這個函式來釋放所使用的空間。
釋放空間,刪除堆疊,釋放所有節點所佔之記憶體:
void free_stack(void)
{
struct node *x, *y;
if(top->link != null){
x = top->link; /*將節點指標x指向top節點指標的連結*/
while(x->link != null){
y = x; /*將節點指標y指向節點指標x*/
x = x->link; /*將節點指標x指向x節點指標的連結*/
free(y); /*釋放y節點空間*/
}
free(x); /*釋放x節點空間*/
}
else;
free(top); /*釋放top節點空間*/
}
}
以鏈結串列表示佇列
佇列(queue)的資料處理對新增的資料是由尾端插入,而資料的刪除是由前端消除。也就是說佇列處理具有「先進先出」(first-in,first-out:FIFO)的特性。
#include <stdio.h>
#define N 6
void create_empty_queue(void);
void insert_node(int key);
/* 定義 node 為一個節點結構 */
struct node { /* 定義一個單向鏈結節點結構 */
int data; /* data 用來儲存節點資料值 */
struct node *link; /* 為一個 node 指標,它指向下一個節點 */
};
int main(void) {
int i;
int a[N] = {33, 72, 48, 56, 12, 11};
create_empty_queue( );
printf("\n將資料放入佇列的尾端:");
for (i = 0; i < N; i++) {
printf(" %d", a[i]);
insert_node(a[i]);
}
printf("\n");
}
struct node *front, *rear;
/* front : 為一個 node 指標,它指向 queue 的前端(串列的頭) */
/* rear : 為一個 node 指標,它指向 queue 的尾端(串列的尾) */
/* 產生一個空串列,它只有 front 及 rear 兩個節點 */
void create_empty_queue(void) /* Constructor */
{
front = (struct node *) malloc(sizeof(struct node)); /*動態要求一個節點的空間*/
rear = (struct node *) malloc(sizeof(struct node));
front->link = NULL; /*設定開端節點楓欄位是空的*/
rear->link = NULL; /*設定尾端節點楓欄位是空的*/
}
新增,將資料(key)放入佇列(queue)的尾端:
void insert_node(int key)
{
struct node *newp, *nextp;
newp = (struct node *) malloc(sizeof(struct node));
newp->data = key; /*設定新節點的資料內容是key*/
newp->link = NULL; /*設定新節點的連結欄位是空的*/
if (front->link == NULL) /* queue is empty */
{
front->link = newp; /*設定開端節點欄位連結到新節點*/
rear->link = newp; /*設定尾端節點欄位連結到新節點*/
}
else
{
nextp = rear->link; /*節點指標nextp指向rear節點連結到的節點*/
nextp->link = newp; /*將節點指標nextp連結到新的節點*/
rear->link = newp; /*將節點指標rear連結到新的節點*/
}
}
刪除,自佇列(queue)的前端刪除資料:
int delete_node(void)
{
int key;
struct node *nextp;
nextp = front->link; /*暫時的節點指標nextp指向堆疊的第一個節點*/
front->link = nextp->link; /*將頂端節點指標front指向堆疊的第二個節點*/
key = nextp->data; /*將暫時節點指標nextp指向堆疊資料取出*/
free(nextp); /*釋放節點空間*/
return(key);
}
- free()函式是用來釋放記憶體空間。當程式執行過程中不需要某個鏈結串列時,可以用這個函式來釋放所使用的空間。
釋放空間,刪除堆疊,釋放所有節點所佔之記憶體:
/*釋放不再需要用的節點空間*/
void free_all_queue(void){
struct node *thisp, *nextp;
if (front->link != NULL)
{
thisp = front->link; /*將節點指標thisp指向front節點指標的連結*/
while(thisp->link != NULL)
{
nextp = thisp; /*將節點指標nextp指向節點指標thisp*/
thisp = thisp->link; /*將節點指標thisp指向thisp節點指標的連結*/
free(nextp);
}
free(thisp);
}
else;
free(front);
free(rear);
}
反轉,將資料反轉:
void reverse(void)
{
struct node *prevp, *thisp, *nextp;
nextp = front->link; /*將指標nextp指向front節點指標的link欄位連到的節點*/
thisp = NULL; /*將節點指標thisp指向NULL*/
while(nextp!= NULL)
{
prevp = thisp; /*將節點指標prevp指向節點指標thisp*/
thisp = nextp; /*將節點指標thisp指向節點指標nextp*/
nextp = nextp->link; /*將節點指標nextp指向nextp節點指標的link欄位連結到的節點*/
thisp->link = prevp; /*將節點指標thisp的link欄位連結到prevp指標指向的節點*/
}
front->link = thisp; /*將節點指標front的link欄位連結到thisp指標指向的節點*/
}
二元樹
指一個節點擁有兩個指標欄位。
二元樹
- 根節點:二元樹節點的第一個節點。
- 葉節點:二元樹節點中無後繼節點。
根、左子樹與右子樹:
以鏈結串列表示節點結構
二元搜尋樹
二元搜尋樹的性質:
1.根節點的值比其左子樹的每一個節點值大。
2.比其右子樹的每個一節點值小。
建構二元搜尋樹
演算法:
1.如果為一個空樹 2.將新項目加入樹的根節點。 其它 如果根節點的輸入值與新項目的輸入值相等 3.放棄加入-鍵入值重複。 其它 如果新項目的輸入值小於根節點的輸入值 4.將新節點加入概節點的左子樹 其它 5.將新節點加入概節點的右子樹
例子如下:觀看程式執行的步驟
tree_node_t *tree_insert(tree_node_t *rootp, int new_key) {
if (rootp == NULL) { /* Simple Case 1 - 空樹 */
rootp = (tree_node_t *)malloc(sizeof (tree_node_t));
rootp->key = new_key;
rootp->leftp = NULL;
rootp->rightp = NULL;
} else if (new_key == rootp->key) { /* Simple Case 2 */
/* 放棄加入-鍵入值重複 */
} else if (new_key < rootp->key) { /* Insert in */
rootp->leftp = tree_insert /* left subtree */
(rootp->leftp, new_key);
} else { /* Insert in right subtree */
rootp->rightp = tree_insert(rootp->rightp, new_key);
}
return (rootp);
}
顯示二元搜尋樹
演算法:
1.如果為一個空樹 2.顯示左子樹。 3.顯示根節點項目。 4.顯示右子樹。
例子如下:觀看程式執行的步驟
void tree_inorder(tree_node_t *rootp) {
if (rootp != NULL) {
tree_inorder(rootp->leftp);
printf("%d ", rootp->key);
tree_inorder(rootp->rightp);
}
搜尋二元搜尋樹
演算法:
1.如果為一個空樹 2.目標輸入值不存在。 其它 如果目標輸入值位於根節點 3.則在根節點找到目標輸入值。 其它 如果目標鍵值小於根節點輸入值 4.搜尋左子樹 其它 5.搜尋右子樹
例子如下:觀看程式執行的步驟
tree_node_t *tree_search(tree_node_t *rootp, int target) {
if (rootp == NULL) {
return NULL;
} else if (rootp->key == target) {
return rootp;
} else if (rootp->key > target) {
return tree_search(rootp->leftp, target);
} else {
return tree_search(rootp->rightp, target);
}
}