C语言概述

过程式
发行时间
1972 年
设计者
丹尼斯·里奇(Dennis Ritchie)
实现者
丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)
最新发行时间C99 (2000年3月)
主要实现产品
ClangGCCMSVCTurbo CWatcom C
启发语言
B语言汇编语言
影响语言
awk, BitC, csh, C++, C#, Concurrent C, D, Java, JavaScript, Objective-C, Perl, PHP
跨平台

 

C语言,是一种通用的、过程式的编程语言,广泛用于系统应用软件的开发。具有高效、灵活、功能丰富、表达力强和较高的移植性等特点,在程序员中备受青睐。

C语言是由UNIX的研制者丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)于1970年研制出的B语言的基础上发展和完善起来的。目前,C语言编译器普遍存在于各种不同的操作系统中,例如UNIXMS-DOSMicrosoft WindowsLinux等。C语言的设计影响了许多后来的编程语言,例如C++Objective-CJavaC#等。

后来于1980年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局(American National Standard Institution)为C语言订定了一套完整的国际标准语法,称为ANSI C,作为 C语言的标准。1980年代至今的有关程序开发工具,一般都支持符合ANSI C的语法。

 

设计哲学

C语言是一个程序语言,设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言也很适合搭配汇编语言来使用。尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。

特色

  • C语言是一个有结构化程序设计、具有变量作用域(variable scope)以及递归功能的过程式语言。
  • 传递参数是以值传递(Pass-by-value),也可以通过指针来传递参数(Pass-by-address)。
  • 不同的变量类型可以用结构体(struct)组合在一起。
  • 只有32个保留字(reserved keywords),使变量、函数命名有更多弹性。
  • 部份的变量类型可以转换,例如整型和字符型变量。
  • 通过指针(pointer),C语言可以容易的对存储器进行低级控制。
  • 编译预处理(preprocessor)让C语言的编译更具有弹性。

历史

早期发展

C语言的第一次发展在1969年到1973年之间。之所以被称为“C”是因为C语言的很多特性是由一种更早的被称为B语言的编程语言中发展而来。早期操作系统的核心大多由汇编语言组成,随着C语言的发展,C语言已经可以用来编写操作系统的核心。1973 年Unix操作系统核心正式用C语言改写,这是C语言第一次应用在操作系统的核心编写上。

K&R C

1978 年,丹尼斯·里奇(Dennis Ritchie)和Brian Kernighan合作出版了《C程序设计语言》的第一版。书中介绍的C语言标准也被C语言程序员称作“K&R C”,第二版的书中也包含了一些ANSI C的标准。K&R C主要介绍了以下特色:

  • 结构(struct)类型
  • 长整数(long int)类型
  • 无符号整数(unsigned int)类型
  • 把运算符=+=-改为+=-=。因为=+=-会使得编译器不知道用户要处理i = -10还是i =- 10,使得处理上产生混淆。

即使在后来ANSI C标准被提出的许多年后,K&R C仍然是许多编译器的最低标准要求,许多老旧的编译仍然运行K&R C的标准。

ANSI C 和 ISO C

主条目: ANSI C

1989年,C语言被 ANSI 标准化(ANSI X3.159-1989)。标准化的一个目的是扩展K&R C。这个标准包括了一些新特性。在K&R出版后,一些新特性被非官方地加到C语言中。

  • void 函数
  • 函数返回 structunion 类型
  • void * 数据类型

在ANSI标准化自己的过程中,一些新的特性被加了进去。ANSI也规定了一套标准函数库。ANSI ISO国际标准化组织)成立 ISO/IEC JTC1/SC22/WG14 工作组,来规定国际标准的C语言。通过对ANSI标准的少量修改,最终通过了 ISO 9899:1990。随后,ISO标准被 ANSI 采纳。

传统C语言到ANSI/ISO标准C语言的改进包括:

  • 增加了真正的标准库
  • 新的预处理命令与特性
  • 函数原型允许在函数申明中指定参数类型
  • 一些新的关键字,包括 constvolatilesigned
  • 宽字符、宽字符串与字节多字符
  • 对约定规则、声明和类型检查的许多小改动与澄清

WG14工作小组之后又于1995年,对1985年颁布的标准做了两处技术修订(缺陷修复)和一个补充(扩展)。下面是 1995 年做出的所有修改:

  • 3 个新的标准库头文件 iso646.h、wctype.h 和 wchar.h
  • 几个新的记号与预定义宏,用于对国际化提供更好的支持
  • printf/sprintf 函数一系列新的格式代码
  • 大量的函数和一些类型常量,用于多字节字符宽字节字符

