C语言 :通用的过程式编程语言

更新时间:2023-02-09 07:27

C语言是一种通用、面向过程的编程语言,具有高效、简洁、灵活等特点,被广泛应用于各个领域的软件开发和系统编程。C语言的诞生可以追溯到20世纪70年代初期,在贝尔实验室的研究员丹尼斯·里奇(Dennis M. Ritchie)的努力下,C语言逐渐成为了计算机的主要开发语言,并在学术界和工业界得到广泛的应用。

C语言是一种命令式过程语言,支持结构化编程、词法变量作用域和递归,具有静态类型系统。从设计上讲,C语言的设计目标是提供足够高级的抽象和结构,接近底层硬件的控制。这使得C语言非常适合于系统级编程和底层开发,能够直接访问硬件资源,并具有高效的性能。C语言常用于从最大的超级计算机到最小的微控制器和嵌入式系统等各种计算机架构。

C语言是编程语言B的后继语言,最初由里奇于1972年至1973年在贝尔实验室开发,用于构建在Unix上运行的实用程序。在20世纪80年代,C语言逐渐流行起来。时至今日,它已成为使用最广泛的编程语言之一,几乎所有现代计算机架构和操作系统都有C编译器可用。由C语言原设计者合著的《C编程语言》一书,是C语言的首要参考语言规范。20世纪80年代起,由美国国家标准学会(ANSI C)和国际标准化组织(ISO)主要负责对C语言的标准化。

一个遵循标准的C程序,有高可移植性,仅需少量修改,就可在多种计算机平台和操作系统上进行编译。此外,C语言具有简单的语法和丰富的库函数,为程序员提供了广泛的功能和灵活的编程方式。

自2000年以来,C语言在衡量编程语言受欢迎程度的TIOBE指数排名中保持在前两位。

概述

C语言是一种通用的、面向过程式的计算机编程语言,与大多数ALGOL族(ALGOrithmic Language,指令式编程语言族)的大多数过程式编程语言类似。和C++、C#、Java等面向对象编程语言有所不同的是,C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言描述问题比汇编语言迅速、工作量小、可读性好、易于调试、修改和移植,一般只比汇编语言代码生成的目标程序效率低10%-20%,而代码质量与汇编语言相当。因此,C语言十分适用于编写系统级软件。

从设计细节中来说,C语言采用静态类型系统和有结构化程序设计的特性,具有变量作用域和递归功能。C语言中的可执行代码包含在子程序(函数)中,其函数参数大多采用值传递方式,而数组等特殊数据结构则通过传递指针(指向数组第一个元素的地址)来传递。

C语言还具有具有以下的特性:

1.基本数据类型包括字符、整型和浮点数等,还有指针、数组、结构和联合等派生数据类型。

2.变量类型可以转换,如整数型和字符型变量。

3.可以通过指针对存储器进行低级控制。

4.不同的变量类型可以组合在一起形成结构体。

5.支持基本的控制流,如条件判断、循环等。

6.函数可以返回各种数据类型的值,并且可以递归调用。

7.C语言目前有44个保留字,允许灵活的变量和函数命名。

8.编译预处理机制增强了C语言的灵活性。

发展历史

早期发展

C语言最早由丹尼斯·里奇(Dennis Ritchie)为了在PDP-11电脑上运行的Unix系统所设计出来的编程语言,初次发展在1969年到1973年之间。其起源也与unix操作系统的开发密切相关。它源于BCPL语言,后者由马丁·理察德(Martin Richards)于1967年左右设计实现。

B语言

1969年,肯·汤普逊(Ken Thompson)为提议在PDP-7上开发一个新的阶层式操作系统的计划,做出了一个分时多任务操作系统,成为第一版UNIX。他希望有一种编程语言来为该平台开发实用程序。1970年,肯·汤普逊(Ken Thompson)为运行在PDP-7上的首个Unix系统创建了一个最新开发的BCPL系统编程语言的精简版本(BCPL是一门"无类型"的编程语言:它仅能操作一种数据类型,即机器字/machine word)。当时还没有BCPL的官方说明,肯修改了语法,使其更加简洁,这个语言被称为B语言,它也是无类型的,类似于被称为SMALGOL的简化ALGOL。与BCPL一样,B语言也有一个引导编译器,以方便移植到新机器上。

