顯示具有 C 標籤的文章。 顯示所有文章
顯示具有 C 標籤的文章。 顯示所有文章

2010年3月12日 星期五

[Linux Programming] 在 Ubuntu 中使用 C 語言連結 MySQL

首先.......必須安裝 MySQL server,一般來說安裝 "mysql-server" 都可以裝到目前最新版的套件

接著要安裝開發程式所需要的 library:

Ubuntu 8.04 => libmysqlclient15-dev

相關的 header 檔案都會放在 /usr/include/ 裡面,而實際運作的函式庫則會存在 /usr/lib/mysql

若是撰寫 mysql 相關程式時,編譯方式如下:

gcc <程式名稱> -I /usr/include/mysql -L /usr/lib/mysql -l mysqlclient -o <輸出檔案名稱>

2010年3月7日 星期日

[Linux Programming] 在 Ubuntu 中使用 dbm 資料庫

在 ubuntu 中 dbm 相關開發套件是不會預先安裝的,因此如果要使用的話,必須安裝「libgdbm-dev」套件。

而由於安裝的是 gdbm 套件,若要使用 gdbm 的相關函式當然沒問題,但若是要使用 ndbm 的相關函式,則必須進行相容性的轉換,作法如下:
  1. 在程式中 include gdbm-ndbm.h

  2. 編譯時加上 -l gdbm_compat -l gdbm 兩個參數以連結函式庫
函式庫說明可以參考以下連結:

2008年11月18日 星期二

[Oracle] 安裝 & 開發環境與工具

安裝方式

Windows 應該就不用說了吧? 直接上官方網站下載安裝就行啦!

要在 Linux 練習的話,可以參考此篇文章,在 Ubnutu 上面安裝 Oracle XE。


開發環境與工具

.NET

PHP

Oracle Application Express

這是一個用來快速開發 web application 的工具,詳情可到官方網站了解。

C & C++

Java

2008年1月13日 星期日

C - File attributes

今天在研究怎麼取得檔案的相關屬性,簡單的以檔案大小為例

在書中說使用 io.h head file 搭配 filelength() function 就可以取得....

在 Windows 中似乎沒問題,不過在 Linux 上似乎就不行了,於是上網查了一下資料,發現要使用 stat(const char *filename, struct stat *buf) function 才可以取得檔案屬性

而若要用此 function,必須要引用「sys/stat.h」 head file,裡面定義了 stat struct,其中有許多 member 分別表示不同的 file attribute,詳細可以參考「The meaning of the File Attributes」一文。

接著是 stat 的使用方式,可以參考「Reading the Attributes of a File」一文,以下用個簡單範例來說明:
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int main(void)
{
//開啟檔案
FILE *fptr;
fptr = fopen("fileTest.txt", "r")

//取得檔案屬性
struct stat myFileStat;
stat(fpath, &myFileStat);
printf("file serial number : %d\n", myFileStat.st_ino);
printf("device contains the file : %d\n", myFileStat.st_dev);
printf("user ID : %d\n", myFileStat.st_uid);
printf("group ID : %d\n", myFileStat.st_gid);
printf("file size in bytes : %d\n", myFileStat.st_size);
printf("last access time : %d\n", myFileStat.st_atime);

//關閉檔案
fclose(fptr);

return 0;
}
上面只介紹了一部份的屬性,詳細的屬性資訊可以參考上面的連結。

C - fgets & strncmp & strrchr

