[C程序設計語言]第二部分,c程序設計第二部分
聲明:原創作品,轉載時請注明文章來自
SAP師太博客,並以超鏈接形式標明文章原始出處,否則將追究法律責任!
運算符的優先級... 1
字符串常量與字符數組... 1
字符串... 2
枚舉常量... 3
C程序存儲空間布局... 3
四種存儲類別... 4
static. 5
const. 7
sizeof8
register. 8
位移規則... 9
函數默認返回值... 9
運算符的優先級
運算符
結合性
() [] -> .
從左至右
++ -- + - ! ~ (類型) * & sizeof
從右至左
* / %
從左至右
+ -
從左至右
<< >>
從左至右
< <= > >=
從左至右
== !=
從左至右
&
從左至右
^
從左至右
|
從左至右
&&
從左至右
||
從左至右
?:
從左至右
= += -= *= /= %= &= ^= |= <<= >>=
從右至左
,
從左至右
字符串常量與字符數組
從技術角度看,字符串常量就是字符數組。字符串的內部表示使用一個空字符 \0 作為串的結尾,因此,存儲字符串的物理存儲單元數比在括在雙引號中的字符數多一個。這種表示方法也說明,C語言對字符串的長度沒有限制,但程序必須掃描完整個字符串後才能確定字符串的長度。標准庫函數strlen(s)可以返回字符串s的長度,但長度不包括末尾的 \0。
字符串
char *str = "abcd";//可以將字符串賦給一個 char * 類型的變量
printf("%c\n",*str);//a
printf("%s\n",str);//abcd
下面結果一樣:
char ca[] = "abcd";//也可以將字符串賦給一個字符數組
printf("%c\n", *ca);//a
printf("%s\n", ca);//abcd
printf("%d\n",
sizeof(ca));//5
C語言中的字符串是用空字符 \0 結束的字符數組。字符串是用指向字符串中第一個字符的指針訪問的。字符串的值是其第一個字符的地址。因此,C語言中的字符串就是一個指針,事實上,字符串就是指向其第一個字符的指針。
在用字符串直接量初始化char *類型的變量時,某些編譯器可能會把該字符串放在不能修改該字符串的某個存儲單元中,所以,如果希望能夠修改字符串直接量,應該把該字符串存儲在某個字符數組中。
上面還可以這樣寫:
char ca[] = { 'a', 'b', 'c', 'd', '\0' };
char *s 可以接收的數據類型有如下這些:字符串常量(char *s="str",實質上是將字符數組的第一個元素地址賦值給了變量s)、字符數組、字符指針。
輸出一個字符串時使用 printf("%s\n", s),第二個參數不是 *s(C語言沒有提供將整個字符串作為一個整體進行處理的運算符),如果輸出的是字符串,則直接是字符串地址即可。也就是說,字符串可以通過一個指向其第一個元素的指針來訪問。
char str1[]="str1";
char *str2="str2";
//直接使用數組首地址或字符指針
printf("%s\n",str1);
printf("%s\n",str2);
在函數定義中,形式參數 char s[] 與 char *s 是等價的
可用一個字符串直接初始化一個字符數組:
char string [] ="first";,編譯器根據字符串的長度確定數組的大小。特別要注意的是:字符串“first”除了包含五個字符外,還包含一個結束該字符串的特別的字符,該字符表示“空字符”,因此,數組string實際上有6個元素,空字符用字符常量 \0 表示,C語言中的所有字符串都是用該字符結束的。在聲明一個字符串的字符數組時(指定數組大小時),數組的大小應該能夠足以容納字符串中的字符和結束該字符串的空字符。也可以在初始化值列表中用單個字符常量初始化字符數組:
char string[] = { 'f', 'i', 'r', 's', 't', '\0' };,這與前面的定義等價。又因為字符串實際上是字符數組,所以可以直接使用數組下標來訪問字符串中的單個字符。還可以用scanf函數和轉換說明符 %s 把字符直接輸入到字符數組中(字符串末會有 \0 結束字符):
char string2 [20];
scanf("%s",string2);
數組名是數組的起始地址,因此&是不需要的。也可以用print函數和轉換說明符 %s 輸出代表字符串的字符數組。下面的語句打印了數組string2:
printf("%s",string2);,它和scanf函數一樣,printf函數也不關心字符數組的大小,它打印字符串的字符直到遇到終止字符串的空字符為止。
枚舉常量
enum boolean{NO, YES=1}
在沒有說明的情況下,enum類型中第一個枚舉名的值為0,第二個為1,依次類推。如果只指定了部分枚舉名的值,那麼指定值的枚舉名的值將依著最後一個指定值值向後遞增。
C程序存儲空間布局
C程序一直由下列部分組成:
1)正文段——CPU執行的機器指令部分;一個程序只有一個副本;只讀,防止程序由於意外事故而修改自身指令;
2)初始化數據段(數據段)——在程序中所有賦了初值的全局變量,存放在這裡。
3)非初始化數據段(bss段)——在程序中沒有初始化的全局變量;內核將此段初始化為0。
4)棧——自動變量以及每次函數調用時所需要保存的信息(返回地址;環境信息)。
5)堆——動態存儲分。int c* = (int *)malloc(sizeof(int));分配的空間就存儲在堆裡。
|-----------|
| |
|-----------|
| 棧 |
|-----------|
| | |
| \|/ |
| |
| |
| /|\ |
| | |
|-----------|
| 堆 |
|-----------|
| 未初始化 |
|-----------|
| 初始化 |
|-----------|
| 正文段 |
|-----------|
四種存儲類別
實際上,程序中的每一個標識符有以下幾種屬性:存儲類別、存儲期、作用域、連接(linkage).
auto register extern static 標識符的存儲類別有助於確定其存儲期、作用域和連接
存儲期:標識符在內存中的存在期
作用域:可引用標識符的區域
連接:對於有多個源文件的程序,該標識符是只被當前源文件識別,還是因為用了合適的聲明而被所有源文件識別。
四種存儲類別說明符有兩種存儲期:自動存儲期和靜態存儲期。
自動存儲期變量在進入聲明該變量的程序塊時建立,退出該程序塊時撤銷。只有變量才有自動存儲期。函數的局部變量(參數與函數體中聲明的變量)通常具有自動存儲期。
關鍵字auto明確地聲明了具有自動存儲期的變量,
auto float x,y;因為局部變量在默認情況下具有自動存儲期,所以很少使用關鍵字auto。
具有自動存儲期的變量簡單地稱為自動變量。
因為局部變量在只在需要的時候存在,所以自動存儲是節省內存的一種方法。
在自動變量聲明前使用存儲類型說明符register可
建議編譯器把該變量裝載到計算機的一個調整硬件寄存器中。使用頻繁的變量可以使用。
register int counter=1; 編譯器也可能會忽略register聲明,例如,可能沒有足夠數目的寄存器可供編譯器使用。
關鍵字register只能和具有自動存儲期的變量一起使用。
register聲明通常是不必要的,當今的優化編譯器能夠識別頻繁使用的變量,並能夠在不需要程序員作出register聲明的情況下把這些變量放到寄存器中。
關鍵字extern和static用來聲明具有靜態存儲期的變量和函數的標識符。具有靜態存儲期的標識符從程序開始執行起就一直存在。對於具有靜態存儲期的變量而言,存儲空間是程序開始執行時一次性分配和初始化,對於函數而言,函數名從程序開始執行時就存在。
有兩種類型的標識符具有靜態存儲期:外部標識符(如全局變量和函數名)和static聲明的局部變量。
默認情況下,全局變量和函數名具有存儲類別extern。
全局變量在整個程序執行期間都保持其值。
全局變量和函數可被在其聲明或定義之後的該文件中的所有函數中引用,這是使用函數原型的原因之一。
一個標識符只能使用一種存儲類別。
static
1. 全局靜態變量
在全局變量之前加上關鍵字static,全局變量就被定義成為一個全局靜態變量。
1)內存中的位置:靜態存儲區(靜態存儲區在整個程序運行期間都存在)。全局變量本身就是靜態存儲方式,靜態全局變量當然也是靜態存儲方式
2)初始化:未經初始化的全局靜態變量會被程序自動初始化為0
3)作用域:靜態全局變量則限制了其作用域,即只在定義該變量的源文件內有效(准確地講從定義之處開始到文件結尾),在同一源程序的其它源文件中不能使用它。非靜態全局變量的作用域是整個源程序(多個源文件可以共同使用)。
static對全局變量的修飾,可以認為是限制了只能是本文件引用此變量,並沒有改變它的存儲位置。
定義全局靜態變量的好處:
<1>不會被其他文件所訪問,修改
<2>其他文件中可以使用相同名字的變量,不會發生沖突。
2. 局部靜態變量
在局部變量之前加上關鍵字static,局部變量就被定義成為一個局部靜態變量。
1)內存中的位置:靜態存儲區
2)初始化:對基本類型的靜態局部變量若在說明時未賦以初值,則系統也會自動賦予0值。而對自動變量不賦初值,則其值是不定的
3)作用域:作用域仍為局部作用域,當定義它的函數或者語句塊結束的時候,作用域隨之結束。
注:當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置,從原來的棧中存放改為靜態存儲區。但是局部靜態變量在離開作用域之後,並沒有被銷毀,而是仍然駐留在內存當中,直到程序結束。退出該函數後,盡管該變量還繼續存在,但不能使用它。
當static用來修飾全局變量的時候,它就改變了全局變量的作用域(在聲明他的文件之外是不可見的),但是沒有改變它的存放位置,還是在靜態存儲區中。
static對棧變量的修飾,可以認為棧變量的生命周期延長到程序執行結束時。一般來說,棧變量的生命周期由OS管理,在退棧的過程中,棧變量的生命也就結束了。但加入static修飾之後,變量已經不在存儲在棧中,而是和全局變量一起存儲。同時,離開定義它的函數後不能使用,但如再次調用定義它的函數時,它又可繼續使用,而且保存了前次被調用後留下的值
另外,靜態變量(非全局與全局)只能使用常量表達式初始化,因為它們是在編譯時計算其值的。
3. 靜態函數
在函數的返回類型前加上關鍵字static,函數就被定義成為靜態函數。
函數的定義和聲明默認情況下是
extern的,但靜態函數只是在聲明他的文件當中可見,不能被其他文件所用。
定義靜態函數的好處:
<1> 其他文件中可以定義相同名字的函數,不會發生沖突
<2> 靜態函數不能被其他文件所用。
存儲說明符auto,register,extern,static,對應兩種存儲期:自動存儲期和靜態存儲期。
auto和register對應自動存儲期。具有自動存儲期的變量在進入聲明該變量的程序塊時被建立,它在該程序塊活動時存在,退出該程序塊時撤銷。
關鍵字extern和static用來說明具有靜態存儲期的變量和函數。用static聲明的局部變量具有靜態存儲持續期(static storage duration),或靜態范圍(static extent)。雖然他的值在函數調用之間保持有效,但是其名字的可視性仍限制在其局部域內。靜態局部對象在程序執行到該對象的聲明處時被首次初始化。
static對函數的修飾與對全局變量的修飾相似,只能被本文件中的函數調用,而不能被同一程序其它文件中的函數調用。
C語言中使用靜態函數的好處:
- 靜態函數會被自動分配在一個一直使用的存儲區,直到退出應用程序實例,避免了調用函數時壓棧出棧,速度快很多。
- 關鍵字“static”,譯成中文就是“靜態的”,所以內部函數又稱靜態函數。但此處“static”的含義不是指存儲方式,而是指對函數的作用域僅局限於本文件。使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名,因為同名也沒有關系。
const
任何變量的聲明都可以使用const限定符限定,該限定符指定變量的值不能被修改。對數組而言,const限定符指定數組所有元素的值都不能被修改(數組類型變量本身也不能被修改,即不能改變它的指向,這與使用final定義的數組的Java是一樣的,但內容不能修改這與Java又不一樣)。但是,如果const限定符修飾數組參數時,函數中仍然不能修改數組元素的值,但在函數中能讓數組重新指向另外的數組,但調用者未改變(因為你在函數中改變了它的指向,當然不會修改原來的內容),如下面程序:
void val(
constint a[]) {//這裡的
const int a[]相當於const int * a
int b[] = { 1 };
printf("%d\n", a[0]);//輸出:2
/*
* 可以修改其指向。數組在作為參數傳遞時,實質上傳遞的
* 是第一個元素的地址,這裡的參數聲明好比為 int *a,但
* 如果不是做為參數這樣聲明時,則是不能夠再次讓數組變量
* 指向另外一個新的數組
*/
a = b;
// 編譯出錯:assignment of read-only location '*a'
// a[0] = 3;
printf("%d\n", a[0]);//輸出:1
}
int main(
void) {
int a[] = { 2 };//這裡
int a[] 相當於 int * const a
val(a);
printf("%d\n", a[0]);//值還是2,沒有被修改
int b[] = { 1 };
//! a = b;//不能使用數組變量指向新的數組,但在參數聲明時可以
return 1;
}
數組名實際上是一個指向非常量的常量指針。
當遇到形如一維數組 int b[] 的函數參數時,編譯器把該參數轉換為指針形式 int * b,這兩種形式是可以互換的。
int *const cpi=&i,不能修改常量指針cpi的值,該指針總是指向同一位置,但它所指之處的值可以改變。
const int *pci(或者 int const *pci),pci的類型是“指向const int的指針”,pci本身可以被修改以指向另一個地方,但它所指之處的值不能通過pci來改變。
intconst a;
constint a;
上面兩個語句等價。
指向常量的常量指針具有最少訪問權:
int main(
int argc,
char * argv[]) {
int x = 5, y;
//指向常量的常量指針
constint *
const ptr = &x;
//下面兩行編譯出錯
//!*ptr = 7;
//!ptr = &7;
return 0;
}
指向非常量數據的非常量指針(
int * ptr)
指向非常量數據的常量指針(
int *
const ptr)
指向常量數據的非常量指針(
constint * ptr 或
int const * ptr)
指向常量數據的常量指針(
constint *
const ptr 或
intconst *
const ptr)
在使用函數之前檢查一下函數原型,確定該函數是否能夠修改傳給它的值。
sizeof
sizeof運算符在用於數組名時以整數形式返回該數組所占用的字節數。
sizeof運算符可用於任何變量名、變量類型和常量。
register
register聲明告訴編譯器,它所聲明的變量在程序中使用頻率較高,會將它們存放在寄存器中,這樣可以使用程序更小,執行速度更快。
register聲明只適用於自動變量以及函數的形式參數。
不能對register變量進行取地址操作&
位移規則
左移時右邊補0
unsigned類型的無符號值進行右移位時,左邊空出的部分將用0填補;當對signed類型的帶符號值進行右移時,某些機器對左邊空出的位用符號位填補(即“算術移位”),而另一些機器對左邊空出的部分用0填補(即“邏輯移位”)。
函數默認返回值
如果函數定義中省略了返回值類型,則默認為int類型。