因为PDP-7的性能不佳,肯·汤普逊丹尼斯·里奇决定把第一版unix移植到PDP-11/20的机器上,开发第二版UNIX。但是由于B语言的速度太慢,而且无法利用PDP-11的字节寻址能力等特性,最终用B语言编写的实用程序寥寥无几。

新B语言和第一版C语言

1971年,丹尼斯开始改进B语言,以利用功能更强大的PDP-11的特性。一个新增的重要功能是字符数据类型,他称之为新B语言(NB)。与此同时,肯开始使用NB语言编写Unix内核,并决定了NB语言的发展方向,于是有了int和char数组。此外,还增加了指针、生成指向其他类型的指针的能力、所有类型的数组以及从函数返回的类型。表达式中的数组变成了指针,并编写了新的编译器,语言也更名为C语言。

C语言编译器和一些实用程序被收录到第二版Unix中,第二版Unix也被称为Research Unix。

结构重写与Unix内核重写

在艾伦·斯奈德(Alan Snyder)的推动下,同时也是出于业界对BCPL和PL/I中文件包含机制实用性的认可,C语言预处理器于1973年左右被引入第四版Unix,这是C语言第一次应用在操作系统的核心编写上。此时,C语言已经具备了一些强大的功能,如结构类型(struct)。预处理器的最初版本只提供了包含文件和简单的字符串替换:无参数宏的#include和#define。此后不久,迈克·莱斯克(Mike Lesk)和约翰·雷泽(John Reiser)等人对其进行了扩展开发,纳入了带参数和条件编译的宏。

1975年C语言开始移植到其他机器上使用。史蒂芬·强生(Stephen C. Johnson)为C语言实现了一套“可移植编译器”,这套编译器修改起来相对容易,并且可以为不同的机器生成代码。从那时起,C在大多数计算机上被使用,从最小的微型计算机到与CRAY-2超级计算机。当时的C语言很规范,即使没有一份正式的标准,人们也可以写出C程序,并且无须修改就可以运行在任何支持C语言和最小运行时环境的计算机上。

由此可知,从发明起C语言就是为系统级编程而设计,程序的运行效率至关重要,因此,C语言与真实机器能力的高度匹配的能力也就不足为奇。

K\u0026R C

1978年,布莱恩·克尼根(Brian Kernighan)和丹尼斯·里奇出版了第一版《C程序设计语言》,该书以作者名字的首字母"K\u0026R"命名,多年来一直是C语言的非正式规范。该书描述的C语言规范版本通常被称为"K\u0026R C"。该书第二版涵盖了后来的ANSI C标准。K\u0026R引入了多种语言特性,包括:

因为许多旧的编译器仍在使用K\u0026R C规范,而且K\u0026R C代码不与后来的ANSI标准冲突,是合法的标准C代码,所以即使在1989年ANSI标准发布之后,K\u0026R C仍然被认为是C程序员考虑实现最大可移植性时所采用的基础规则。在C语言的早期版本中,只有返回类型为int以外的函数才必须在函数定义之前声明,即未声明的函数被假定为返回类型为int。下面是一个早期版本C语言函数定义和声明的实例:

在K\u0026R C语言中,被注释掉的int类型指定符可以省略,但在后来的标准中却是必需的。由于K\u0026R函数声明中不包含任何关于函数参数的信息,因此不进行函数参数类型检查,不过如果调用本地函数时使用了错误的参数个数,或者多次调用外部函数时使用了不同的参数个数或类型,一些编译器会发出警告信息。人们开发了一些单独的工具,如unix的lint工具,可以检查多个源文件中函数使用的一致性。

