C/C++ 之 C发展史及 各标准特性说明
Tips:
 ?1. 本人當初學習C/C++的記錄。
 ?2. 資源很多都是來自網上的,如有版權請及時告知!
 ?3. 可能會有些錯誤。如果看到,希望能指出,以此共勉!
C 發展史
??1951年,IBM的Jhon Backus(Fortran開發小組組長)基于匯編語言著手研發Fortran語言。
 ??1960年,圖靈獎獲得者Alan J.Perlis在巴黎舉行的有全世界一流軟件專家參加的討論會上,發表了"算法語言Algol 60報告",確定了程序設計語言Algol 60。Algol60語言的第一個編譯器由Edsger W. Dijkstra來實現。1962年,Alan J.Perlis又對Algol 60進行了修正。Algo60語言,是從Fortran演化的一個標準,目的是標準化一種數值計算語言。
 ??1963年,劍橋大學將ALGOL 60語言發展成為CPL語言(Combined Programming Language)。
 ??1967年,劍橋大學的Matin Richards對CPL語言進行了簡化,于是產生了BCPL語言。
 ??1970年,美國貝爾實驗室的Ken Thompson將BCPL進行了修改,并為它起了一個有趣的名字“B語言”(最初是為Unix設計的)。并且他用B語言寫了第一個UNIX操作系統(第二版)。
 ??1973年,美國貝爾實驗室的D.M. Ritchie在B語言的基礎上最終設計出了一種新的語言,他取了BCPL的第二個字母作為這種語言的名字,這就是C語言。此后,其用C語言對Unix進行了重寫(第三版)。
C 標準發展
??最初,C語言沒有官方標準。1978年Brian W.Kernighian和Dennis M.Ritchie出版了名著《The C Programming Language》,從而使C語言成為目前世界上流行最廣泛的高級程序設計語言。而這本書附錄中的C Referrence Manual成為了事實上的C語言標準,被人們稱為K&R C或者Classic C。但是,該附錄中只定義了C語言,卻沒有定義C庫。由于C的出現離不開Unix,因此,Unix上實現的庫成為了一個C語言事實上的標準庫。
 ??隨著C語言的日益流行,美國國家標準化組織(ANSI)在1983年成立了一個委員會(X3J11),以制定C語言標準。該標準于1989年正式被批準采用。它就是ANSI C標準。該標準定義了C語言標準和C的標準庫。1990年,國際標準化組織(ISO)也要制定了一個C標準,以使C語言在國際上統一標準,方便使用,他們直接采用了ANSI C標準,發布了標準文件:ISO/IEC 9899-1990 Programming languages – C。最終第一版的C標準被稱為C98或C90。算是C語言的第一個官方標準。由于ANSI C出現較早,該標準也稱為 ANSI C。
 ??1994年,ISO 對C語言標準的修訂工作正式開始。1999年12月1日,國際標準化組織(ISO)和國際電工委員會(IEC)旗下的C語言標準委員會(ISO/IEC JTC1/SC22/WG14)正式發布了標準文件: ISO/IEC 9899:1999 - Programming languages – C。這就是大家熟知的C99標準。這成為了C語言的第二個官方標準。
 ??C99的修訂為C語言引入了大量新的特性,其借鑒了C++98標準一些特性。然而,如今不是所有C的編譯器都支持C99標準,而完全或幾乎完全支持C99標準的主流編譯器有:GCC、Clang、Intel C++ Compiler等。另外,Visual Studio2013也部分支持了C99語法特征。
 ??C99標準之后,新的C語言標準是國際標準化組織(ISO)和國際電工委員會(IEC)在2011年12月8日正式發布的C11標準,官方正式名為ISO/IEC 9899:2011。
 ??C標準由ISO和IEC旗下的C語言標準委員會(ISO/IEC JTC1/SC22/WG14)編寫,在其官方網站(http://www.open-std.org/)上可以找到標準的草稿,草稿是免費的!
C99 新特性
- 對編譯器限制增加了,比如源程序每行要求至少支持到4095字節,變量名函數名的要求支持到63字節 (extern要求支持到31)
- 預處理增強了。例如:
- 宏支持取參數 #define Macro(…) __VA_ARGS__
- 使用宏的時候,參數如果不寫,宏里用#,## 這樣的東西會擴展成空串。(以前會出錯的)
- 支持 // 行注釋(這個特性實際上在C89的很多編譯器上已經被支持了)
- 增加了新關鍵字restrict(C++目前并未引入), 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){.numberN=valueN, .numberK=valueK,…} 這有點像 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對應新的 long long 類型。
- 浮點數的內部數據描述支持了新標準,這個可以用 #pragma 編譯器指定
- 除了已經有的 line file 以外,又支持了一個 func 可以得到當前的函數名
- 對于非常數的表達式,也允許編譯器做化簡
- 修改了對于 / % 處理負數上的定義,比如老的標準里 -22 / 7 = -3, -22 % 7 = -1 而現在 -22 / 7 = -4, -22 % 7 = 6
- 取消了不寫函數返回類型默認就是 int 的規定
- 允許 struct 定義的最后一個數組寫做 [] 不指定其長度描述
- const const int i; 將被當作 const int i; 處理
- 增加和修改了一些標準頭文件。
- 輸入輸出對寬字符還有長整數等做了相應的支持
增加restrict指針
??關鍵字restrict只用于限定指針;該關鍵字用于告知編譯器,所有修改該指針所指向內容的操作全部都是基于(base on)該指針的,即不存在其它進行修改操作的途徑;這樣的后果是幫助編譯器進行更好的代碼優化,生成更有效率的匯編代碼。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 變元,可確保它們在該原型中指向不同的對象。
 ??但要注意:restrict是C99中新增的關鍵字,在C89和C++中都不支持,在gcc中可以通過-std=c99來得到對它的支持。
