C语言是C++的子集,C++是C语言的超集。C++是在C语言的基础上,添加了面向对象和泛型编程的功能后,扩展而成的。
C语言是一种可移植的系统语言,起源于Unix操作系统的开发。C语言是一种灵活高效的高级程序设计语言,但是同时也具有一些面向机器的低级语言特征,所以有人称它是一种中级语言。
1)起源
虽然早在1957年4月IBM(John W. Bacus)就推出了第一个高级程序设计语言FORTRAN(FORmula TRANslation公式转换),1958年GAMM/ACM(John W. Backus、Peter Naur、C. A. R. Hoare)又推出了ALGOL(ALGOrithmic Language算法语言)。但它们都是为数学和科学计算设计的,不适合于系统编程。
1964年John Kemeny 和Thomas Kurtz简化Fortran发明了BASIC(Beginners All-purpose Symbolic Instruction Code BASIC初学者通用指令码)语言,1970年瑞士计算机科学家Niklaus Wirth又在ALGOL68的基础上创造了Pascal语言。前者是为专入门的文科学生设计的,简单易学,至今仍然是业余爱好者的宠物;后者则是结构化程序设计语言的典范、结构优美,曾被广泛用于计算机教学;但是,它们的功能有限、灵活性不够、效率也不高,也都不适合于编写系统编程。
附注:1991年和2002年微软公司的Alan Cooper和Paul Vick分别开发出Visual Basic和Visual Basic .NET。1983年Borland公司的Anders Hejlsberg和Philippe Kahn 开发出Turbo Pascal、1985年Niklaus Wirth和苹果公司的Larry Tesler又创造了Object Pascal、1995年Borland公司的Anders Hejlsberg等人在其基础上开发出Delphi。
1970年,AT&T贝尔实验室的Ken Thompson和Dennis Ritchie等人在DEC公司的PDP-7小型机上开发出了Unix操作系统,最初的实现是用汇编语言写成的。为了使Unix操作系统具有可移植性,迫切需要一种高级语言工具。为此,(出生在美国纽约的计算机科学家)Dennis Ritchie以B语言为基础,参考了Algol68,于1972年设计出了C语言。1973年他们用C语言重写了Unix,1975年又利用C语言将Unix移植到了PDP-11上。
2)特点
C语言是一种可移植的系统语言,拥有充分的控制语句和数据结构功能,包含丰富的操作符,从而能够提供强大的表达能力,可以用于许多不同的应用领域。但是,C语言并不是面向科学家和计算机业余爱好者的,而是专门为程序员设计的。
为了进行高效的系统编程,C语言提供了强大的功能和极大的灵活性。与其它高级语言相比,C语言的语法简洁、表达自由、库函数丰富。如果将编程比作造房子,则Fortran和Basic等语言就像一些已经预先造好的大预制件,使用起来简单快捷,但是灵活性差、且功能有限,只能造某些固定模式的房屋;而C语言就像一块块的小砖,使用起来虽然繁琐,但是灵活性强、而且功能无限,能够造各式各样的建筑物,不过这就要求C语言程序员具有很高的专业水平。
因此,C语言假设使用者都是计算机专家,采取的是程序员负责制。它不进行完备的类型检查,对数组越界也没有限制。为了进行高效的系统编程,C语言还提供了指针和指针运算,程序员可以随意操作全部内存,任意修改任何内容。
表达的自由性和操作的任意性,也给C语言带来了很多编程问题和安全隐患。特别是C语言的++/--运算符和指针运算,更是倍受指责。
与其它高级语言相比,C语言提供了一些低级语言特征,更面向机器。所以,也有人称C语言是介于高级语言和低级语言之间的一种中级语言。
3)K&R C
开始的很多年,C语言没有国际标准,只有一个事实标准K&R C。直到1989年和1990年,才分别推出了ANSI C和ISO C(C89或C90);1999年ISO又推出了第2版(C99)。
不像Algol、COBOL和Ada等语言,C语言并不是政府部门或国际组织的产物,而属于个人的作品。虽然由于C语言性能优越,使用得越来越广泛,但是在最初的十几年间,C语言唯一的“标准”是1978年Brian Kemighan和Dennis Ritchie编写的《C程序设计语言》(The C Programming Language)一书,通常称其为K&R C或经典C。
该书的附录“C参考手册”(C Reference Manual)成为了C语言的实现指南,但是书中缺少对库函数标准的描述,一般以Unix实现的库函数所为事实标准。
说明:因为C语言的语法成分简单,很多基本功能(例如I/O语句)都是靠库函数来实现的。所以,C语言比其它高级语言更依赖于库函数。
4)C89/C90标准
1983年ANSI(American National Standards Institute 美国国家标准协会)设立了一个X3J11小组,着手进行C语言的标准化。并最终于1989年推出ANSI C (ANSI X3.159-1989),1990年它又成为国际标准ISO C(ISO/IEC 9899:1990 Programming languages – C,程序设计语言——C),原来叫做ANSI C或ISO C,现在通常称其为C89或C90。
标准的指导原则是:
l 相信程序员;
l 不妨碍程序员做需要完成的事情;
l 让语言保持短小简单;
l 只提供一种方法来执行一种操作;
l 使程序运行速度快,即使不能保证其可移植性。(不追求定义的抽象统一,更优先考虑运行效率)
这些其实也正是C语言的设计初衷,所以ANSI/ISO的C标准,对原始的C语言修改并不多。标准C对K&R C的主要改变是,增加了函数原型(prototype),强调对函数的输入参数进行严格的类型检查;并补充定义了C语言的标准函数库。
函数原型的类型检查是指:在编译时,对调用函数的实参和函数定义时的形参的类型是否一致,进行严格的检查。目的是减少程序纠错的难度(这类问题在运行时很难查出),同时保证运行的安全和稳定性(避免函数调用栈溢出)。(这也为实现C++函数的参数型多态性提供了条件。)
例如:
u K&R C:(过时的)
power(); // 函数的前向声明
power(x, n) // 函数定义
int x, n;
{
……
}
int p = power(3) * power(3.9, 5.4); // 使用(编译可通过,运行时产生逻辑错误)
u C89/C90:(推荐的)
int power(int x, int n); // 函数声明1(函数原型)
int power(int, int); // 函数声明2(函数原型)
int power(int x, int n) // 函数定义
{
……
}
int p = power(3) * power(3.9, 5.4); // 使用(编译不能通过,错误:第1个调用
// 的参数太少、警告:第2个调用的参数自动转换为(3, 5))
l C89/C90对K&R C的其它改变有:
n 删除了关键字:entry(条目/入口)
n 增加了关键字:const(常型变量)、enum(枚举类型)、signed(有符号的,例如signed char)、void(空/无,可用于函数返回值和形参、通用指针类型)、volatile(易变变量,防止编译器错误的优化)
n 传递结构:允许将结构本身作为参数传递给函数(原来只允许传地址)
n 函数原型:增加了函数原型(便于编译器进行类型检查)
n 增加了预处理指令:#elif(else if)、#error(错误,强制编译停止)、#line(修改当前行号和源文件名)、#pragma(附注/编译指令,编译器定义的与实现有关的指令)
n 定义了固有宏:__LINE__(当前行号)、__FILE__(源文件名)、__DATE__(当前系统日期)、__TIME__(当前系统时间)、__STDC__(标准C版时为1)
5)C99标准
对C90的修订工作开始于1994年,在ISO C++(1998)标准推出之后,ISO又于1999年12月16日,推出了C语言标准的第2版:ISO/IEC 9899:1999 Programming languages – C(程序设计语言——C),一般称其为C99。
(1)修订目标
C99保持了C语言的本质特性,C继续是一种短小、清晰和高效的语言。C99并没有增加新的本质特性,但是为了满足新的需要,也进行很多小的修订。
l C99主要的修订目标有三点:
n 支持国际化编程,引入了支持国际字符集Unicode的数据类型和库函数;
n 修正原有版本的明显缺点。如整数的移植方法,例如int8_t、int16_t、int32_t和int64_t等类型;
n 针对科学和工程的需要,改进计算的实用性。例如添加了复数类型和新数学函数。
(2)具体修改
l C99对C89/C90的具体修改有:
n 增加了C++的//注释风格:原来C语言之支持多行注释:/*……*/,C99现在也识别单行注释:// ……
n 增加了关键字:inline(内联函数)、restrict(限制)、_Bool(布尔类型)、_Complex(复数)、_Imaginary(虚数)
u 内联函数(inline):链接时不是进行函数调用而是嵌入函数体代码(可减少调用的时间开销,适用于要求运行速度快的小型函数)
u restrict:只能用于指针,表明该指针是数据对象的唯一且初始的方式(不是通过指针的赋值运算另外来得的),便于编译器进行代码优化。
附注:微软公司VC扩展的restrict,是让编译器限制别名(如用typedef定义的)的使用,必须类型名完全一致(只是类型等价还不够)时,类型检验才能通过
u _Bool:布尔类型,为整数类型,一般用1(或非0)表示真、用0表示假。如果包含了C99新增加的标准库头文件stdbool.h,则可以用bool来代替_Bool,并可使用true和false,从而与标准C++兼容
u 复数和虚数类型:
l 如果包含了C99新增加的标准库头文件complex.h后,就可以用complex来代替_Complex、用imaginary来代替_Imaginary
l 有三种复数和虚数类型:float _Complex、double _Complex、long double _Complex;float _Imaginary、double _Imaginary、long double _Imaginary
l 在C99内部,是用二元数组来实现复数的,第一个数组元素为实部、第二个数组元素为虚部
l 可以用实数和(定义在complex.h中的)I来初始化一个复变量。例如:
#include <complex.h>
double complex z = 6.0 – 8.5 * I, w = 3.0, u = 4.0 * I;
C99的关键字(Keywords)(共37个)
auto | enum | restrict | unsigned |
break | extern | return | void |
case | float | short | volatile |
char | for | signed | while |
const | goto | sizeof | _Bool |
continue | if | static | _Complex |
default | inline | struct | _Imaginary |
do | int | switch |
|
double | long | typedef |
|
else | register | union |
|
其中:黑色的为K&R C原有的关键字,绿色的为C90添加的关键字,红色的为C99新增的关键字。(C90删除了K&R C的关键字entry)
n 增加了数据类型:(unsigned) long long [int](64位整数)(对应的打印输出格式为%lld或%llu)
n 定义了可移植整数类型:因为同一整数类型,在不同字长的计算机系统中,可能位数不一样,这给移植带来了问题。因此,C99在新增加的头文件inttypes.h中定义了已有整数类型的一些别名,便于程序移植。例如:int8_t、int16_t、int32_t、int64_t,uint8_t、uint16_t、uint32_t、uint64_t;intptr_t、uintptr_t。以及表示对应类型常量的方法,例如INT8_C(128)、INT32_C(1234)。
n 增加了预定义宏:C99新加了两个预定义宏:__STDC_HOSTED__(是本机环境时为1,否则为0)和__STDC__VERSION__(=199901L时为C99,否则为C89/C90)
n 提供了一个预定义标识符:__func__,为一个代表函数名的字符串(该函数中包含有此标识符),该标识符具有函数作用域。(宏具有文件作用域)
n 增加了浮点常量的十六进制格式:p或P表示后跟二进制指数(的十进制值)。例如:0xa.1cp10 = (10 + 1/16 + 12/256) * 210 = 10352.0
n 增加了浮点数的十六进制打印格式符:%a或%A(代替十进制的%e或%E)、%La或%LA(代替十进制的%Le或%LE)
n 可指定初始化的条目:在对数组和结构进行初始化时,原来必须从头到尾顺序指定初值,最多只能省掉对尾部部分赋初值(未被初始化的条目全被置为0)。C99可以用[i] = v的方法,在花括号中为指定条目i赋初值v。例如:
int a[6] = {[5] = 123}; // 最后一个数组元素的初值为123,其余的值全为0
int days[12] = {31, 28, [4] = 31, 30, 31, [1] = 29}; // 则初值序列为31 29 0 0
// 31 30 31 0 0 0 0 0
n 支持变长数组(VLA = Variable-Length array):原来的C语言(包括C++)要求数组定义中的大小必须是整数常量[表达式],而C99允许使用变量。例如:
#define SIZE 12
……
int m, n;
……
int a1[5], a2{SIZE}; // C89可以
float a3[n], a4[m][n]; // C89不允许,C99可以
注意:这里的变长,不是说数组的大小可以随意改变;而是指在定义时,数组的大小可以是变量。一旦定义完成,数组的大小就已固定,不允许再进行改变。
n 复合文字(compound literal混合直接量):可以用于定义无名数组、给函数传递数组和结构常量参数、给其他数组和结构赋初值等。例如:
int *pt = (int []) {53, 16, 88}; // 定义无名数组
int sum(int a[], int n); // 函数原型
int total = sum((int []) {4, 3, 5, 2}, 4); // 给函数传递数组常量参数
struct book { // 定义结构
char tutle[41];
char author[31];
float price;
};
struct book bk; // 定义结构变量
……
bk = (struct book) {“C Programming”, “Dennis Ritchie”, 9.8}; // 给结构赋初值
n 允许在代码块的任何地方定义变量:原来C语言的块变量定义,必须位于块的开始处(在任何可执行代码之前)。C99允许在代码块的任何地方(包括控制语句部分)定义变量(~C++),例如:for(int i = 0; i < 10; i++) {……}
n 旧关键字的新位置:C99允许将类型限定词(如const、volatile、restrict)和存储限定词(如auto、register、static)放在函数原型和函数头形参的方括号内。例如:
void f(int *const a1, int *restrict a2, double static a3[20]); // 老风格
void f(int a1[const], int a2[restrict], double a3[static 20]); // 新风格
n 弹性数组成员(flexible array member):C99允许在结构的最后定义一个大小可伸缩的弹性数组成员,可以用于结构指针,根据允许情况来动态分配内存。但是操作就像它本来就有这么多数组元素似的,增加了编程的灵活性和源码的可读性。例如:
strtuct flex {
int count;
double average;
double scores[]; // 弹性数组成员
}
struct flex *pf;
……
pf = malloc(sizeof(struct flex) + n * sizeof(double));
pf->count = n;
……
double sum = 0;
for (int i = 0; i < n; i++) {
fstrm >> pf->scores[i];
sum += pf->scores[i]
}
pf->average = sum / n;
n 编译指令:在C99中定义了如下三个标准编译指令:
#pragma STDC FP_CONTRACT on-off-switch // 允许或禁止浮点表达式压缩
#pragma STDC FENV_ACCESS on-off-switch // 通知是否访问浮点环境
#pragma STDC CX_LIMITED_RANGE on-off-switch // 通知复数是否范围有限
其中的on-off-switch取值为ON、OFF和DEFAULT之一。
n C99还新提供了_Pragma预处理运算符,可以将字符串转换成常规的编译指令。例如:
_Pragma(“use_bool true false”)
等价于
#pragma use_bool true false
(3)新增标准库
l C99的标准库和头文件
n 标准头文件:<assert.h>、<complex.h>、<ctype.h>、<errno.h>、<fenv.h>、<float.h>、<inttypes.h>、<iso646.h>、<limits.h>、<locale.h>、<math.h>、<setjmp.h>、<signal.h>、
<stdarg.h>、<stdbool.h>、<stddef.h>、<stdint.h>、<stdio.h>、<stdlib.h>、<string.h>、<tgmath.h>、<time.h>、<wchar.h>、<wctype.h>
n 新增加的头文件:在上面的标准头文件中,C99新增加的为:<complex.h>、<fenv.h>、<inttypes.h>、<iso646.h>、<stdbool.h>、<wchar.h>、<wctype.h>。下面将逐个加以简单介绍
n complex.h:定义了宏complex、imaginary和I,定义了各种数学函数(与不同数学函数相比,增加的前导字符c表示复数,尾部字符f和l分别表示float和long double类型,没有尾部字符的为double型),例如:
double complex ccos(double complex z);
float complex ccosf(float complex z);
long double complex ccosl(long double complex z);
C99中的复数处理函数(省略了f和l版)
原型 | 说明 |
double carg(double complex z); | 返回z的相位角/辐角(弧度) |
double cimag(double complex z); | 返回z的虚部(实数形式) |
double complex conj(double complex z); | 返回z的共轭复数 |
double complex cproj(double complex z); | 返回z在黎曼域上的投影 |
double creal(double complex z); | 返回z的实部(实数形式) |
注意:该库VC05不支持
n fenv.h:提供对浮点数环境的访问和控制
u 开启/关闭对浮点环境的访问:#pragma STDC FENV_ACCESS ON / OFF
u 浮点环境类型:fenv_t(整个浮点环境)、fexcept_t(浮点状态标志的集合)
u 标准的浮点异常和控制宏:
| 宏 | 表示 |
异常 | FE_DIVBYZERO | 抛出被0除异常 |
FE_INEXACT | 抛出不精确值异常 | |
FE_INVALID | 抛出非法值异常 | |
FE_OVERFLOW | 抛出上溢异常 | |
FE_UNDERFLOW | 抛出下溢异常 | |
FE_ALL_EXCEPT | 抛出位异常或实现所支持的所有浮点异常 | |
控制 | FE_DOWNWARD | 向下舍入 |
FE_TONEAREST | 最近舍入 | |
FE_TOWARDZERO | 趋0舍入 | |
FE_UPWARD | 向上舍入 | |
FE_DFL_ENV | 默认环境(const fevn_t*类型) |
u 库函数
原型 | 说明 |
void feclearexcept(int excepts); | 清除异常excepts |
void fegetexceptflag(fexcept_t *flagp, int excepts); | 将excepts异常的标志存储到flagp中 |
void feraiseexcept(int excepts); | 抛出excepts异常 |
void fesetexceptflag(const fexcept_t *flagp, int excepts); | 将异常标志excepts设置为flagp状态 |
int fetestexcept(int excepts); | 返回excepts指定查询状态的标志位 |
int fegetround(void); | 返回当前的舍入方向 |
int fesetround(int round); | 将舍入方向设为round,成功返回0 |
void fegetenv(fenv_t *envp); | 将当前环境存入envp |
int feholdexcept(fenv_t *envp); | 将当前环境存入envp,清除浮点状态标志,成功返回0 |
void fesetenv(const fenv_t *envp); | 建立envp表示的环境 |
void feupdateenv(const fenv_t *envp); | 建立envp表示的环境,并抛出自动存储的浮点异常 |
注意:该库VC05也不支持
n inttypes.h:C语言中的同一整数类型,在不同字长的计算机系统中,可能位数不一样,这给移植带来了问题。因此,C99在新增加的头文件inttypes.h中定义了可移植的整数类型。
固定长度类型
类型名 | printf说明符 | scanf说明符 | 最小值 | 最大值 |
int8_t | PRId8 | SCNd8 | INT8_MIN | INT8_MAX |
int16_t | PRId16 | SCNd16 | INT16_MIN | INT16_MAX |
int32_t | PRId32 | SCNd32 | INT32_MIN | INT32_MAX |
int64_t | PRId64 | SCNd64 | INT64_MIN | INT64_MAX |
uint8_t | PRIu8 | SCNu8 | 0 | UINT8_MAX |
uint16_t | PRIu16 | SCNu16 | 0 | UINT16_MAX |
uint32_t | PRIu32 | SCNu32 | 0 | UINT32_MAX |
uint64_t | PRIu64 | SCNu64 | 0 | UINT64_MAX |
最小长度类型
类型名 | printf说明符 | scanf说明符 | 最小值 | 最大值 |
int_least8_t | PRILEASTd8 | SCNLEASTd8 | INT_LEAST8_MIN | INT_LEAST8_MAX |
int_least16_t | PRILEASTd16 | SCNLEASTd16 | INT_LEAST16_MIN | INT_LEAST16_MAX |
int_least32_t | PRILEASTd32 | SCNLEASTd32 | INT_LEAST32_MIN | INT_LEAST32_MAX |
int_least64_t | PRILEASTd64 | SCNLEASTd64 | INT_LEAST64_MIN | INT_LEAST64_MAX |
uint_least8_t | PRILEASTu8 | SCNLEASTu8 | 0 | UINT_LEAST8_MAX |
uint_least16_t | PRILEASTu16 | SCNLEASTu16 | 0 | UINT_LEAST16_MAX |
uint_least32_t | PRILEASTu32 | SCNLEASTu32 | 0 | UINT_LEAST32_MAX |
uint_least64_t | PRILEASTu64 | SCNLEASTu64 | 0 | UINT_LEAST64_MAX |
最快的最小长度类型
类型名 | printf说明符 | scanf说明符 | 最小值 | 最大值 |
int_fast8_t | PRIFASTd8 | SCNFASTd8 | INT_FAST8_MIN | INT_FAST8_MAX |
int_fast16_t | PRIFASTd16 | SCNFASTd16 | INT_FAST16_MIN | INT_FAST16_MAX |
int_fast32_t | PRIFASTd32 | SCNFASTd32 | INT_FAST32_MIN | INT_FAST32_MAX |
int_fast64_t | PRIFASTd64 | SCNFASTd64 | INT_FAST64_MIN | INT_FAST64_MAX |
uint_fast8_t | PRIFASTu8 | SCNFASTu8 | 0 | UINT_FAST8_MAX |
uint_fast16_t | PRIFASTu16 | SCNFASTu16 | 0 | UINT_FAST16_MAX |
uint_fast32_t | PRIFASTu32 | SCNFASTu32 | 0 | UINT_FAST32_MAX |
uint_fast64_t | PRIFASTu64 | SCNFASTu64 | 0 | UINT_FAST64_MAX |
最大长度类型
类型名 | printf说明符 | scanf说明符 | 最小值 | 最大值 |
intmax_t | PRIdMAX | SCNdMAX | INTMAX_MIN | INTMAX_MAX |
uintmax_t | PRIuMAX | SCNuMAX | 0 | UINTMAX_MAX |
可保存指针值的整数类型
类型名 | printf说明符 | scanf说明符 | 最小值 | 最大值 |
intptr_t | PRIdPTR | SCNdPTR | INTPTR_MIN | INTPTR_MAX |
uintptr_t | PRIuPTR | SCNuPTR | 0 | UINTPTR_MAX |
注意:该库VC05也不支持。但是微软在VC中定义了扩展的C++关键字:__int8、__int16、__int32和__int64;在Windows API定义了INT32、INT64、UINT32和UINT64等数据类型;在.NET框架也定义了Int16、Int32、Int64、UInt16、UInt32和UInt64等结构类型。
n iso646.h:定义了运算符的替代字符串
宏 | and | and_eq | bitand | bitor | compl | not | not_eq | or | or_eq | xor | xor_eq |
运算符 | && | &= | & | | | ~ | ! | != | || | |= | ^ | ^= |
注意:该C库VC05支持
n stdbool.h:定义了bool(_Bool)、false(0)和true(1)等宏。
注意:该C库VC05也不支持。但是C++标准支持,它们都是C++的关键字。
n wchar.h:定义了扩展的多字节字符和宽字符工具:(参见wchar.doc,该C库VC05支持)
u 数据类型:
l wchar_t:可表示本地环境所支持的最大扩展字符集的一种整数类型,在VC05中定义为unsigned short(2B)
l wint_t:可表示最大扩展字符集任何值以及其他值的一种整数类型,在VC05中定义为int(4B)
l size_t:由sizeof返回的整数值类型,在VC05中定义为unsigned int(4B)(在Win64中定义为unsigned __int64)
l mbstate_t:为一种非数组的对象类型,用于保存多字节字符与宽字符之间转换所需状态信息。在VC05中定义为int(4B)
l struct tm:用来保存日历时间成分的结构类型,至少应该包含下列成员:
int tm_sec; // seconds after the minute — [0, 60]
int tm_min; // minutes after the hour — [0, 59]
int tm_hour; // hours since midnight — [0, 23]
int tm_mday; // day of the month — [1, 31]
int tm_mon; // months since January — [0, 11]
int tm_year; // years since 1900
int tm_wday; // days since Sunday — [0, 6]
int tm_yday; // days since January 1 — [0, 365]
int tm_isdst; // Daylight Saving Time flag
在VC05中定义也为:
struct tm {
int tm_sec; /* seconds after the minute - [0,59] */
int tm_min; /* minutes after the hour - [0,59] */
int tm_hour; /* hours since midnight - [0,23] */
int tm_mday; /* day of the month - [1,31] */
int tm_mon; /* months since January - [0,11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday - [0,6] */
int tm_yday; /* days since January 1 - [0,365] */
int tm_isdst; /* daylight savings time flag */
};
u 符号常量宏:
l NULL(空指针,VC05中定义为((void *)0))
l WCHAR_MAX(wchar_t的最大值,VC中定义为0xFFFF[65535])
l WCHAR_MIN(wchar_t的最小值,VC05中定义为0)
l WEOF(宽字符文件的结尾,是EOF的宽字符表示,在VC05中定义为(wint_t)(0xFFFF)[65535])
u 宽字符I/O函数:
l I/O函数:(字符和串格式符从%c和%s改为%lc和%ls)
int fwprintf(FILE * stream, const wchar_t * format, ...);
int fwscanf(FILE * stream, const wchar_t * format, ...);
int swprintf(wchar_t * s, size_t n, const wchar_t * format, ...);
int swscanf(const wchar_t * s, const wchar_t * format, ...);
int vfwprintf(FILE * stream, const wchar_t * format, va_list arg);
int vfwscanf(FILE * stream, const wchar_t * format, va_list arg);
int vswprintf(wchar_t * s, size_t n, const wchar_t * format, va_list arg);
int vswscanf(const wchar_t * s, const wchar_t * format, va_list arg);
int vwprintf(const wchar_t * format, va_list arg);
int vwscanf(FILE * stream, const wchar_t * format, va_list arg);
int wprintf(const wchar_t * format, ...);
int wscanf(const wchar_t * format, ...);
wint_t fgetwc(FILE *stream);
wchar_t *fgetws(wchar_t * s, int n, FILE * stream);
wint_t fputwc(wchar_t c, FILE *stream);
int fputws(const wchar_t * s, FILE * stream);
int fwide(FILE *stream, int mode);
wint_t getwc(FILE *stream);
wint_t getwchar(void);
wint_t putwc(wchar_t c, FILE *stream);
wint_t putwchar(wchar_t c);
wint_t ungetwc(wint_t c, FILE *stream);
l 字符串工具:
double wcstod(const wchar_t * nptr, wchar_t ** endptr);
float wcstof(const wchar_t * nptr, wchar_t ** endptr);
long double wcstold(const wchar_t * nptr, wchar_t ** endptr);
long int wcstol(const wchar_t * nptr, wchar_t ** endptr, int base);
long long int wcstoll(const wchar_t * nptr, wchar_t ** endptr, int base);
unsigned long int wcstoul(const wchar_t * nptr, wchar_t ** endptr, int base);
unsigned long long int wcstoull(const wchar_t * nptr, wchar_t ** endptr, int base);
wchar_t *wcscpy(wchar_t * s1, const wchar_t * s2);
wchar_t *wcsncpy(wchar_t * s1, const wchar_t * s2, size_t n);
wchar_t *wcscat(wchar_t * s1, const wchar_t * s2);
wchar_t *wcsncat(wchar_t * s1, const wchar_t * s2, size_t n);
int wcscmp(const wchar_t *s1, const wchar_t *s2);
int wcscoll(const wchar_t *s1, const wchar_t *s2);
int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n);
size_t wcsxfrm(wchar_t * s1, const wchar_t * s2, size_t n);
wchar_t *wcschr(const wchar_t *s, wchar_t c);
size_t wcscspn(const wchar_t *s1, const wchar_t *s2);
size_t wcslen(const wchar_t *s);
wchar_t *wcspbrk(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcsrchr(const wchar_t *s, wchar_t c);
size_t wcsspn(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcsstr(const wchar_t *s1, const wchar_t *s2);
wchar_t *wcstok(wchar_t * s1, const wchar_t * s2, wchar_t ** ptr);
wchar_t *wmemchr(const wchar_t *s, wchar_t c, size_t n);
int wmemcmp(wchar_t * s1, const wchar_t * s2, size_t n);
wchar_t *wmemcpy(wchar_t * s1, const wchar_t * s2, size_t n);
wchar_t *wmemmove(wchar_t *s1, const wchar_t *s2, size_t n);
wchar_t *wmemset(wchar_t *s, wchar_t c, size_t n);
size_t wcsftime(wchar_t *s, size_t maxsize, const wchar_t *format, const struct tm *timeptr);
size_t wcsfxtime(wchar_t *s, size_t maxsize, const wchar_t *format, const struct tmx *timeptr);
l 转换函数:
wint_t btowc(int c);
int wctob(wint_t c);
int mbsinit(const mbstate_t *ps);
size_t mbrlen(const char * s, size_t n, mbstate_t * ps);
size_t mbrtowc(wchar_t * pwc, const char * s, size_t n, mbstate_t * ps);
size_t wcrtomb(char * s, wchar_t wc, mbstate_t * ps);
size_t mbsrtowcs(wchar_t * dst, const char ** src, size_t len, mbstate_t * ps);
size_t wcsrtombs(char * dst, const wchar_t ** src, size_t len, mbstate_t * ps);
n wctype.h:提供与ctype.h类似的宽字符函数。(也参见wchar.doc,该C库VC05也支持)
u 类型:
l wint_t:可保存扩展字符值的一种整数类型,VC05中定义为unsigned short(2B)
l wctrans_t:表示特定字符映射的一种标量类型,VC05中定义为wchar_t(2B)
l wctype_t:表示字符分类的一种标量类型,VC05中也定义为unsigned short(2B)
u 符号常量宏:
l WEOF:宽字符输入的文件结尾,VC05中定义为(wint_t)(0xFFFF) [65535]
u 函数:
l 分类函数:
int iswalnum(wint_t wc);
int iswalpha(wint_t wc);
int iswcntrl(wint_t wc);
int iswdigit(wint_t wc);
int iswgraph(wint_t wc);
int iswlower(wint_t wc);
int iswprint(wint_t wc);
int iswpunct(wint_t wc);
int iswspace(wint_t wc);
int iswupper(wint_t wc);
int iswxdigit(wint_t wc);
l 可扩展分类函数:
int iswctype(wint_t wc, wctype_t desc);
wctype_t wctype(const char *property);
l 转换函数:
wint_t towlower(wint_t wc);
wint_t towupper(wint_t wc);
wint_t towctrans(wint_t wc, wctrans_t desc);
wctrans_t wctrans(const char *property);
(4)数学改进
除了上面以及讲过的新增关键字_Complex(复数)和_Imaginary(虚数)、float和long double版的数学函数、以及复数运算的标准库外。C99对数学库也进行了许多修改,增加了两个数据类型,并添加了大量新的数学函数。
l math.h:除了上面这些新增加的库和头文件外,为了支持IEC 60559:1989(Binary floating-point arithmetic for microprocessor systems微处理器系统的二进制浮点运算)浮点运算标准,C99还对数学库进行了如下扩展:
n float_t和double_t类型:是C99新增加的分别进行float和double运算最快的的类型。(位数:double_t≥float_t、float_t≥float、double_t≥double)
n float和long double版函数:原来的很多数学函数只有double版,C99新增加了float版和long double版的数学函数。做法是:改变输入参数和返回值,并在函数名的尾部,增加一个字符f或l,来分别表示float或long double类型(没有尾部字符的为原来的double型)。例如:
double cos (double x);
float cosf (float x);
long double cosl (long double x);
n 新符号常量:
C99新增加的数学符号常量
宏 | 说明 |
HUGE_VALF | 最大的float值 |
HUGE_VALL | 最大的long double值 |
INFINITY | 无穷大 |
NAN | 非数 |
FP_INFINITE | 浮点无穷大分类号 |
FP_NAN | 非浮点数分类号 |
FP_NORMAL | 正常浮点数分类号 |
FP_SUBNORMAL | 精度降低的浮点数分类号 |
FP_ZERO | 浮点数0的分类号 |
FP_FAST_FMA | 速度≥double操作数的乘法和加法的函数fma |
FP_FAST_FMAF | 速度≥float操作数的乘法和加法的函数fmaf |
FP_FAST_FMAL | 速度≥long double操作数的乘法和加法的函数fmal |
FP_ILOGB0 | 表示ilogb(0)函数返回值的整数常理表达式 |
FP_ILOGBNAN | 表示ilogb(NAN)函数返回值的整数常理表达式 |
MATH_ERRNO | = 1 |
MATH_ERREXCEPT | = 2 |
MATH_ERRHANDLING | = MATH_ERRNO、MATH_ERREXCEPT或 MATH_ERRNO | MATH_ERREXCEPT |
n 新函数:
C99新增加的数学函数
(float和long double版数学函数未写出)
原型 | 说明 |
int fpclassify(real-floating x); | 返回x的浮点数分类值的宏 |
int isfinite(real-floating x); | x有穷时返回非0值的宏 |
int isinf(real-floating x); | x无穷时返回非0值的宏 |
int isnan(real-floating x); | x非数时返回非0值的宏 |
int isnormal(real-floating x); | x为正常数时返回非0值的宏 |
int signbit(real-floating x); | x为负数时返回非0值的宏 |
double exp2(double x); | 2x |
double expm1(double x); | ex-1 |
int ilogb(double x); | 返回x(以FLT_RADIX为基数)的指数int值 |
double log1p(double x); | log(1+x) |
double log2(double x); | log2x |
double logb(double x); | 返回x(以FLT_RADIX为基数)的指数 |
double scalbn(double x, int n); | 返回x*FLT_RADIXn |
double scalbln(double x, long n); | 返回x*FLT_RADIXn |
double cbrt(double x); |
|
double hypot(double x, double y); |
|
double erf(double x); | 返回x的误差函数 |
double erfc(double x); | 返回x的补余误差函数 |
double lgamma(double x); | ln|Γ(x)| |
double tgamma(double x); | Γ(x) |
double nearbyint(double x); | (使用浮点环境指定的舍入方向)将x舍入到最近的整数(double值) |
double rint(double x); | 似nearbyint,但是抛出inexact异常 |
long lrint(double x); | (使用浮点环境指定的舍入方向)将x舍入到最近的整数(long值) |
long long llrint(double x); | (使用浮点环境指定的舍入方向)将x舍入到最近的整数(long long值) |
double round(double x); | 将x舍入到最近的整数(四舍五入)(double值) |
long lround(double x); | 将x舍入到最近的整数(四舍五入)(long值) |
long long llround(double x); | 将x舍入到最近的整数(四舍五入)(long long值) |
double trunc(double x); |
|
double remainder(double x, double y); | 返回x除以y的余数 |
double remquo(double x, double y, int *quo); | 返回值同remainder,将x/y取整后放入quo |
double copysign(double x, double y); | sign(x)*|y| |
double nan(const char *tagp); | 返回NaN类型的double表示 |
double nextafter(double x, double y); | y=x时返回x、y>x时返回比x大的下一个double值、y<x时返回比x小的下一个double值 |
double fdim(double x, double y); | |x-y| |
double fmax(double x, double y); | max(x, y) |
double fmin(double x, double y); | min(x, y) |
double fma(double x, double y, double z); | (x*y)+z |
int isgreater(real-floating x, real-floating y); | 返回x>y的宏 |
int isgreaterequal(real-floating x, real-floating y); | 返回x>=y的宏 |
int isless(real-floating x, real-floating y); | 返回x<y的宏 |
int islessequal(real-floating x, real-floating y); | 返回x<=y的宏 |
int islessgreater(real-floating x, real-floating y); | 返回x<y || x>y的宏 |
int isunordered(real-floating x, real-floating y); | x和y中至少一个为NaN时返回1,否则0 |
注意:该库VC05也不支持。但是VC05支持原数学函数的float和long double版。
(5)扩展字符支持
除了上面讲过的wcahr_t类型定义和新增加的两个宽字符标准库(<wchar.h>、<wctype.h>)外,C99中还有其他对扩展字符支持方面。
l 其它扩展字符支持:因为C语言最初并不是为国际字符集设计的,所以除了上面的wchar.h和wctype.h库外,还有如下一些扩展字符支持的方面:
n 三元字符(trigraph):考虑到有些键盘并不能提供C中所使用的全部符号,早在C89中就已经定义了一些三元字符序列,作为这些符号的可选表示:(为C语言的保留字,但不是关键字,VC05支持)
三元字符 | ??= | ??( | ??/ | ??) | ??’ | ??< | ??! | ??> | ??- |
符号 | # | [ | / | ] | ^ | { | | | } | ~ |
三元字符在任何地方都是同样的含义,即使在字符串中也是如此。
n 二元字符(digraph):由于三元符号太笨拙,C99新增加了二元字符:(也为C语言的保留字,但不是关键字,不过VC05不支持)
二元字符 | <: | :> | <% | %> | %: |
符号 | [ | ] | { | } | # |
与三元字符不同,二元字符在字符串中没有特别意义(视作原来的两个字符)。
n UCN(Universal-Character-Name通用字符名):C99允许使用ISO/IEC 10646(Unicode)国际字符集中的字符(包括汉字)作为标识符的一部分,但是扩展字符中的数字字符也不能作为标识符的起始字符。可以使用/u或/U开头的4个(16位)或8个(32位)十六进制数字(/uhhhh、/UHHHH、/uhhhhhhhh、/UHHHHHHHH)来表示扩展字符。(VC05支持[作为C++标准的一部分])。例如:
wchar_t buf[80];
wchar_t ch1 = '变', ch2 = L'变'; // ch1为多字节字符,ch2为宽字符
int 变量1 = 25, /u53D8/u91CF2 = 30; // 如果变量名后不加1和2,
// 则报变量重名的错误
变量1 *= /u53D8/u91CF2;
swprintf(buf, 80, L"%lc = 0x%X = 0x%X, %lc = 0x%X = 0x%X",
ch2, ch1, ch2, L'量', '量', L'量');
pDC->TextOut(10, 10, buf);
swprintf(buf, 80, L"变量1 = %d, /u53D8/u91CF2 = %d",
变量1, /u53D8/u91CF2);
pDC->TextOut(10, 40, buf);
输出结果为:(“变量”的GB码为B1E4 C1BF、Unicode码为53D8 91CF)
变=0xB1E4=0x53D8, 量=0xC1BF=91CF
变量1=750, 变量2=30
n 前缀与修识符:宽字符常量和宽字符串常量,可以用L前缀来表示。在格式化I/O函数中,可以用%lc和%ls修识符来显示宽字符及其串数据
6)C和C++的差别
虽然C++是C的超集,但是它们之间还是存在细小的差别,特别是C99增加的新特性中,有许多当前的C++标准还不支持。下面列出一些C和C++的差别:
l 函数原型
在C++中是必须的,在C中(虽然被极力推荐,但仍然)是可选的。例如:
int myfun(); // 在C中被认为是前向声明
// 在C++中等价于函数原型:int myfun(void);
int main() {
……
myfun(20,45); // 在C++会报错(函数不是两个输入参数)
……
}
int myfun(int a, int b) {……} // 在C++被认为是另一个函数
l 强制转换
在C中,自动转换似乎可以无处不在。例如,可以不经过强制转换,直接将void *型的变量或表达式赋值给其他指针类型的变量。但是这样做在C++中确是不被允许的,而必须使用显式的强制转换。这是因为,太任意的自动类型转换,会造成很多很难查出的程序问题。所以,C++的新规定,是出于程序安全和故障排查的考虑而制定的。
l char常量
在C中char常量被处理为int类型(4B),而在C++则被处理为char类型(1B)。例如:
char ch = ‘A’;
在C中常量A被存储为一个int值,但变量ch则只占1个字节;而在C++中常量A和变量ch都只占1个字节。又例如:
int x = ‘ABCD’;
在C中是可以的(变量x的值为1094861636),在C++中则是非法的(但在VC05中可以)。而
char c = ‘ABCD’;
在C和C++中都是可以的,不过会发出截断警告(变量c的值为D)
l const修识符
n 在缺省情况下,全局的常型变量,即在函数体外部声明的,例如:
const double PI = 3.14159;
在C中具有外部链接(只能在一个文件中定义,在其它文件中,必须用extern进行引用声明后,才能被使用);在C++中则具有内部链接(可以定义在头文件中定义,使每个包含该头文件的代码文件,都有该变量的一个拷贝)。
n 可以在C中,通过将全局的常型变量声明为静态的,使其变成内部链接的:
static const double PI = 3.14159;
也可以在C++中,使用extern关键字使全局的常型变量变成外部的。
n 在C++中常型变量可以用来声明数组的大小,但是在C中不行。例如:
const int SIZE = 100;
double a[SIZE]; // 在C++中等价于double a[100];
// 在C中则创建了一个变长数组,还需要给它分配空间
n 在C++中可以使用一个已经初始化了的常型变量给另一个常型变量赋值,但在C中这样做是非法的。例如:
const double RATE = 0.5;
const double STEP = 24.0;
const double LEVEL = RATE * STEP; // 在C++中合法,在C中非法
l 结构和联合
n 声明了一个带有标记(tag)的结构或联合之后,在C++可以直接用此标记作为类型名,但是在C中则不行(必须前面带有struct关键字才可以)。例如:
struct point {
int x;
int y;
};
struct point p1; // 在C和C++中都可以
point p2; // 在C++中可以,但在C中不行
n 嵌套的结构在C中可以直接引用,在C++中则必须采用定位符。例如:
struct box {
struct point {int x; int y;} ul;
struct point lr;
};
struct box rect; // 在C和C++中都可以
struct point dot1; // 在C中可以,但在C++中不行
box::point dot2; // 在C++中可以,但在C中不行
l 枚举
n 与结构类似,在C++允许声明枚举变量时省略enum关键字,但是C不行。例如:
enum season {spring, summer, autumn, winter};
season ssn = spring; // 在C中非法,在C++中合法
n 在枚举的使用中,C++比C更严格。在C++中,不允许把整数值赋给枚举变量,也不能递增一个枚举变量。例如:
enum season {spring, summer, autumn, winter};
enum season ssn;
ssn = spring; // 在C和C++中都可以
ssn = 2; // 在C中会发出警告,但在C++中是一个错误
ssn = (enum season)1; // 在C和C++中都可以
ssn++; // 在C中可以,但在C++中是一个错误
l 指向void的指针
在C++中不能把指向void的指针,不经显示转换,而直接赋給其它类型的指针。但是在C中则可以。例如:
int a[4] = {1, 2, 3, 4};
int *pi;
void *pv;
pv = a; // 在C和C++中都可以
pi = pv; // 在C中可以,但在C++中非法
pi = (int *)pv; // 在C和C++中都可以
l 布尔类型:C++中的布尔类型bool和值true与false都是关键字。在C99中_Bool类型是关键字,但是bool和true与false却是在stdbool.h中定义的typedef类型和符号常量宏。
l 可选的拼写:and、or和not_eq等在C++中都是关键字;但在C99中是在iso646.h头文件中定义的宏。
l 宽字符类型:wchar_t在C++是关键字;但在C99中是在wchar.h等头文件中定义的typedef类型。
l 复数类型:在C99中可以通过关键字_Complex和_Imaginary来使用内置的复数类型。但是C++中是通过头文件complex中的模版类来使用复数类型的。(在C中也可以通过complex.h来使用各种复函数)
l 内联函数:与全局常型变量类似,C99的内联函数默认是外部链接的,而C++的内联函数则默认是内部链接的。在C中还允许混合使用函数的内联和外联,但是在C++中是不允许的。
l 标准C++中目前所没有的C99特性:
n 指定初始化条目;
n 复合初始化条目;
n 受限指针;
n 变长数组;
n 弹性数组成员;
n long long和unsigned long long类型;(VC05支持)
n 可移植的整数类型;
n 通用字符名;(VC05支持)
n 附加的数学库函数;
n 通过fenv.h访问浮点环境;
n 若干预定义的标识符,如__func__;
n 具有可变数目的参数宏。
7)参考文献
l Stephen Prata(云巅工作室译). C Primer Plus(第五版)中文版. 人民邮电出版社,2005. 16开/626页/60元。
l ISO/IEC 9899:1999 Programming languages – C(C99.pdf)
l ISO/IEC 9899:TC2(2005.5.6)Programming languages – C(Committee Draft)(ISO C CD 2005-5-6 N1124.pdf)