C

From YuntechWiki

Jump to: navigation, search

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;

由上例可知,一個變數有:

  1. 一個名字
  2. 一個型態
  3. 一個資料值
  4. 以及儲存這個資料值的記憶體空間

共四個部份。

變數使用前要先進行宣告 (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」等。至於變數的命名,還必須遵守以下的規則:

  1. 變數名稱開頭不可以為數字。
  2. 變數名稱區分大小寫字母;例如 Ave與AVE會視為兩個不同的變數。
  3. 變數中間不可以有空白。
  4. 不可以使用C語言中的保留字;例如goto return default…等。
  5. 不可使用特殊字元;例如-,*$@...等。

保留字

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),是將一個原始問題,拆成數個相關的子問題。這種方式可以幫助程式設計師設計整個程式。

Top down1.png


庫存函數

是指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-1.png

 if(條件運算式) 
 {
    程式敘述;
 }

例如以下:

 int score = 78;
 
 if (score >= 60) {
     printf(“您通過了!”);
 }

if…else

「如果...就...,否則就...」的意思。

If-else-1.png

 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-elseif-else-1.png

 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

「如果...就...,否則如果...就...」的意思。

Nested if1.png

 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-1.png

 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 = &num; /*讓指標變數nump指向變數num的位址*/
  • 取得變數在記憶體的位址使用&位址運算子

指標和非指標變數的比較

Pointer.png

int num=100;
int *nump;
nump=&num;
 
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};

Arrary1 1.png

二維陣列的語法宣告如下:

資料型態 陣列名稱[列數][行數];
element-type name[Row-size][Col-size];;
int A[3][4];

Arrary2.png

陣列初值化

在宣告陣列的同時,將數值指定給陣列,設定陣列的初始值。

一維陣列

配置相同於陣列容量個數的初始值:宣告陣列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}};

Arrary3.png

以初始值個數決定二維陣列大小:就跟一維陣列相同,二維陣列可以用空的[]讓初始值決定其大小。但只有列數可以使用[],因為要先知道每一列的大小,編繹器才知何時將資料填入下一行。

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)。

int main(void) {    
int size = 5;
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語言裡並沒有「字串」的資料型態,但可以由字元陣列來組成字串。

字元與字串的區別

Char1.png

字元:是以單引號(')所包圍任一字母、符號或數字。

字串:則是以雙引號(")包圍起來,而且編譯器會在字串結束處,加上一個結束字元\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」,因此必須多留一個位置給字串結束符號存放(如圖所示)。 Char2.png

字串的輸入/輸出

使用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"};,二維陣列可得到與指標陣列相同的答案,差別在於指標陣列可以彈性的配置空間,可減少節省空間。二維陣列會浪費較多的空間且沒有彈性。

Char3.png

例子如下:

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("比較後排序內容:%s\n",list_original[i]);} | for (i=0;i<3;i++) {
| printf("比較後排序內容:%s\n",list[i]);}

Char44.png Char5.png

指標陣列的好處:

1.指標陣列會比陣列複製整個字串所需的空間少。

2.排序時,只複製指標而不是整個字串,執行時間會比較快。

3.排序後,原來陣列資料的順序會保留下來。

遞迴

函式除了可以呼叫其他函式,也可以呼叫自己,呼叫自己本身的函式被稱為遞迴函式

遞迴程式的特色

  1. 含有終止條件(termination condition);
  2. 含有遞迴呼叫,而遞迴呼叫所傳入的資料值一定會趨近終止條件;
  3. 答案的產生需要經過累計(例如:累加或累乘)的過程。

遞迴演算法,通常包含一個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 陣列中。

Recursive44.png

古典案例的遞迴

河內塔:有三根杆子A,B,C。A杆上有N個(N>1)穿孔圓盤,盤的尺寸由下到上依序變小。請按下列規則將所有圓盤移至C杆:

  1. 每次只能移動一個圓盤;
  2. 大盤不能疊在小盤上面。

請印出移動的次序。 提示:可將圓盤臨時置於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])。

Struct1.pngStruct2.png


平行陣列,例子如下:

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,*aillanep的形態是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);
}

Malloc111.png

  • 新配置的記憶體區塊是由稱為堆積(heap)的記憶區中配置出來的。

鏈結串列

鏈結串列(linked list)是一連串相連的節點。

結構與指標成員

要動態建立鏈結串列,必須使用指標成員。因為無法預知串列中會有多少個元素,所以在需要時候可以動態的建立節點,然後以指標成員指向下一個節點。

宣告語法如下:

struct student
{
char initial[4];
int grade;
struct student *linkp;
 
};
  • struct node*型態的成員linkp指向另一個相同型態的節點。

Malloc22.png

節點設定初始值

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;

Malloc33.png

連接節點

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);
 
}

Malloc44.png

以鏈結串列表示堆疊

以下程式中堆疊資料的新增與刪除都從堆疊的頂端(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指標指向的節點*/
}

觀看程式執行的步驟

二元樹

指一個節點擁有兩個指標欄位。

二元樹

Tree1.png

  • 根節點:二元樹節點的第一個節點。
  • 葉節點:二元樹節點中無後繼節點。

根、左子樹與右子樹

Tree2.png

以鏈結串列表示節點結構

Tree3.png

二元搜尋樹

二元搜尋樹的性質:

1.根節點的值比其左子樹的每一個節點值大。

2.比其右子樹的每個一節點值小。

Tree4.png

建構二元搜尋樹

演算法:

 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);
}
}
Retrieved from "http://wiki.plweb.org/C"
Personal tools