inline(內聯)關鍵字
??內聯函數除了保持結構化和函數式的定義方式外,還能使程序員寫出高效率的代碼。函數的每次調用與返回都會消耗相當大的系統資源,尤其是當函數調用發生在重復次數很多的循環語句中時。一般情況下,當發生一次函數調用時,變元需要進棧,各種寄存器內存需要保存。當函數返回時,寄存器的內容需要恢復。如果該函數在代碼內進行聯機擴展,當代碼執行時,這些保存和恢復操作旅游活動會再發生,而且函數調用的執行速度也會大大加快。函數的聯機擴展會產生較長的代碼,所以只應該內聯對應用程序性能有顯著影響的函數以及長度較短的函數
新增數據類型
_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,因此在編寫新的應用程序時,應該使用<complex.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位。對應的常量后綴是ll/ull/LL/ULL;格式化輸入輸出為%lld,%llu,%llx……
對數組的增強
可變長數組
??C99中,程序員聲明數組時,數組的維數可以由任一有效的整型表達式確定,包括只在運行時才能確定其值的表達式,這類數組就叫做可變長數組,但是只有局部數組才可以是變長的。
 ??可變長數組的維數在數組生存期內是不變的,也就是說,可變長數組不是動態的。可以變化的只是數組的大小。
int sum2d(int , int, int ar[*][*]); // 只能用在函數聲明中
數組聲明中的類型修飾符
??在C99中,如果需要使用數組作為函數變元,可以在數組聲明的方括號內使用static關鍵字,這相當于告訴編譯程序,變元所指向的數組將至少包含指定的元素個數。也可以在數組聲明的方括號內使用restrict,volatile,const關鍵字,但只用于函數變元。如果使用restrict,指針是初始訪問該對象的惟一途徑。如果使用const,指針始終指向同一個數組。使用volatile沒有任何意義。(類型限定詞和static關鍵字只能用于具有數組類型的函數形參的第一維中)。由于形參中的VLA被自動調整為等效的指針,因此這些類型限定詞實際上限定的是一個指針,例如:
void func3( int, int, int a[const][*] ); // 等價于void func( int, int, int ( *const a )[*] ); void func4( int, int, int a[const static 20][*] ); //static表示傳入的實參的值至少要跟其所修飾的長度表達式的值一樣大。單行注釋
??// 行注釋也是從C++過來的東西。引入了單行注釋標記 “//” , 可以象C++一樣使用這種注釋了。//注釋另外一個最大的好處還是在于排版方便。
 ??/* / 注釋是不支持嵌套的,所以只要里頭出現一個/就會導致注釋結束。不過這樣也帶來了一個有點可怕的陷阱,比如原來用/* */注釋的宏:
你要是改成這樣:
#define macro(arg1, arg2) \func(arg1, // xxxxxxx \arg2 // xxxxxxx \)這就杯具了~~因為這樣等價于:
#define macro(arg1, arg2) func(arg1, // xxxxxxx arg2 // xxxxxxx )arg2就落入第一個//的魔掌,變成注釋了~~
 ??總得來說,用//寫注釋肯定是比較方便的。只要能保證別在一個文件里面一會兒用/* */做行注釋,一會兒用//做行注釋就行了。
 建議所有的單行注釋都可以用//搞。而文檔化注釋(函數頭部、文件頭部或者重要數據結構頭部的那些注釋),還是建議用正統的格式來寫:
