國立屏東大學 資訊工程學系 程式設計(一)
程式設計主要的目的是為了解決問題,而大多數的問題又與資料處理相關。在C語言中,資料可以變數或常數的方式呈現,並加以運算或處理。另外,在C語言還將資料分類成不同的資料型態(Data Type),例如我們可將數字分成整數、實數等。本章將就C語言的資料型態及變數(variable)與常數(constant)的宣告、初始化做一說明。在開始之前,先讓我們回顧一下在第5章所談到的變數與記憶體位址的概念:
<note>
</note>
在第5章中,我們已經粗略地介紹了變數與記憶體的關係,本節將進一步詳細說明。由於程式設計的需要,我們必須在程式執行的階段利用記憶體空間來存放一些資料,並且可以對這些資料進行運算與處理。我們將這些在程式執行階段所使用到的資料項目稱為變數(variable),C語言要求所有的變數必須先宣告後才能使用。假設我們需要在程式中處理一個整數的運算,因此我們必須先進行整數變數的宣告:
int x;
上述的程式碼宣告了一個名稱為x的變數,在程式執行時,作業系統會分配足以放置整數的記憶體空間供它使用。所謂的足夠的空間指得是存放一個整數所需的空間,其大小視作業系統而定,通常是32bits,也就是4個bytes。
<note tip>若要知道您所在的作業系統,使用多少空間來存放一個整數,可以使用sizeof(int),它會傳回一個整數所佔的記憶體空間大小(其單位為byte)。</note>
讓我們假設程式開始執行,並且變數x所需的記憶體空間被配置在0x7ffff34fff00記憶體位址,那麼程式的symbol table內容為:
symbol | type | address |
---|---|---|
x | int | 0x7ffff34fff00 |
當中記載了一個名為x的符號,其型態為int,並且存放於0x7ffff34fff00位址起始的連續四個bytes(因為已經記載了其型態為int,所以系統可以知道其佔了四個bytes)。接著假設我們執行以下的程式碼:
x=38;
系統會檢視符號x所在的記憶體空間與其型態,然後將數值38放入對應的記憶體空間內,請參考figure 1。
由於一個整數假設為32bits,也就是四個bytes,所以我們會將0x7ffff34fff00~0x7ffff34fff03的記憶體空間,以38的二進位100110來儲存。通常描述記憶體空間的圖有兩種畫法,其一為figure 1的橫式,或是figure 2的直式,兩者的意義相同。
<note tip>記憶體位址的單位為byte,且通常以16進位來表示。0x7fff34fff00開頭處的0x就表示數值為16進位。 </note>
除卻這些細節,通常我們在設計C語言程式時,只需要抽像化地將變數x表達為記憶體中的某塊位置,不用特別去注意其所在的記憶體位置為何,figure 3就是我們常用的思考方式。
如第5章所說明的,如果我們想要知道一個變數到底存放在哪個記憶體位址,可以使用&運算子來取得。請參考以下的程式,它宣告了一個整數變數,並將其值與記憶體位址加以輸出。
h variableAndAddress.c
#include <stdio.h> int main() { int x; x=38; printf("The value of x is %d.\n",x); printf("The memory address of x is %p\n", &x); }
<note tip> 在printf()函式中,如要輸出記憶體位址,其format specifier為%p。 </note>
現在,讓我們正式地介紹C語言變數宣告的語法(syntax)。請參考以下列的語法說明:
type variableName[=value]?[,variableName[=value]?]*
<note tip> 在上面的語法說明中,「[]」為選擇性的語法單元,其後接續「*」表示該語法單元可出現0次或多次;「?」表示出現0次或1次。另外還有「+」代表1次或多次。本書將使用這種表示法做為語法的說明。 </note>
其中type為型態、variableName為變數的名稱,中括號內的部份則是選擇性的(可以有,也可以忽略),為該變數的初始數值。到目前為止,我們只介紹過int整數資料型態,其它可以使用的資料型態將在本章稍後加以介紹。
下面的程式碼片段宣告了一個名為x的整數變數,並且在後續設定其數值為38。
int x; ... x=38;
我們也可以將變數宣告與數值給定同時以一行程式碼來完成:
int x=38;
<note important> 未設定初始值的變數,其數值是不可知的(有些系統會在配置記憶體空間時,同時將空間內所以的位元皆設定為0)。任何一個變數都不應該在未設定數值前就將它拿來運用,否則程式可能會遇到不可預期的錯誤。 </note>
我們也可以同時宣告有多個相同型態的變數,例如下面的程式碼,同時宣告了三個整數變數x、y與z,其中x不指定初始值,y與z的初始值則分別為3與6。要注意的是,我們在兩個變數宣告的中間,是以','加以隔開。
int x, y=3, z=6;
<note important>
</note>
變數名稱在程式語言中又稱為識別字(identifier),其代表在程式執行階段的某個資料項目,因此建議使用較具意義的變數名稱,才容易理解、提升程式碼的可讀性(readability)。C語言變數命名具備以下規定:
<note> C語言共有以下34個保留字:
auto | break | case | char | const | continue | default | do |
double | else | enum | extern | float | for | goto | if |
inline | int | long | register | restrict | return | short | signed |
sizeof | static | struct | typedef | union | unsigned | void | volatile |
while |
</note>
C語言是case-sensitive的語言,意即大小寫會被視為不同的字元,因此以下的宣告其變數名稱皆是正確且不相同的:
int JUN, jun, Jun, JUn, JuN, jUn, jUN, juN;
為了讓程式碼的可讀性提升,使用有意義的變數名稱是相當重要的,有時我們甚至會使用一個以上的英文單字為變數命名,此時可以適當地調整大小寫或加上底線,例如下面是正確的宣告:
int bestStudent, BestStudent, best_student;
建議使用良好的命名規則,例如Camel Case或Hungarian Notation。目前C語言程式設計師通常以lower camel case法為變數命名,使用Hungarian Notation的程式市設計師也不再少數。
<note tip>
</note>
在程式碼中,經宣告並給定初始值後,就不再(也不允許)變更其數值的資料,就稱為常數(constant)。
C語言的常數宣告語法如下:
const type variableName=value[,variableName=value]*
其實,常數的宣告就如同變數宣告一樣,只要在最前面加上const這個保留字即可,同時所有常數的宣告都必須給定初始值。請參考下面的程式碼片段:
const int x=3, y=5; ... x=6; ...
上面的程式碼正確地宣告了兩個整數常數,但後續我們又改變了其中一個常數的數值,這樣會導致在編譯時的錯誤。您會得到“error: read-only variable is not assignable”的錯誤訊息。
除了前述的常數宣告外,我們還可以使用#define這個preprocessor directive來定義常數。例如:
#define PI 3.1415926 int main() { int radius=5; float area; area = PI * radius * radius; ... }
這個程式以#define定義了一個符號“PI”其值為3.1415926。當程式被編譯時,preprocessor會先將程式碼進行掃描,將其中所有出現PI之處,都改以3.1415926代替,然後再將代換後的程式碼交由compiler進行編譯。
C語言提供多種資料型態,包含基本資料型態(basic data type)與自定資料型態(user-defined data type)兩類。本章僅就基本資料型態做一說明,自定資料型態請參閱後續章節。
顧名思義,整數型態就是用以表示整數的資料。C語言中的整數型態,以integer的前三個字母int表示,唸做int或是integer都可。在現在的系統中,int通常為32位元(但在一些較舊的PC上,int也可能只是16位元),其中最左邊的bit代表正負數 → 0代表正整數或0,1代表負整數。以32bits為例,最大的正整數為<latex>(0111 1111 1111 1111 1111 1111 1111 1111)_2=2,147,483,647</latex>,也就是<latex>$2^{31}$-1</latex>;至於最小的負數並不是<latex>(1111 1111 1111 1111 1111 1111 1111 1111)$_2$= -2,147,483,647</latex>,而是<latex>(1000 0000 0000 0000 0000 0000 0000 0000)$_2$</latex>,其值為<latex>-2,147,483,648=- 2^{32}</latex>。
若不想用最左邊的bit來表達正負號,可以使用unsigned這個保留字加在整數型態的前面。例如unsigned int可表達的範圍為0到<latex>4,294,967,295</latex>,也就是<latex>$2^{32} - 1$</latex>。unsigned除了是保留字外,我們也稱它為修飾字(modifier),因為它可以加在其它保留字的前面,用以限縮或拓展其可表達的數值範圍。
除了unsigned修飾字外,整數int型態還可以搭配short與long兩個修飾字,將其表達空間加以調整。假設int為32bits,那麼short int則為16bits,long int則為64bits。您也可以再搭配unsigned修飾字一起使用,因此C語言一共有以下6種整數型態:
<note important>
</note>
如果想要知道您所使用的系統上,各種整數資料型態的最小值與最大值,可以使用在limits.h標頭檔中的巨集(macro)定義(關於巨集請參考本書第X章)。limits.h標頭檔共定義了9個與整數資料型態相關的巨集定義,詳如table 1:
您可以使用上述9個巨集數值,印出各整數資料型態的最大值與最小值,請參考下面這個程式:
h printIntLimits.c
#include <stdio.h> #include <limits.h> int main() { printf("short int [%d, %d]\n",SHRT_MIN, SHRT_MAX); printf("int [%d, %d\n", INT_MIN, INT_MAX); printf("long int [%ld, %ld]\n", LONG_MIN, LONG_MAX); printf("unsigned short int [0, %u]\n", USHRT_MAX); printf("unsigned int [0, %u]\n", UINT_MAX); printf("unsigned long int [0, %lu]\n", ULONG_MAX); }
<note tip> unsigned的整數其format specifier為%u;long型態的整數可在其format specifier前加上l,如%ld, %lu。 </note>
以64位元的Mac OS X 10.8.4為例,各整數型態的數值範圍如table 2:
除了宣告變數為某種型態外,我們也可以直接在程式碼中使用整數數值,本節將說明各種型態的整數數值的表示方法。
在C語言中,整數數值可依其所使用的進位系統分成十進制(decimal, base 10)、二進制(binary, base 2)、八進制(octal, base 8)與十六進制(hexadecimal, base 16)等四種表示法。
我們還可以在數值後面加上L(或l)、U(或u),強制該數值為long型態或是unsinged型態,兩者也可以混用表示unsigned long型態,例如:13L, 376l, 0374L, 0x3ab3L, 0xffffffUL, 03273LU等皆屬之。
除了本書已經介紹過的%d、%u、%ld、%lu等適用於整數型態的format specifier外,C語言還有%o與%x用以表示八進制與十六進制的整數數值,全部彙整於table 3:
我們可以使用scanf()與printf()函式,配合format specifier來取得或輸出特定的整數型態的數值。請參考intIO.c程式範例,示範如何取得各種型態的整數,並且加以輸出:
h intIO.c
#include <stdio.h> int main() { int x; short int y; long int z; printf("Please input an int:"); scanf("%d",&x); printf("%d_decimal = %o_octal = %x_hexadecimal.\n", x, x, x); printf("Please input a short int in octal:"); scanf("%ho",&y); printf("%hd_decimal = %ho_octal = %hx_hexadecimal.\n", y, y, y); printf("Please input a long int in hexadecimal:"); scanf("%lx",&z); printf("%ld_decimal = %lo_octal = %lx_hexadecimal.\n", z, z, z); }
顧名思義,浮點數型態就是用以表示小數的資料。C語言中有3種符點數的型態:float, double與long double,分別實作了IEEE 754當中的單精確度、倍精確度與擴充精確度:
一般而言,float型態適用於對小數的精確度不特別要求的情況,例如體重計算至小數點後兩位、學期成績計算至小數點後一位等情況。而double則用在重視小數的精確度的場合,例如台幣對美金的匯率、工程或科學方面的應用等。至於long double,則更進一步提供精確度,但非常少機會使用到。
由於在不同平台上,浮點數的實作差異甚大,所以C語言的標準並沒有提到float, double與long double該提供多少的精確度。本節以IEEE 754標準為參考,將浮點數的數值範圍與精確度做一整理,請參考table 4。如果還需要更詳細的資訊,請參考定義在float.h標頭檔中的巨集。
浮點數數值的表達有兩種方式:
C語言默認的浮點數型態為double,如果您要特別強制一個數值之型態為float或long double,可以在數值後接上一個F或L(大小寫皆可)。例如:3.45L, 3.45f等皆屬之。
適用於浮點數型態的format specifier,彙整於table 5:
所謂的字元型態就是用以表示文字、符號等資料,在C語言中只有一種字元型態:
在不同的系統中,字元的數值可能會代表不同意義,視其所採用的字元集(character set)而定。現行最常見的字元集為ASCII(American Standard Code for Information Interchange),請參考Wikipedia關於ASCII的說明。
在C語言中,一個char型態的數值就是一個整數。具體來說,C語言使用8 bits的整數,使用從00000000到11111111共256種可能的數值來對映到ASCII的字元。例如'A'的ASCII數值為65,'0'為48等。
因此,我們可以把char型態的數值當成整數來進行運算,例如:
char c; int i; i ='a'; // i的值為97 c = 65; // c的值為'A' c = c + 1; // c的值為'B'
既然char型態就是整數,那可不可以再配合unsigned使用呢?因為char型態的整數數值是用以對應特定的字元集(如ASCII),而每個字元集都有其可表達的字元個數要求,C語言會自動將char定義為singed或unsigned以符合字元集的需求。因此我們通常不會特別在char前加上unsigned。但是,如果您有某些較小的整數資料要處理,就可以考慮使用char來代替int。因為int為32 bits,甚至short int也要使用到16 bits,若您只需要處理一些介於-128到127之間的數值,那您就可以考慮改用char來代替int;或是宣告為unsigned char來處理那些介於0到255的正整數資料。
字元數值的表達方法有兩種:
C語言針對一些特殊字元,提供一組escape sequence,如table 6
除此之外,還可以使用八進制或十六進制來表達字元:
適用於字元型態的format specifier,只有一個 %c。您可以搭配%c於scanf()與printf()函式使用,以取得或輸出字元資料。此外,您還可以使用getchar()與putchar()函式來取得或輸出一個字元,例如:
char c; //宣告一個字元變數c c = getchar(); //以getchar()取得使用者輸入的字元,並放置於變數c putchar(c); //將字元變數c輸出
如果在程式碼中,我們想要把某個數值之型態加以轉換,可以使用顯示型態轉換(explicit conversion)來對數值進行強制的轉型(casting)。使用的方法很簡單,只要在想要轉型的數值前加上一組()其中指定欲轉換的型態即可,例如:
int x; long int y; y=(long)x; y = (long)(x+837); x = (int)sizeof(int);