C99

在ANSI的标准确立后,C语言的规范在一段时间内没有大的变动,然而C++在自己的标准化创建过程中继续发展壮大。《标准修正案一》在1995年为C语言创建了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广得国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。它通常被称为C99。C99被ANSI于2000年3 月采用。

在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] 的形式。不过考虑到效率和实现,不定长数组不能用在全局,或 structunion 里。
  • 变量声明不必放在语句块的开头,for 语句提倡写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效。
  • 允许采用(type_name){xx,xx,xx} 类似于 C++ 的构造函数的形式构造匿名的结构体。
  • 初始化结构的时候允许对特定的元素赋值,形式为:
    struct {int a[3],b;} foo[] =  { [0].a = {1}, [1].a = 2 };
    struct {int a, b, c, d;} foo =  { .a = 1, .c = 3, 4, .b = 5}  // 3,4 是对 .c,.d 赋值的
  • 格式化字符串中,利用 /u 支持 unicode 的字符。
  • 支持 16 进制的浮点数的描述。
  • printf scanf 的格式化串增加了对 long long int 类型的支持。
  • 浮点数的内部数据描述支持了新标准,可以使用 #pragma 编译器指令指定。
  • 除了已有的 __line__ __file__ 以外,增加了 __func__</tt> 得到当前的函数名。
  • 允许编译器化简非常数的表达式。
  • 修改了 / % 处理负数时的定义,例如C89中定义 -22 / 7 = -3, -22 % 7 = -1, 而C99中为 -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的大部分特性的时候[1]微软Borland却似乎对此不感兴趣。

语法

Hello World 程序

下面是一个在标准输出设备 (stdout) 上,印出 "Hello World" 字符串的简单程序,这样子的程序,通常作为初学编程语言时的第一个程序:

#include <stdio.h>

int main(int argc, const char * argv[])
{
printf("Hello, world!/n");
return 0;
}

进一步了解

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处运行。ifelse后面的语句可以是另个if语句,这种套叠式的结构,允许更复杂的逻辑控制流程得以实现。在一般情况下,else一定与最接近的if成对,必要时可用括号{}越过此限制。比较下面两种情况:

if (運算式)
if (運算式)
語句;
else
語句;
if (運算式) {
if (運算式)
語句;
}
else
語句;

switch通常用于对几种有明确值的条件进行控制。它要求的条件值通常是整数或字符。与switch搭配的条件转移是case。使用case后面的标值,控制程序将跳到满足条件的case处一直往下运行,直到语句退出或遇到break。通常可以使用default把其他例外的情况包含进去。如果switch语句中的条件不成立,控制程序将跳到default处运行;如果省略default子句,则直接运行下一语句。switch是可以嵌套的。

switch (<運算式>) {
case <值1>:
<語句>
break;
case <值2>:
<語句>
default:
<語句>
}

循环语句

C语言有三种形式的循环语句:

do 
<語句>
while (<表達式>);

while (<表達式>)
<語句>;

for (<表達式1> ; <表達式2> ; <表達式3>)
<語句>;

whiledo中,语句将运行到表达式的值为零时结束。在do...while语句中,循环将至少被运行一次。这三种循环结构可以互相转化:

for (<表達式1>; <表達式2>; <表達式3>)
<語句>;

如果<语句>中不使用continue语句的话,相当于

<表達式1>;
while (<表達式2>) {
<語句>;
<表達式3>;
}

当循环条件一直为真时,将产生死循环。

跳转语句

跳转语句包括四种:goto,continue,break和return

goto语句是无条件转移语句:

goto 標記;

标记必须在当前函数中定义,使用“标记:”的格式定义。程序将跳到标记处继续运行。由于goto容易产生阅读上的困难,所以应该尽量少用。

continue语句用在循环语句中,作用是退出当前一轮的循环,马上开始下一轮循环。

break语句用在循环语句或switch中,作用是退出当前循环,跳到循环体外继续运行。但是使用break只能跳出一层循环。在要跳出多重循环时,可以使用goto使得程序更为简洁。

当一个函数运行退出后要返回一个值时,使用returnreturn可以跟一个表达式或变量。如果return后面没有值,将运行不返回值。

在C语言中的运算符号