在该版本的C语言中,大量的扩展、不一致的标准库、C语言的普及和Unix编译器不能精确执行K\u0026R 规范等多方面因素,推动了C语言进一步的标准化。

ANSI C和ISO C

在20世纪70年代末和80年代,随着C语言越来越受欢迎,它的各种版本也被应用在各种大型机、小型机和微型计算机上,包括IBM等大型公司的产品。1983年,美国国家标准学会(American National Standards Institute,简称ANSI)成立了一个委员会X3J11,以制定C语言的标准规范。X3J11基于Unix实现制定了C语言标准;然而,Unix C库的不可移植部分交给了IEEE的1003工作组,这部分也成为1988年POSIX标准的基础。在1989年,C语言标准被正式通过为ANSI X3.159-1989 "Programming Language C"。这个版本的语言通常被称为ANSI C,标准C,或C89。

在1990年,ANSI C标准被国际标准化组织(ISO)少量修改后采用为ISO/IEC 9899:1990,有时称为C90。因此,术语“C89”和“C90”指的是同一个编程语言。同其他国家标准机构一样,ANSI不再独立开发C标准,而是转向国际C标准,由ISO/IEC JTC1/SC22/WG14工作组维护。

C标准化工作的目标是产生K\u0026R C语言的超集,其中还包括许多随后引入的非官方特性。标准委员会还增加了一些附加功能,如函数原型(借鉴C++)、void指针、对国际字符集和区域设置的支持以及预处理器增强。尽管参数声明的语法已扩展以包括C++中使用的风格,但K\u0026R标准的接口仍然有效,以便与现有源代码兼容。具体而言,K\u0026R C语言到ANSI/ISO标准C语言的改进主要包括:

当前的C编译器的支持C89,并且大多数现代C代码都是基于它构建的。在没有依赖硬件的前提下,仅使用标准C编写的程序,都可以在资源限制范围内,在所有具有符合C现的平台上正确运行。但是如果没有这些限制,如使用了非标准库(如图形用户界面库,即GUI库),或依赖于编译器和平台特定的属性(如数据类型的精确大小和端序),程序可能只能在特定的平台或编译器上编译。在必须由符合标准C或基于K\u0026R C的编译器进行编译时,可以使用__STDC__宏将代码分成标准C部分和K\u0026R部分,以防止在基于K\u0026R C的编译器上使用仅在标准C中可用的功能。

在ANSI/ISO标准化过程之后,C语言规范在几年内保持相对静态。仅在1995年对C90标准进行了规范修正(ISO/IEC 9899/AMD1:1995,或称为C95),纠正了一些细节,并扩大了对国际字符集的支持。

C99

C标准在20世纪90年代末进一步修订,并于1999年出版ISO/IEC 9899:1999,通常称为"C99"。此后,它已经通过技术勘误进行了三次修订。

C99引入了一些新特性,包括内联函数、几种新的数据类型(包括long long int和表示复数的复数类型)、可变长度数组和灵活的数组成员、对IEEE 754浮点数的支持改进、支持可变参数宏,以及以“//”开头的一行注释,类似BCPL或C++中的注释一样,并且已经在几个C编译器中作为扩展实现了。C99在很大程度上与C90向后兼容,但在某些方面更加严格,如没有类型说明符的声明不再隐式地假定为int。标准宏“__STDC_VERSION__”被定义为值“199901L”,表示支持C99。此外,C99标准要求以转义字符的形式(例如\u0040或\U0001f431)支持Unicode标识符,并建议支持原始Unicode名称。

C11

在2007年,对C标准的再一次修订开始了,被非正式地称为"C1X"。2011年12月8日,ISO/IEC 9899:2011正式发布。C标准委员会通过了一些基线,对未经测试的新特性进行了限制。此外,C11标准为C和库添加了许多新特性,包括泛型、匿名结构体、改进的Unicode支持、原子操作、多线程和带边界检查的函数等。它还将现有C99库的部分内容变为可选,并提高了与C++的兼容性。标准宏“__STDC_VERSION__”被定义为“201112L”,表示支持C11。