另外最好別用//來壘多行注釋。
分散代碼與聲明
??解除了原先必須在block的第一條語句之前聲明變量的限制:現在C99也和C++一樣,可以在代碼中隨時聲明變量了。
#include <stdio.h> int main(void) {int num1 = 3;int num1 = 3;printf("%d\n", num1);int num2 = 10; // 變量定義printf("%d\n", num2);return(0); }??其實混合聲明對于編碼的重要意義在于它對提高代碼的可讀性方面幫助很大。在C89里面,稍微復雜一點的函數中通常會看到這樣的壯觀景象:
int load_elf_file(const char *filename, int argc, int argv, int *retcode) {int elf_exec_fileno;int i, retval;uint k, elf_bss, elf_brk, elf_entry;uint start_code, end_code, end_data;struct elf_phdr *elf_ppnt, *elf_phdata;struct elfhdr elf_ex;struct file *file;...; }??這個就是C89必須在Block第一條語句之前聲明變量帶來的后果,不管一個變量是否一直到Block的最后一行才被用到,你也不得不在一開始就聲明它。
預處理程序的修改
變參宏(Variadic Macros)
??宏可以帶變元,在宏定義中用省略號(…)表示。內部預處理標識符__VA_ARGS__決定變元將在何處得到替換。變參宏的最大好處是可以讓你很容易地用宏來封裝一些帶變參的函數(主要是打印函數)
 如可以這樣寫一個輸出到stderr的宏:
??如果在__VA_ARGS__前面加上##,就可以寫出允許變參部份為空的變參宏。比如我自己常用的調試信息打印宏:
#define debug(fmt, ...) \printf("[DEBUG] %s:%d <%s>: " fmt, \__FILE__, __LINE__, __func__, ##__VA_ARGS__)??##__VA_ARGS__表示變參“…”部份允許為空。當變參部份為空時__VA_ARGS__會展開成空字符串,并且##前面那個逗號也會在展開時去掉。于是你可以這樣調用這個宏:debug(“Hello”);
 
_Pragma運算符
??C99引入了在程序中定義編譯指令的另外一種方法:_Pragma運算符。格式如下:
 _Pragma("directive")
 ??其中directive是要滿打滿算的編譯指令。_Pragma運算符允許編譯指令參與宏替換。
 ??如果字符串文字具有L前綴,則刪除該前綴。
 ??刪除前導和結尾雙引號。
 ??用雙引號替換每個換碼序列’。
 ??用單個反斜杠替換每個換碼序列\
內部編譯指令
STDC FP_CONTRACT ON/OFF/DEFAULT 若為ON,浮點表達式被當做基于硬件方式處理的獨立單元。默認值是定義的工具。
 STDC FEVN_ACCESS ON/OFF/DEFAULT 告訴編譯程序可以訪問浮點環境。默認值是定義的工具。
 STDC CX_LIMITED_RANGE ON/OFF/DEFAULT 若值為ON,相當于告訴編譯程序某程序某些含有復數的公式是可靠的。默認是OFF。
新增的內部宏
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
for語句內的變量聲明
??C99中,程序員可以在for語句的初始化部分定義一個或多個變量,這些變量的作用域僅于本for語句所控制的循環體內。在C89中,這樣是不可以的,具體可以在VC6中驗證(VC6支持到C89)。for循環的初始化語句中聲明的任何變量的作用域是整個循環(包括控制和迭代表達式)。
 如下:
復合賦值
??C99復合賦值中,可以指定對象類型的數組、結構或聯合表達式。當使用復合賦值時,應在括弧內指定類型,后跟由花括號圍起來的初始化列表;若類型為數組,則不能指定數組的大小。建成的對象是未命名的。
 例: double *fp = (double[]) {1.1, 2.2, 3.3};
 ??該語句用于建立一個指向double的指針fp,且該指針指向這個3元素數組的第一個元素。 在文件域內建立的復合賦值只在程序的整個生存期內有效。在模塊內建立的復合賦值是局部對象,在退出模塊后不再存在。
伸縮型數組成員
??C99具有一個稱為伸縮型數組成員(flexible array member)的新特性。結構中的最后一個元素允許是未知大小的數組,這就叫做柔性數組成員。利用這一新特性可以聲明最后一個成員是一個具有特殊屬性的數組的結構體。該數組成員的特殊屬性之一是它不立即存在。第二個特殊屬性是您可以編寫適當的代碼使用這個伸縮型數組成員,就像它確實存在并且擁有您需要的任何數目的元素一樣。柔性數組成員在做變長的報文或字符串處理時極為好用,也是一個幾乎所有的C碼農都應該掌握的技巧。
 聲明一個伸縮型數組成員的規則:
