C語言入門: 宣告,賦值,PRINTF

C 語言的編譯與執行:

原始程式 –> 經由編譯器 –> 變成目的檔 –> 經由連結器 –> 變成執行檔
SOURCE.C –> COMPILER –> SOURCE.OBJ –> LINKER –> SOURCE.EXE

 

1. 起手式

C 語言是個會區分大小寫,並忽略換行的程式語言,下面是一個最簡單的 C 程式:

int main()
{
 return 0;
}

 

每個程式都有所謂的骨架,就是它基本的架構。main() 是 C 語言的主程式,每一個 C 的程式都必須有一個 main 函數,函數名稱後面接著一對小括號,而下面的一對大括號 {} 則是 main 函數的程式主體。

在 C 語言中,變數需要經過宣告才能使用。你可以想像這些宣告

然後,主程式也是一個函式,那函式的話就會需要 所謂的參式。也因為 C 語言會忽略換行,所以上面的程式也可以寫成一行:

int main() {  return 0; }

這樣也可以運作,只不過程式的可讀性就很差,所以一般來說我們不建議這樣寫,還是希望一行一行分清楚。

不過光只有這樣,在編輯器中還是不會跑成功的噢。我們必須在程式碼的最上方加入:

#include <stdio.h>

這邊的意思是:使用 #include 來將 stdio.h 檔案的內容加到你的程式的最前面

stdio.h 的意思是「standard input/output」也稱 C 標準庫頭文件。

stdio.h 這個檔案包含在你安裝的 C compiler 中,包含了許多宣告輸入、輸出的函數,包括從標準輸入裝置輸入資料,然後傳送到標準輸出裝置。

標準輸入裝置通常是鍵盤,而標準輸出裝置通常是螢幕。這些函數構成了 <stdio.h> 的主體、也稱 C 標準庫頭文件,

需要用到標準的輸入輸出功能時,只要用 #include 的方式把 stdio.h 引入,就不用整個重打 stdio.h 的內容。

這些函數構成了C標準庫頭文件<stdio.h>的主體,大多數功能都繼承自電腦科學界大神邁克·萊斯克 (Michael Lesk) 在 1970 年代早期於貝爾實驗室所編寫的一個「可移植的I/O程序庫」。

 

C 程式可以看成是許多函數 (functions) 的集合,每個 function 會負責某項工作,依照輸入的參數對資料作處理,然後將結果傳回。

其中 main function,也就是 main() ,是程式真正運行的地方。當一支程式開始執行時,首先就會從 main function開始執行,再從 main() 中去呼叫其他 functions。

因為我們是在作業系統下執行你的程式,所以在這個例子裡 main() 會把值回傳給作業系統。

main() 裡面的內容包括在大括號 { } 裡面,也就是 main() 的作用範圍。

好啦,我們完成起手式了:

#include <stdio.h>

int main(){
  ...
}

 

 

2. 宣告變數

講完主程式之後,來看看變數。

在 C 程式語言裡 我們常常需要把一些數字存起來,存放的地方就在變數裡面。可以想像它是一張便條紙,可以給這張便條紙一個名字叫 little_card。

接下來我們必須要告訴電腦:「這張卡片是用來存放哪種類型的變數」,比如整數、浮點數、字串… 。

整數的話,宣告寫 int,代表「integer」。最後就可以在便條紙上面寫數字,比如說 5。

int little_card = 5;

之後我們就從這張 little_card 找到我們要的 5。

事實上,所謂「宣告一個變數」在做的事情,就是去向記憶體要一塊位置來存放這個變數。

這邊要注意的是——int 屬於的 C 語言的特殊用字之一,代表有其特殊意義,所以替變數取名字的時候,注意不要和這些用字重複。

比如把剛剛的 little_card 改取叫「int」,變成「int int = 5;」就是一件很可怕的事。(別問,你會怕)

其實關於變數的命名,還有一些可以補充的:

變數名稱只能包含連續的大小寫英文字母、 _ (底線)、以及數字等三類字元、開頭不能用數字、字母的大小寫有差( h1 和 H1 代表兩個不同變數)… 等等。

不過這邊初學者可以先看看有個印象就好,有需要時再去上網搜尋 “Naming convention (programming)” 參考慣用的規則,來替變數與 function 命名。

#include <stdio.h> 

int main(){ //我是起手式
  int little_card = 5; //跟記憶體要一塊叫little_card的變數位址來存5
  printf("%d", little_card); 
  return = 0;
}

然後我們要把 little_card 印出來啦,這裡的 % 是一個特殊字元,代表什麼,代表printf要處理的參數。所以 %d 代表 decimal (十進位),意思是告訴電腦:要把後面這個變數以十進位的方式印出來噢!