今天練習寫 C 時,使用了在 stdio.h 中所提供的 char *gets(char *str) function,但是在 compile 的時候卻出現了以下警告訊息:
warning: the `gets' function is dangerous and should not be used.
喔? 這個 function 竟然被 compiler 說是危險不應該使用的.....原因是為何呢?

看到了以下兩篇文章,終於知道了答案:

原來當輸入的值超過接收這個值的變數範圍時,就會有 overflow 的情況發生。

overflow 又會怎樣呢? 這讓我想起了之前參加 OpenSource 研討會時,有一個講 Embedded System Security 的主講人有提到這個,overflow 的確會造成資訊安全上的漏洞,因此這個情形要盡量應該避免。

那要用什麼才會比較安全呢? 答案是 char *fgets(char *str, int num, FILE *stream) function,透過第二個參數可以指定最多可以取得多長的字串,如此一來,就不怕因為使用者亂輸入而造成 overflow 的情況發生了!

但是需要注意的是,函式所取得的長度是 (num - 1) 並非 num 喔! 這是需要注意一下的地方。


另外,取得值之後,若要跟另外的字串做比較,一般來說是用 int strcmp(const char *s1, const char *s2) function,若兩個字串相同,則回傳 0。

但是我怎麼試都不是 0,明明輸入的字串跟原本的字串都相同阿?

後來才發現,我用 fgets 取得輸入值,在 BufferSize 的地方設定為 10(也就是說最多取到長度為 9),而輸入值的長度只有 5,於是 fgets 很好心的幫我在字串後面加上 New Line(\n) 的標記..........就是因為這樣比對出來才不會是 0!!!

用 strcmp 看來是不行了,於是找了一下,發現有 int strncmp(const char *s1, const char *s2, size_t n) function 可以用,透過此 function 就可以指定比對的字串以及所要比對的長度囉!!

因此,我只要在第三個參數的部分設定為 5 即可!


還有另外一個 solution,即是透過 fgets 取得輸入後,將最後的「\n」給去除,只要用以下的程式碼即可:
#include <string.h>
char *usrInput;
*strrchr(usrInput, '\n') = '\0';

2008年1月11日 星期五

C - Function Pointer

今天在研究 C Function Pointer 的部分,原本有些誤解,後來查了一些資料後,終於釐清 Function Pointer 的觀念了!

Function Pointer 顧名思義,就是指向 Function 的指標

在 C 語言中,不論是 variable、array、struct、或是 function(一段程式碼),都有所屬的啟始記憶體位置

由此可知,main function 也是有其啟始記憶體位置。

而 function pointer 的宣告跟使用 function 時所要注意的地方是相同的,有以下幾點必須注意:
  1. 回傳值型態(return type)
  2. 參數數量(augument count)
  3. 參數型態(argument type)

以下直接用一個簡單範例來說明 function pointer 的使用:
#include <stdio.h>

//function宣告
int doAdd(int, int);
int doMinus(int, int);

int main(void) {
//宣告 function pointer
//注意所設定的參數數量與型態
int (*my_func_ptr)(int, int);

//function pointer 指向 doAdd
my_func_ptr = doAdd;
printf("function pointer 指向 doAdd => %d\n", (*my_func_ptr)(5, 3)); //結果:8

//function pointer 指向 doMinus
my_func_ptr = doMinus;
printf("function pointer 指向 doMinus => %d\n", (*my_func_ptr)(5, 3)); //結果:2

return 0;
} //end main


int doAdd(int a, int b) {
return a + b;
} //end doAdd

int doMinus(int a, int b) {
return a - b;
} //end doMinus
從上面的範例可以看出,doAdd() 與 doMinus() 兩個 function 的回傳值型態、參數數量、參數型態都是相同的,只是名稱不同而已。

而名稱不同,卻不影響 function pointer 的使用,因為我們所用的是 function 的啟始記憶體位置

而 function pointer 的使用,有一點相當重要,即是 function pointer 的宣告;而 function pointer 的宣告,即是要注意到「回傳值型態」、「參數數量」、「參數型態」這三個部分。

當 function pointer 的宣告完成後,另外一個需要注意的就是每個 function 的啟始記憶體位置;而每個function 的啟始記憶體位置,即為 function 的名稱

在程式中,就是透過將 function pointer 指向不同 function 的啟始記憶體位置,來執行不同的 function。


參考資料

2007年12月30日 星期日

The C Programming Language 讀後整理(2) - Types, Operators, and Expres

Variable Names

以一般的 C 開發習慣來說,variable name 會使用小寫的英文字母來命名;而 symbolic constants 則會用大寫的英文字母來命名 。


另外變數的命名有幾點需要注意:
  1. 名稱的長度必須在 31 的字元之內
  2. 不可使用 key words (例如:if、else、int、float .... etc)


Data Types and Sizes

C 所提供的基本型態,在之前已經介紹過了。

另外需要注意的是,同樣的型態在不同的 OS 中,或是在不同的 CPU 中(32 bits 或 64 bits),所使用的長度可能不一樣,例如:int 有可能用 16 bits 或是 32 bits 來表示。

另外,有兩個與變數值為正或負相關的關鍵字為「signed」與「unsigned」。

一般若沒有使用的情況下,則會預設為 signed,表示變數是可以表示出正負數的;而 unsigned 則是指定正數(非負數)。

想當然爾,每個型態所使用的長度是相同的,但有些要表現負數,有些卻不用,然而這樣的差別,就影響了各型態所能表現值的範圍;舉例來說,每個 char 佔了 8 bits 的記憶體空間,若是在 signed 的情況下,可以表現 -128~127;但若是在 unsigned 的情況下,就可以表現 0~255。因此可以看出,關鍵字的使用會影響到變數值可表現的範圍。

以下用段程是碼來顯示各型態變數值可表現的範圍:(under Linux)
#include <stdio.h>
#include <limits.h>

main() {
//signed data type
printf(" signed char min : %d\n", SCHAR_MIN); //-128
printf(" signed char max : %d\n", SCHAR_MAX); // 127
printf(" signed short min : %d\n", SHRT_MIN); //-32768
printf(" signed short max : %d\n", SHRT_MAX); // 32767
printf(" signed int min : %d\n", INT_MIN); //-2147483648
printf(" signed int max : %d\n", INT_MAX); // 2147483647
printf(" signed long min : %ld\n", LONG_MIN); //-2147483648
printf(" signed long max : %ld\n", LONG_MAX); // 2147483647

//unsigned data type
printf("unsigned char max : %u\n", UCHAR_MAX); //255
printf("unsigned short max : %u\n", USHRT_MAX); //65535
printf("unsigned int max : %u\n", UINT_MAX); //4294967295
printf("unsigned long max : %lu\n", ULONG_MAX); //4294967295
} //end main


Constants

由於 C 支援了許多不同型態的變數,因此在表示的時候,為了辨識不同型態的變數值,會有不同的表示方式,以下一一來說明。

整數(interger)

以整數型態來說,若是 int 則是沒有任何問題,不需要加上特殊符號,因為 C 預設的 integer 型態即為 int;而若是 long,則必須要在變數值後方加上大寫或小寫 L,例如:123456789L;而若加上是 unsigned 的數值,又必須在變數值後方加上大寫或小寫 U,因此 unsigned long 的變數值,後方要加上「ul」或「UL」。

而整數一般常用的表示方式為 10 進位(octal)或是 16 進位(hexadecimal)。其中變數值以「0」為開頭的為 10 進位的變數值,例如:031(表示為 31);「0x」開頭的則是 16 進位的變數值,例如:0x1F(表示 31)。

綜合以上說明,舉例來說,若變數值為 0x1FUL,則表示此變數為 unsigned long 型態,其值為 31。

浮點數(floating)

再來是浮點數型態,由於 C 預設 float 型態為 double,因此 double 型態的變數值後方就不需多加任何符號;但若是 float,則需要在變數值後方加上大寫或小寫 F;另外一個比較特殊的,浮點數變數值加上大寫或小寫 L 則是表示 long double。

字元(character)

字元這個部份能表示的可就多了! 在一般的表示方法中,所表示的為 ASCII 的字元,每個字元有其對應的 int 值,例如:'0' 代表 48,因此字元可以用來進行比對。

此外,還有許多特殊符號或是特殊效果,必須使用跳脫字元來表示,看起來像多個字元,但其實對 C 來說,只是單一個字元! 以下列表說明:

跳脫字元
意義
跳脫字元意義
\a
alert(cell) character
\\
backslash
\b
backspace
\?
question mark
\f
form feed
\'
single quote
\n
new line
\"
double quote
\r
carriage return
\000
octal number
\t
horizontal tab
\xhh
hexadecimal number
\v
vertical tab



其中有一個較為特殊的是「\0」,其所代表的意義為 null,用來做為 string 的結尾表示之用,因此一個 string 儲存於記憶體時,除了 string所需要的記憶體長度外,還必須要多一個 bit 的空間來儲存這個代表 string 結尾的字元。

另外,針對 string 處理的部份,standard header 提供了「string.h」,其中包含了許多關於 string 的 function 可以使用,例如:計算 string 長度的 strlen(s)

列舉(enumeration)

最後 enumeration 是比較特殊的部份,而 enumeration 其實就是一群 int 變數值的集合,然後用比較容易瞭解的方式去表示。

在 enumeration 中會出現很多變數名稱,如果沒有指定,預設從第一個開始,其 int value 為 0,第二個則為 1,其他依此類推..........

舉例來說:
//第一個 NO 所代表的 int value 為 0
//第二個 YES 所代表的 int value 為 1
enum boolean { NO, YES };

//為每一個變數名稱指定值(每個跳脫字元都屬於 int value)
enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t'
NEWLINE = '\n', VTAB = '\v', RETURN='\r' };

//指定第一個的 int value 為 1,之後不指定就會從 1 遞增下去
//FEB = 2、MAR = 3、APR = 4 ..... etc
enum months { JAN=1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC};
PS.變數名稱多以大寫英文字母來表示。


Declarations

在這個部份,並沒有什麼特殊,只是記得變數使用前必須要宣告!

另外,可以在宣告變數時直接給定其值,不需多一行來指定。

其中比較需要注意的是,用「const」關鍵字所宣告的變數,其值是不能夠被改變的;若程式中去修改,compiler 會發出錯誤警告。

例如:
//用 const 宣告的變數,其值都不可被修改
const double e = 2.71283745345;
const char msg[] = "warning: ";
另外,使用 const 的宣告方式,也可以用在 function 的參數上,例如:string.h header 中的「int strlen(const char[])」,如此一來即表示,該傳入的參數在 function 中是不能被更改的!!


Arithmetic Operators

在算術相關的操作上,比較需要注意的是「%」(取餘數)符號無法使用在 float 或 double 型態上的變數。

另外,「/」與「%」針對負數的處理,可能會因為機器的不同而有所不同。


Relational and Logical Operators

在優先權的部份,arithmetic operators 是比 relational operators 來的稍微高一些,使用時必須注意一點。

以下直接用範例來說明:
//條件判斷的部份在 for loop 的中間一段,條件如下:
//(1)i 小於 lim - 1
//(2)由getchar()取得的字元不是代表new line(n)
//(3)由getchar()取得的字元不是代表結尾(EOF)
for(i = 0 ; i < lim - 1 && (c = getchar()) != 'n' && c != EOF ; i++)

//可以用括號的方式將條件敘述清楚,雖然多了幾個括號,不過不會有優先權搞錯的問題
//因此改寫如下:
for(i = 0 ; (i < (lim - 1)) && ((c = getchar()) != 'n') && (c != EOF) ; i++)

//以下兩種寫法,其實都是可以用的
//只是第一種看起來比較容易讓人理解,但實際使用上可能情況更為複雜
if(!valid)
if(valid == 0)
因此,若有很多條件判斷時,程式在撰寫上必須多注意,不然很容易產生很難處理的 bug。


Type Conversions

由於寫程式的時候會遇到的情況很多,因此對於變數進行型態上的轉換是很平常的事情,因此在型態轉換上有幾點必須注意的事情:

  1. 一般來說,若對變數進行操作,則會被轉換為共通的型態。

  2. 有些情況下 compiler 會進行自動轉換,不過規則是較小型態的變數會轉換為較大型態的變數。

  3. 若是將較大型態的變數值指定給較小型態的變數,compiler 會發出警告,但不會認為是錯誤的(但會喪失部份精確度)

因此舉例來說,char 型態的變數,本就是由一個 byte 的記憶體位置所組成,因此也可以當作 int 來表示或使用。

以下用個程式範例,來說明如何將數字 string 轉為 int 值:
int atoi(char s[]) {
int i, n;

n = 0;
//在 for loop 中,s[i] 會自動被轉為 int
for(i = 0 ; s[i] >= '0' && s[i] <= '9' ; i++)
n = 10 * n + (s[i] - '0'); //括號中的敘述會自動轉為 int
return n;
} //end atoi
另外一個程式,是將大寫字母轉換為小寫字母:
int lower(int c) {
if(c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
} //end lower
以上的作法是因為那些 char 型態的變數都屬於 ASCII 的範圍內,因此可以順利轉換為 int。

不過,變數型態的轉換其實不需要這麼麻煩,因為 C 提供的 standard header 中就有名稱為「ctype.h」的 header 提供給程式設計師方便不同型態間的轉換。

若是透過 ctype.h 中所提供的 fcuntion,就不需要考慮在不同平台之間 character set 可能不一樣的問題了! 若是要字母轉小寫,可以使用 tolower(int ch),若是要檢查是不是數字,可以用 isdigit(int ch)

除了上述由 compiler 所自行進行的轉換外,也可以強制進行變數型態的轉換,轉換的語法如下:
(type-name)expression
在某些情況下,強制轉換是需要的,例如在 math.h 中提供的 sqrt(double num) function,其中參數部分就必須是 double,因此若有一個 integer variable 為 n,可以透過以下方式代入:
sqrt((double)n)
透過此方式,就可以在代入 sqrt function 計算之前將型態強制轉換為 double。

但是若是事先有定義出 function 的 prototype,compiler 就會自行協助轉換的工作,例如:
double sqrt(double); // function prototype

root2 = sqrt(2); // 2 將會自動轉為 double 型態代入


Bitwise Operators

C 提供了六種運算子作為 bit 的處理! 分別是:

運算元
說明
&
AND
|
OR
^
Exclusive OR
<<
位元左移
>>
位元右移
~
1's 補數

這些用在 bit 的操作,一般來說可能不會常使用,不過在特殊的用途上其實還蠻常會運用到的,例如:網路程式的開發。

AND 通常是作為 mask 之用。

OR 通常作為將特定 bit 開啟(0 -> 1)之用。

至於位元左右移,可以達成乘以 2 or 除以 2 的效果。

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