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 的效果。

沒有留言:

張貼留言