最後,前面說過 main() 這個函數需要回傳一個整數給作業系統,這也是 main() 前面的 int 所代表的意義。

既然已經指定要回傳整數,在程式結束時我們會用 return 來表示我們要回傳的值是多少。在這個例子中的回傳值是 0。

我們也可以設定 return 不為 0,表示異常情形。

 

有​​時候一行宣告一個變數很麻煩,程式會變得很太長。

int Annie;
int Emily;
int Joy;

所以通常我們習慣上會把一些變數放在同一行做宣告,中間用逗號隔開。

int Annie, Emily, Joy;

這邊要注意的是,我們通常會相關的變數要宣告在一起, 不相關的變數就不要宣告在一起。這樣看一行就知道這些變數是有關係的,程式會比較好懂。

(1) 宣告整數型 (INT)

我們提過 int 是宣告整數用。但除了 int 之外,C 程式裡面還可以把變數宣告成其他種類的整數,包括不同長度的型別: short, long, long long:

  • 基本型:int,32 bit
  • 短整數型:short / short int,16 bit
  • 長整數型:long / long int,32 bit

這邊的 short、long,代表宣告一個變數時,該變數所佔的記憶體儲存空間。

比如我們宣告 int ,其實是在向記憶體要一個 32 位元大小的記憶體空間。( 8 bit = 1 byte, 32 bit = 4 byte,也就是 4 個 word)

等等,你說它們的不同在於長度的不同,但 long 型和 long long型,它們的大小不是一樣的嗎?

事實上,這關乎到 C 或 C++ 語言的標準。 在C / C++ 的標準裡,對於某種特定的變數類型到底應該佔用多大的記憶體空間, 定義得非常的廣。

在 C 語言的標準裡,只要求 long 型變數長度不短於 int 型、short 型不大於 int 型即可。

這樣定義所帶來的問題,就變成不同編譯器裡關於 int型到底有多長,很多都是不同的。比方說在 Turbo C 裡,int 型長度是 16 bit。但在 Visual C++ 環境下的 int 長度是 32 bit,short 型是 16 bit。

這邊你一定會覺得很困惑——那當我使用一個編譯環境時,要怎麼知道每種資料類型所佔的空間到底是多少 byte 啊?

別緊張,這邊介紹一種辦法:「sizeof」。sizeof 這個內建的運算元是讓你在 C/C++ 語言中專門測定 int, long, short,甚至是 char (宣告字元)、double (宣告浮點數) 這些型別所佔用的 byte 數量。

sizeof 也可用在變數上面,可知一變數用了多少個 byte,寫法是:sizeof(variable); 。

#include <stdio.h>

int main(void)
{
  printf("int: %u bytes, char: %u bytes,\n", sizeof(int), sizeof(char));
  printf("long: %u bytes, double: %u bytes.\n", sizeof(long), sizeof(double));
  return 0;
}

 

另外我們還可以在整數型別前面加上 unsigned,用來表示這個整數變數不包含負數的部份。

如果你很確定變數 x 的值不會是負的,也可以把它宣告成 unsigned,這樣原本用來表達負數所需的空間就可以拿來表達更大的正數。

譬如同樣使用 32 bits,原本 int 如果範圍是 -2,147,483,648 ~ 2,147,483,647,宣告成 unsigned int 範圍就變成 0 ~ 4,294,967,295。

 

(2) 宣告字元 (char)

char 型別會用到 8-bit 的記憶體空間,靠 8 bits 所儲存的數值來表達字元。也就是說,一個字元就佔了 1 byte (8 bit)。這邊 1 個位元組 (byte) 我們也稱為一個「字組」(1 word)。

記得在宣告一個字元時,記得要用單引號而不是用雙引號噢!(雙引號是用在宣告一個 string)

char word = '@';

所以說,在記憶體裡面不是真的存「@」,而是將其轉換成相對應的數字來保存。

既然已知在記憶體中會使用一個字組 (word) 來表示一個字元型的數, 然而一個字組所能夠表示的最多的數的數量是 2^8 次方,也就是 256。

所以說我們要定義一個字碼表的話,這個字碼表的大小一定不會超過 256 個字元。

可以猜測到,一定存在這樣一個字碼表,要使用者輸入一個字元,然後程式會把對應的編碼秀出來。這就是 ASCII 編碼。

ASCII 編碼表全名是 American Standard Code for Information Interchange (美國標准訊息交換碼),是由美國國家標準局(ANSI)所製定的。 經過修改後,被 ISO 接納為國際標準。

 

(3) 宣告浮點型 (fLOAT, DOUBLE)

除非記憶體不夠大,不然宣告時不要用 float,盡量用 double。

float 的精準度不夠高,雖然 double 的size更大,但精準度更好,不然容易遇到奇怪的進位錯誤。