C17

C17(也称为C18)是ISO/IEC 9899:2018的非正式名称,于2017年编写并于2018年6月发布,被用来替代C11标准。它没有引入新的语言特性,只进行了技术修正,对C11中的缺陷进行了补充。标准宏“__STDC_VERSION__”被定义为“201710L”。

C23

C23是下一个C编程语言标准的非正式名称,很可能成为ISO/IEC 9899:2024,取代C17(标准ISO/IEC 9899:2018)。它在2016年以C2x的形式,预计将于2024年发布。

Embedded C

历史上,嵌入式C编程需要对C语言进行非标准扩展,以支持定点运算、多个不同内存块和基本I/O操作等特殊功能。在2008年,C标准委员会发布了一份技术报告,对C语言进行扩展,提供一个共同的标准,以解决这些问题。它包括许多在普通C中不可用的特性,如定点运算、命名地址空间和基本I/O硬件寻址。

技术细节

语法

字符集

基本的C源字符集包括以下字符:

其中,换行表示文本行的结束,它不一定要对应于一个实际的单个字符,但是为了方便起见,C将其视为一个字符。多字节编码字符可以在字符串文字中使用,但它们不是完全可移植的。基本的C执行字符集包含相同的字符,以及警报、退格和回车等表示,每版C标准修订版都增加了关于扩展字符集的支持。

保留字

截至C11,有44个保留字,也称为关键字,这些关键字是预定义的,除了它们预定用途之外,不能被用于的任何其他目的:

C23将添加14个保留字,并将以前定义一些关键字成为备选关键字,包括:_Alignas、_Alignof、_Bool、_Static_assert、_Thread_local。

操作符

C语言支持丰富的运算符集,这些运算符是在表达式中使用的符号,用于指定在计算该表达式时要执行的操作。

数据类型

在C编程语言中,数据类型构成了数据元素存储的语义和特征。它们在语言语法中以内存位置或变量声明的形式表达。数据类型还决定了数据元素的操作类型或处理方法。C语言提供了基本的运算类型,如整数和实数类型,以及建立数组等语法。

基础数据类型

主要类型

C语言提供了char、int、float和double四种基本算术类型说明符,以及有符号、无符号、短和长等修饰符。下表列出了在指定大量特定存储大小声明时允许的组合。

整数类型的实际大小因实现而异,标准C仅要求数据类型之间的大小关系,以及每个数据类型的最小大小,具体关系要求是:

int类型应该是目标处理器效率最高的整数类型,提供了很大的灵活性。在实际应用中,char通常是8位大小,short通常是16位大小(无符号类型也是如此)。这对于多数平台都成立,例如1990年代的SunOS 4 Unix、微软 MS-DOS、现代Linux以及Microchip MCC18用于嵌入式8位PIC微控制器。POSIX要求char的大小也为8位。

标准C中的各种规则使得unsigned char成为用于存储任意非位字段对象的数组的基本类型。浮点类型(float)的实际大小和行为也因实现而异。唯一的要求是long double不得小于double,double不得小于float。

布尔类型

C99增加了布尔(True/False)类型“_Bool”。此外,\u003cstdbool.h\u003e头文件定义了bool作为该类型的别名,并提供了true和false的宏。“_Bool”的功能与普通整数类型类似,但有一个例外:对“_Bool”的赋值如果不是0(false),则存储为1(true)。这种规定是为了避免在隐式缩小转换中出现整数溢出。例如,在以下代码中

如果unsigned char的大小为8位,变量b的值将为false。这是因为值256不适配布尔数据类型,导致只有它的低8位被使用,从而产生一个零值。不过,改变类型后,之前的代码就会正常运行:

“_Bool”类型还能确保真值之间恒相等

