C语言

本页使用了标题或全文手工转换,现处于中国大陆简体模式
求闻百科,共笔求闻
C语言
C程序设计语言》,第一部介绍C语言的书籍
编程范型过程式指令式编程过程式)、结构化编程
设计者丹尼斯·里奇(Dennis Ritchie)
实现者丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)
发行时间1972年
稳定版本
ISO/IEC 9899:2018C18
(2018年6月 )
操作系统跨平台
主要实现产品
ClangGCCMSVCTurbo CWatcom C
受启发于
BBCPLCPL)、ALGOL 68[1]汇编语言PL/IFORTRAN
施影响于
大量
如:awkBitCcshC++C#
DJavaJavaScriptObjective-CPerlPHPRust

C是一种通用的编程语言,广泛用于系统软件应用软件的开发。于1969年至1973年间,为了移植与开发UNIX操作系统,由丹尼斯·里奇肯·汤普逊,以B语言为基础,在贝尔实验室设计、开发出来。

C语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点,在程序设计中备受青睐,成为最近25年使用最为广泛的编程语言[2]。目前,C语言编译器普遍存在于各种不同的操作系统中,例如Microsoft WindowsmacOSLinuxUnix等。C语言的设计影响了众多后来的编程语言,例如C++Objective-CJavaC#等。

二十世纪八十年代,为了避免各开发厂商用的C语言的语法产生差异,由美国国家标准局为C语言订定了一套完整的国际标准语法,称为ANSI C,作为C语言的标准。二十世纪八十年代至今的有关程序开发工具,一般都支持符合ANSI C的语法。

概述

特性

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

历史

20世纪70年代,肯·汤姆森为了使其设计的Unix系统更加高效,使用B语言的变种(即C语言)重写了Unix。在1978年,丹尼斯·里奇布莱恩·柯林汉合作出版了《C程序设计语言》第一版,事实上即为K&R C标准[5]。在这之后,制定标准的工作转移到ISO和ANSI。目前最新的C语言标准是C18

语法

Hello World 程序

Brian Kernighan于1978年亲笔书写的“Hello World”程序

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

#include <stdio.h>

int main(void)
{
    printf("Hello, world!\n");
    return 0;
}

其中只有int,void,return为C语言的关键字,预处理器会将#include <stdio.h>替换为stdio.h文件的内容。

main函数是C语言程序的入口点

"Hello, world!\n"中的\n是一个转义字符,形式为\加上一个字符。所起的作用在ASCII码中规定。

printf是声明于stdio.h的函数,关于printf的更多细节,参见printf

关于格式化字符串的更多信息,参见格式化字符串

进一步了解

C语言由函数和变量组成,C的函数就像是Fortran中的子程序和函数[4][6]

在C语言中,程序从 main 开始执行。main 函数通过调用和控制其他函数进行工作。例如上面的printf。程序员可以自己写函数,或从库中调用函数。在上面的return 0; 使得 main 返回一个值给调用程序的壳层,表明程序是否成功运行。

一个C语言的函数由返回值、函数名、参数列表和函数体组成。函数体的语法和其它的复合的语句部分是一样的。

复合语句

C语言中的复合语句(或称语句块)的格式为:

{
    語句;
    語句;
    /* ... */
}

[6] 复合语句可以使得几个语句从文法上变成一个语句。

有时必须使用复合语句,否则会产生错误。例如,在运用循环语句的时候,如果循环体(即循环中执行部分)包含多个语句(以分号隔开),则必须使用花括号将他们合并成一个复合语句。如果不这么做,系统仅把第一个分号前的内容看做循环体。

需要注意的是,部分C编译器并不支持在任意位置使用复合语句。

条件语句

C语言有两种条件语句形式,分别是ifswitch

If 的格式如下:

if (運算式) // 如果
    語句; 
// 有時還會有 else:
else      // 否則
    語句;

表达式的值非零表示条件为真;如果条件为假,程序将跳过if处的语句,直接执行if后面的语句。但是如果if后面有else,则当条件为假时,程序跳到else处执行。ifelse后面的语句可以是另个if语句,这种套叠式的结构,允许更复杂的逻辑控制流程得以实现。在一般情况下,else一定与最接近的if成对,因此常用括弧{}越过此限制。比较下面两种情况:

if (邏輯表達)
    if (邏輯表達式)
        語句; 
    else
        語句;

下面是if的另一种格式if……else if……else……

if (逻辑表达式)
    语句;
else if (逻辑表达式)  //在下面可以加入多个“else if()”语句
    语句;