int 和 float 比大小的時候得注意,12 / 12 = 1;然而兩個一樣的double相除,可能會變成 0.999…之類的數字,所以不能拿 int 1 和 double 1 去比。

兩個整數相除會變成無條件捨去,比如 5 / 2 = 2。若希望得到的答案是 2.5,先把其中一個轉成 double。(一個是 double,一個是 int 時,會把 int 拉到 double 的層級去算,理由請參考下方型別轉換)

 

(4) 型別轉換

當兩種型別混用時,位階較低的型別會轉成位階較高的;位階高低順序如下: double, float, unsigned long, long, unsigned int , int。

int main(void)
{
  char ch;
  int i;
  float fl;

  ch ='C'; 
  i = ch; 
  fl = i; 

  ch = ch + 1; 
  i = fl + 2*ch; 
  fl = 2.0*ch + i; 

  printf("ch = %c, i = %d, fl = %2.2f\n", ch, i, fl);

  return 0;
}

printf 出來的答案:ch = D, i = 203, fl = 339.00。

把字元 ‘C’ 存在變數 ch 中 (只佔 1 byte),接下來轉成 int 存在 i 中,再轉成 float 存在 fl 中。

由於 ch 的值是 ‘C’ (ASCII code 67) 會被轉成 int 來運算,然後得到的結果會被轉成 float 再和 fl 相加,最後再降成 int 存到 i 之中。最終都轉成 float。

 

3. 賦值 (Assign)

另外,這邊的等號 (=) 並不是代表「兩者相等的等於」,其意義是賦值 (Assign),代表把右邊的值、放到左邊的變數裡。

賦值可以出現很多次, 我們能現在賦值這個變數為 0,下一個賦值它為 1000。

int i = 0; //i是0
int i = 1000; //i變成1000

新的賦值會把舊的值蓋過去,所以原本的 0 就不見了,i 只剩下新的值 1000。如果你用「等於」去理解,就會覺得很奇怪,怎麼 i 一下等於 0 、一下等於 1000,事實上不是這樣噢!

假設我們現在有 7 個包包 (bags) 與 5 個皮夾 (wallets),結果搞錯了!紀錄時把包包寫成 5 個、皮夾寫成 7 個,該怎麼寫一支程式交換這兩個變數的值呢?

如果第一行寫 bags = wallets,第二行再寫 wallets = bags:

int bags, wallets;
bags = wallets //bags現在被改成wallets的值
wallets = bags; //wallets被assign自己的值

就會爆掉,因為 bag 的值已經在第一行被蓋掉了!

所以可以利用賦值的特性,先找一個暫存的地方把 bag 存起來才不會值不見。

temp = bags; //把bags先放到temp變數裡
bags = wallets; // wallets賦值給bags
wallets = temp; //temp再蓋掉wallet的值

等等,那可以寫成這樣嗎:

int i, j;
i = j = 0;

這邊的意思就不會是 i 等於 j 等於 0;由於 assign 規定從右邊算起,所以這邊是 i = (j = 0) 的意思,先把 j 的值設成 0 、再把 i 的值設成 j 的值 (此時為 0 )。

 

來玩個小案例吧!假設我們要開 10 間水果店,每家水果店都有 12 顆蘋果和 3 顆橘子,最後把我們總共有的水果 (包括蘋果與橘子) 數量印出來:

#include <stdio.h>

int main() {
  int apples, oranges, fruit_stores;
  apples = 12;
  oranges = 3;
  fruit_store = (apples*12 + oranges*3)*10;
  printf("Total amount of fruit: %d", fruit_stores);
  return 0;
}

 

4. PRINTF

上面簡單提到過 printf 的標準格式:

printf("%d\n", your_value);

%d 代表 decimal (十進位),意思是告訴電腦:要把後面這個變數以十進位的方式印出來。

但你也可以調整 printf 的格式,比如你也可以印 16 進位 “%x”,或是 printf(%.2f, 1.234)、代表印出小數點後兩位並自動四捨五入。

這些就是要用時再Google 搜尋「c printf format」即可,不用特別背。

呼叫 printf 之後,並不是把資料直接噴到螢幕上,而是會先噴到暫存的 buffer 裡面,直到buffer滿、或有 \n 出現、或有 scanf (輸入),才會把資料噴到螢幕上,這叫 flushing the buffer。

程式結束會把 buffer 清掉,就會印出來。

有時候為了讓資料能立刻顯示到螢幕上,可以用 fflush() 強迫把 buffer 裡的東西送出。

當你發現有時候輸入或輸出的顯示順序會亂掉,可以試著在 printf() 之後用 fflush() 來確保資料不會被卡在 buffer 裡。