天天看點

C99新特性

C99标準的新特性 2010-6-29   先簡單介紹下C語言的标準. C語言的發展曆史大緻上分為三個階段:Old Style C、C89和C99.Ken Thompson和Dennis Ritchie發明C語言時有很多文法和現在并不一樣,但為了向後相容性(Backward Compatibility), 這些文法仍然在C89和C99中保留下來了.C89是最早的C語言規範,于1989年提出,1990年先由ANSI(美國國家标準委員會,American National Standards Institute)推出ANSI版本,後來被接納為ISO國際标準(ISO/IEC 9899:1990),因而有時也稱為C90,最經典的C語言教材[K&R]就是基于這個版本的,C89是目前最廣泛采用的C語言标準,大多數編譯器都完全支援C89.C99标準(ISO/IEC 9899:1999)是在1999年推出的,加入了許多新的特性,但目前仍沒有得到廣泛支援,在C99推出之後相當長的一段時間裡,連gcc也沒有完全實作C99的所有特性.   現在介紹一下C99相對于C89或者ANSI C的特性. 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(内聯)關鍵字 内聯函數除了保持結構化和函數式的定義方式外,還能使程式員寫出高效率的代碼.函數的每次調用與傳回都會消耗相當大的系統資源,尤其是當函數調用發生在重複次數很多的循環語句中時.一般情況下,當發生一次函數調用時,函數與參數需要進棧,各種寄存器記憶體需要儲存.當函數傳回時,寄存器的内容需要恢複,如果該函數在代碼内進行内聯擴充,當代碼執行時,這些儲存和恢複操作不會再發生,因為沒有函數的進棧與退棧了,而且函數調用的執行速度也會大大加快,函數的内聯擴充會産生較長的代碼,是以隻應該内聯對應用程式性能有顯著影響的函數 以及長度較短的函數,即短小精悍且多次調用的函數.   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、對數組的增強 可變長數組(VLA)   C99中,程式員聲明數組時,數組的維數可以由任一有效的整型表達式确定,包括隻在運作時才能确定其值的表達式,這類數組就叫做可變長數組.但是隻有局部數組才可以是變長的.可變長數組的維數在數組生存期内是不變的,也就是說,可變長數組不是動态的.可以變化的隻是數組的大小.可以使用*來定義不确定長的可變長數組.   在棧中配置設定VLA 時,仿佛調用了alloca 函數。無論其作用域如何,其生存期與通過調用alloca 在棧中配置設定資料時相同;直到函數傳回時為止。如果在其中配置設定VLA 的函數傳回時釋放棧,則釋放配置設定的空間。 執行個體如下: #include <stdio.h> void foo(int); int main(void) {      foo(4);      return(0); } void foo (int n) {      int i;      int a[n];      for (i = 0; i < n; i++)      {          a[i] = n-i;      }        for (i = n-1; i >= 0; i--)      {          printf("a[%d] = %d/n", i, a[i]);      }   } example% cc test.c example% a.out a[3] = 1 a[2] = 2 a[1] = 3 a[0] = 4     數組聲明中的類型修飾符   在C99中,如果需要使用數組作為函數參數,可以在數組聲明的方括号内使用static關鍵字,這相當于告訴編譯程式,參數所指向的數組将至少包含指定的元素個數,也可以在數組聲明的方括号内使用restrict, volatile, const關鍵字,但隻用于函數參數,如果使用restrict,指針是初始通路該對象的惟一途徑,如果使用const,指針始終指向同一個數組,使用volatile沒有任何意義, 5、單行注釋     字元// 引入包含直到(但不包括)新換行符的所有多位元組字元的注釋,除非// 字元出現在字元常量、字元串文字或注釋中.