()、 []、 -> 、 .、 !、 ++、 -- 、(cast) 括号、指针、成员、逻辑非、自加、自减、强制转换
++ 、 -- 、 * 、 & 、 ~ 、 ! 、 + 、 - 、 sizeof 单目运算符
* 、 / 、 %算术运算符
+ 、 - 算术运算符
<< 、 >>位运算符
< 、 <= 、 > 、 >=关系运算符
== 、 !=关系运算符号
&位与
^位异或
|位或
&&逻辑与
||逻辑或
 ? 、 :条件运算符
= 、 += 、 -= 、 *= 、 /= 、 %= 、 < 、 <= 、 > 、 >= 、 &= 、 |= 、 ^=赋值运算符
,顺序运算符

比较特别的是,位右移(>>)操作符可以是算术(左端补最高有效位)或是逻辑(左端补 0)位移。例如,将 11100011 右移 3 位,算术右移后成为 11111100,逻辑右移则为 00011100。因算术位右移较适于处理带负号整数,所以几乎所有的编译器都是算术位右移。[2]

运算符的优先级从高到底大致是:单目运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、赋值运算符(=)和逗号运算符。

数据类型

基础数据类型

注意:以下是典型的数据位长和范围。但是编译器可能使用不同的数据位长和范围。这取决于使用的编译器。请参考具体的参考手册。

在头文件<limits.h>和<float.h>中说明了基础数据的长度。float,double 和long double的范围就是在IEEE 754标准中提及的典型数据。

关键字位长范围格式化字符串
char1-128..127(或0..255,与体系结构相关) %c
unsigned char10..255
signed char1-128..127
int2(16位系统) 或
4
-32768..32767 或
-2147483648..2147483647
 %i, %d
unsigned int2 或
4
0..65535 或
0..4294967295
 %u
signed int2 或
4
-32768..32767 或
-2147483648..2147483647
 %i, %d
short int2-32768..32767 %hi
unsigned short20..65535 %hu
signed short2-32768..32767
long int4 或
8[3]
-2147483648..2147483647 或
-9223372036854775808..9223372036854775807
 %li, %ld
unsigned long4 或
8
0..4294967295 %lu
signed long4 或
8
-2147483648..2147483647 或
-9223372036854775808..9223372036854775807

long long8-9223372036854775808..9223372036854775807 %lli
unsigned long long80..18446744073709551615 %llu
float43.4x10-38..3.4x10+38 (7 sf) %f, %e, %g
double81.7x10-308..1.7x10+308 (15 sf) %f, %e, %g
long double8 或以上编译器相关 %Lf, %Le, %Lg

数组

如果一个变量名后面跟着一个有数字的中括号,这个声明就是数组声明。字符串也是一种数组。它们以ASCII的NUL作为数组的退出。要特别注意的是,方括内的索引值是从0算起的。

例如:

int myvector [100];/* 从myvector[0]至myvector[99]止共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 语言区别于其他同时代高级语言的主要特征之一。

指针不仅可以是变量的地址,还可以是数组、数组元素、函数的地址。通过指针作为形式参数可以在函数的调用过程得到一个以上的返回值(不同于return(z)这样的仅能得到一个返回值。

指针是一把双刃剑,许多操作可以通过指针自然的表达,但是不正确的或者过分的使用指针又会给程序带来大量潜在的错误。

例如:

int *pi;     /* 指向整型数据的指针变量 */
int *api[3]/* 由指向整型数据的指针构成的数组,长度为 3 */
char **argv; /* 指向一个字符指针的指针 */

储存在指针中的地址所指向的数值在程序中可以由 * 读取。例如,在第一个例子中, *pi 是一个整型数据。这叫做引用一个指针。

另一个运算符 &,叫做取地址运算符,它将返回一个变量、数组或函数的存储地址。因此,下面的例子:

int i, *pi; /* int and pointer to int */
pi = &i;

i*pi 在程序中可以相互交替使用,直到 pi 被改变成指向另一个变量的地址。

字符串

C语言的字符串其实就是char型数组,所以使用字符串并不需要引用库。但是C标准库确实包含了一些用于对字符串进行操作的函数,使得它们看起来就像字符串而不是数组。使用这些函数需要引用头文件<string.h>

 

文件输入/输出

在C语言中,输入和输出是经由标准库中的一组函数来实现的。在ANSI/ISO C中,这些函数被定义在头文件<stdio.h>中。

标准输入/输出

有三个标准输入/输出是标准I/O库预先定义的:

  • stdin 标准输入
  • stdout 标准输出
  • stderr 输入输出错误


下面的这个例子显示了一个过滤程序(filter program)是怎样构成的。