else
    语句;
if (邏輯表達式甲) {
    if (邏輯表達式乙)
        語句;
}
else 
    語句;

要注意这里的缩进和换行只用于方便阅读。编译器并不会根据缩进层级猜测 if 和 else 的对应关系。

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

switch () {
  case :
  case :
    語句段1; // 甲乙都會執行
    // 更多語句…
    break;  // 跳轉到 switch 末尾處
  case :
    語句段2;
    break; 
  default:  // 無論如何都會匹配
    語句段3;
}

简单的条件判断也可用?:

運算式?值1:值2;
:
   a=b>c?b:c // 如果變數"b"的值大於變數"c" 把變數"b"的值賦予變數"a"

循环语句

C语言有三种形式的循环语句[6]

do 
    語句
while(判斷式);

和:

while(判斷式) 
    語句;

和:

for(起始化;判斷式;運算式)
    語句;

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

for(起始化;判斷式;運算式)
    語句;

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

起始化;
while (判斷式) {
    語句;
    運算式;
}

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

跳转语句

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

goto 標記;

goto语句是无条件转移语句,且标记必须在当前函数中定义,使用“标记:”的格式定义。程序将跳到标记处继续执行。由于goto(特别是向回 goto 和长距离的 goto)容易产生阅读上的困难,所以对新手应该尽量少用。GCC 编译器拓展支持对指针 goto和宏内 goto,一定程度上增强了 goto 的可读性。


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

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

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

在C语言中的运算符号

C和C++运算符

数据类型

数据类型 (C语言)

数组

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

例如:

int myvector [100]/* 從myvector[0]至[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]

例子中notfull创建了一个3*3的二维数组,初始化时有些元素并未赋值。如下:

1  ?  ?
1  2  3
4  5  ?

根据C标准的规定,在存在初始化列表时,如果初始化列表中未提供对所有元素的初始化,则剩余元素会被默认初始化,并使用与静态变量相同的初始化规则[7]

指针

如果一个变量声明时在前面使用 * 号,表明这是个指针型变量[6]。换句话说,该变量存储一个地址,而 *(此处特指单目运算符 *,下同。C语言中另有双目运算符 * 表示乘) 则是取内容操作符,意思是取这个内存地址里存储的内容。把这两点结合在一起,可将 int *a;看作是 “*a 解得的内容类型为 int”,对更复杂的声明也如此[注 1]。指针是 C 语言区别于其他同时代高级语言的主要特征之一。

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

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

例如:

int *pi;     // 指向整型数据的指针 pi
int * api[3];// 由指向整型数据的指针构成的数组,长度为 3
char ** argv;// 指向一个字符指针的指针
struct { int member; } stinst,
  * pst = & stinst;
// pst是一个指向一个匿名结构体的指针

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

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

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

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

当指针指向结构体时,可以使用运算符 -> 代替 *和. 的作用,如 (*p).mp->m 等效。

字符串

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[])
{
    char c;
    while ((c=getchar())!=EOF)
        putchar(c);
    perror("getchar() got EOF");
    return -1;
}

函数

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

内存管理

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

大多数C语言实现使用栈(Stack)来保存函数返回地址/栈帧基址、完成函数的参数传递和函数局部变量的存储。然而,在部分极特殊的平台上,使用栈并不能获得最大效率。此时的实现由编译器决定[6]。 如果程序需要在运行的过程中动态分配内存,可以利用(Heap)来实现。

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

  • 静态分配

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

  • 自动分配

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

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

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

  • 动态分配

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

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

安全问题

C语言的特色之一是:语言不负责内存边界检查,这是因为在运行时进行内存边界检查会造成性能问题,与UNIX哲学不符。此特性容易导致缓冲区溢出问题。然而,部分编译器(如英特尔编译器)会出于安全性的考量,提供方法以进行运行时内存边界检查[8]

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> 时间相关

在94年的修正版中

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

在C99中增加了六个库

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

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

工具软件

工具软件可以帮助程序设计者避免一些程序中潜藏或容易出现的问题,例如常会造成程序未预期动作或是执行期错误的代码。

许多语言都有自动源代码检查及审计工具,C语言也有类似工具,像是Lint。可以在程序刚写好时用Lint找出可能有问题的程序,通过Lint后再用C编译器进行编译,许多编译器也可以设置是否要针对一些可能有问题的代码提出警告。MISRA C是一套针对嵌入式系统的法则,可主要也是避免一些可能有问题的代码。

也有一些编译器、程序库或操作系统可以处理一些非标准C语言的功能,例如边界值检查、缓存溢出侦测、序列化自动垃圾回收功能。

使用像ValgrindIBM Rational Purify等软件工具,或者链接有特别malloc函数的程序库,有助于找出一些运行期存储器使用的问题。

保留关键字

以下是C语言的保留关键字:[9]

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

C99新增关键字

_Bool _Complex _Imaginary inline restrict

C11新增关键字

_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local

经典错误

“void main()”的用法并不是任何标准制定的[10][11]。 C语言标准语法是“int main()”,任何实现都必须支持int main(void) { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[12]。 在 C++ 标准中,main的标准类型应是int,否则类型是由实现定义的。任何实现都必须支持int main() { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[13]

参见

脚注

注解

  1. C 的声明使用这种“解方程”的形式,于是便出现了多种复杂的声明。https://cdecl.org 是一个基于网页的实用“翻译工具”。

参考资料

  1. Ritchie, Dennis M. The Development of the C Language. 1993-01 [2008-01-01]. The scheme of type composition adopted by C owes considerable debt to Algol 68, although it did not, perhaps, emerge in a form that Algol's adherents would approve of. 
  2. TIOBE Programming Community Index [TIOBE编程社区指数]. 2012 [2012-11-03] (英语). 
  3. Brian W. Kernighan and Dennis M. Ritchie. The C programming Language. Prentice-Hall. 1988. ISBN 0-13-110362-8 (英语). In C, all function arguments are passed ``by value. 
  4. 4.0 4.1 4.2 4.3 4.4 4.5 Dennis M. Ritchie,Brian W. Kernighan. C程序设计语言. 北京: 机械工业出版社. 2004年1月. ISBN 9787111128069 (中文). 
  5. Stephen Prata. C Primer Plus(第5版). 北京: 人民邮电出版社. 2005年2月: 3–4. ISBN 9787115130228 (中文). 
  6. 6.0 6.1 6.2 6.3 6.4 6.5 6.6 ISO/IEC 9899:2018 (PDF). [2020-06-10]. 
  7. ISO/IEC 9899:2011. International Organization for Standardization. [2020-03-08] (英语). §6.7.9 Initialization 21 If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration. 
  8. check-pointers, Qcheck-pointers. Intel. [2021-06-01] (英语). 
  9. ISO/IEC 9899:2011. International Organization for Standardization. [2020-03-08] (英语). 
  10. Can I write "void main()"? The definition void main() { /* ... */ } is not and never has been C++, nor has it even been C.
  11. 用 C99 进行开放源代码的开发. [2011-01-21]. 
  12. “The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters: int main(void) { /* ... */ } or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ } or equivalent; or in some other implementation-defined manner.”,引自ISO/IEC 9899:1999, Section 5.1.2.2.1 Program startup
  13. “An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main: int main() { /* ... */ } and int main(int argc, char* argv[]) { /* ... */ }.”,引自 ISO/IEC 14882, 第一版(1998)、第二版(2003)与第三版(2011), section 3.6.1 Main function

参考资料

  • Brian Kernighan, Dennis Ritchie: The C Programming Language. Also known as K&R — The original book on C.
    • 1st, Prentice Hall 1978; ISBN 978-0-13-110163-0. Pre-ANSI C.
    • 2nd, Prentice Hall 1988; ISBN 978-0-13-110362-7. ANSI C.
  • ISO/IEC 9899. The official C:1999 standard, along with technical corrigenda and a rationale. As of 2005 the latest version is ISO/IEC 9899:TC2.
  • Samuel P. Harbison, Guy L. Steele: C: A Reference Manual. This book is excellent as a definitive reference manual, and for those working on C compilers. The book contains a BNF grammar for C.
    • 4th, Prentice Hall 1994; ISBN 978-0-13-326224-7.
    • 5th, Prentice Hall 2002; ISBN 978-0-13-089592-9.
  • Derek M. Jones: The New C Standard: A Cultural and Economic Commentary, Addison-Wesley, ISBN 978-0-201-70917-9, online material
  • Robert Sedgewick: Algorithms in C, Addison-Wesley, ISBN 978-0-201-31452-6 (Part 1–4) and ISBN 978-0-201-31663-6 (Part 5)
  • William H. Press, Saul A. Teukolsky, William T. Vetterling, Brian P. Flannery: Numerical Recipes in C (The Art of Scientific Computing), ISBN 978-0-521-43108-8

外部链接