结构体

结构体(Structures)将多个可能具有不同数据类型的数据项的存储汇集到一个由单个变量引用的内存块中。以下示例声明了数据类型struct birthday,其中包含了一个人的姓名和生日。结构体定义后,是对变量John的声明,该变量分配了所需的存储空间。

结构体的内存布局是由每个平台的语言实现决定的,但有一些限制:第一个成员的内存地址必须与结构体本身的地址相同;可以使用复合字面量来初始化或分配结构体。函数可以直接返回一个结构体,但是这种方式在运行时通常不太高效。自C99以来,结构体最后一个成员也可以是柔性数组。实际运用中,还常常使用包含指向自身类型的结构体指针的结构体来构建链式数据结构

联合

联合类型(Unions)是一种特殊的构造,允许使用不同类型的来访问同一内存块。例如,可以声明一个数据类型的联合,以允许将相同的数据读取为整数、浮点数或任何其他用户声明的类型。在下面的示例代码中,联合u的总大小是u.s的大小——这恰好是u.s.u和u.s.d大小的总和,而且s大于i和f。当将某个值赋给u.i,如果u.i小于u.f,则可能会保留u.f的某些部分。值得注意的是,从联合中读取成员与强制转换不同,因为成员的值不会被转换,只是被读取。

枚举

枚举(Enumerated)用于定义一组具有离散值的常量,它可以让数据更简洁,更易读。枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。定义一个枚举类型,需要使用enum关键字,后面跟着枚举类型的名称,以及用大括号{}括起来的一组枚举常量。每个枚举常量可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从0开始递增。它在C语言实现中是以int类型储存的。举个例子:

指针

指针(Pointers)是一种数据类型,它包含特定类型变量的存储位置的地址。每种数据类型T都有一个对应的类型T的指针类型。它们使用星号(*)类型声明符,在基本存储类型之后和变量名之前进行声明。星号前后的空格是可选的。

也可以为指针数据类型声明指针,从而创建多个间接指针,如char**和int***,包括数组类型的指针。后者不如数组指针常见,其语法也比较容易混淆:

元素pc需要十个内存块,每个内存块大小与指向char的指针相同(在多数平台上通常为40或80字节),但元素pa只有一个指针(大小为4或8字节),它指向的数据是一个十字节数组(sizeof *pa == 10)。

数组

数组(Arrays)是相同类型的值的集合,在内存中连续存储。除void和函数类型外,每种类型T都可以存在"由N个类型元素组成的数组"类型,即该类型数组。大小为N的数组由从0到N-1(包括N-1)的整数索引。下面是一个简单的例子:

数组可以多维初始化,但不能赋值。数组将指向第一个元素的指针传递给函数。多维数组的定义为数组的数组,除了最外层维度之外,所有维度定义时都必须确定常量大小:

常用库

C语言使用库作为其主要的扩展方法。在C语言中,库是一组包含在单个“存档”文件中的函数。每个库通常都有一个头文件,其中包含库中包含的函数的原型,并声明了与这些函数一起使用的特殊数据类型和宏符号。如果程序想要使用库,必须包含库的头文件,并且库必须与程序链接在一起,可以使用编译器标志来完成链接(例如,-lm,缩写为“链接数学库”)。

最常见的C库是C标准库,它由ISO和ANSI C标准指定,并随每个C实现一起提供(针对嵌入式系统等有限环境的实现可能仅提供标准库的子集)。C标准库支持流输入和输出、内存分配、数学、字符字符串和时间值。标准头文件(例如,stdio.h)为这些标准库的接口规定提供了详细的说明。

另一个常见的C库函数集是专门针对Unix和类Unix系统的应用程序使用的,与标准库有所不同的是提供了与内核接口的函数,在各种标准中有详细说明(POSIX和Single UNIX规范)。

内存管理

编程语言中最重要的功能之一,是提供管理内存和存储在内存中的对象的管理。C语言提供了三种主要的方式来为对象分配内存:

