C语言
来自ITwiki,开放的信息技术大百科
C,是一种通用的程序设计语言,它主要用来进行系统程序设计。具有高效、灵活、功能丰富、表达力强和移植性好等的特点,在程序员中备受青睐。
C语言是由UNIX的研制者丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)于1970年研制出的B语言的基础上发展和完善起来的。C语言可以广泛应用于不同的操作系统,例如UNIX、MS-DOS、Microsoft Windows及Linux等。C语言是一种面向过程的语言,同时具有高级语言和汇编语言的优点。在C语言的基础上发展起来的有支持多种程序设计风格的C++语言,网络上广泛使用的Java、JavaScript,微软的C#等。
1983年,美国国家标准委员会(ANSI) 对C语言进行了标准化,于1983年颁布了第一个C语言标准草案(83 ANSI C),后来于1987年又颁布了另一个C语言标准草案(87 ANSI C)。最新的C语言标准是在1999年颁布并在2000年3月被ANSI采用的 C99 ,但由于未得到主流编译器厂家的支持,直到2004年C99 并未被广泛使用,增加了若干新特性后 C99 已经逐渐让C语言和C++分道扬镳。
目录[隐藏] |
C语言的特色
- C语言是系统程序语言。流行的操作系统核心部分几乎无一例外的选择了C语言。
- C语言保留了低级语言的特性,例如C语言允许使用指针来对任意内存做直接修改。
- C语言使用了预处理机制,使得程序员可以在编译源程序之前对程序结构做出控制。常用的预处理机制包括宏与编译选择等。
C语言的主要不足是缺乏对真实事物的描述手段。用C语言表达复杂的逻辑系统将 会十分困难,而且代码量也明显高于C++语言。同时C语言过度的信赖程序员的做法也一直存在很大争议,是否允许程序员随意修改内存中的任何东西一直都是争 论的焦点所在。C语言的不足可以由C语言发展而来的新的编程语言所改进。Cyclone语言拥有提防内存错误的特性。C++和Objective C提供了用于面向对象的编程结构。Java和C#在提供面向对象编程结构的同时,也提供了垃圾收集机制,这使得对内存的管理更加透明与自动化。
C語言的主要特性
- C語言保留了低階語言的特性,例如涉及記憶體的指針。
- C語言通過參數在函數裏傳遞數值。
- 使用了預處理機制,使得程式裏可以通過包含例如巨集處理的方式來處理根源程式。
- C語言提供了一套標準庫,這些庫裏提供了十分有用的功能。
但是並不是所有的這些特性都是有效的。例如,預處理通常作為一個獨立的程式被處理,這使得預處理的程式並不一定被完全編譯。
雖然C是高階語言,但是它同時擁有一些組合語言的特性,對其他的語言來說這是接近低階語言的特點。例如,在C語言裏,程式師可以對電腦記憶 體進行管理。在默認的情況下,C語言不會對陣列的範圍進行檢查,也就是說即使陣列越界,C語言也不會作出錯誤提示。對電腦記憶體的管理使得程式員可以編出 更快捷、更有效的程式,這對於設備驅動程式來說尤為重要。但是這也使得程式容易產生令人討厭的「臭蟲」,例如緩衝器溢出錯誤。然而,這些錯誤可以由一些工具來避免。
C語言的不足可以由從C語言發展而來的更新的編程語言改進。Cyclone語言的擁有提防對於記憶體錯誤的特性。C++和Objective C提供了用於面向物件的編程結構。Java和C#增加了面向物件的結構使得對記憶體的管理自動化。
C語言的歷史
C語言的第一次發展在1969年到1973年之間。C之所以被稱為C是因為C語言的很多特性是由一種更早的被稱為B語言的編程語言中發展而來的。
到了1973年,C語言已經可以用來編寫Unix操作系統的內核。這是第一次用C語言來編寫操作系統的內核。丹尼斯·裡奇和Brian Kernighan在1978年出版了《C程序設計語言》(The C Programming Language,經常簡稱為「白皮書」或「K&R」)。
1980年以後,貝爾實驗室使得C變得更為廣泛的流行,使得C一度成為了操作系統和應用程序編程的首選。甚至到今天,它仍被廣泛用於編寫操作系統以及作為廣泛的計算機教育的語言。2005年4月,C++之父稱C++用戶超過300萬。
1980年代晚期,布賈尼·斯特勞斯特盧普和貝爾實驗室為C語言添加了面向對象的特性.這種語言成為了C++。C++現在廣泛應用的在Microsoft Windows下運行的商業應用程序的編制,然而C仍然是UNIX世界的熱門編程語言。
C语言的版本
K&R C
C不断的从它的第一版本进行改进。在1978年,Kernighan和里奇的《C程序设计语言》第一版出版。它介绍了下面的有关C语言版本的特性:
- struct数据类型
- long int数据类型
- unsigned int数据类型
- 把运算符=+改为+=,依次类推。因为=+使得编译器混淆。
在以后的几年里,《C程序设计语言》一直被广泛作为C语言事实上的规范。在这本书中,C语言通常被表述成“K&R C”。(第二版的包括了ANSI C标准)
K&R C通常被作为C编译器所支持的最基本的C语言部分。虽然现在的编译器并不一定都完全遵循ANSI标准,但K&R C作为C语言的最低要求仍然要编程人员掌握。但是无论怎样,现在使用广泛的C语言版本都已经与K&R C相距甚远了,因为这些编译器都使用ANSI C标准。 //....
ANSI C和ISO C(1985年)
1989年,C语言被ANSI标准化。(ANSI X3.159-1989)。标准化的一个目的是扩展K&R C。这个标准包括了一些新的特性。在K&R出版后,一些新的特征被“非官方”的加到C语言中。
- void函数
- 函数返回struct或union类型
- void *数据类型
在ANSI标准化自己的过程中,一些新的特征被加了进去。ANSI也规定了一套标准函数库。ANSI ISO(国际标准化组织)成立 ISO/IEC JTC1/SC22/WG14工作组来规定国际标准的C语言。通过对ANSI标准的少量修改,最终通过了ISO 9899:1990。随后ISO标准被ANSI采纳。
传统C语言到ANSI/ISO标准C语言的改进包括:
- 增加了真正的标准库
- 新的预处理命令与特性
- 函数原型允许在函数申明中指定参数类型
- 一些新的关键字,包括const、volatile与signed
- 宽字符、宽字符串与字节多字符
- 对约定规则、声明和类型检查的许多小改动与澄清
ANSI C和ISO C(1995年)
作为对标准的维护与更新,WG14工作小组在1995年对1985年颁布的标准做了两处技术修订(缺陷修复)和一个补充(扩展)。下面是1995年做出的所有修改:
- 3个新的标准库头文件 iso646.h、wctype.h和wchar.h
- 几个新的记号与预定义宏,用于对国际化提供更好的支持
- printf/sprintf函数一系列新的格式代码
- 大量函数和一些类型与常量,用于多字节字符和宽字节字符
C99
在ANSI标准化后,WG14小组继续致力于改进C语言。新的标准很快推出,就是ISO9899:1999(1999年出版)。这个版本就是通常提及的C99。它被ANSI于2000年三月采用。
在C99中包括的特性有:
- 对编译器限制增加了,比如源程序每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节 (extern 要求支持到 31)
- 预处理增强了。例如:
- 增加了新关键字 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 的规定
- 允许 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 做了扩展
- 输入输出对宽字符还有长整数等做了相应的支持
但是各个公司对C99的支持所表现出来的兴趣不同。当GCC和其它一些商业编译器支持C99的大部分特性的时候,微软和Borland却似乎对此不感兴趣。
C语言的Hello World程序
下面是一个在标准输出设备上输出Hello World的简单程序,这种程序通常作为开始学习编程语言时的第一个程序:
#include <stdio.h>
int main(void)
{
printf("Hello, world!/n");
return 0;
}
进一步了解C
C语言由函数和变量组成。C的函数就像是Fortran中的子程序和函数。
在C语言中,程序从main开始执行。main函数通过调用和控制其他函数进行工作。例如上面的printf。程序员可以自己写函数,或从库中调用函数。在上面的return 0;使得main返回一个值给调用程序的外壳,表明程序已经成功运行。
一个C语言的函数由返回值、函数名、参数列表(或void表示没有返回值)和函数体组成。函数体的语法和其它的复合的语句部分是一样的。
复合语句
C语言中的复合语句(或称语句块)的格式为:
{语句;语句;……}
复合语句可以使得几个语句变成一个语句。
但一般情况下,我们不推荐这样多个语句顺序书写, 因为这样会使其可读性减弱,加大代码维护难度。
条件语句
C语言有三种条件语句形式。两种是if,另一种是switch。
两种if包括:
if (条件表达式)
语句;
以及
if (条件表达式)
语句;
else
语句;
在条件表达式中,任何非零的值表示条件为真;如果条件不满足,程序将跳过if后面的语句,直接执行if后面的语句。但是如果if后面有else,则当条件不成立时,程序跳到else处执行。
switch通常用于对几种有明确值的条件进行控制。它要求的条件值通常是整数或字符。与switch搭配的条件转移是case。使用case后面的标值,控制程序将跳到满足条件的case处一直往下执行,直到语句结束或遇到break。通常可以使用default把其它例外的情况包含进去。如果switch语句中的条件不成立,控制程序将跳到default处执行。switch是可以嵌套的。
switch (<表达式>) {
case <值1> :
<语句>
case <值2> :
<语句>
default :
<语句>
}
循环语句
C语言有三种形式的循环语句:
do
<语句>
while (<表达式>);
while (<表达式>)
<语句>;
for (<表达式1> ; <表达式2> ; <表达式3>)
<语句>;
在while和do中,语句将执行到表达式的值为零时结束。在do...while语句中,循环体将至少被执行一次。这三种循环结构可以互相转化:
for (e1; e2; e3)
s;
相当于
e1;
while (e2) {
s;
e3;
}
当循环条件一直为真时,将产生死循环。
跳转语句
跳转语句包括四种:goto,continue,break和return。
goto语句是无条件转移语句:
goto 标记
标记必须在当前函数中定义,使用“标记:”的格式定义。程序将跳到标记处继续执行。由于goto容易产生阅读上的困难,所以应该尽量少用。
continue语句用在循环语句中,作用是结束当前一轮的循环,马上开始下一轮循环。
break语句用在循环语句或switch中,作用是结束当前循环,跳到循环体外继续执行。但是使用break只能跳出一层循环。在要跳出多重循环时,可以使用goto使得程序更为简洁。
当一个函数执行结束后要返回一个值时,使用return。return可以跟一个表达式或变量。如果return后面没有值,将执行不返回值。
在C99中运算符号
+ -() [] -> . ! ++ -- (cast) | 括号、成员、逻辑非、自加、自减、强制转换 |
++ -- * & ~ ! + - sizeof | 单目运算符 |
* / % | 算术运算符 |
算术运算符 | |
<< >> | 位运算符 |
< <= > >= | 关系运算符 |
== != | 关系运算符号 |
& | 位与 |
^ | 位异或 |
| | 位或 |
&& | 逻辑与 |
|| | 逻辑或 |
?: | 条件运算符 |
= += -= *= /= %= <<= >>= &= |= ^= | 赋值运算符 |
, | 顺序运算符 |
数据类型
基础数据类型
注意:以下是典型的数据位长和范围。但是编译器可能使用不同的数据位长和范围。这取决于使用的编译器。请参考具体的参考手册。
在头文件<limits.h>和<float.h>中说明了基础数据的长度。float,double和long double的范围就是在IEEE 754标准中提及的典型数据。
关键字 | 位长 | 范围 |
char | 1 | -128..127 or 0..255 |
unsigned char | 1 | 0..255 |
signed char | 1 | -128..127 |
int | 2 or 4 | -32768..32767 or -2147483648..2147483647 |
short int | 2 | -32768..32767 |
long int | 4 | -2147483648..2147483647 |
float | 4 | 3.4x10-38E..3.4x10+38E |
double | 8 | 1.7x10-308E..1.7x10+308E |
long double | 12 | ??? |
数组
如果一个变量名后面跟着一个有数字的中括号,这个声明就是数组声明。字符串也是一种数组。它们以ASCII的NUL作为数组的结束。
例如:
- int myvector [100];
- char mystring [80];
- float mymatrix [3] [2] = {2.0 , 10.0, 20.0, 123.0, 1.0, 1.0}
- int notfull [3][3] = {{1},{1,2,3},{4,5}} (*)
- char lexicon [10000] [300] ; /* 共一万个最大长度为300的字符数组。*/
- int a[3][4];
上面最后一个例子创建了一个数组,但也可以把它看成是一个多维数组。注意数组的下标从0开始。这个数组的结构如下:
a[0][0] | a[0][1] | a[0][2] | a[0][3] |
a[1][0] | a[1][1] | a[1][2] | a[1][3] |
a[2][0] | a[2][1] | a[2][2] | a[2][3] |
例子(*)创建了一个3*3的二维数组,初始化时有些元素并未赋值.如下:
- 1 0 0
- 1 2 3
-
4 5 0
为0的位置的数值是随机的.
指针
如果一个变量声明时在前面使用*号,表明这个变量是一个指针。换句话说,该变量是一个地址,而*则是取内容操作符,意思是取这个内存地址里存储的内 容。指针是C语言区别于其他同时代高级语言的主要特征之一。指针是一把双刃剑,许多操作可以通过指针自然的表达,但是不正确的或者过分的使用指针又会给程 序带来大量潜在的错误。
例如:
- int *pi; /* 指向整型数据的指针 */
- int *api[3]; /* 由指向整型数据的指针构成的数组,长度为3 */
- char **argv; /* 指向一个字符指针的指针 */
储存在指针中的地址所指向的数值在程序中可以由*读取。例如,在第一个例子中,*pi是一个整型数据。这叫做引用一个指针。
另一个运算符&,叫做取地址运算符,它将返回一个变量、数组或函数的存储地址。因此,下面的例子:
- int i, *pi; /* int and pointer to int */
- pi = &i;
i和*pi在程序中可以相互交替使用,直到pi被改变成指向另一个变量的指针。
字符串
要使用字符串并不需要引用库,但是C标准库确实包含了一些用于对字符串进行操作的函数,使得它们看起来就像字符串而不是数组。使用这些函数需要引用头文件<string.h>。
- strcat(dest, source) - 连接两个字符串,把source加到dest末尾。
- strchr(s, c) -在字符串c中找出字符s第一次出现的位置。当没有找到时,返回Null。
- strcmp(a, b) - 比较字符串a和b的大小。如果两个字符串相同,返回0;如果a>b,返回正数;如果a<b,返回负数。
- <- 把source中的n个字符追加到dest后面。null后面的值将不会被添加。
- strncmp(a, b, n) - 比较字符串a和b中n个字符的大小。如果两个字符串相同,返回0;如果a>b,返回正数;如果a<b,返回负数。
- strncpy(dest, source, n) - 把字符串source拷贝到字符串dest中。(最多拷贝n个)
- strrchr(s, c) - 在s中查找最后一次出现c的位置。返回这个位置。如果找不到,返回null。
文件输入/输出
在C语言中,输入和输出是经由标准库中的一组函数来实现的。在ANSI/ISO C中,这些函数被定义在头文件<stdio.h>中。
标准输入/输出
有三个标准输入/输出是预定义的:
- stdin 标准输入
- stdout 标准输出
- stderr 输入输出错误
这些定义在运行过程中是自动的打开和关闭的,所以它们并不需要进行显式定义。
下面的这个例子显示了一个过滤程序(filter program)是怎样构成的。
#include <stdio.h>
int main(){
int c;
while (( c = getchar()) != EOF ) {
if (anErrorOccurs) {
fputs("an error eee occurred/n", stderr);
break;
}
putchar(c);
}
}
传递命令行参数
在命令行赋予程序的参数将通过在main()函数中定义一个整型参数(int)和一个指向字符指针的指针型参数(char **)来实现,前者传递命令行参数的个数,后者传递命令行参数内容。习惯上将这两个参数分别命名为argc和argv。程序文件名被作为第一个命令行参数。
对于下列程序:
#include <stdio.h>
int main(int argc, char **argv) {
int i;
for(i=0;i<argc;i++)
printf("%d:%s/n",i,argv[i]);
return 0;
}
输入命令(假设该程序生成C:/TC/a.exe): a one two three
则会得到下面的输出结果:
0:C:/TC/A.EXE
1:one
2:two
3:three
C语言的标准库
以下列出由C语言提供的标准函数库,函数库通过#include进行引用。
在C89标准中:
- <assert.h>
- <ctype.h>
- <errno.h>
- <float.h>
- <limits.h>
- <locale.h>
- <math.h>
- <setjmp.h>
- <signal.h>
- <stdarg.h>
- <stddef.h>
- <stdio.h>
- <stdlib.h>
- <string.h>
- <time.h>
在95年的修正版中
- <iso646.h>
- <wchar.h>
- <wctype.h>
在C99中增加了六个函数库
- <complex.h>
- <fenv.h>
- <inttypes.h>
- <stdbool.h>
- <stdint.h>
- <tgmath.h>
C语言的保留关键字
char | short | int | unsigned |
long | float | double | struct |
union | void | enum | signed |
const | volatile | typedef | auto |
register | static | extern | break |
case | continue | default | do |
else | for | goto | if |
return | switch | while | sizeof |