书名:C语言本质
ISBN:978-7-115-65837-1
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
著 陈 鹏
责任编辑 邓昱洲
人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
网址 http://www.ptpress.com.cn
读者服务热线:(010)81055410
反盗版热线:(010)81055315
本书主要介绍C语言的语法与语义,包括数据类型、变量、控制语句、循环语句、数组、函数、指针等,以及如何使用C语言编写一些常规程序。同时,本书对函数调用过程、内存管理、程序运行等知识展开介绍,帮助读者初步掌握计算机体系结构方面的知识,了解在操作系统环境下程序的基本内存布局、大致运行过程等。
本书适合高校计算机相关专业的师生阅读,也可供对C语言感兴趣的读者参考。
学习语言旨在沟通。无论是自然语言还是计算机程序设计语言,不同之处是沟通的对象。在编撰本书的过程中,我的一个潜在想法是期望学生将计算机看作一个“生物”,甚至是一个“人”。计算机有自己的“生理结构”(如常见的冯·诺依曼体系结构)和“思维方式”,因此,要想非常通畅地与计算机进行交流,就必须真正了解它。
当然,要想真正了解计算机,就要了解计算机专业体系,包括计算机的构造特征(如计算机体系结构、计算机组成原理)、计算机的语言体系(如汇编语言、高级程序设计语言)以及计算机的“思维方式”(如图灵机、计算理论和算法理论)等。我想,虽然C语言只是计算机专业体系中的一小部分,但是作为一门基础性的计算机程序设计语言,它有着非常重要的启蒙作用。
本书沿着两条主线介绍C语言。一条主线围绕“语言”,从高级程序设计语言的角度,介绍C语言的语法与语义,包括数据类型、变量、控制语句、循环语句、数组、指针等。通过这条主线,读者可以学会如何使用C语言编写一些常规程序,最终能编写一个简单的聊天机器人程序。另一条主线围绕“计算”,将C语言作为了解和探究计算机体系的“钥匙”,对函数调用过程、内存管理、程序运行等知识展开介绍。通过这条主线,读者可以掌握计算机体系结构方面的知识,了解在操作系统环境中程序的基本内存布局、大致运行过程等知识。
陈鹏
2026年1月
请发送邮件至dengyuzhou@ptpress.com.cn索取本书课后习题答案及配套代码和练习题。
第01章 绪论如果从20世纪50年代中后期,约翰·巴克斯(John Backus)发明FORTRAN语言算起,现代程序设计语言的发展历史已超过了一甲子,并据不完全统计,出现的编程语言数量已超过了2500种[1]。
[1] 该数据来源于比尔·金纳斯利(Bill Kinnersley)整理的“编程语言列表”,访问于2023年12月7日。
迄今为止,在出现过的编程语言中,有些曾盛行一时,现今却应用寥寥(如ALGOL 60、COBOL、Pascal等),有些自发明之后就没有被大众接受和广泛应用(如Haxe、F#、Clojure等),还有些一直在相对特定的领域发挥着重要作用(如Haskell、Coq、Prolog等),而其中发展历史最为悠久、生命力最强、影响力最大,且在系统编程领域应用最为广泛的当数C语言。
C语言诞生于20世纪60年代末至70年代初期,它是由贝尔实验室的丹尼斯·里奇(Dennis Ritchie)在为UNIX系统设计系统编程语言的过程中所发明的。在超过50年的发展过程中,C语言得到了极其广泛的应用,尤其在系统编程领域,基本上处于绝对优势地位。当今主流的操作系统(如Linux、Windows、MacOS、iOS的内核)主要由C语言编写,一些常见的数据库系统(如Oracle、MySQL、MS SQL Server和PostgreSQL)也是由C语言编写的。此外,许多实时性的嵌入式系统应用基本上也采用C语言开发。C语言之所以能够成为编程语言领域的“常青树”,其核心原因在于C语言具备以下非常重要的特性。
1.可移植性与高效性
C语言基本上被视为可移植的汇编语言。它一方面尽可能地接近机器,另一方面对当前几乎所有处理器的架构都是通用的。对于绝大多数存在的体系架构,至少存在1个对应的C语言编译器。目前,由于现代编译器产生的二进制代码经过高度优化,要想通过手工编写的汇编代码来改进编译器的输出不是一项容易的任务。
C语言的可移植性和高效性使其他编程语言的编译器、库以及解释器也通常由C语言来实现。例如,Python、Ruby和PHP等解释性语言的解释器都是由C语言来实现的。C语言甚至被其他语言的编译器用来与机器通信。例如,C语言是Eiffel和Forth语言的中介语言。这意味着,这类语言的编译器并不为每种所支持的体系架构产生机器码,它只是产生中间C语言代码,再由C编译器处理机器代码生成。
此外,C语言正在逐渐成为开发者之间交流的通用语言,C语言中所使用的许多原理在许多其他语言中被采纳。例如,argc和argv作为命令行参数被用来进行循环构造和说明变量类型等。
2.内存操纵
C语言完美适用于系统编程的重要特征是支持访问任意内存地址和指针算术。由于在硬件和软件的界面中,计算机系统和微控制器将它们的外围设备和I/O引脚映射为内存地址,系统应用必须能够通过这些定制的内存地址与世界通信。因此,对于系统编程而言,C语言必须能够操纵任意内存地址。
3.确定性的资源使用
系统编程语言通常不能依赖垃圾回收机制这一公共语言特征。对一些嵌入式系统而言,不能依赖所谓的动态分配机制。嵌入式应用在时间和内存资源方面都是非常有限的。它们通常被用于实时系统,其中不能够支持对垃圾回收器的不确定性的调用。如果因内存不足而不能使用动态分配,则为内存管理提供其他机制就非常重要,如C指针所允许的将数据置于特定的地址。而对于资源约束系统而言,那些过度依赖动态分配和垃圾回收机制的语言是不适用的。
4.代码更小
C语言具有非常小的运行时,其代码的内存“足迹”比其他大多数编程语言更小。例如,C++对异常情况具有良好的支持,因此,实现相同功能时,C++的二进制代码量约是C语言的2倍。
C语言是从BCPL语言和B语言发展演化而来的。其中,BCPL语言由马丁·理查兹(Martin Richards)在1967年开发,用于编写操作系统软件和编译器。肯·汤普森(Ken Thompson)在借鉴BCPL语言的诸多核心特性的基础上,开发了B语言,并尝试将B语言作为他所开发的UNIX操作系统上的主要编程语言。
BCPL和B语言都是无类型语言,这意味着它们唯一的数据类型是机器字,通过特殊运算符或函数来调用、访问其他类型的对象。而在C语言中,基本的数据类型包括字符和多种大小的整型、浮点型,派生的数据类型通过指针、数组、结构体、联合体等构造而成。
1978年,丹尼斯·里奇和布莱恩·克尼汉(Brian Kernighan)撰写了《C程序设计语言》(The C Programming Language)一书,该书被C语言开发者们称为K&R,并且很多年来一直被当作C语言非正式的标准说明。因此,该版本的C语言也被称为K&R C(C78)。
20世纪80年代,C语言在不同计算机上快速发展,出现了各种变体。虽然这些变体相似,但彼此并不兼容,因此需要对C语言进行标准化。1983年,美国国家标准研究所(ANSI)计算机和信息处理(X3)分委会建立了X3J11技术工作组。1989年,X3J11技术工作组为C语言提供了一个虽有歧义但无关机器的标准,被称为ANSI C(C89)。1990年,在ISO/IEC 联合技术第1委员会第22分委员会第14工作组(ISO/IEC JTC1/SC22/WG14)的努力下,ISO批准ANSI C成为国际标准;于是ISO C诞生了,其标准名为ISO/IEC 9899: 1990[2](C90)。
[2] 9899 是C语言在 ISO 标准中的代号,而冒号后面的1990表示修订的版本是在1990年发布的。
作为K&R C的一个超集,ANSI C和C90语言标准整合了许多非官方特性,包括从C++借鉴的函数原型(Function Prototypes)、指向void的指针以及对国际字符集和本地语言环境的支持,从而改进了预处理机制。
在随后的几年里,C语言标准委员会又不断地改进C语言,并在1999年正式发布ISO/IEC 9899:1999(C99)。
虽然C99标准大部分与C90标准兼容,但是在某些方面更加严格;此外,C99标准引入了许多特性,包括内联函数(Inline Functions)、可变长度的数组、灵活的数组成员(用于结构体)、复合字面量、指定成员的初始化器、对IEEE 754浮点数的改进、支持可变参数个数的宏定义,并在数据类型上增加了long long int与复数类型。
2007年,C语言标准委员会重新修订C语言,并在2011年正式发布ISO/IEC 9899: 2011(C11)。C11标准为C语言和库增加了许多新特性,包括字节对齐说明符、泛型宏(Type Generic Macros)、对多线程的支持、静态断言、边界检查、原子操作以及对 Unicode 的支持。
2018年,C语言标准委员会发布了C语言的最新标准——ISO/IEC 9899: 2018(C17)。C17标准并没有引入新的语言特性,只是对C11标准做了一些技术纠正,并对一些缺陷进行了澄清。
C语言版本的发展的年份和GCC命令行选项见表1.1。
表1.1 C语言版本的发展概况
| C语言版本 |
年份 |
GCC命令行选项 |
|---|---|---|
| K&R C |
1978年 |
— |
| ANSI C |
1989年 |
-ansi, |
| C90 |
1990年 |
-std=c89 |
| C99 |
1999年 |
-std=c99 |
| C11 |
2011年 |
-std=c11 |
| C17 |
2018年 |
-std=c17 |
从某种程度而言,C语言已经成为开发人员的通用语言。作为一门古老且持续流行的语言,许多编程和算法知识都可以找到相应的C语言实现。此外,书本中或互联网上许多新算法的实现,首先(或仅)由其作者以C语言代码的形式提供。
另外,C语言中的程序结构、函数栈、指针等核心概念都与计算机体系结构紧密相关,因此,学习C语言最大的一个好处在于能够增强对计算机的了解。也就是说,如果掌握了C语言的知识,无疑可以提升对计算机知识体系的了解和掌握。
与计算机交流时,要使用计算机可“理解”的语言。尽管这种语言不是我们日常生活中的自然语言,但是学习C语言和学习自然语言(如中文)肯定是有相似之处的,如图1.1所示。

图1.1 学习中文与学习C语言的步骤

图1.1 学习中文与学习C语言的步骤(续)
学习中文首先需要学习汉字,这在C语言的学习中对应着学习标识符;然后通过汉字可以组成词,这在C语言的学习中对应着由标识符组成的常量、变量和关键字;接着在中文学习的过程中,需要通过词组合成词组,这在C语言的学习中对应着表达式;中文学习的第四步是通过词组组成句子,这在C语言的学习中对应着程序设计的语句;最后一步是用句子组合成一段有意义的段落,这在C语言的学习中就对应着一段完整的程序。
请在你所使用的操作系统(如Windows、Ubuntu)中安装一个Dev-C++开发环境,并尝试使用Dev-C++编程开发环境编译与运行“Hello World!”程序。
|
(1)Dev-C++的下载地址:https://sourceforge.net/projects/orwelldevcpp/files/latest/download。 (2)按照提示在操作系统中安装Dev-C++。 (3)编译与运行程序。 |
首先,安装Dev-C++软件,然后就可以开始编写程序了。在Dev-C++主界面的“文件”菜单下选择“新建”→“源代码”命令,如习题图1.1所示。

习题图1.1
接着在编辑器中编写代码,代码内容如下。
#include<stdio.h>
int main()
{
printf("Hello World!");
return 0;
} |
在代码编写完成后,需要将源代码文件保存起来(见习题图1.2),并为文件命名,如“hello.c”。

习题图1.2
之后就可以开始编译与运行,如习题图1.3所示。

习题图1.3
在“运行”菜单中可以看到3个执行程序的重要选项:“编译”“运行”以及“编译运行”。
1)“编译”就是检查和确认程序的内容是否正确,只有程序内容正确才可以运行程序。
2)“运行”就是直接运行可执行文件。
3)“编译运行”就是把前两者结合到一起执行。
这3个选项既可以在“运行”菜单中单击后运行,也可以直接使用对应的快捷键运行,非常方便。
单击“编译运行”命令后,编译器将进行编译、链接等操作,最后运行生成的程序,结果如习题图1.4所示。

习题图1.4
第02章 C语言程序基础C语言允许使用的字母、数字和特殊符号如表2.1所示。
表2.1 C语言允许使用的字母、数字和特殊符号
| 类型 |
对应的内容 |
|---|---|
| 字母 |
A、B、…、Y、Z a、b、…、y、z |
| 数字 |
0、1、2、3、4、5、6、7、8、9 |
| 特殊符号 |
~、!、@、#、%、^、&、*、( )、_、-、+、=、|、\、{ }、[ ]、:、;、"、'、< >、,、.、?、/ |
将字母、数字、特殊符号进行合理的组合就形成了常量、变量和关键字。常量是不会变化的实体,而变量则是可以改变的实体。关键字是携带特殊含义的字。在编程语言中,常量通常被称为文字(literal),而变量通常被称为标识符(identifier)。
1.常量
在程序执行过程中,值不发生改变的量被称为常量。常量主要分为一级常量和二级常量,具体内容如图2.1所示。

图2.1 常量的类型
这里主要讨论一级常量中的整型常量、实型常量和字符型常量。
(1)整型常量
整型常量的构造规则如下。
1)一个整型常量至少由一位数构成。
2)整型常量不能有小数点。
3)整型常量可以是0、正数或者负数。如果在整型常量前没有出现符号,通常假定为正数。
4)在整型常量中不允许出现冒号或者空格。
5)整型常量可允许的取值范围是-2147483648~2147483647(这里以int类型为例,当然,准确的值可能还与使用的编译器相关)。
整型常量的典型例子如426、728、-800、-7705等。
(2)实型常量
实型常量也被称为浮点型常量。实型常量包括分数型常量和指数型常量。
分数型常量的构造规则如下。
1)分数型常量至少有一位数。
2)分数型常量必须有一个小数点。
3)分数型常量要么为正,要么为负;默认符号为正。
4)在分数型常量中不允许出现冒号或者空格。
分数型常量的典型例子如325.34、426.0、-32.76、-48.5687等。
当实型常量的值过小或过大时,通常会使用指数型常量表示。以指数型常量表示时,实型常量可被视为由两部分组成:在“e”或“E”之前的部分被称为尾数部分,在“e”或“E”之后的部分被称为指数部分。例如,0.000342记作3.42e-4(3.42×10-4)。
指数型常量的构造规则如下。
1)尾数部分和指数部分应该通过一个字母e或者E分隔开。
2)尾数部分必须有一个正号或者负号;默认符号为正。
3)指数部分至少有一位数,可以是正数,也可以是负数;默认符号为正。
4)以指数形式表达的实型常量范围是-3.4e38~3.4e38。
指数型常量的典型例子如3.2e-5、4.1e8、-0.2E3、-3.2e-5等。
(3)字符型常量
字符型常量的构造规则如下:一个字符型常量是由一对单引号(')括起的单个字母、单个数字或者单个特殊符号。
字符型常量的典型例子如'a'、'b'、'='、'5'、'A'等。
2.变量
一个变量应该有一个名字,在内存中占据一定的存储单元。变量定义必须放在变量使用之前。要注意变量名和变量值是两个不同的概念,如图2.2所示。

图2.2 变量名与变量值
变量定义的一般形式如下。
| 类型说明符 |
变量名标识符 |
, |
变量名标识符 |
, |
…… |
; |
其中,变量名标识符的构造规则如下。
1)一个变量名由字母、数字和下画线(_)组成。一些编译器允许变量名的长度达247个字符。要尽量避免创建不必要的长变量名。
2)变量名的第一个符号必须是字母或者下画线(_)。
变量名(标识符)的典型例子如si_int、Avg、basic_salary、pop_e等。
3.关键字
关键字是一些预定义的符号,这些符号的含义已经向C编译器进行过解释。这些关键字不能用作变量名——如果将关键字用作变量名,则相当于尝试为关键字指派一个新的含义,这对于编译器而言是不允许的。因此,关键字通常也被称为“保留字”。C语言的常用关键字如图2.3所示。

图2.3 C语言的常用关键字
与许多其他编程语言一样,C语言也具有基本的数据类型。C语言的数据类型可分为基本类型、构造类型、指针类型和空类型(void)四大类型,如图2.4所示。

图2.4 数据类型
其中,基本类型包括整型、实型、字符型等。
1.整型
整型包括许多种不同的子类型,其典型的取值范围和字节数如表2.2所示。
表2.2 整型的分类及其典型的取值范围和字节数
| 类型说明符 |
取值的范围 |
字节数 |
|---|---|---|
| int |
-2147483648~2147483647,即-231~(231-1) |
4 |
| unsigned int |
0~4294967295,即0~(232-1) |
4 |
| short int |
-32768~32767,即-215~(215-1) |
2 |
| unsigned short int |
0~65535,即0~(216-1) |
2 |
| long int |
-2147483648~2147483647,即-231~(231-1) |
4 |
| unsigned long |
0~4294967295,即0~(232-1) |
4 |
| long long |
-9223372036854775808~9223372036854775807,即-263~(263-1) |
8 |
| unsigned long long |
0~18446744073709551615,即0~(264-1) |
8 |
注:long int类型在Windows操作系统中为4字节,在64位Linux操作系统中为8字节。
整型变量的定义如下。
int Age; |
表示定义一个变量名为Age的整型变量。
2.实型
实型分为单精度浮点型(float)、双精度浮点型(double)和长双精度浮点型(long double)3类,如表2.3所示。
表2.3 实型的分类及其典型的取值范围和字节数
| 类型说明符 |
取值的范围 |
字节数 |
|---|---|---|
| float |
10-37~1038 |
4 |
| double |
10-307~10308 |
8 |
| long double |
10-4931~104932 |
16 |
注:long double类型的字节数在不同操作系统中具有不同的值。
实型变量定义的格式和书写规则与整型相同,例如,float x,y; (定义单精度浮点型变量),double a,b,c; (定义双精度浮点型变量)。
3.字符型
字符变量定义如下。
char myName; |
数据类型的大小与计算机的字长(word size)密切相关。字长通常是CPU一次可以自然处理的二进制位数,例如32位、64位。它通常决定了指针的大小以及内存对齐方式,而数据类型的大小则受编译器、硬件平台、操作系统等多个因素的共同影响。表2.4列出了典型情况中,32位字长和64位字长下主要数据类型的大小。
表2.4 32位和64位字长下主要数据类型的大小
| C声明 |
字节数 |
||
|---|---|---|---|
| 有符号 |
无符号 |
32位系统 |
64位系统 |
| char |
unsigned char |
1 |
1 |
| short |
unsigned short |
2 |
2 |
| int |
unsigned int |
4 |
4 |
| long |
unsigned long |
4 |
8 |
| int32_t |
uint32_t |
4 |
4 |
| int64_t |
uint64_t |
8 |
8 |
| char * |
— |
4 |
8 |
| float |
— |
4 |
4 |
| double |
— |
8 |
8 |
在字符集中,有一类字符具有这样的特性:当从键盘上输入这个字符时,在显示器上就可以显示这个字符,即输入什么就显示什么。这类字符被称为可显示字符,如a、b、c、$、+和空格符等都是可显示字符。
另一类字符没有这种特性。它们在键盘上找不到对应的键(当然可以用特殊方式输入),或者按键后不能显示键面上的字符。其实,这类字符是为控制而设计的,故被称为控制字符。
在C语言中,构成字符常量的控制字符必须用转义字符表示。转义字符是C语言中表示字符的一种特殊形式,以“\”开头,后面是一个字符或一个八进制/十六进制数。例如,退格符用“\b”表示,换行符用“\n”表示。转义字符中的“\”表示它后面的字符已失去原来的含义,转变成了另外的特定含义。反斜杠与其后面的字符共同构成一个特定的字符。常用的转义字符及其含义如表2.5所示。
表2.5 常用的转义字符及其含义
| 转义字符 |
含义 |
ASCII码 |
|---|---|---|
| \n |
换行 |
10 |
| \t |
横向跳到下一制表位置 |
9 |
| \b |
退格 |
8 |
| \r |
回车 |
13 |
| \f |
走纸换页 |
12 |
| \\ |
反斜杠(\) |
92 |
| \' |
单引号符 |
39 |
| \" |
双引号符 |
34 |
| \a |
鸣铃 |
7 |
| \ddd |
1~3位八进制数所代表的字符 |
— |
| \xhh |
1~2位十六进制数所代表的字符 |
— |
通常,使用转义字符表示ASCII码字符集中不可打印的控制字符和特定功能的字符,如用于表示字符常量的单引号('),用于表示字符串常量的双引号(")和反斜杠(\)等。
C语言中最小的单元为token。关键字、标识符、常量、字符串文字和运算符都属于token。标点符号也属于token,如方括号 ([ ])、大括号 ({ })、圆括号 ( ( ) ) 和逗号 (,)等。
例2.1 如下C语句的token数是 。
printf("i = %d, &i = %x", i, &i);
A.3
B.26
C.10
D.21
答案:C。
解析如下:
语句printf("Hello, World! \n"); 包含如下token:printf、(、"Hello, World! \n"、)、;。因此共有5个token。
例2.1中的语句printf("i = %d, &i = %x", i, &i);包含10个token:printf、(、"i = %d, &i = %x"、,、i、,、&、i、)、;。
输入/输出是以计算机主机为主体而言的。从设备(如键盘、磁盘、光盘、扫描仪等)向计算机传输数据被称为输入,从计算机向设备(如显示器、打印机等)传输数据被称为输出。
在C语言中,所有数据的输入/输出都是由库函数完成的,因此都是函数语句。
在使用C语言库函数时,要用预编译命令#include将相关头文件包括到源文件中。
由于在使用标准输入/输出库函数时需要用到stdio.h头文件,因此源文件开头应有以下预编译命令。
#include<stdio.h> |
或
#include "stdio.h" |
stdio是标准输入与输出(Standard Input & Output)的意思。
1.putchar()
putchar()是字符输出函数,其功能是在显示器上输出单个字符,函数原型如下。
| putchar |
( |
字符变量 |
) |
○ putchar('A')表示输出大写字母A。
○ putchar(x)表示输出字符变量x的值。
○ putchar('\101')表示输出字符A(通过对应的ASCII码)。
○ putchar('\n')表示换行。
对控制字符,putchar()执行控制功能,不在屏幕上显示。
在使用putchar()前,必须包含stdio.h头文件。
2.getchar()
getchar()的功能是从键盘上输入一个字符,函数原型如下。
| getchar |
( |
) |
getchar()通常赋予输入的字符为一个字符变量,所以能构成赋值语句。
char c; c=getchar(); |
使用getchar()还应注意以下几个问题。
1)getchar()只能接收单个字符,输入的数字也按字符处理。当输入多于一个字符时,只接收第一个字符。
2)在使用getchar()前,必须包含stdio.h头文件。
1.printf()
虽然printf()是一个标准库函数,它的函数原型在stdio.h头文件中,但作为一个特例,不要求在使用 printf()之前必须包含stdio.h头文件。
printf()调用的函数原型如下。
| printf |
( |
" |
格式控制字符串 |
" |
, |
输出表列 |
) |
; |
其中,格式控制字符串用于指定输出格式。格式控制字符串可由格式字符串和非格式字符串组成。格式字符串是以%开头的字符串,在%后接各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。
1)“%d”表示按十进制整型输出。
2)“%ld”表示按十进制长整型输出。
3)“%c”表示按字符型输出。
非格式字符串在输出时照原样打印输出,在显示中起提示作用。
输出表列给出了各个输出项,要求格式字符串和各输出项在数量和类型上一一对应。
1)printf("a=%d,%5d,%o,%x\n",a,a,a,a);
2)printf("b=%f,%lf,%5.4lf,%e\n",b,b,b,b);
3)printf("c=%lf,%f,%8.4lf\n",c,c,c);
4)printf("d=%c,%8c\n",d,d);
格式控制字符串的一般形式如下。
| [标志] |
[输出最小宽度] |
[.精度] |
[长度] |
类型 |
其中,方括号([])中的项为可选项。类型字符用以表示输出数据的类型,其格式字符和含义如表2.6所示。
表2.6 格式字符及其含义
| 格式字符 |
含义 |
|---|---|
| d |
以十进制形式输出带符号的整数(正数不输出符号) |
| o |
以八进制形式输出无符号的整数(不输出前缀o) |
| x,X |
以十六进制形式输出无符号的整数(不输出前缀ox) |
| u |
以十进制形式输出无符号的整数 |
| f |
以小数形式输出单、双精度实数 |
| e,E |
以指数形式输出单、双精度实数 |
| g,G |
以%f或%e中较短的输出宽度输出单、双精度实数 |
| c |
输出单个字符 |
| s |
输出字符串 |
常用数据类型对应的格式符如表2.7所示。
表2.7 常用数据类型对应的格式符
| 数据类型 |
格式符 |
|
|---|---|---|
| 字符型 |
signed char |
%c |
| unsigned char |
%c |
|
| 整型 |
signed short |
%d |
| unsigned short |
%u |
|
| singed long |
%ld |
|
| unsigned long |
%lu |
|
| 整型 |
unsigned hexadecimal |
%x |
| unsigned octal |
%o |
|
| 实型 |
float |
%f |
| double |
%lf |
|
| 字符串 |
char [] |
%s |
%ld中的l是长度修饰符,表示对应的格式字符为long类型。%ld表示long类型,%lf表示double类型。在格式控制字符串中,标志字符为-、+、#、空格4种,其含义如表2.8所示。
表2.8 标志字符及其含义
| 字符 |
含义 |
|---|---|
| - |
结果左对齐,右边填空格 |
| + |
输出符号(正号或负号) |
| 空格 |
输出值为正时冠以空格;输出值为负时冠以负号 |
| # |
对c、s、d、u格式字符无影响;对于o格式字符,在输出时加前缀o;对于x格式字符,在输出时加前缀0x;对于e/E、f 格式字符,强制输出小数点;对于g/G格式字符,强制保留小数点和尾随的零 |
例2.2 将字符'A'以字符、十进制数和八进制数的方式输出。system("pause")的含义是调用操作系统的命令行,执行一条名为pause的系统命令,从而暂停程序执行,等待用户按键。通常需要包含stdlib.h头文件。
#include<stdio.h>
#include<stdlib.h>
int main()
{
printf("%c\n",'A');
printf("%d\n",'A');
printf("%o\n",'A');
system("pause");
return 0;
} |
2.scanf()
scanf()被称为格式输入函数,即按用户指定的格式,从键盘上将数据输入到指定的变量中。
scanf()的函数原型如下。
| scanf |
( |
" |
格式控制字符串 |
" |
, |
地址列表 |
) |
; |
其中,格式控制字符串用于指定输入格式,但不能显示非格式字符串,即不能显示提示字符串。在地址列表中给出的各变量的地址是由地址运算符“&”后接变量名组成的。例如,&a、&b分别表示变量a和变量b 的地址。
scanf("%5d",&a); |
在上述例子中输入“12345678”时,只将12345赋予变量a,其余部分会被截去。
scanf("%4d%4d",&a,&b); |
在上述例子中输入“12345678”时,将1234赋予变量a,将5678赋予变量b。
使用scanf()还应注意以下几点。
1)scanf()中没有精度控制,如“scanf("%5.2f",&a);”是非法的,不能用此语句输入小数位数为两位的实数。
2)scanf()要求给出变量地址,如果给出变量名,则会出错。例如“scanf("%d",a);”是非法的,“scnaf("%d",&a);”才是合法的。
3)在输入多个数值时,若格式控制字符串中没有非格式字符作为输入数据之间的间隔符,则可用空格、Tab或回车作为间隔符。C编译在遇到空格、Tab、回车或非法数据(如对“%d”输入“12A”时,A为非法数据)时,即认为该数据结束。
4)在输入字符数据时,若格式控制字符串中无非格式字符,则认为所有输入的字符均为有效字符。
scanf("%c%c%c",&a,&b,&c); |
上述例子输入为d e,将“d”赋予a,“ ”赋予b,“e”赋予c。
只有输入为def,才能将“d”赋予a,“e”赋予b,“f”赋予c。
如果在格式控制字符串中加入空格作为间隔符,则输入时各数据之间可加空格。
scanf ("%c %c %c",&a,&b,&c); |
在C语言中,printf和scanf格式字符串与后面的变量是一一对应的,例如定义以下3个变量。
char myCode; int myAge; float myWeight; |
那么,scanf语句如下所示。
scanf("%c%d%f",&myCode, &myAge, &myWeight); |
其中,若每个格式符对应后面的一个变量,则要求类型也一一对应,这类似于填空。%c对应myCode,%d对应myAge,%f对应myWeight。
|
不同的数据类型对应不同的格式符。 |
输入输出的格式可以类比为图2.5所示的多功能格尺。首先,输入输出的格式字符串会“抠”出几个洞;然后,紧跟其后的变量会相应地按序进行填充。

图2.5 多功能格尺
C语言运算符的类型如表2.9所示。
表2.9 C语言运算符的类型
| 运算符 |
具体类型 |
|---|---|
| 算术运算符 |
加(+)、减(-)、乘(*)、除(/)、取余(或称模运算,%)、自增(++)、自减(--) |
| 关系运算符 |
大于(>)、小于(<)、等于(==)、大于或等于(>=)、小于或等于(<=)和不等于(!=) |
| 逻辑运算符 |
与(&&)、或(||)、非(!) |
| 位操作运算符 |
位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>) |
| 赋值运算符 |
简单赋值(=)、复合算术赋值(+=、-=、*=、/=、%=)和复合位运算赋值(&=、|=、^=、>>=、<<=) |
| 条件运算符 |
条件求值(? :) |
| 逗号运算符 |
组合成一个表达式(,) |
| 指针运算符 |
取内容(*)和取地址(&) |
| 求字节数运算符 |
计算数据类型所占的字节数(sizeof) |
| 特殊运算符 |
括号()、下标[]、成员(→,.)等 |
在C语言中,由赋值运算符表达的赋值表达式的形式如下。
| lhs |
赋值运算符 |
rhs |
; |
其中,lhs是任意完全对象类型的可修改的左值(lvalue)表达式,rhs是与lhs兼容或者可以潜在转换为lhs类型的表达式。赋值运算符则包括简单赋值运算符和复合赋值运算符。这里的左值指的是一个具有存储位置的实体表达式,左值必须有存储空间,可以容纳变量的值。
1.简单赋值运算符
简单赋值运算符为“=”。
char MyCode; MyCode = "z"; |
例2.3 编译运行下述C语言程序,观察结果并做出解释。
#include<stdio.h>
int main()
{
int lhs;
lhs++ = 4;
printf("%d\n",lhs);
return 0;
}编译过程报错,错误提示如下。
[Error] lvalue required as left operand of assignment |
原因在于lhs++不是一个左值。
2.复合赋值运算符
在赋值运算符“=”之前加上其他双目运算符可构成复合赋值运算符,如+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=,具体的运算符如表2.10所示。
表2.10 复合赋值运算符
| 运算符 |
含义 |
|---|---|
| *= |
乘赋值 |
| /= |
除赋值 |
| %= |
取余赋值 |
| += |
加赋值 |
| -= |
减赋值 |
| <<= |
左移赋值 |
| >>= |
右移赋值 |
| &= |
按位与赋值 |
| ^= |
按位异或赋值 |
| |= |
按位或赋值 |
构成复合赋值表达式的一般形式如下。
| 变量 |
双目运算符 |
= |
表达式 |
; |
上述形式等价于下列形式。
| 变量 |
= |
变量 |
双目运算符 |
表达式 |
; |
int a=1; a+=3; |
上述代码等价于下列代码。
a=a+3; |
例2.4 使用C语言表达出y = ax2+bx+c,其中,x、y是实数,a、b、c是整型数。
乍看起来,此题首先定义变量,凡是要使用的变量必须首先定义,将a、b、c定义为整型变量,将x、y定义为实型变量。
然后给出ax2+bx+c的算术表达式,并赋值给y。
然而在C语言中,无法用“x2”表达x的二次方,只能使用“x*x”或“x^2”来表示x2。当然,如果需要使用更多的幂运算,则可能需要使用库函数pow(参见附录C)或者自己编程实现。正确的代码如下。
int main()
{
int a,b,c;
float x,y;
y = a*x*x+b*x+c;
return 0;
} |
|
从语法上来讲,虽然上例的程序片段是正确的,但是其中的变量都没有实际赋值。这样做并不可取,通常也是不对的。 |
C语言中内置的算术运算符只有加(+)、减(-)、乘(*)、除(/)、取余(或称模运算,%)、自增(++)、自减(--)这7种。更复杂的算术运算是通过这些基本的运算来实现。
3.赋值运算中的类型转换
在赋值运算中,有可能出现表达式的类型和左值变量的类型不相同的情形。在这种情形下,表达式的值类型会根据赋值符号左值的变量类型进行转换。
例2.5 观察下述程序的输出,并体会赋值运算中的类型转换。
#include<stdio.h>
int main()
{
int i;
float b;
i = 3.5;
b = 30;
printf("i=%d,b=%f",i,b);
return 0;
}在上述程序中,由于3.5是一个浮点数,它不能存储在整数变量中,此时浮点型(float)转换为整型(int)。与之相反,30转换为30.000000,并存储在浮点型变量b中。
在C语言中,逗号(,)也是一种运算符,其功能是把两个表达式连接起来组成一个表达式,被称为逗号表达式,一般形式如下。
| 表达式1 |
, |
表达式2 |
从语法角度来看,通过表达式就可以组成语句。
1.基本的算术运算符
加法运算符“+”:双目运算符,即应有两个量参与加法运算,如a+b、4+8等,具有左结合性。
减法运算符“-”:双目运算符。“-”也可作为负值运算符,此时为单目运算符,如-x、-5等。具有左结合性。
乘法运算符“*”:双目运算符,具有左结合性。
除法运算符“/”:双目运算符,具有左结合性;当参与的运算量均为整型时,结果也为整型,舍去小数;如果运算量中有一个是实型,则结果为双精度实型。
2.自增、自减运算符
自增、自减运算符的作用是使变量的值加1或减1。
++i(--i):在使用i之前,使i的值加(减)1。
i++(i--):在使用i之后,使i的值加(减)1。
例2.6 请给出下列两个程序的运行结果。
#include<stdio.h>
int main()
{
int i=3,j;
j = ++i;
printf("%d\n",j);
return 0;
} |
程序的运行结果:4。
#include<stdio.h>
int main()
{
int i=3,j;
j = i++;
printf("%d\n",j);
return 0;
} |
程序的运行结果:3。
例2.7 与k=n++完全等价的表达式是 。
A.k=n, n=n+1
B.k+=n+1
C.k=++n
D.n=n+1, k=n
答案:A
解析:k=n++等价于k=n,n=n+1,因此选项A正确;选项B和选项C错误;值得注意的是,选项D等价于k=++n。
例2.8 请给出下列C语言程序的输出结果。
#include<stdio.h>
int main()
{
int i=10;
printf("%d,%d,%d,%d\n",i++,++i,i,i++);
printf("%d\n",i);
return 0;
} |
输出结果如下。
12,13,13,10 13 |
需要注意的是,之所以会输出这样的结果,原因在于printf()对参数的计算顺序是从右向左的。使用不同编译器可能会产生不同的结果。
一个完整的程序
我们以一个完整的C语言程序为例,介绍一下它的组成,如图2.6所示。

图2.6 一个完整的C语言程序
以上述程序为例,一个完整的C语言程序可能包含一个头文件,具体包含什么样的头文件,取决于你在程序中使用什么样的库函数。程序至少要包含一个名为main的主函数,它的返回类型为int类型;在函数体内,根据你所需要完成的任务,定义相应的变量并使用相应的语句,其中语句末尾通常以分号(;)结束。
对于一个完整的C语言程序而言,需要注意以下几个方面。
1)一个C语言程序必须包括一个主函数main()。
2)函数体或者语句块使用大括号({})括起来。
3)语句用分号(;)结尾。
C语言的注释包括块注释和行注释。
/* 这是块注释 */ |
//这是行注释 |
此外,注释可以嵌套。
/* firstprogram.c /*作者 Chen Peng */ */ |
C语言是一种有类型的程序设计语言,在声明变量的时候会给出对应的数据类型。这个数据类型是变量的重要属性,在输入/输出和进行各类运算时,数据类型和变量必须一一对应和匹配,如图2.7所示。

图2.7 输入/输出的格式匹配
仔细观察上述程序:在输入/输出时,格式字符串中的格式与后面对应变量的数据类型是一一对应并完全匹配的。
另外,在程序运行过程中,有时候会出现输入缓冲区的数据不一致,可以使用下面的语句来清空输入缓冲区。
fflush(stdin); |
1)C语言是强类型的语言,不同的数据类型在计算机中所占据的空间和存储方式都不同。
2)常量(文字)是不可改变的,而变量(标识符)是可以改变的。
3)要区分字符和字符串。'a'是一个字符,而"a"则是一个字符串(参见第5章的字符数组)。
4)变量具有两层含义:一是作为内存地址的标识,二是作为可变数据的载体。此外,变量名对大小写敏感。
5)在printf( )和scanf( )的格式字符串中,int对应%i,float对应%f,char对应%c。
6)自增(++)和自减(--)位于变量前或后,含义有所区别。
7)&是取址符,在scanf()的格式字符串中,必须在变量前使用&。
8)注释包括块注释(/*...*/)和行注释(//)。
1.指出下列变量名中有效的变量名和无效的变量名,并给出理由。
| BASICSALARY |
_basic |
basic-hra |
| #MEAN |
group. |
422 |
| population in 2006 |
over time |
mindovermatter |
| FLOAT |
hELLO |
queue. |
| team’svictory |
Plot # 3 |
2015_DDay |
2.一个C语言变量的起始符号是 。
A.字母
B.数字
C.下画线(_)之外的所有特殊符号
D.B和C都正确
3.下列语句中错误的是 。
A.mes = 123.56;
B.con = 'T' * 'A';
C.this = 'T' * 20;
D.3 + a = b;
4.语句:
char ch = 'Z';
将在字符变量ch中存储 。
A.字符Z
B.Z的ASCII值
C.Z以及单引号
D.A和B都正确
5.下列中不是字符常量的是 。
A.'Thank You'
B.'Enter values of P, N, R'
C.'23.56E-03'
D.上述所有选项
6.如果有错误,请指出下列C语言语句中错误的内容。
(1) int = 314.562 * 150 ;
(2) name = 'Ajay' ;
(3) varchar = '3' ;
(4) 3.14 * r * r * h = vol_of_cyl ;
(5)k = ( a * b ) ( c + ( 2.5a + b ) ( d + e ) ;
(6) m_inst = rate of interest * amount in rs ;
(7) si = principal * rateofinterest * numberofyears / 100 ;
(8)area = 3.14 * r ** 2 ;
(9)volume = 3.14 * r ^ 2 * h ;
(10)k = ( (a * b ) + c ) ( 2.5 * a + b ) ;
(11)a = b = 3 = 4 ;
(12)count = count + 1 ;
(13)date = '2 Mar 04' ;
7.请给出下列程序的输出结果。
(1)程序1
#include<stdio.h>
int main( )
{
int i = 2, j = 3, k, l ;
float a, b ;
k = i / j * j ;
l = j / i * i ;
a = i / j * j ;
b = j / i * i ;
printf( "%d %d %f %f", k, l, a, b ) ;
return 0;
} |
(2)程序2
#include<stdio.h>
int main( )
{
int a, b ;
a = -3 - - 3 ;
b = -3 - - ( - 3 ) ;
printf ( "a = %d b = %d", a, b ) ;
return 0;
} |
(3)程序3
#include<stdio.h>
int main( )
{
float a = 5, b = 2 ;
int c ;
c = a % b ;
printf ( "%d", c ) ;
return 0;
} |
(4)程序4
#include<stdio.h>
int main( )
{
printf ( "nn \n\n nn\n" ) ;
printf ( "nn /n/n nn/n" ) ;
return 0;
} |
(5)程序5
#include<stdio.h>
int main( )
{
int a, b ;
printf ( "Enter values of a and b" ) ;
scanf ( " %d %d ", &a, &b ) ;
printf ( "a = %d b = %d", a, b ) ;
return 0;
} |
(6)程序6
#include<stdio.h>
int main( )
{
int p, q ;
printf ( "Enter values of p and q" ) ;
scanf ( " %d %d ", p, q ) ;
printf ( "p = %d q =%d", p, q ) ;
return 0;
} |
(7)程序7
#include<stdio.h>
int main()
{
int x=12;
printf("%d,%o,%x,%u,",x,x,x,x);
return 0;
} |
(8)程序8
#include<stdio.h>
int main()
{
int x=235;
double y=3.1415926;
printf("x=%-6d,y=%-14.5f\n",x,y);
return 0;
} |
(9)程序9
#include<stdio.h>
int main()
{
printf("%f,%4.2f\n",3.14,3.14159);
return 0;
} |
(10)程序10
#include<stdio.h>
int main()
{
printf("*\n**\n***\n****\n");
return 0;
} |
(11)程序11
#include<stdio.h>
int main()
{
printf("This\tis\ta\tC\tprogram.\n");
return 0;
} |
(12)程序12
#include<stdio.h>
int main()
{
char x='a',y='b';
float z=3.125;
printf("%e\\%c\n",z,y);
printf("x=\'%3x\',\'%-3x\'\n",x,x);
return 0;
} |
8.按要求编写程序。
1)通过键盘输入学生5门课程的分数,并计算、输出该学生5门课程的平均分数。假设每门课程的满分是100分,则分数值精确到小数点后一位。
2)通过键盘输入以华氏度为单位的温度值,请将该温度值转换为摄氏度的温度值并输出。
提示:摄氏度值 = (华氏度值-32) / 1.8
3)通过键盘输入矩形的长、宽和圆的半径,并编写程序计算矩形的面积、周长及圆的面积、周长。
4)通过键盘将两个数字输入到C、D两个位置并编写一个程序来交换 C、D 的内容。
5)通过键盘输入一个5位数,并编写一个程序来计算各位数字的总和。
提示:使用取余运算符“%”。
6)通过键盘输入一个5位数,并编写一个程序反转该数字。
7)通过键盘输入一个4位数,并编写一个程序获得该数字的第一位和最后一位数字之和。
8)在一个城镇中,男性占比是52%,总识字率是48%。假设识字男性占总人口的35%,编写一个程序计算出男性和女性文盲的总数(假设全镇人口数为8万)。
9)出纳员有面额为10、50和100的纸币。如果要提取的金额以百为单位,通过键盘输入,编写程序找出出纳员必须提供给取款人的每种面额纸币的总数。
10)通过键盘输入 15 件商品的总售价和从这些商品上赚取的总利润,并编写一个程序计算出一件商品的平均成本价。
11)通过键盘输入一个5位数,编写一个程序打印一个新数字,使该5位数每位数字加1,如输入的数字是12391,则输出应该显示为23402。