#include <stdio.h>

int main(int argc, const char * argv[])
{
int c;
while (1)
{
c = getchar();
if (c==EOF)
{
perror("getchar()");
return -1;
}
putchar(c);
}

return 0;
}

函数

C语言的基本结构单位是函数。系统首先调用main函数(主函数),通过函数的嵌套调用,再调用其他函数。函数可以是系统自带的函数,也可以是用户定义的函数。C语言中,不允许函数的嵌套声明。

内存管理

C语言的特色之一是:程序员必须亲自处理内存的分配细节。

C语言使用栈(Stack)来保存函数返回地址/栈祯基址、完成函数的参数传递和函数局部变量的存储。 如果程序需要在运行的过程中动态分配内存,可以利用(Heap)来实现。

基本上C程序的元素存储在内存的时候有3种分配策略:

  • 静态分配

如果一个变量声明为全局变量或者是函数的静态变量,这个变量的存储将使用静态分配方式。静态分配的内存一般会被编译器放在数据段代码段来存储,具体取决于实现。这样做的前提是,在编译时就必须确定变量的大小。 以 IA32 的 x86 平台及 gcc 编译器为例,全局及静态变量放在数据段的低端;全局及静态常量放在代码段的高端。

  • 自动分配

函数的自动局部变量应该随着函数的返回会自动释放(失效),这个要求在一般的体系中都是利用栈(Stack)来满足的。相比于静态分配,这时候,就不必绝对要求这个变量在编译时就必须确定变量的大小,运行时才决定也不迟,但是C89仍然要求在编译时就要确定,而C99放松了这个限制。但无论是C89还是C99,都不允许一个已经分配的自动变量运行时改变大小。

所以说C函数永远不应该返回一个局部变量的地址

要指出的是,自动分配也属于动态分配,甚至可以用alloca函数来像分配堆(Heap)一样进行分配,而且释放是自动的。

  • 动态分配

还有一种更加特殊的情况,变量的大小在运行时有可能改变,或者虽然单个变量大小不变,变量的数目却有很大弹性,不能静态分配或者自动分配,这时候可以使用(Heap) 来满足要求。ANSI C 定义的堆操作函数是malloc、calloc、realloc和free。

使用(Heap) 内存将带来额外的开销和风险。

安全问题

C语言的特色之一是:语言不负责内存边界检查。

C语言的标准文文件要求了一个平台移植C语言的时候至少要实现的一些功能和封装的集合,称为“标准库”,标准库的声明头部通过预处理器命令#include进行引用。

在C89标准中:

文件简介说明
<assert.h>断言相关
<ctype.h>字符类型判断
<errno.h>标准报错机制
<float.h>浮点运算
<limits.h>各种体系结构限制
<locale.h>本地化接口
<math.h>数学函数
<setjmp.h>跨函数跳转
<signal.h>信号(类似UNIX信号定义,但是差很远)
<stdarg.h>可变参处理
<stddef.h>一些标准宏定义
<stdio.h>标准I/O库
<stdlib.h>标准工具库函数
<string.h>ASCIIZ字符串及任意内存处理函数
<time.h>时间相关

在95年的修正版中

  • <iso646.h>
  • <wchar.h>
  • <wctype.h>

在C99中增加了六个库

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>

以上是C语言的标准,而各个平台各自又对C库函数进行的各种扩充,就浩如烟海了。如POSIX CGNU C等。

保留关键字

charshortintunsigned
longfloatdoublestruct
unionvoidenumsigned
constvolatiletypedefauto
registerstaticexternbreak
casecontinuedefaultdo
elseforgotoif
returnswitchwhilesizeof

C99新增关键字

_Bool_Complex_Imaginaryinlinerestrict

经典错误

1.void main的用法并不是任何标准制定的,是Microsoft制定的。正确的语法是int main。

C//

C//是一种结构化的并行语言,读作C parallel。它基于标准C语言但扩展了为数不多的构造用来表示并行性和进程交互。C//的核心是称为一致区域的构造,它方便了结构化的、确定的、终止的和复合的并行程序的开发。

注释

  1. ^ GCC从3.0版本开始正式支持C99(-std=c99)
  2. ^ 经测试,gcc4.4.3使用C99标准的结果依然是00011100,即逻辑移位。
  3. ^ GCC、Clang等实现中,64位代码的long类型为64位,而MSVC中则维持32位

参考文献

外部链接

参见

转载出自:http://blog.chinaunix.net/u3/118714/showart_2326912.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值