6、分散代碼與聲明      現在,C 編譯器接受關于可執行代碼的混合類型聲明,如以下示例所示: #include <stdio.h> int main(void) {      int num1 = 3;      printf("%d/n", num1);        int num2 = 10;      printf("%d/n", num2);      return(0); } 7、預處理程式的修改   1、具有可變數目的參數的宏 C 編譯器接受以下形式的#define 預處理程式指令: #define identifier (...) replacement_list #define identifier (identifier_list, ...) replacement_list 如果宏定義中的identifier_list 以省略号結尾,則意味着調用中的參數比宏定義中的參數(不包括省略号)多。否則,宏定義中參數的數目(包括由預處理标記組成的參數)與調用中參數的數目比對。對于在其參數中使用省略号表示法的#define 預處理指令, 在其替換清單中使用辨別符__VA_ARGS__。以下示例說明可變參數清單宏工具。   #define debug(...) fprintf(stderr, __VA_ARGS__) #define showlist(...) puts(#__VA_ARGS__)  //這裡使用了字元化符号# #define report(test, ...) ((test)?puts(#test):printf(__VA_ARGS__))   debug(“Flag”); debug(“X = %d/n”,x); showlist(The first, second, and third items.); report(x>y, “x is %d but y is %d”, x, y);   其結果如下: fprintf(stderr, “Flag”); fprintf(stderr, “X = %d/n”, x); puts(“The first, second, and third items.”); ((x>y)?puts(“x>y”):printf(“x is %d but y is %d”, x, y));

  2、_Pragma操作符 _Pragma ( string-literal ) 形式的一進制操作符表達式處理如下: ■ 如果字元串文字具有L 字首,則删除該字首。 ■ 删除前導和結尾雙引号。 ■ 用雙引号替換每個換碼序列'。 ■ 用單個反斜杠替換每個換碼序列//。 預處理标記的結果序列作為pragma 指令中的預處理程式标記進行處理。 删除一進制操作符表達式中的最初四個預處理标記。 與#pragma 比較,_Pragma 的優勢在于:_Pragma 可以用于宏定義。 _Pragma("string") 與#pragma 字元串行為完全相同。考慮以下示例。首先列出示例的 源代碼,然後在預處理程式使其通過預處理之後,再列出示例的源代碼。     #include <omp.h> #include <stdio.h> #define Pragma(x) _Pragma(#x) #define OMP(directive) Pragma(omp directive) void main() {      omp_set_dynamic(0);      omp_set_num_threads(2);      OMP(parallel)      {          printf("Hello!/n");      } }   下面是預處理程式完成後的源代碼。 void main() {      omp_set_dynamic(0);      omp_set_num_threads(2); # pragma omp parallel      {          printf("Hellow!/n");      } }  

  3、内部編譯指令 STDC FP_CONTRACT ON/OFF/DEFAULT         若為ON,浮點表達式被當做基于硬體方式處理的獨立單元,預設值是定義的工具.

STDC FEVN_ACCESS ON/OFF/DEFAULT         告訴編譯程式可以通路浮點環境,預設值是定義的工具.

