语言标准
K&R C
起初,C语言没有官方标准。1978年由
美国电话电报公司(AT&T)贝尔实验室正式发表了C语言。布莱恩·柯林汉(Brian Kernighan) 和 丹尼斯·里奇(Dennis Ritchie) 出版了一本书,名叫《
The C Programming Language》。这本书被 C语言开发者们称为
K&R,很多年来被当作 C语言的非正式的标准说明。人们称这个版本的 C语言为
K&R C。
[3]
K&R C主要介绍了以下特色:
结构体(struct)类型
长整数(long int)类型
无符号整数(unsigned int)类型
把运算符=+和=-改为+=和-=。因为=+和=-会使得编译器不知道使用者要处理i = -10还是i =- 10,使得处理上产生混淆。
即使在后来
ANSI C标准被提出的许多年后,K&R C仍然是许多编译器的最 准要求,许多老旧的编译器仍然运行K&R C的标准。
ANSI C / C89标准
1970到80年代,C语言被广泛应用,从大型主机到小型微机,也衍生了C语言的很多不同版本。
1989年,美国国家标准协会(ANSI)通过了C语言标准,被称为
ANSI X3.159-1989 "Programming Language C"。因为这个标准是1989年通过的,所以一般简称
C89标准。有些人也简称
ANSI C,因为这个标准是美国国家标准协会(ANSI)发布的。
1990年,
国际标准化组织(ISO)和
国际电工委员会(IEC)把C89标准定为C语言的国际标准,命名为
ISO/IEC 9899:1990 - Programming languages -- C
[5]
。因为此标准是在1990年发布的,所以有些人把简称作
C90标准。不过大多数人依然称之为
C89标准,因为此标准与ANSI C89标准完全等同。
1995年,国际标准化组织(ISO)和国际电工委员会(IEC)再次发布了C89标准修订版,名叫ISO/IEC 9899:1990/Amd 1:1995 - C Integrity
[7]
,有些人简称为
C95标准。
C99标准
1999年1月,国际标准化组织(ISO)和国际电工委员会(IEC)发布了C语言的新标准,名叫
ISO/IEC 9899:1999 - Programming languages -- C
[8]
,简称
C99标准。这是C语言的第二个官方标准。
在C99中包括的特性有:
-
增加了对编译器的限制,比如源程序每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节(extern 要求支持到 31)。
-
增强了预处理功能。例如:
-
宏支持取可变参数 #define Macro(...) __VA_ARGS__
-
使用宏的时候,允许省略参数,被省略的参数会被扩展成空串。
-
支持 // 开头的单行注释(这个特性实际上在C89的很多编译器上已经被支持了)
-
-
增加了新关键字 restrict, inline, _Complex, _Imaginary, _Bool
-
支持 long long, long double _Complex, float _Complex 等类型
-
-
支持不定长的数组,即数组长度可以在运行时决定,比如利用变量作为数组长度。声明时使用 int a[var] 的形式。不过考虑到效率和实现,不定长数组不能用在全局,或 struct 与 union 里。
-
变量声明不必放在语句块的开头,for 语句提倡写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效。
-
允许采用(type_name){xx,xx,xx} 类似于 C++ 的构造函数的形式构造匿名的结构体。
-
复合 字面量:初始化结构的时候允许对特定的元素赋值,形式为:struct test{int a[3],b;} foo[] = { [0].a = {1}, [1].a = 2 };
-
struct test{int a, b, c, d;} foo = { .a = 1, .c = 3, 4, .b = 5 }; // 3,4 是对 .c,.d 赋值的
-
格式化字符串中,利用 \u 支持 unicode 的字符。
-
支持 16 进制的浮点数的描述。
-
浮点数的内部数据描述支持了新标准,可以使用 #pragma 编译器指令指定。
-
除了已有的 __line__ __file__ 以外,增加了 __func__ 得到当前的函数名。
-
允许编译器化简非常数的表达式。
-
修改了 /% 处理负数时的定义,这样可以给出明确的结果,例如在C89中-22 / 7 = -3, -22% 7 = -1,也可以-22 / 7= -4, -22% 7 = 6。 而C99中明确为 -22 / 7 = -3, -22% 7 = -1,只有一种结果。
-
取消了函数返回类型默认为 int 的规定。
-
允许 struct 定义的最后一个数组不指定其长度,写做 [](flexible array member)。
-
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 用于复制 ... 的参数。里增加了 struct tmx ,对 struct tm 做了扩展。
-
输入输出对宽字符以及长整数等做了相应的支持。
C11标准
- 2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)再次发布了C语言的新标准,名叫 ISO/IEC 9899:2011 - Information technology -- Programming languages -- C [9] ,简称 C11标准,原名 C1X。这是C语言的第三个官方标准,也是C语言的最新标准。新的标准提高了对C++的兼容性,并增加了一些新的特性。这些新特性包括:
-
_Noreturn 函数标记,类似于 gcc 的 __attribute__((noreturn))。
-
_Generic 关键字。
-
多线程(Multithreading)支持,包括:
-
_Thread_local存储类型标识符,<threads.h>头文件,里面包含了线程的创建和管理函数。
-
_Atomic类型修饰符和<stdatomic.h>头文件。
-
-
删除了 gets() 函数,使用一个新的更安全的函数gets_s()替代。
-
增加了边界检查函数接口,定义了新的安全的函数,例如 fopen_s(),strcat_s() 等等。
-
增加了更多浮点处理宏。
-
静态断言(static assertions),_Static_assert(),在解释 #if 和 #error 之后被处理。
-
新的 fopen() 模式,(“…x”)。类似 POSIX 中的 O_CREAT|O_EXCL,在文件锁中比较常用。
-
新增 quick_exit() 函数作为第三种终止程序的方式。当 exit()失败时可以做最少的清理工作
语言组成
基本构成
- 数据类型常量与变量常量其值不可改变,符号常量名通常用大写。变量是以某标识符为名字,其值可以改变的量。标识符是以字母或下划线开头的一串由字母、数字或下划线构成的序列,请注意第一个字符必须为字母或下划线,否则为不合法的变量名。变量在编译时为其分配相应存储单元。数组指针如果一个变量声明时在前面使用 * 号,表明这是个指针型变量。换句话说,该变量存储一个地址,而 *(此处特指单目运算符 * ,下同。C语言中另有 双目运算符 *) 则是取内容操作符,意思是取这个内存地址里存储的内容。指针是 C 语言区别于其他同时代 高级语言的主要特征之一。 [18]字符串C语言的字符串其实就是以'\0'字符结尾的char型数组,使用字符型并不需要引用库,但是使用字符串就需要C标准库里面的一些用于对字符串进行操作的函数。它们不同于字符数组。使用这些函数需要引用 头文件<string.h>。 [19]文件输入/输出在C语言中,输入和输出是经由标准库中的一组函数来实现的。在 ANSI C中,这些函数被定义在头文件<stdio.h>;中。标准输入/输出有三个标准输入/输出是标准I/O库预先定义的:stdin标准输入stdout标准输出运算C语言的运算非常灵活,功能十分丰富,运算种类远多于其它 程序设计语言。在表达式方面较其它程序语言更为简洁,如自加、自减、逗号运算和三目运算使表达式更为简单,但初学者往往会觉的这种表达式难读,关键原因就是对运算符和运算顺序理解不透不全。当多种不同运算组成一个运算表达式,即一个运算式中出现多种运算符时,运算的优先顺序和结合规则显得十分重要。在学习中,对此合理进行分类,找出它们与数学中所学到运算之间的不同点之后,记住这些运算也就不困难了,有些运算符在理解后更会牢记心中,将来用起来得心应手,而有些可暂时放弃不记,等用到时再记不迟。
关键字
-
由ISO标准定义的C语言关键字共32个:auto double int struct break else long switchcase enum register typedef char extern return unionconst float short unsigned continue for signed voiddefault goto sizeof volatile do if while static inline
- 基本数据类型void:声明函数无返回值或无参数,声明无类型指针,显示丢弃运算结果。(C89标准新增)char:字符型类型数据,属于 整型数据的一种。(K&R时期引入)int:整型数据,表示范围通常为编译器指定的内存字节长。(K&R时期引入)double:双精度浮点型数据,属于浮点数据的一种。(K&R时期引入)_Complex:复数的基本类型(C99标准新增)_Generic:提供重载的接口入口( C11标准新增)类型修饰关键字short:修饰int,短整型数据,可省略被修饰的int。(K&R时期引入)long:修饰int,长整型数据,可省略被修饰的int。(K&R时期引入)long long:修饰int,超长整型数据,可省略被修饰的int。( C99标准新增)signed:修饰整型数据,有符号数据类型。(C89标准新增)unsigned:修饰整型数据,无符号数据类型。(K&R时期引入)restrict:用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。(C99标准新增)复杂类型关键字struct: 结构体声明。(K&R时期引入)union: 联合体声明。(K&R时期引入)enum: 枚举声明。(C89标准新增)typedef:声明类型别名。(K&R时期引入)sizeof:得到特定类型或特定类型变量的大小。(K&R时期引入)inline:内联函数用于取代宏定义,会在任何调用它的地方展开。( C99标准新增)存储级别关键字auto:指定为 自动变量,由编译器自动分配及释放。通常在栈上分配。与static相反。当变量未指定时默认为auto。(K&R时期引入)static:指定为 静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部。(K&R时期引入)extern:指定对应变量为 外部变量,即标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。(K&R时期引入)const:指定变量不可被当前线程改变(但有可能被系统或其他线程改变)。(C89标准新增)volatile:指定变量的值有可能会被系统或其他线程改变,强制编译器每次从内存中取得该变量的值,阻止编译器把该变量优化成寄存器变量。(C89标准新增)
流程控制关键字- 跳转结构return:用在 函数体中,返回特定值(如果是void类型,则不返回函数值)。(K&R时期引入)continue:结束当前循环,开始下一轮循环。(K&R时期引入)break:跳出当前循环或switch结构。(K&R时期引入)goto:无条件跳转语句。(K&R时期引入)分支结构if:条件语句,后面不需要放分号。(K&R时期引入)else:条件语句否定分支(与if连用)。(K&R时期引入)switch:开关语句(多重分支语句)。(K&R时期引入)case:开关语句中的分支标记,与switch连用。(K&R时期引入)default:开关语句中的“其他”分支,可选。(K&R时期引入)
编译语法结构
顺序结构- 顺序结构的程序设计是最简单的,只要按照解决问题的顺序写出相应的语句就行,它的执行顺序是自上而下,依次执行。例如:a = 3,b = 5,现交换a,b的值,这个问题就好像交换两个杯子水,这当然要用到第三个杯子,假如第三个杯子是c,那么正确的程序为:c = a; a = b; b = c;执行结果是a = 5,b = c = 3如果改变其顺序,写成:a = b; c = a; b =c;则执行结果就变成a = b = c = 5,不能达到预期的目的,初学者最容易犯这种错误。顺序结构可以独立使用构成一个简单的完整程序,常见的输入、计算,输出三步曲的程序就是顺序结构,例如计算圆的面积,其程序的语句顺序就是输入圆的半径r,计算s = 3.14159*r*r,输出圆的面积s。不过大多数情况下顺序结构都是作为程序的一部分,与其它结构一起构成一个复杂的程序,例如分支结构中的 复合语句、循环结构中的循环体等。 [22]
选择结构循环结构- 循环结构可以减少源程序重复书写的工作量,用来描述重复执行某段算法的问题,这是程序设计中最能发挥计算机特长的程序结构,C语言中提供四种循环,即goto循环、while循环、do while循环和 for循环。四种循环可以用来处理同一问题,一般情况下它们可以互相代替换,但一般不提倡用goto循环,因为强制改变程序的顺序经常会给程序的运行带来不可预料的错误。特别要注意在循环体内应包含趋于结束的语句(即循环变量值的改变),否则就可能成了一个 死循环,这是初学者的一个常见错误。三个循环的异同点:用while和do…while循环时,循环变量的初始化的操作应在循环体之前,而for循环一般在语句1中进行的;while循环和for循环都是先判断表达式,后执行循环体,而do…while循环是先执行循环体后判断表达式,也就是说do…while的循环体最少被执行一次,而while循环和for就可能一次都不执行。另外还要注意的是这三种循环都可以用break语句跳出循环,用continue语句结束本次循环,而 goto语句与 if构成的循环,是不能用break和 continue语句进行控制的。顺序结构、分支结构和循环结构并不彼此孤立的,在循环中可以有分支、顺序结构,分支中也可以有循环、顺序结构,其实不管哪种结构,均可广义的把它们看成一个语句。在实际编程过程中常将这三种结构相互结合以实现各种算法,设计出相应程序,但是要编程的问题较大,编写出的程序就往往很长、结构重复多,造成可读性差,难以理解,解决这个问题的方法是将C程序设计成模块化结构。 [24]具体内容:for循环for循环结构是c语言中最具有特色的循环语句,使用最为灵活方便,它的一般形式为:for(表达式1;表达式2;表达式3)循环体语句 。(其中;不能省略)表达式表达式1为初值表达式,用于在循环开始前为循环变量赋初值。表达式2是循环控制逻辑表达式,它控制循环执行的条件,决定循环的次数。表达式3为循环控制变量修改表达式,它使for循环趋向结束。循环体语句是在循环控制条件成立的情况下被反复执行的语句。但是在整个for循环过程中,表达式1只计算一次,表达式2和表达式3则可能计算多次,也可能一次也不计算。循环体可能多次执行,也可能一次都不执行。先执行表达式2,然后执行循环结构,最后表达式3,一直这样循环下去。for循环语句是c语言种功能最为强大的语句,甚至在一定程度上可以代替其他的循环 语句。dodo循环结构,do 1 while⑵;的执行顺序是1->2->1...循环,2为循环条件。whilewhile循环结构,while(1) 2; 的执行顺序是1->2->1...循环,1为循环条件以上循环语句,当循环条件表达式为真则继续循环,为假则跳出循环。
程序结构
- C语言的模块化程序结构用函数来实现,即将复杂的C程序分为若干模块,每个模块都编写成一个C函数,然后通过主 函数调用函数及函数调用函数来实现一大型问题的C程序编写,因此常说:C程序=主函数+子函数。因此,对函数的定义、调用、值的返回等中要尤其注重理解和应用,并通过上机调试加以巩固。 [25]
判断语句(选择结构):- if 语句:“如果”语句;if—else 语句:“若…(则)…否则…”语句;switch 语句:“切换”语句;switch—case:“切换—情况”语句。
循环语句(循环结构):- while 语句:“当…”语句;do—while 语句:“做…当…(时候)”语句;for 语句:条件语句(即“(做)…为了…”语句)。
跳转语句(循环结构:是否循环):- goto 语句:“转舵”语句,也称“跳转”语句;break 语句:“中断”(循环)语句,即结束整个循环;continue 语句:“继续”语句(结束本次循环,继续下一次循环);return 语句:“返回”语句。需要说明的是:
-
2、每个源文件可由一个或多个函数组成。4、源程序中可以有 预处理命令(包括include 命令,ifdef、ifndef命令、define命令),预处理命令通常应放在源文件或源程序的最前面。5、每一个说明,每一个语句都必须以分号结尾。但预处理命令, 函数头和花括号“}”之后不能加分号。(结构体、联合体、枚举型的声明的“}”后要加“ ;”。)6、 标识符,关键字之间必须至少加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。
书写规则- 1、一个说明或一个语句占一行。2、用{} 括起来的部分,通常表示了程序的某一层次结构。{}一般与该结构语句的第一个字母对齐,并单独占一行。3、低一层次的语句或说明可比高一层次的语句或说明缩进若干格后书写。以便看起来更加清晰,增加程序的可读性。在编程时应力求遵循这些规则,以养成良好的编程风格。
-
函数
- C程序是由一组变量或是函数的外部对象组成的。 函数是一个自我包含的完成一定相关功能的执行代码段。我们可以把函数看成一个“ 黑盒子”,你只要将数据送进去就能得到结果,而函数内部究竟是如何工作的的,外部程序是不知道的。外部程序所知道的仅限于输入给函数什么以及函数输出什么。函数提供了编制程序的手段,使之容易读、写、理解、排除错误、 修改和维护。C程序中函数的数目实际上是不限的,如果说有什么限制的话,那就是,一个C程序中必须至少有一个函数,而且其中必须有一个并且仅有一个以main为名,这个函数称为主函数,整个程序从这个 主函数开始执行。C 语言程序鼓励和提倡人们把一个大问题划分成一个个子问题,对应于解决一个子问题编制一个函数,因此,C 语言程序一般是由大量的小函数而不是由少量大函数构成的,即所谓“小函数构成大程序”。这样的好处是让各部分相互充分独立,并且任务单一。因而这些充分独立的小模块也可以作为一种固定规格的小“构件”, 用来构成新的大程序。C语言发展的那么多年来,用C语言开发的系统和程序浩如烟海。在发展的同时也积累了很多能直接使用的库函数。ANSI C提供了标准C语言库函数。C语言初学者比较喜欢的 Turbo C 2.0提供了400多个运行时函数,每个函数都完成特定的功能,用户可随意调用。这些函数总体分成输入输出函数、数学函数、字符串和内存函数、与BIOS和DOS有关的函数、 字符屏幕和图形功能函数、过程控制函数、目录函数等。Windows系统所提供的Windows SDK中包含了数千个跟Windows应用程序开发相关的函数。其他操作系统,如Linux,也同样提供了大量的函数让应用程序开发人员调用。作为程序员应尽量熟悉目标平台库函数其功能。这样才能游刃有余地开发特定平台的应用程序。比如作为Windows应用程序的开发者,应尽量熟悉Windows SDK;作为Linux应用程序开发者,应尽量熟悉Linux系统调用和POSIX函数规范。
运算符号
- 比较特别的是,比特右移(>>)运算符可以是算术(左端补最高有效位)或是逻辑(左端补 0)位移。例如,将 11100011 右移 3 比特,算术右移后成为 11111100,逻辑右移则为 00011100。因算术比特右移较适于处理带负号整数,所以几乎所有的编译器都是算术比特右移。
()、 []、 -> 、 .、!、 ++、 -- 圆括号、方括号、指针、成员、逻辑非、自加、自减 ++ 、 -- 、 * 、 & 、 ~ 、! 单目运算符 +、 - 、 sizeof、(cast) * 、 / 、% 算术运算符 + 、 - 算术运算符 << 、 >> 位运算符 < 、 <= 、 > 、 >= 关系运算符 == 、!= 关系运算符号 & 位与 ^ 位异或 | 位或 && 逻辑与 || 逻辑或 ? 、: 条件运算符 /= 、%= 、 &= 、 |= 、 ^= 赋值运算符 = 、 += 、 -= 、 *= 、 , 顺序运算符
-