“你好,世界!”#

来自程序的第一声问候#

下面是一个简单的C语言程序:

#include <stdio.h>

int main(void)
{
    printf("Hello, world!"); /*print Hello, world!*/
    return 0;
}

我们把这份代码保存在hello_world.c(或者其他文件名)中。 如果你正确地安装了IDE,编译并运行了程序,那么你可以看到以下的结果:

Hello, world!

我们借着这个例子来初探C语言中的一些内容:

初探C语言#

头文件(header file)与预处理指令(preprocessing directive)#include#

上面程序的第一行是一个预处理指令

#include <stdio.h>

预处理指令告诉编译器的指令,它告诉编译器除了代码本身以外一些额外的信息,指导编译器翻译。 预编译指指令通常以#开头,以换行结束

这里我们的#include是一个预编译指令,表示头文件(以及对应源文件)的包含。 它告诉编译器我们需要将stdio.h这个文件包含到我们的文件中去。

#include

  1. #include <文件名>
  2. #include "文件名"

其中:1.的语法适用于标准库1的头文件; 2.的语法适用于其他头文件。

Detail

关于#include的详细信息, 见预处理指令/include

而头文件就是这样一个被包含的文件,它通常以.h为后缀名,内部有一些符号的声明。

stdio.h有什么功能,为什么我们需要包含它?

stdio.h是提供 标准输入输出(Standard Input and Output) 功能的头文件。它对输入/输出提供支持。 在这里,我们使用了其中的一个函数printf(),来讲我们的字符串输出到屏幕上, 所以需要引用这一个头文件。

尽管不是所有程序都需要#include,但是大部分情况我们需要这样一些定义好的一些文件, 来使用它们中的一些功能。

什么是C标准库?

简而言之,C标准库是C标准中提供的一系列标准功能的总称。它们以头文件的形式提供这些功能。 所有标准C编译器都需要支持这些头文件。

详情

关于C标准库的详细信息,见附录/C标准库列表

语句(Statement)#

语句是C语言的最小单位,是程序执行的内容。一般来说,语句都会以;(分号)结束。 中间可以有换行

表达式语句

表达式;

上面的程序有两条语句:

printf("Hello, world!");
return 0;

我们等会会说明这两条语句的意义。

这几条语句可以每个占一行(或多行),也可以写到一行里。 但是为了阅读上的便利,我们通常会让它们每个占一行(或多行)。

多条语句可以由大括号{}围起来,这样就形成了复合语句(compound statements), 又称块(block)

复合语句

{语句...}

其中语句...代表由多条语句构成的序列。

详情

关于语句的详细信息,见语句

函数(Function)#

函数的是C语言的一个单元。

函数定义

返回类型 函数名(形参列表) 复合语句

函数有两重含义。首先它能像数学中的函数一样返回(return)一个值。 其次它能执行某种功能(如同英语中Function的含义)。

在上面的程序中我们定义(define)了一个函数:

int main(void)
{
    ...
}

Attention

在函数定义中,形参列表的)后面没有;。(因为它不是语句)

对照上面的的语法我们可以得到以下的信息:

  • 这个函数的返回类型: int
  • 这个函数的函数名: main
  • 这个函数的形参(形式参数)列表: void
  • 这个函数的复合语句: 夹在{}之间的内容

一个函数所拥有的复合语句(也就是大括号之间的内容)叫做函数体(function body), 它指出了函数具体要执行哪些语句。main()这个函数拥有的函数体是:

{
    printf("Hello, world!");
    return 0;   
}

参数(Argument)和函数调用(Function Call)#

函数调用表达式

函数名(实参列表)

printf("Hello, world!");

我们在这一行执行了函数调用。函数调用就是将其他地方写好的函数“调”出来使“用”, 程序会在此时进入到被调用函数的函数体中执行语句。 这里我们调用了名叫“printf”的函数,用来在控制台上输出内容。

Hint

“printf”是格式化输出(print format)的缩写。

printf()在哪里呢?

上文说过,在stdio.h中。

括号中是实参(实际参数)列表,就像数学式 \(f(x)\) 当中那样,把参数写在()(小括号)中, “传递”到这个函数中。不难看出prinf就相当于这里的 \(f\), 而"Hello, world!"就相当于这里的 \(x\)

Attention

C语言中的符号都应该是英文(半角)符号,不要成中文(全角)符号。

Attention

函数调用表达式是表达式,要想变成语句, 需要后附;(参见上文的语句语法

返回语句(Return Statement)#

函数通过返回语句会返回一个值。在这个语句之后,程序会离开当前的函数,返回到调用它的地方, 并把值传回。

返回语句

return 表达式;

我们的main()函数通过下面的语句返回了0。

return 0;

主函数(Main Function)#

main()是一个特殊的函数。它的调用者能只能是操作系统2, 而在代码中不能调用它。它是整个程序的“入口”3。如果程序运行没有错误, 我们就在主函数中返回0并结束程序。

Attention

注意不要写成mian。

关键字(Keyword)#

关键字是C语言中特殊的单词,表示一定的语法意义。 在C语言代码的命名中不能以关键字作为名字

程序中的intvoidreturn都是关键字。换言之,我们不能定义一个叫return()的函数。

C语言的关键字有以下几个:

auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while _Alignas _Alignof _Atomic _Bool _Complex _Decimal128 _Decimal32 _Decimal64 _Generic _Imaginary _Noreturn _Static_assert _Thread_local

注释(Comment)#

返回语句

  1. /*注释内容*/
  2. // 注释内容

其中,1.的注释可以出现在程序任何地方;2.只能出现在行尾。

注释是给代码提供注解,帮助人更好地理解代码的方式。注释的内容在编译时会从代码移除

注释被包括在/**/中,换言之,在/**/中的所有内容都会被编译器去除掉。 你可以在里面任意的写内容,不需要遵照程序语言语法。

//也可以注释内容,但是它的作用范围只能到换行处,而/**/中可以有换行。

好习惯

程序开发中应该勤写有意义的注释,帮助他人和自己理解代码内容。

字符串字面量(String Literal)#

字符串字面量

"字符序列"

其中,字符序列是除了换行以外的字符组成的序列,这包括转义序列。

我们将字符(Character)的序列称为字符串(String)。 在C语言中,可以使用双引号""包围的字符序列表示一个字符串(称之为字符串字面量)。

Note

字符串字面量和关键字是不冲突的(因为它不是命名)。 我们完全可以定义一个叫"return"的字符串字面量。

如上面语法所说的那样,不能在字符串字面量中使用换行。那么, 如果我们需要在字符串中写入一个换行怎么办?

转义序列(Escape Sequence)#

在C语言中,如果遇到一些特殊的字符无法写入字符串字面量的,可以使用转义序列来表示。 转义序列是由\开始,后随一个字母或几个数字的序列。 比如,换行符就可以使用\n表示。以下列举一些常见的转义序列。

转义序列 描述
\0 空终止字符4
\' 单引号
\" 双引号
\? 问号
\\ 反斜杠
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表
\v 垂直制表

(摘自zh.cppreference.com

基本源字符集(Basic Source Character Set)#

除了字符串以外,一份C语言代码由以下字符构成, 他们叫做基本源字符集:

  • 5 个空白字符(空格、水平制表、垂直制表、换页、换行)
  • 10 个数字字符,从 09
  • 52 个字母,从 AZ 以及从 az
  • 29 个标点字符: _ { } [ ] # ( ) < > % : ; . ? * + - / ^ & | ~ ! = , \ " '

(摘自zh.cppreference.com

动手实现,从修改这份代码开始#

如果编译器报错了……#

……那就仔细看看它是怎么说的。

在开发过程中,我们很容易遇到某些由于误写导致的语法错误。由于编译器无法准确识别你的语法, 它就会报错,不生成程序。幸运的是,编译器提示我们哪里出错了,帮助我们改正。

要注意一点,由于一份文档中可能不止有一个错误,所以有可能在照着编译器提示修改了一遍之后, 它还会报告新的错误。通常现代的编译器都会“尽力而为”,尽可能找出所有错误。 但是由于语法错误引起的语法结构崩坏使得编译器无法很好地分析接下来的内容。 修改、尝试、再修改、再尝试,这是程序开发的必经之路。

既然编译器都知道我错在哪里了,为什么不帮我修正?

编译器仅仅知道在这里无法匹配语法规则,并不能知道具体怎么修正。 编译器的提示仅仅是它自己的“猜测”,并不能清晰的知道你的真正意图。 编程语言要求的式简洁无歧义。如果能根据上下文理解修改,那么这样的理解会存在歧义, 也违反了简洁性要求(因为简洁性就是要尽可能少地依赖上下文)。

如果我们编译下面的程序:

#include <stdio.h>
int main(void)
printf("Hello, world!");
return 0;

编译器会提示以下错误:

Error

应输入“{” (hello_world.c:3)

(根据不同的编译器,提示的格式和内容都会有区别)

这里提示我们hello_world.c的第3行出了错误,那么是什么错误?它提示我们应输入{。 如果你还记得函数复合语句语法的话, 可以知道函数定义在)后是复合语句,而复合语句是被{}包围的。这就是它如此提示的原因。

如果我们编译下面的程序:

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

编译器会提示以下错误5

Error

未定义的标识符“printf” (hello_world.c:3)

这是因为编译器找不到printf的定义。它的定义在stdio.h中,而你没有#include它。

Try-this

试一试编译以下程序,看看编译器输出什么错误,你能发现怎么修改它吗?

#include <stdio>
int main(void)
{
    printf("Hello, world!");
    return 0;
}

输出一些不一样的#

我们稍稍修改一些在双引号之间的内容。

#include <stdio.h>
int main(void)
{
    printf("My C Program!");
    return 0;
}
My C Program!

你也可以自己输入一些内容看看。

我们还有一些没有讲的内容#

如果你仔细地看过本章内容,会发现我们其实有一些术语还没阐释明白:

  • 表达式
  • 类型
  • 形式参数和实际参数
  • 字符
  • 字面量

稍安勿躁,我们后面会一步一步的讲解它们。而且对于这一章的所有概念,我们都会在后面详细介绍, 并且了解它的具体用法。

留一个练习吧:

本章练习#

Exercise

实现一个程序,分两行输出“hello”和“world”

#include <stdio.h>
int main(void)
{
    printf("hello\nworld");
    return 0;
}

  1. 准确来说, 是实现控制下的文件,通常包含标准库及其他在标准包含目录。 

  2. 也包括其他有宿主的情况。 而任何独立程序(引导程序、操作系统核心等)的入口点可能不是main()。 

  3. 在程序运行开始时,程序会先做一些初始化的工作,然后调用main()。 

  4. 这是八进制转义序列的一个特例。 

  5. 有些编译器不会为此报错,如MSVC。但是你最好还是#include <stdio.h>。