STDC CX_LIMITED_RANGE ON/OFF/DEFAULT    若值為ON,相當于告訴編譯程式某程式某些含有複數的公式是可靠的,預設是OFF.

  4、新增的内部宏 __STDC_HOSTED__                   若作業系統存在,則為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   8、for語句内的變量聲明 C99中,程式員可以在for語句的初始化部分定義一個或多個變量,這些變量的作用域僅于本for語句所控制的循環體内.在C89中,這樣是不可以的,具體可以在VC6中驗證.VC6支援到C89. C 編譯器接受作為for 循環語句中第一個表達式的類型聲明: for (int i=0; i<10; i++){ //loop body }; for 循環的初始化語句中聲明的任何變量的作用域是整個循環(包括控制和疊代表達式)。   9、複合指派 C99中,複合指派中,可以指定對象類型的數組、結構或聯合表達式,當使用複合指派時,應在括弧内指定類型,後跟由花括号圍起來的初始化清單;若類型為數組,則不能指定數組的大小,建成的對象是未命名的, 例:  double *fp = (double[]) {1.1, 2.2, 3.3}; 該語句用于建立一個指向double的指針fp,且該指針指向這個3元素數組的第一個元素,在檔案域内建立的複合指派隻在程式的整個生存期内有效,在子產品内建立的複合指派是局部對象,在退出子產品後不再存在.   10、柔性數組結構成員 C99中,結構中的最後一個元素允許是未知大小的數組,這就叫做柔性數組成員,但結構中的柔性數組成員前面必須至少一個其他成員,柔性數組成員允許結構中包含一個大小可變的數組,sizeof傳回的這種結構大小不包括柔性數組的記憶體,包含柔性數組成員的結構用malloc()函數進行記憶體的動态配置設定,并且配置設定的記憶體應該大于結構的大小,以适應柔性數組的預期大小.   11、複合指派初始化符 C99中,該特性對經常使用稀疏數組的程式員十分有用,指定的初始化符通常有兩種用法:用于數組,以及用于結構和聯合. 用于數組的格式:[index] = vol;  其中,index表示數組的下标,vol表示本數組元素的初始化值,例如: int x[10] = {[0] = 10, [5] = 30};   其中隻有x[0]和x[5]得到了初始化.這種方式不錯,但是VC6對他不支援.   用于結構或聯合的格式如下:  member-name(成員名稱)    對結構進行指定的初始化時,允許采用簡單的方法對結構中的指定成員進行初始化. 例如:  struct example{ int k, m, n; } object  = {m = 10, n = 200};        其中,沒有初始化k,對結構成員進行初始化的順序沒有限制.   指定的初始化函數為初始化稀疏數組提供了一種機制,這在數字程式設計的實踐中很常見。指定的初始化函數可以對稀疏結構進行初始化,這在系統程式設計中很常見,并且可以通過任何成員對聯合進行初始化,而不管其是否為第一個成員。 請看以下這些示例。此處的第一個示例顯示了如何使用指定的初始化函數來對數組進行初始化: enum { first, second, third };   const char *nm[] =               {                    [third]   = "third member",                    [first]   = "first member",                    [second]= "second member",               };   下面的示例證明了如何使用指定的初始化函數來對結構對象的字段進行初始化: division_t result = { .quot = 2, .rem = -1 };   下面的示例顯示了如何使用指定的初始化函數對複雜的結構進行初始化(否則這些結構可能會被誤解): struct { int z[3], count; } w[] = { [0].z = {1}, [1].z[0] = 2 };   通過使用單個訓示符可以從兩端建立數組: int z[MAX] = {1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0}; 如果MAX 大于10,則數組将在中間位置包含取值為零的元素;如果MAX 小于10,則前五個初始化函數提供的某些值将被後五個初始化函數的值覆寫。   聯合的任何成員均可進行初始化: union { int i; float f;} data = { .f = 3.2 };   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>               支援系統時間函數

C99新增的頭檔案和庫   <complex.h>            支援複雜算法   <fenv.h>               給出對浮點狀态标記和浮點環境的其他方面的通路   <inttypes.h>           定義标準的、可移植的整型類型集合,也支援處理最大寬度整數的函數   <iso646.h>             首先在此1995年第一次修訂時引進,用于定義對應各種運算符的宏   <stdbool.h>            支援布爾資料類型類型,定義宏bool,以便相容于C++   <stdint.h>             定義标準的、可移植的整型類型集合,該檔案包含在<inttypes.h>中   <tgmath.h>             定義一般類型的浮點宏   <wchar.h>              首先在1995年第一次修訂時引進,用于支援多位元組和寬位元組函數   <wctype.h>             首先在1995年第一次修訂時引進,用于支援多位元組和寬位元組分類函數    

14、__func__預定義辨別符 用于指出__func__所存放的函數名,類似于字元串指派.

15. 幂等限定符 類型限定符: 如果同一限定符在同一說明符限定符清單中出現多次(無論直接出現還是通過一個或多個typedef),行為與該類型限定符僅出現一次時相同。 在C90 中,以下代碼會導緻錯誤:   const const int a; int main(void) { return(0); }   %example cc -xc99=none test.c "test.c", line 1: invalid type combination   但是,對于C99,C 編譯器接受多個限定符。 %example cc -xc99 test.c %example   16. Static 及數組聲明符中允許的其他類型限定符 數組聲明符: 現在,關鍵字static 可以出現在函數聲明符中參數的數組聲明符中,表示編譯器至少可以假定許多元素将傳遞到所聲明的函數中。使優化器能夠作出以其他方式無法确定的假定。   C 編譯器将數組參數調整為指針,是以void foo(int a[]) 與void foo(int *a) 相同。   如果您指定void foo(int * restrict a); 等類型限定符,則C 編譯器使用實質上與聲明限定指針相同的數組文法void foo(int a[restrict]); 表示它。   C 編譯器還使用static 限定符保留關于數組大小的資訊。例如,如果您指定void foo(int a[10]),則編譯器仍将它表達為void foo(int *a)。按以下所示使用static 限定符:void foo(int a[static 10]),讓編譯器知道指針a 不是NULL,并且使用它可通路至少包含十個元素的整數數組。   其它特性的改動 放寬的轉換限制 限制 C89标準 C99标準 資料塊的嵌套層數 15 127 條件語句的嵌套層數 8 63 内部辨別符中的有效字元個數 31 63 外部辨別符中的有效字元個數 6 31 結構或聯合中的成員個數 127 1023 函數調用中的參數個數  31 127   不再支援隐含式的int規則和隐式函數聲明. 每個聲明中的聲明說明符中應至少指定一個類型說明符,現在不支援沒有類型就預設是int的聲明語句.比如在C89中, auto i = 0;是合法的.   現在,C 編譯器會對任何隐式int 聲明都發出警告,如以下示例所示:   volatile i; const foo() { return i; }   cc test.c "test.c", line 1: warning: no explicit type given "test.c", line 3: warning: no explicit type given   對傳回值的限制,C99中,非空類型函數必須使用帶傳回值的return語句.              擴充的整數類型 擴充類型 含義 int16_t 整數長度為精确16位 int_least16_t 整數長度為至少16位 int_fast32_t  最穩固的整數類型,其長度為至少32位 intmax_t 最大整數類型 uintmax_t 最大無符号整數類型   對整數類型提升規則的改進 C89中,表達式中類型為char,short int或int的值可以提升為int或unsigned int類型.

C99中,每種整數類型都有一個級别.例如:long long int 的級别高于int, int的級别高于char等.在表達式中,其級别低于int或unsigned int的任何整數類型均可被替換成int或unsigned int類型.