2007年11月25日 星期日

The C Programming Language 讀後整理(1) - A Tutorial Introduction

變數型態(type)介紹

在 C 中,所有的變數在使用前都必須宣告其型態(type),以下是 C 提供的基本型態
  1. char:character,為單一 byte
  2. short:short integer
  3. int:integer
  4. long:long integer
  5. float:floating point
  6. double:doule-precision floating point

而每種變數型態都有其大小的範圍:(以下用個簡單範例來說明)
#include <stdio.h>
#include <limits.h> //要取得變數型態的大小範圍值,必須 include 此 header file

main()
{
//short(short integer) 的範圍 => 16 bits
printf("%d\n", SHRT_MIN); //-32768
printf("%d\n", SHRT_MAX); // 32767

//int(interget) 的範圍
printf("%d\n", INT_MIN); //-2147483648
printf("%d\n", INT_MAX); // 2147483647
}
以上是在 Linux 的環境上的執行結果,每個 int 的長度為 32 bits;而各種變數型態在不同的 OS 平台上可能會有不同的長度,這可能需要注意一下。


使用不同型態的變數,撰寫方式也必須不同

以下用一個華氏(fahrenheit)轉攝氏(celsius)範例來說明:
#include <stdio.h>

main()
{
float fahr, celsius;
int lower, upper, step;

lower = 0;
upper = 300;
step = 20;

//以 while 的方式撰寫
fahr = lower;
while(fahr <= upper) {
//由於指定變數型態為 float,前面相除的兩個變數必須以 float 方式去撰寫
//若沒有,會被 compiler 視為 int 而遭截斷為 0
celsius = (5.0 / 9.0) * (fahr - 32.0);
//以比較漂亮的方式印出結果
printf("%3.0f\t%6.1f\n", fahr, celsius);
fahr = fahr + step;
} //end while
//以 while 的方式撰寫 =============== End

//以 for 的方式撰寫
for(fahr = 0 ; fahr <= upper ; fahr = fahr + step)
printf("%3.0f\t%6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32.0));
//以 for 的方式撰寫 =============== End
}


Symbolic Constant 的使用

在程式中,如果直接使用某個數字,而未註明其用途,可能會讓其他看程式碼的人不知道原作者的用意為何。因此最好每個值都可以賦與其意義,讓使用上不會太過於突兀,其中一個方法就是使用「Symbolic Constant」。

而使用的語法如下:
#define [name] [replacement text]
以下用個簡單範例來說明:
#include <stdio.h>

//Symbolic Constant 的定義 (名稱通常以「大寫」表示)
#define LOWER 0
#define UPPER 300
#define STEP 20

main()
{
int fahr;
for(fahr = LOWER ; fahr <= UPPER ; fahr = fahr + STEP)
printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
}
如此一來,每個值都知道其代表意義為何了!!


Character Input and Output

在程式中,字元(字串)資料的處理是相當普遍的。

在 C 中,資料的 input 與 output 的功能是由 library 所提供,因此不太需要須考慮其實作的細節。

接著,文字資料的輸入或輸出方面,不論來源是鍵盤,或是檔案,都會被視為由許多字元所組合而成的 stream;而 text stream 則是一行一行分開,且有順序的字元,每一行都包含 0 或以上的字元,且在結尾帶有一個「n」(newline)字元。

File Copying

C standard library 中提供了許多存取單一字元的 function,其中最常用的就是 putchar() 以及 getchar() 兩個 function,以下有個簡單範例將來源資料一個一個字元取出並印出來:
#include <stdio.h>

main()
{
//為了確保變數可以存入所有字元,因此宣告型態為 int
int c;

//取得一個字元
while((c = getchar()) != EOF) { //未到檔案結尾(End of File),繼續執行
putchar(c); //輸出一個字元
printf("\n%s\n", "-----------------------");
} //end while
}
PS. 其實 EOF 的值為 -1

Character & Word & Line Counting

首先說明計算的規則:
  • Character:非 EOF 字元
  • Word:非空白、n、t .... 等字元
  • Line:n 字元

以下用個簡單範例說明:
#include <stdio.h>

#define IN 1 //在 word 內
#define OUT 0 //在 word 外

main()
{
//number of characters
//number of words
//number of lines
long nc, nw, nl;
nc = nw = nl = 0; //初始化變數

//保留輸入字元之用
int c;

//檢查是否在 word 中
int state;

while((c = getchar()) != EOF) {
++nc; //character count + 1

//檢查是否為 new line
if(c == '\n')
++nl;

//檢查字元是否為空白、new line 或是 tab
if(c == ' ' || c == '\n' || c == '\t')
state = OUT; //設定在 word 之外
else if(state == OUT) { //字元不是空白,也不是 new line,也不是 tab,但狀態正在 word 之外
//表示進入新的 word 中
state = IN; //設定為進入 word 中
++nw;
}
} //end while

printf("%ld %ld %ld\n", nc, nl, nw);
}
【註】輸入 Ctrl + D 即為丟出 EOF 訊息!


Arrays

看過上面範例後,如果要計算 0~9 各出現了幾次,要怎麼做呢? 有兩種作法:
  1. 宣告 10 個變數,分別儲存 0~9 所出現的次數
  2. 宣告 1 個長度為 10 的 array,每個 element 分別儲存 0~9 出現的次數
顯而易見的,使用 array 是比較好的選擇,以下用個範例程式來說明:
#include <stdio.h>

main()
{
int c, i, nwhite, nother;
int ndigit[10]; //宣告長度為 10 的 int array

nwhite = nother = 0;
for(i = 0 ; i < 10 ; i++)
ndigit[i] = 0;

while((c = getchar()) != EOF) {
if(c >= '0' && c <= '9') //檢查是否為數字
ndigit[c - '0']++; //由於 c 是 char 型態,因此透過此方式轉為 int
else if(c == ' ' || c == '\n' || c == '\t')
nwhite++;
else
nother++;
} //end while

printf("digits = ");
for(i = 0 ; i < 10 ; i++)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n", nwhite, nother);
}


Function

function 的目的在於將程式封裝,讓使用 function 的人不需要知道 function 是如何達成其所宣告可達成的功能,只要拿來使用即可。

這個概念在 OO 設計中是相當重要的一部分,當然 C 也提供了這個功能,以下用個範例程式來說明:
#include <stdio.h>

//function 要在前面先進行宣告
//此處定義的參數名稱不會與以下的程式中的變數名稱有所衝突(僅宣告而已)
//甚至可以省略為 int power(int, int)
int power(int m, int n);

main()
{
int i;

for(i = 0 ; i < 10 ; i++)
printf("%6d %6d %6d\n", i, power(2, i), power(-3, i));
}


//回傳型態:int
//function 名稱:power
//參數 1:base
//參數 2:n
int power(int base, int n) //計算次方的 function
{
int i, p;

p = 1;
for(i = 1 ; i <=n ; i++)
p = p * base;
return p;
}


Arguments - Call by Value

在 C 中,所有 function 的參數都是 Call by Value,也就是呼叫 function 時,程式會將要傳入的變數複製一份再傳入,而不會用原本的變數,因此在 function 中對變數的任何操作,並不會影響到原本的變數。

對於 function 來說,參數值可以視為 funtion 開始就初始化並已經指定好值的變數。

PS. 其實參數名稱所代表的是 memory address,也就是之後會提到的 pointer


Character Array

在一般程式語言中很常用的 string 型態,在 C 中是沒有的;而在 C 中若是要表示 string,就必須要使用 char array。

除此之外,為了方便辨識 string 的結尾,C 會在 char array 最後方加入「n」與「0」 兩個字元,如下所示:

範例字串:this is a dog!
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t
h
i
s

i
s

a

d
o
g
!
\n
\0

表示上面那個 string,最少需要長度為 16 的 char 陣列,其中 index 為 14、15 個兩個字元,是 C 用來做 string 結尾表示之用,因此在處理 string 的時候,必須要注意這個地方。

以下用個範例來說明 char array 的使用:
#include <stdio.h>

#define MAXLINE 1000

//宣告 function
int getline(char[], int maxline);
void copy(char[], char[]);

main()
{
int len; //目前資料行的長度
int max; //目前最大的資料行長度
char line[MAXLINE]; //目前讀取的資料行
char longest[MAXLINE]; //最長資料行的儲存陣列

max = 0;
while((len = getline(line, MAXLINE)) > 0) { //取得每一行資料,並檢查長度是否大於0
if(len > max) { //資料長度比目前最長的還要更長
max = len; //修改最長資料長度變數
copy(longest, line); //最長資料陣列的資料也隨著更換
}

//印出最長的資料行
if(max > 0)
printf("%s", longest);
} //end while
}

//讀取每行的資料進入 s 陣列,並回傳陣列長度
int getline(char s[], int lim)
{
int c, i;

//資料行檢查條件:
//(1)尚未到達資料行最大行數(通常這不會到達)
//(2)未遇到 EOF 字元 (按下 Enter 或是 Ctrl+D 會送出 EOF 字元)
//(3)未遇到 new line 字元
for(i = 0 ; (i < lim - 1 && (c = getchar()) != EOF && c != '\n') ; i++)
s[i] = c;
if(c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0'; //「n」與「0」為 string 結束的最後兩個字元
return i;
}

//將 from 陣列的內容複製到 to 陣列 (假設陣列夠大)
void copy(char to[], char from[])
{
int i;

i = 0;
while((to[i] = from[i]) != '\0') //檢查是否到了 string 結束字元
i++;
}
由上面的程式可知道,要印出 string 型態的變數,必須使用「%s」這個符號,而 C 也會自動將表示 string 結尾的兩個字元給忽略不印。


External Variables and Scope

這個部份要談到外部變數(external variables)以及其有效範圍。

在一般的 function 中,變數的有效範圍僅限於該 function 內,每次呼叫相同的 function ,裡面的變數值也不一定會相同;且 function 之間的變數是毫無相關的。

但若是有共同變數的需求呢? 此時就要借用 external variable 的方式來達成了!

其中,最重要的關鍵字即為「extern」的使用,以下用個範例來說明:
#include <stdio.h>

#define MAXLINE 1000

int max;
char line[MAXLINE];
char longest[MAXLINE];

//修改為使用 external variable 的版本,因此不需要參數了
int getline(void); //void 表示沒有參數(可省略)
int copy(void);

main() {
int len;
//extern int max; //使用 extern 關鍵字,就知道使用的是 external variable
//extern char longest[];

max = 0;
while((len = getline()) > 0) {
if(len > max) {
max = len;
copy();
}
} //end while

if(max > 0)
printf("%s\n", longest);
} //end main


//取得一行資料的內容
int getline(void) {
int c, i;
extern char line[]; //使用 extern 關鍵字,就知道使用的是 external variable

for(i = 0 ; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n' ; i++)
line[i] = c;
if(c = '\n') {
line[i] = c;
i++;
}
line[i] = '\0';
return i;
} //end getline


//複製陣列
int copy(void) {
int i;
extern char line[], longest[]; //使用 extern 關鍵字,就知道使用的是 external variable

i = 0;
while((longest[i] = line[i]) != '\0')
i++;
} //end copy
外部變數的使用,有時候固然方便,不過也會因為不小心誤改了變數值而造成其他 function 執行上的錯誤。

就軟體工程的觀念來說,此種方式會讓 function 不夠模組化,因為必須依賴 external variable 才能正常運作,對於 reuse 來說並不會有什麼幫助,因此使用上還是要注意一下。

PS. 其實不使用 extern 關鍵字也是可以通過編譯的! (extern 整行拿掉)

一般來說,若有一群 external variable 需要定義被使用,通常會使用額外的檔案來進行變數的定義,然後在程式中把該檔案引用進來;而通常這一類的檔案都稱為 header,副檔名為「.h」,在檔案一開始就會被 include 進來。

就像每個程式一開始的「#include <stdio.h>」就是標準的用法,將常用的 I/O function 給引用進來。


參考資料
  1. The GNU C Library - A.5.2 Range of an Integer Type


沒有留言:

張貼留言