在C語言中,**僅有4種基本數據型別——整型、浮點型、指針和聚合型別(如陣列和結構等)。**所有其他的型別都是從這4種基本型別的某種組合派生而來。
整型分爲signed和unsigned,包括char,int,short int,long int。
(當可移植問題比較重要時,字元是否爲有符號數就會帶來兩難的境地。最佳妥協方案就是把儲存於char型變數的值限制在signed char和unsigned char的交集內,這可以獲得最大程度的可移植性,同時不犧牲效率。並且只有當char型變數顯式宣告爲signed或unsigned時,纔對它執行算術運算。)
**signed關鍵字一般只用於char型別,因爲其他整型型別在缺失情況下都是有符號數。**至於char是否爲signed則因編譯器而異。所以char可能等同於signed char,也可能等同於unsigned char。
**宣告爲列舉型別的變數實際上是整型。**例如:
enum Jar_Type{CUP, PINT, QUART, HALF_GALLON, GALLON};
浮點型型別有float,double,long double。
浮點數位面值在預設情況下都是double型別的。
指針只是地址的另一個名字罷了。指針變數就是一個其值爲另外一個(一些)記憶體地址的變數。
指針常數與非指針常數在本質上是不同的,因爲編譯器負責把變數賦值給計算機記憶體中的位置,程式設計師事先無法知道某個特定變數將儲存到記憶體中的哪個位置。因此,你通過操作符獲得一個變數的地址而不是直接把它的地址寫成字面值常數的形式。因此,把指針常數表達爲數值字面值的形式幾乎沒有用處,所以C語言內部並沒有特地定義這個概念(有一個例外,NULL,它可以用零值來表示。)
**C語言存在字串的概念:它就是一串以NUL位元組結尾的零個或多個字元。**字串通常儲存在字元陣列中,這也是C語言沒有顯式的字串型別的原因。由於NUL位元組是用於終結字串的,所以在字串內部不能有NUL位元組。不過在一般情況下,這個限制並不會造成問題。
例如:
「Hello」,"\aWarning!\a",「Line 1\nLine2」,""
需注意的是,即使是空字串,依然存在作爲終止符的NUL位元組。
ANSI C宣告如果對一個字串常數進行修改,其效果是未定義的。因此實踐中,請儘量避免這樣做,如果需要修改字串,請把它儲存於陣列中。
你可以把字串常數賦值給一個「指向字元的指針」,後者指向這些字元所儲存的地址。但是,你不能把字串常數賦值給一個字元陣列,因爲字串常數的直接值是一個指針,而不是這些字元本身。
如果覺得不能賦值或複製字串顯得不方便,標準C函數庫提供了一組函數用於操縱字串。
C語言中的初始化是,在變數名後面跟一個等號,後面是你想要賦給變數的值。例如:
int j = 15;
爲了宣告一個數組,在陣列名後面要跟一對方括號,方括號裏面是一個整數,指定陣列中的元素個數。
如果下標值是從那些已知正確的值計算得來,那麼久無需檢查它的值。如果一個用作下標的值是根據某種方法從使用者輸入的數據產生而來的,那麼在使用它之前必須進行檢測,確保它們位於有效的範圍內。
宣告指針,例如:
int *a;
這條語句表示表達式a產生的結果型別是int。
不允許使用int a;式的指針宣告,原因是當時同時爲多個指針變數進行宣告時,這容易造成誤導。正確的多指針變數同時宣告形式爲:
int *b, *c, *d;
在宣告指針變數時也可以爲它指定初始值,例如:
int a = 25;
int *ptr = &a;
int b[10];
int *point = b;
int *p = &b[0];
char *message = "Hello world!";
C語言中存在隱式宣告:函數如果不顯式地宣告返回值的型別,則預設認爲其返回的是整型。例如:
f(int x)
{
return x+1;
}
依賴隱式宣告不是一個好主意,這不同於C++中的auto,因此不要使用。
**C語言支援typedef機制 機製,它允許你爲各種數據型別定義新名字。**例如
typedef char *ptr_to_char;
ptr_to_char a;
其表示把ptr_to_char作爲指向字元的指針型別的新名字,其宣告a是一個指向字元的指針。
使用typedef宣告型別可以減少使宣告變得又臭又長的危險,尤其是那些複雜的宣告(在結構中尤其管用)。而且如果以後要修改程式所使用的一些數據型別時,修改一個typedef宣告比修改程式中與這種型別有關的所有變數(和函數)的所有宣告要容易的多。
ANSI C允許宣告常數,例如:
const int a;
由於a的值無法被修改,所以你無法把任何東西賦值給它,因此只能通過初始化來給常數賦值,以用於後續處理。例如:
const int a = 15;
在函數中宣告爲const的形參在函數被呼叫時會得到實參的值。
**當涉及指針變數時,情況變得更爲有趣,因爲有兩樣東西都有可能成爲常數——指針變數和它所指向的實體。**例如:
int const *pci;
其爲一個指向整型常數的指針,你可以修改指針的值,但你不能修改它所指向的值。
int * const cpi;
其爲一個指向整型的常數指針,此時指針是常數,它的值無法修改,但可以修改它所指向的整型值。
int const * const cpci;
其無論是指針本身還是它所指向的值都是常數,不允許修改。
編譯器可以確認4種不同類型的作用域:檔案作用域、函數作用域、程式碼塊作用域和原型作用域。識別符號宣告的位置決定它的作用域。
任何在程式碼塊的開始位置宣告的識別符號都具有程式碼塊作用域。你應該避免在巢狀的程式碼塊中出現相同的變數名。
任何在所有程式碼塊之外宣告的識別符號都具有檔案作用域,它表示這些識別符號從它們的宣告之處直到它所在的原始檔結尾處都是可以存取的。在檔案中定義的函數名也具有檔案作用域。
原型作用域只適用於在函數原型中宣告的參數名。
函數作用域只適用於語句標籤,語句標籤用於goto語句。
關鍵字register可以用於自動變數(即在程式碼塊內部宣告的一般變數,儲存於堆疊中)的宣告,提示它們應該儲存於機器的硬體暫存器而不是記憶體中,這類變數稱爲暫存器變數。通常,暫存器變數比儲存於記憶體的變數存取起來效率更高。但是編譯器並不一定要理睬register關鍵字,且如果宣告的register變數太多,編譯器只選取前幾個儲存於暫存器中,其餘的就按普通自動變數處理。
在有些計算機中,如果把指針宣告爲暫存器變數,程式的效率將能得到提高,尤其是那些頻繁執行間接存取操作的指針。