- 伸縮型數組成員必須是最后一個數組成員。
- 結構中必須至少有一個其他成員。
- 伸縮型數組就像普通數組一樣被聲明,但是它的方括號是空的。
 比如:
注意:
- sizeof返回的這種結構大小不包括柔性數組的內存
- 直接聲明帶有伸縮數組成員結構體的變量沒有任何意義,因為伸縮數組沒有內存,例如:word_counter_t wd; // 這里變量wd中沒有word
- 包含柔性數組成員的結構用malloc()函數進行內存的動態分配,并且分配的內存應該大于結構的大小,以適應柔性數組的預期大小。當給其指針動態分配內存空間時,多余的空間會分配給scores數組
指定的初始化符
??C99中,該特性對經常使用稀疏數組的程序員十分有用。簡單來說,就是在初始化結構體和數組時,可以通過指定具體成員名或數組下標來賦初值。
 用于數組的格式:
 [index] = vol; // 其中,index表示數組的下標,vol表示本數組元素的初始化值。
 例如:
 int x[10] = {[0] = 10, [5] = 30}; 其中只有x[0]和x[5]得到了初始化
 用于結構或聯合的格式如下:
 .成員名 = vol; // 其中, vol表示本數組元素的初始化值。
 例如:
 struct example{ int k, m, n; } object = {.m = 10, .n = 200};
 注意:該方法只能用在定義時初始化,不能再后續使用
 
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,但不起作用。
C99新增的庫
C89中標準的頭文件
<assert.h> 定義宏assert() <ctype.h> 字符處理 <errno.h> 錯誤報告 <float.h> 定義與實現相關的浮點值 <limits.h> 定義與實現相關的各種極限值 <locale.h> 支持函數setlocale() <locale.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年第一次修訂時首次引進,用于支持多字節和寬字節分類函數__func__預定義標識符
??用于指出__func__所存放的函數名,類似于字符串賦值。
冪等限定符
??C99中,如果同一類型限定符在同一說明符限定符列表中出現多次(無論直接出現還是通過一個或多個typedef),行為與該類型限定符僅出現一次時相同。
 ??在C90中,以下代碼會導致錯誤:
但是,對于C99,C編譯器接受多個限定符。
Static及數組聲明符中允許的其他類型限定符
??現在,關鍵字static可以出現在函數聲明符中及參數的數組聲明符中,表示編譯器至少可以假定許多元素將傳遞到所聲明的函數中。使優化器能夠作出以其他方式無法確定的假定。
如果您指定void foo(int * restrict a); 等類型限定符,則C 編譯器使用實質上與聲明限定指針相同的數組語法void foo(int a[restrict]); 表示它。
## 其它特性的改動
1.放寬的轉換限制
| 數據塊的嵌套層數 | 15 | 127 | 
| 條件語句的嵌套層數 | 8 | 63 | 
| 內部標識符中的有效字符個數 | 31 | 63 | 
| 外部標識符中的有效字符個數 | 6 | 31 | 
| 結構或聯合中的成員個數 | 127 | 1023 | 
| 函數調用中的參數個數 | 31 | 127 | 
每個聲明中的聲明說明符中應至少指定一個類型說明符,現在不支持沒有類型就默認是int的聲明語句。比如在C89中, auto i = 0;是合法的
| int16_t | 整數長度為精確16位 | 
| int_least16_t | 整數長度為至少16位 | 
| int_fast32_t | 最穩固的整數類型,其長度為至少32位 | 
| intmax_t | 最大整數類型 | 
| uintmax_t | 最大無符號整數類型 | 
??但是各個公司對C99的支持所表現出來的興趣不同。當GCC和其它一些商業編譯器支持C99的大部分特性的時候,微軟和Borland卻似乎對此不感興趣。
C11 標準
_Noreturn void thrd_exit(int res);
C和C++做程序的區別
??所以C與C++的最大區別在于它們的用于解決問題的思想方法不一樣。之所以說C++比C更先進,是因為“設計這個概念已經被融入到C++之中”。
參考
- 官方標準文檔
總結
以上是生活随笔為你收集整理的C/C++ 之 C发展史及 各标准特性说明的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: PC-lint使用说明
- 下一篇: C/C++之数据类型
