對C語言編程標準以及聲明的基本理解
c語言標準
1978年,丹尼斯·里奇(Dennis Ritchie)和Brian Kernighan合作出版了《C程序設計語言》的第一版。書中介紹的C語言標準也被C語言程序設計師稱作“K&R C”,第二版的書中也包含了一些ANSI C的標準。K&R C主要介紹了以下特色:
結構(struct)類型
長整數(long int)類型
無符號整數(unsigned int)類型
把運算符=+和=-改為+=和-=。因為=+和=-會使得編譯器不知道用戶要處理i = +10還是i =- 10,使得處理上產生混淆。
即使在后來ANSI C標準被提出的許多年后,K&R C仍然是許多編譯器的最低標準要求,許多老舊的編譯仍然運行K&R C的標準。
C89
1989年,C語言被 ANSI 標準化(ANSI X3.159-1989)。標準化的一個目的是擴展K&R C。這個標準包括了一些新特性。在K&R出版后,一些新特性被非官方地加到C語言中。
void 函數
函數返回 struct 或 union 類型
void * 數據類型
在 ANSI標準化自己的過程中,一些新的特性被加了進去。ANSI也規(guī)定一套了標準函數庫。ANSI ISO(國際標準化組織)成立 ISO/IEC JTC1/SC22/WG14 工作組,來規(guī)定國際標準的C語言。通過對ANSI標準的少量修改,最終通過了 ISO 9899:1990。隨后,ISO標準被 ANSI 采納。
傳統(tǒng)C語言到ANSI/ISO標準C語言的改進包括:
增加了真正的標準庫
新的預處理命令與特性
函數原型允許在函數申明中指定參數類型
一些新的關鍵字,包括 const、volatile 與 signed
寬字符、寬字符串與字節(jié)多字符
對約定規(guī)則、聲明和類型檢查的許多小改動與澄清
WG14工作小組之后又于1995年,對1985年頒布的標準做了兩處技術修訂(缺陷修復)和一個補充(擴展)。下面是 1995 年做出的所有修改:
3 個新的標準庫頭文件 iso646.h、wctype.h 和 wchar.h
幾個新的記號與預定義宏,用于對國際化提供更好的支持
printf/sprintf 函數一系列新的格式代碼
大量的函數和一些類型與常量,用于多字節(jié)字符和寬字節(jié)字符
C99
在 ANSI的標準確立后,C語言的規(guī)范在一段時間內沒有大的變動,然而C++在自己的標準化建立過程中繼續(xù)發(fā)展壯大。《標準修正案一》在1995年為C語言 建立了一個新標準,但是只修正了一些C89標準中的細節(jié)和增加更多更廣得國際字符集支持。不過,這個標準引出了1999年ISO 9899:1999的發(fā)表。它通常被成為C99。C99被ANSI于2000年3月采用。
在C99中包括的特性有:
對編譯器限制增加了,比如源程序每行要求至少支持到 4095 字節(jié),變量名函數名的要求支持到 63 字節(jié)(extern 要求支持到 31)
預處理增強了。例如:
宏支持取可變參數 #define Macro(...) __VA_ARGS__
使用宏的時候,參數如果不寫,宏里用 #,## 這樣的東西會擴展成空串。(以前會出錯的)
支持 // 行注釋(這個特性實際上在C89的很多編譯器上已經被支持了)
增加了新關鍵字 restrict, inline, _Complex, _Imaginary, _Bool
支持 long long, long double _Complex, float _Complex 這樣的類型
支持 <: :> <% %> %: %:%: ,等等奇怪的符號替代,D&E 里提過這個
支持了不定長的數組。數組的長度就可以用變量了。聲明類型的時候呢,就用 int a[*] 這樣的寫法。不過考慮到效率和實現,這玩意并不是一個新類型。所以就不能用在全局里,或者 struct union 里面,如果你用了這樣的東西,goto 語句就受限制了。
變量聲明不必放在語句塊的開頭,for 語句提倡這么寫 for(int i=0;i<100;++i) 就是說,int i 的聲明放在里面,i 只在 for 里面有效。
當一個類似結構的東西需要臨時構造的時候,可以用(type_name){xx,xx,xx} 這有點像 C++ 的構造函數
初始化結構的時候現在可以這樣寫:
struct {int a[3],b;} hehe[] = { [0].a = {1}, [1].a = 2 };
struct {int a, b, c, d;} hehe = { .a = 1, .c = 3, 4, .b = 5} // 3,4 是對 .c,.d 賦值的
字符串里面,/u 支持 unicode 的字符
支持 16 進制的浮點數的描述
所以 printf scanf 的格式化串多支持了 ll / LL(VC6 里用的 I64)對應新的 long long 類型。
浮點數的內部數據描述支持了新標準,這個可以用 #pragma 編譯器指定
除了已經有的 __line__ __file__ 以外,又支持了一個 __func__ 可以得到當前的函數名
對于非常數的表達式,也允許編譯器做化簡
修改了對于/% 處理負數上的定義,比如老的標準里 -22 / 7 = -3, -22 % 7 = -1 而現在 -22 / 7 = -4, -22 % 7 = 6
取消了不寫函數返回類型默認就是 int 的規(guī)定
允許 struct 定義的最后一個數組寫做 [] 不指定其長度描述
const const int i;將被當作 const int i;處理
增 加和修改了一些標準頭文件,比如定義 bool 的 <stdbool.h> 定義一些標準長度的 int 的 <inttypes.h> 定義復數的 <complex.h> 定義寬字符的 <wctype.h> 有點泛型味道的數學函數 <tgmath.h> 跟浮點數有關的 <fenv.h>。<stdarg.h> 里多了一個 va_copy 可以復制 ... 的參數。<time.h> 里多了個 struct tmx 對 struct tm 做了擴展
輸入輸出對寬字符還有長整數等做了相應的支持
相對于c89的變化還有
1、增加restrict指針
C99中增加了公適用于指針的restrict類型修飾符,它是初始訪問指針所指對象的惟一途徑,因此只有借助restrict指針表達式才能 訪問對象。restrict指針指針主要用做函數變元,或者指向由malloc()函數所分配的內存變量。restrict數據類型不改變程序的語義。
如果某個函數定義了兩個restrict指針變元,編譯程序就假定它們指向兩個不同的對象,memcpy()函數就是restrict指針的一個典型應用示例。C89中memcpy()函數原型如下:
代碼: void *memcpy (void *s1, const void *s2, size_t size);
如果s1和s2所指向的對象重疊,其操作就是未定義的。memcpy()函數只能用于不重疊的對象。C99中memcpy()函數原型如下:代 碼: void *memcpy(void *restrict s1, const void *restrict s2,size_t size);
通過使用restrict修飾s1和s2 變元,可確保它們在該原型中指向不同的對象。
2、inline(內聯)關鍵字
內聯函數除了保持結構化和函數式的定義方式外,還能使程序員寫出高效率的代碼。函數的每次調用與返回都會消耗相當大的系統(tǒng)資源,尤其是當函數調 用發(fā)生在重復次數很多的循環(huán)語句中時。一般情況下,當發(fā)生一次函數調用時,變元需要進棧,各種寄存器內存需要保存。當函數返回時,寄存器的內容需要恢復。 如果該函數在代碼內進行聯機擴展,當代碼執(zhí)行時,這些保存和恢復操作旅游活動會再發(fā)生,而且函數調用的執(zhí)行速度也會大大加快。函數的聯機擴展會產生較長的 代碼,所以只應該內聯對應用程序性能有顯著影響的函數以及長度較短的函數。
3、新增數據類型
_Bool
值是0或1。C99中增加了用來定義bool、true以及false宏的頭文件夾<stdbool.h>,以便程序員能夠編寫同時兼容于C與C++的應用程序。在編寫新的應用程序時,應該使用
<stdbool.h>頭文件中的bool宏。
_Complex and _Imaginary
C99標準中定義的復數類型如下:float_Complex;float_Imaginary;double_Complex;double_Imaginary;long double_Complex;long double_Imaginary。
<complex.h>頭文件中定義了complex和imaginary宏,并將它們擴展為_Complex和 _Imaginary,因此在編寫新的應用程序時,應該使用<stdbool.h>頭文件中的complex和imaginary宏。
long long int
C99標準中引進了long long int(-(2e63 - 1)至2e63 - 1)和unsigned long long int(0 - 2e64 - 1)。long long int能夠支持的整數長度為64位。
4、對數組的增強
可變長數組
C99中,程序員聲明數組時,數組的維數可以由任一有效的整型表達式確定,包括只在運行時才能確定其值的表達式,這類數組就叫做可變長數組,但是只有局部數組才可以是變長的。
可變長數組的維數在數組生存期內是不變的,也就是說,可變長數組不是動態(tài)的??梢宰兓闹皇菙到M的大小??梢允褂?來定義不確定長的可變長數組。
數組聲明中的類型修飾符
在C99中,如果需要使用數組作為函數變元,可以在數組聲明的方括號內使用static關鍵字,這相當于告訴編譯程序,變元所指向的數組將至少 包含指定的元素個數。也可以在數組聲明的方括號內使用restrict,volatile,const關鍵字,但只用于函數變元。如果使用 restrict,指針是初始訪問該對象的惟一途徑。如果使用const,指針始終指向同一個數組。使用volatile沒有任何意義。
5、單行注釋
引入了單行注釋標記 "http://" , 可以像C++一樣使用這種注釋了。
6、分散代碼與聲明
7、預處理程序的修改
a、變元列表
宏可以帶變元,在宏定義中用省略號(...)表示。內部預處理標識符__VA_ARGS__決定變元將在何處得到替換。例:#define MySum(...) sum(__VA_ARGS__) 語句MySum(k,m,n);
將被轉換成:sum(k, m, n); 變元還可以包含變元。例: #define compare(compf, ...) compf(__VA_ARGS__) 其中的compare(strcmp,"small", "large"); 將替換成:strcmp("small","large");
b、_Pragma運算符
C99引入了在程序中定義編譯指令的另外一種方法:_Pragma運算符。格式如下:
_Pragma("directive")
其中directive是要滿打滿算的編譯指令。_Pragma運算符允許編譯指令參與宏替換。
c、內部編譯指令
STDCFP_CONTRACT ON/OFF/DEFAULT 若為ON,浮點表達式被當做基于硬件方式處理的獨立單元。默認值是定義的工具。
STDCFEVN_ACCESS ON/OFF/DEFAULT 告訴編譯程序可以訪問浮點環(huán)境。默認值是定義的工具。
STDC CX_LIMITED_RANGE ON/OFF/DEFAULT 若值為ON,相當于告訴編譯程序某程序某些含有復數的公式是可靠的。默認是OFF。
d、新增的內部宏
__STDC_HOSTED__ 若操作系統(tǒng)存在,則為1
__STDC_VERSION__ 199991L或更高。代表C的版本
__STDC_IEC_599__ 若支持IEC 60559浮點運算,則為1
__STDC_IEC_599_COMPLEX__ 若支持IEC 60599復數運算,則為1
__STDC_ISO_10646__ 由編譯程序支持,用于說明ISO/IEC 10646標準的年和月格式:yyymmmL
9、復合賦值
C99中,復合賦值中,可以指定對象類型的數組、結構或聯合表達式。當使用復合賦值時,應在括弧內指定類型,后跟由花括號圍起來的初始化列表;若類型為數組,則不能指定數組的大小。建成的對象是未命名的。
例: double *fp = (double[]) {1.1, 2.2, 3.3};
該語句用于建立一個指向double的指針fp,且該指針指向這個3元素數組的第一個元素。 在文件域內建立的復合賦值只在程序的整個生存期內有效。在模塊內建立的復合賦值是局部對象,在退出模塊后不再存在。
10、柔性數組結構成員
C99中,結構中的最后一個元素允許是未知大小的數組,這就叫做柔性數組成員,但結構中的柔性數組成員前面必須至少一個其他成員。柔性數組成員 允許結構中包含一個大小可變的數組。sizeof返回的這種結構大小不包括柔性數組的內存。包含柔性數組成員的結構用malloc()函數進行內存的動態(tài) 分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。
11、指定的初始化符
C99中,該特性對經常使用稀疏數組的程序員十分有用。指定的初始化符通常有兩種用法:用于數組,以及用于結構和聯合。用于數組的格式: = vol; 其中,index表示數組的下標,vol表示本數組元素的初始化值。
例如: int x[10] = {[0] = 10, [5] = 30}; 其中只有x[0]和x[5]得到了初始化。用于結構或聯合的格式如下:
member-name(成員名稱)
對結構進行指定的初始化時,允許采用簡單的方法對結構中的指定成員進行初始化。
例如: struct example{ int k, m, n; } object = {m = 10,n = 200};
其中,沒有初始化k。對結構成員進行初始化的順序沒有限制。
12、printf()和scanf()函數系列的增強
C99中printf()和scanf()函數系列引進了處理long long int和unsigned long long int數據類型的特性。long long int 類型的格式修飾符是ll。在printf()和scanf()函數中,ll適用于d,i,o,u和x格式說明符。另外,C99還引進了hh修飾符。當使用 d,i,o,u和x格式說明符時,hh用于指定char型變元。ll和hh修飾符均可以用于n說明符。
格式修飾符a和A用在printf()函數中時,結果將會輸出十六進制的浮點數。格式如下:[-]0xh, hhhhp + d 使用A格式修飾符時,x和p必須是大寫。A和a格式修飾符也可以用在scanf()函數中,用于讀取浮點數。調用printf()函數時,允許在%f說明 符前加上l修飾符,即%lf,但不起作用。
13、C99新增的庫
C89中標準的頭文件
<assert.h> 定義宏assert()
<ctype.h> 字符處理
<errno.h> 錯誤報告
<float.h> 定義與實現相關的浮點值
<limits.h> 定義與實現相關的各種極限值
<locale.h> 支持函數setlocale()
<math.h> 數學函數庫使用的各種定義
<setjmp.h> 支持非局部跳轉
<signal.h> 定義信號值
<stdarg.h> 支持可變長度的變元列表
<stddef.h> 定義常用常數
<stdio.h> 支持文件輸入和輸出
<stdlib.h> 其他各種聲明
<string.h> 支持串函數
<time.h> 支持系統(tǒng)時間函數
C99新增的頭文件和庫
<complex.h> 支持復數算法
<fenv.h> 給出對浮點狀態(tài)標記和浮點環(huán)境的其他方面的訪問
<inttypes.h> 定義標準的、可移植的整型類型集合。也支持處理最大寬度整數的函數
<iso646.h> 首先在此1995年第一次修訂時引進,用于定義對應各種運算符的宏
<stdbool.h> 支持布爾數據類型類型。定義宏bool,以便兼容于C++
<stdint.h> 定義標準的、可移植的整型類型集合。該文件包含在<inttypes.h>中
<tgmath.h> 定義一般類型的浮點宏
<wchar.h> 首先在1995年第一次修訂時引進,用于支持多字節(jié)和寬字節(jié)函數
<wctype.h> 首先在1995年第一次修訂時引進,用于支持多字節(jié)和寬字節(jié)分類函數
14、__func__預定義標識符
用于指出__func__所存放的函數名,類似于字符串賦值。
15、其它特性的改動
放寬的轉換限制
不再支持隱含式的int規(guī)則
刪除了隱含式函數聲明
對返回值的約束
C99中,非空類型函數必須使用帶返回值的return語句。
擴展的整數類型
對整數類型提升規(guī)則的改進
C89中,表達式中類型為char,short int或int的值可以提升為int或unsigned int類型。
C99中,每種整數類型都有一個級別。例如:long long int 的級別高于int,int的級別高于char等。在表達式中,其級別低于int或unsigned int的任何整數類型均可被替換成int或unsigned int類型。
但是各個公司對C99的支持所表現出來的興趣不同。當GCC和其它一些商業(yè)編譯器支持C99的大部分特性的時候,微軟和Borland卻似乎對此不感興趣。
GCC 支持C99,通過 --std = c99命令行參數開啟。 例如:gcc --std = c99 test.c。默認情況下GCC是用的GNU89標準
C語言聲明
優(yōu)先級規(guī)則分析
A.聲明從他的第一個標識符(名字)開始讀取,然后按照優(yōu)先級順序依次讀取:
B 優(yōu)先級從高到低依次是:
B.1聲明中被括號括起來的那部分
B.2后綴操作符:
括號()表示這是一個函數,而方括號[]表示這是一個數組。
B.3前綴操作符:星號*表示“指向...的指針”
C 如果const和(或)volatile關鍵字的后面緊跟類型說明符(如int,long等),那么它用做子類型說明符。在其他情況下, const和(或)volatile關鍵字作用于它左邊的臨近的指針星號。
例:
char * const *(*next)();
A 首先,看變量名(標識符)“next”,并注意到它直接被括號所括住
B.1 所以先把括號里的東西作為一個整體,得出“next是一個指向...的指針”
B 然后考慮括號外面的東西,在星號前綴和括號后綴之間作出選擇
B.2 B.2規(guī)則告訴我們優(yōu)先級較高的是右邊的函數括號,所以得出“next是一個函數指針,指向一個返回...的函數”
B.3 然后,處理前綴“*”,得出指針所指的內容
C 最后,把“char * const”解釋為指向字符的常量指針
更直觀一點可以看下圖
所以這個聲明表示“next”是一個指針,它指向一個函數,該函數返回另一個指針,該指針指向一個類型為char的常量指針。
例子2 char *(* c[10]) (int **p)
那么合起來就是c是一個函數指針數組,他的返回類型是char *,參數是int **p
以下是來自c專家編程的一個分析聲明的程序
#include <stdio.h> #include <string.h> #include <ctype.h> #include <stdlib.h> #define MAXTOKENS 100 #define MAXTOKENLEN 64 enum type_tag { IDENTIFIER, QUALIFIER, TYPE}; struct token{ char type; char string[MAXTOKENLEN]; }; int top = -1; struct token stack[MAXTOKENS]; struct token this; #define pop stack[top--] #define push(s) stack[++top] = s enum type_tag classify_string(void) { char *s = this.string; if ( !strcmp(s,"const")) { strcpy(s,"read-only"); return QUALIFIER; } if (!strcmp(s,"volatile")) return QUALIFIER; if (!strcmp(s,"void")) return TYPE; if (!strcmp(s,"char")) return TYPE; if (!strcmp(s,"signed")) return TYPE; if (!strcmp(s,"unsigned")) return TYPE; if (!strcmp(s,"short")) return TYPE; if (!strcmp(s,"int")) return TYPE; if (!strcmp(s,"void")) return TYPE; if (!strcmp(s,"long")) return TYPE; if (!strcmp(s,"float")) return TYPE; if (!strcmp(s,"double")) return TYPE; if (!strcmp(s,"struct")) return TYPE; if (!strcmp(s,"union")) return TYPE; if (!strcmp(s,"enum")) return TYPE; return IDENTIFIER; } void gettoken(void) { char *p = this.string; while((*p = getchar() ) == ' '); if ( isalnum(*p) ) { while( isalnum( *++p = getchar() )); ungetc(*p , stdin); *p = '/0'; this.type = classify_string(); return; } if (*p == '*' ) { strcpy(this.string, "pointer to"); this.type = '*'; return; } this.string[1] = '/0'; this.type = *p; return; } read_to_first_identifer() { gettoken(); while(this.type != IDENTIFIER ) { push( this ); gettoken(); } printf("%s is ",this.string); gettoken(); } deal_with_arrays() { while(this.type = '[' ) { printf("array"); gettoken(); if ( isdigit(this.string[0]) ) { printf("0..%d ",atoi(this.string)-1); gettoken(); } gettoken(); printf("of "); } } deal_with_function_args() { while( this.type != ')' ) { gettoken(); } gettoken(); printf("function returning "); } deal_with_pointers() { while(stack[top].type == '*' ) { printf("%s ", pop.string); } } deal_with_declarator() { switch(this.type) { case '[' :deal_with_arrays();break; case '(' :deal_with_function_args(); } deal_with_pointers(); while( top >= 0 ) { if ( stack[top].type == '(' ) { pop; gettoken(); deal_with_declarator(); } else printf("%s ",pop.string); } } main() { read_to_first_identifer(); deal_with_declarator(); printf("/n"); return 0;}