这三种方法适用情况不同,具有不同的优缺点。例如,静态内存分配几乎没有分配开销,自动分配可能会多一些,而动态内存分配在分配和释放方面都可能都有很大的开销。静态对象的持久性有利于在函数调用之间保持状态信息,自动分配更易于使用,但堆栈空间通常比静态内存或堆空间更有限且短暂,动态内存分配可以分配一些仅在运行时才知道的对象。

在允许的情况下,自动或静态分配通常是最简单的,因为存储由编译器管理,减少手动分配和释放存储的潜在错误,免去了繁琐的工作。然而,许多数据结构在运行时可能改变大小,由于静态分配(和C99之前的自动分配)必须在编译时声明固定大小,因此有许多情况需要使用动态分配。在未特殊说明时,静态对象在程序启动时包含零值或空指针值。但是对于自动和动态分配,只有在明确指定初始值的情况下,对象才会被初始化;否则,它们的值是不确定的(通常是存储中存在的任意位模式,甚至可能不能表示该类型的有效值)。在这种情况下,如果程序尝试访问未初始化的值,则结果是未定义的。

Hello World示例代码

“Hello,World”示例最早出现在《C程序设计语言》的第一版中,已经成为大多数编程教材中引入程序的范例。这个程序将“hello, world”打印到标准输出,通过终端或屏幕显示。一个符合标准的“Hello,World”程序是:

程序的第一行包含一个预处理指令,用#include表示,这个操作使编译器用tdio.h标准头文件的整个文本替换该行。该头文件包含了标准输入和输出函数(如printf和scanf)的声明。尖括号包围的stdio.h表示可以使用一种搜索策略来定位stdio.h文件,该策略首选编译器提供的头文件,而不是其他具有相同名称的自定义头文件,与使用双引号的情况不同。双引号通常包括本地或项目特定的头文件。

第三行定义一个名为main的函数。在C程序中,主函数有特殊的用途;运行时环境调用主函数来开始程序执行。类型说明符int表示main函数求值的结果返回给调用者的值是整数。参数列表中的关键字void表示此函数不接受任何参数。

大括号的开头表示开始定义主函数。第五行调用一个名为printf的函数,该函数为系统库提供。在此调用中,printf函数被提供一个单一参数,即字符串字面值“hello,world\n”中第一个字符的地址。字符串字面值是一个无名的char类型数组,由编译器自动设置,并带有一个最终值为“\0”的字符,以标记数组的末尾(以便printf知道字符串的长度)。“\n”是一个转义序列,C将其翻译为换行字符,在输出上表示为当前行的结束。printf函数的返回值是int类型,但由于未使用,因此会被默认丢弃。(更高级的程序可能会检查返回值以确定printf函数是否成功)。分号“;”表示该语句终止。

大括号的闭合表示主函数代码的结束。根据C99规范及更高版本,与其他函数不同,主函数在达到终止该函数的“}”时会隐式返回一个值为0的值(C99以前需要显式的“return 0;”语句)。运行时系统将其理解为成功执行并退出代码。

应用

系统编程

C语言广泛用于系统编程,特别是实现操作系统和嵌入式系统应用,比如Linux内核、数据库管理系统(如SQLite)等等。

网络开发

历史上,C语言有时被用于Web开发,作为Web应用程序、服务器和浏览器之间信息的“网关”。选择C语言可能是因为其高速度、强稳定性和强可移植性。但是目前使用C进行的Web开发已不再常见,并且市面上存在许多其他Web开发工具。

其他编程语言

C的广泛可用性和高效性带来的一个影响是,许多其他编程语言的编译器、库和解释器通常是用C实现的。例如,PythonPerlRubyPHP等语言的参考实现都是用C编写的。

免责声明
隐私政策
用户协议
目录 22
0{{catalogNumber[index]}}. {{item.title}}
{{item.title}}
友情链接: