深入浅出Linux工具与编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2篇 Linux C语言程序设计

学海聆听:

· 凡事预则立,不预则废。

· 万变不离其中,透过现象看本质。

· 天生我材必有用。

· 形而上者谓之道,形而下者谓之器。

· 勤能补拙,熟能生巧;光说不练,枉学百年。

· 择善人而交,择善书而读,择善言而听,择善行而从。

· 博学而笃志,切问而近思。

· 操千曲而后晓声,观千剑而后识器。

· 发现问题,提出问题,分析问题,解决问题,总结问题。

· 大道至简,道法自然。

· 天行健,君子以自强不息;地势坤,君子以厚德载物。

· 天道酬勤,身体是革命的本钱。

第4章 C语言基础

C语言是计算机编程中最典型、最常用的语言,如操作系统、数据库、通信软件和金融、电力等行业软件都是用C语言编的。C++语言是C语言的扩展,而Java语言许多语法又与C语言类似。所以作者认为,学好计算机请首先学好C语言,C语言是计算机软件行业的地基,学好C语言可以对其他计算机语言触类旁通、一通百通,C语言,编程世界的王者。学好C语言,需要理解和掌握指针,理解了指针就一定程度上理解了C语言,掌握好指针是晋升Linux C高级程序员的必要条件。

4.1 C语言基本概念

计算机语言与人类语言一样,都是一种交流的工具。人类语言是人与人之间交流的工具,计算机语言是人与计算机之间交流的工具。所有语言都有它的语法、语素和语用,都有它的语法规则,这样才能被交流的双方相互理解。计算机语言也不例外,我们编写的计算机程序也必须遵守一定的语法规则,才能被编译器所识别,最后翻译成能被CPU理解并执行的机器语言,其中机器语言是CPU厂商设计的。

1.计算机语言相关概念解释

[1]编译程序:编译程序又称为编译器,是一个语言翻译程序,它把源语言翻译成目标语言。源语言主要指各种计算机语言,目标语言主要指CPU能识别的机器码。例如,英语翻译成汉语的过程中,翻译官相当于编译器。

[2]编译:编译源语言,生成目标代码并形成机器码的过程,相当于现实翻译中的笔译。C语言属编译型语言。

[3]解释:把源语言解释成机器码边执行的过程,相当于现实翻译中的口译。Shell脚本属解释型语言。

[4]编译阶段:编译阶段包括词法分析、语法分析、语义分析、中间代码生成、代码优化、目标代码生成六个阶段,贯穿始终的功能模块有表格管理和出错管理。

[5]为什么需要编译程序:正如我们的母语是汉语,当我们想与一个埃及人交流时,虽然我们不懂阿拉伯语,但如果有一个自动语言翻译机(或翻译官),双方的交流就能变得流畅。由于CPU指令是二进制指令,人们难以在此基础上进行有效编程,所以发明了各种各样的计算机高级语言,然后通过编译器把人们编写的计算机高级语言程序翻译成CPU能理解的二进制指令。

[6]程序:通常指的是人们编写的计算机语言源代码和编译后的执行码,其中源代码称为源程序,执行码称为执行程序,程序是静态的。程序相当于一个企业行政部门出台的行政管理文件。

[7]进程:程序的一次执行过程,进程是动态的。进程相当于企业各部门拿着行政管理文件的执行过程。

[8]CPU:CPU主要与内存通信,其主要功能有解释指令、存数据到内存、从内存取数据、数据计算和中断处理,CPU最突出的功能为解释指令和数据计算。CPU的工作是从内存中取出指令并完成指令的自动化执行。

[9]内存:内存是存放电脑工作数据的空间,断电后数据丢失。内存的最小单位为字节,内存每一个最小单位空间都有其编号(即内存地址), CPU是通过内存地址访问内存数据的。在内存看来,内存里存放的数据都是平等的,都是一串串的二进制字符,没有类型,也没有含义,是计算机程序为内存数据赋予特别的类型和意义。

[10]变量:一段内存空间的抽象,变量类型决定了变量存放内存空间的大小。将计算机语言编译成机器码后,变量相对于内存的操作就转变为对相应内存地址的存取操作。直接存取是一般变量,间接存取是指针变量。C语言变量原则为先定义,后使用。

[11]机器程序:经过编译器编译后形成CPU能理解的机器指令。在机器程序中,只有内存地址,没有变量概念,机器程序中所有变量失去意义,变量的操作都会转换成对内存地址的存取操作。机器程序有如下几种类型的操作:CPU的计算操作、内存空间的申请与释放、CPU把寄存器数据存到内存、取内存数据到CPU寄存器、内存数据从一片空间复制到另一片空间、中断操作等。

[12]Linux系统编译方法:gcc(或cc)a.c(源代码名称)-o a(执行码)。在这里a.c和a都是程序,其中a.c为源程序,a为可执行程序。在界面上输入./a, a就开始了执行之旅,其执行过程称为进程。

2.C语言数据类型种类

图4-1给出了C语言数据类型种类,在C语言中只允许使用下面这些数据类型。

3.32个关键字

C语言有如下32个关键字,这些关键字由系统定义,不能重新做其他定义。

      auto   break   case     char  const   continue   default  do    double
  else   enum   extern   float    for    goto   int   long  register return
  short  signed   sizeof   static  struct  switch  typedef  unsigned  union
  void   volatile   while

图4-1 C语言数据类型种类

4.9种控制语句

C语言控制语句的功能是完成程序流程的控制,C语言有如下9种控制语句。

[1]if( )-else(条件语句)

[2]for( )(循环语句)

[3]while( )(循环语句)

[4]do-while( )(循环语句)

[5]continue(结束本次循环开始下一次循环语句)

[6]break(跳出switch或循环语句)

[7]switch(多分支选择语句)

[8]goto(跳转语句)

[9]return(从函数返回语句)

5.C程序格式和结构特点

C语言习惯用小写字母,大小写敏感;不使用行号,无程序行概念;可使用空行和空格;常采用锯齿形书写格式。

下面以一个实例说明C语言结构特点。

C语言由函数、语句和注释三个部分组成,这三部分的说明和要求如下。

(1)函数与主函数

一个C语言源程序可以由一个或多个源文件组成。每个源文件可由一个或多个函数组成。一个源程序不论由多少个文件组成,都有一个且只能有一个main函数,即主函数。

源程序中可以有预处理命令(include命令仅为其中的一种),预处理命令通常应放在源文件头。

每一个说明,每一个语句都必须以分号结尾。但预处理命令,函数头和花括号“}”之后不能加分号。

标识符、关键字之间必须至少加一个空格以示间隔,若已有明显的间隔符,也可不再加空格来间隔。

(2)程序语句

C程序由语句组成,用“; ”作为语句终止符。

{}表示一个语句的整体。if、for、while包含多条语句时,需要用{}括起来表示一个整体,单条语句则可直接用“; ”表示语句终止。

(3)注释

/* */为注释,不能嵌套,注释不产生编译代码。

6.C语言程序的开发过程

7.书写C语言程序时应遵循的规则

一个说明或一个语句应该占一行。

用{}括起来的部分,通常表示程序的某一层次结构。{}一般与该结构语句的第一个字母对齐,并单独占一行。

使用缩进方式编程,低一层次的语句比高一层次的语句缩进若干空格后书写,以便看起来更加清晰,增加程序的可读性。

有足够的注释。有合适的空行。

8.C语言的字符集

字符是组成语言的最基本的元素。C语言字符集由字母、数字、空格、标点和特殊字符组成。在字符串和注释中还可以使用汉字或其他可表示的图形符号。

(1)字母

小写字母a~z共26个,大写字母A~Z共26个。

(2)数字

0~9共10个。

(3)空白符

空格符、制表符、换行符等统称为空白符,空白符只在字符常量和字符串中起作用。在其他地方出现时,只起间隔作用,编译程序对它们忽略不计。在程序中适当的地方使用空白符将增加程序的清晰性和可读性。

(4) 标点和特殊字符

包括“, ”、“; ”等标点和“[”、“]”、“*”等特殊字符。

9.C语言词汇

在C语言中使用的词汇分为六类,分别为标识符、关键字、运算符、分隔符、常量和注释符,具体说明如下。

(1)标识符

在程序中使用的常量名、变量名、函数名等统称为标识符。除库函数的函数名由系统定义外,其余都由用户自定义。C规定,标识符只能是由字母(A~Z, a~z)、数字(0~9)、下画线(_)组成的字符串,并且其第一个字符必须是字母或下画线。

a、x、x3、BOOK_1、sum5这5个标识符是合法的标识符。

以下标识符是非法的:

          3s     以数字开头;
          s*T    出现非法字符*。

(2)关键字

关键字是由C语言规定的具有特定意义的字符串,通常也称为保留字,用户定义的标识符不应与关键字相同。C语言的关键字分为以下几类:

[1]类型说明符

用于定义、说明变量、函数或其他数据结构的类型,如int、double等。

[2]语句定义符

用于表示一个语句的功能,如if-else就是条件语句的语句定义符。

[3]预处理命令字

用于表示一个预处理命令,如include。

(3)运算符

C语言中含有相当丰富的运算符。运算符与变量、函数一起组成表达式,表示各种运算功能。运算符由一个或多个字符组成。

(4)分隔符

在C语言中采用的分隔符有逗号和空格两种。逗号主要用在类型说明和函数多个形参中分隔各个变量,空格多用于语句各单词之间作为间隔符。

(5)常量

C语言中使用的常量可分为数字常量、字符常量、字符串常量、符号常量、转义字符等多种。

(6)注释符

C语言的注释符是以“/*”开头并以“*/”结尾的字符串,在“/*”和“*/”之间的即为注释。程序编译时,不对注释做任何处理,注释可出现在程序中的任何位置,注释用来向用户提示或解释程序的意义。

10.C语言算法

C语言是结构化设计语言,结构化程序的灵魂是算法。瑞士Niklaus Wirth教授在20世纪60年代提出了“数据结构+算法=程序”的公式。做任何事情都有一定的步骤,为解决一个问题而采取的方法和步骤,就称为算法。算法具有有穷性、确定性、有零个或多个输入、有一个或多个输出、有效性等特征。C语言算法是通过结构化设计方法来实现的,其特点为自顶向下、逐步细化、模块化设计和结构化编码。

11.C语言编译过程

图4-2给出了C语言的编译流程。

图4-2 C语言编译过程

12.计算机语言说明

数学是描述大自然的语言,会计是人们记录商业活动的语言,人类语言(英语、汉语、法语、西班牙语等)则是人与人之间交流的语言,计算机语言是人们和计算机(CPU)交流的语言。

由于CPU只能理解机器语言(CPU指令集),而人们对机器语言(一串二进制数)很难阅读和调试,于是就产生了汇编语言。汇编语言的特点是利用助记符代替相应的机器语言二进制指令,如利用“ADD”助记符代替二进制指令“1000001100000110”,汇编语言较机器语言更有利于人们编程,人们编写的汇编源程序再经汇编编译器翻译成机器语言程序(执行码),执行码就是CPU能理解的二进制指令。

由于汇编语言是面向机器的低级语言,与CPU的指令集息息相关,是按CPU的“思考”方式编写而成的计算机语言。汇编语言与人类思考方法相差较大,于是产生了面向过程的语言(PASCLE、C等)和面向对象的语言(C++、Java等)。但是计算机只能理解机器语言(CPU指令集),人们编写的C语言、C++语言、Java语言源程序要翻译成机器语言,CPU才能够执行,执行翻译工作的是编译程序。就这样,一门新的计算机学科(编译原理)产生了,编译原理介绍了编译程序的实现方法。masm是汇编的编译程序,gcc是Linux下C语言和C++语言的编译程序,JVC是Java的编译程序。

万物皆流,万物归宗。计算机源程序,无论是高级语言C、C++、Java源程序,还是汇编语言源程序,最终都要翻译成机器语言,CPU才能够执行。

13.CPU的独白

嗨,大家好!我叫CPU,是中央处理器(Central Processing Unit)的简称,是电子计算机的主要设备之一,其功能主要是解释计算机指令及处理计算机软件中的数据,所谓的计算机的可编程性主要是指对CPU的编程。

在我看来,外部一切都是地址,我只负责从地址上取数据,然后计算数据,计算完毕向地址上存数据。我的工作主要是围绕着存数据、取数据和计算数据在进行。由于外界的地址只有内存和外设两种,CPU引脚M/IO高电平意味着与内存通信,低电平意味着与外设端口通信。地址以一个字节(8位二进制数)为单位进行编排,微机中内存地址和外设端口地址是单独进行编排的,当我看到MOV指令就知道是与内存通信,看到IN和OUT指令就知道是与外设端口通信。

在我的眼里,地址上(内存和外设端口)的数据都是一堆二进制数,没有类型,也没有任何含义。只有当我开始运行机器语言代码时,这些数据才变得有意义。我是一个优秀且卓越的执行者,完全按照机器语言代码功能进行执行。

人们通过布尔逻辑、数字电路技术把沙粒(二氧化硅)变成了我,我的世界只有100多个或几百多个机器指令,这些机器指令早已由INTEL等芯片厂商设计好。设计完成后,机器指令是固定的,不能再修改。因为我是硬件,不同于软件,设计生产完成后,要么是成功的芯片,要么是失败的芯片,没有第三条道路可走。

在我的身体里,利用寄存器暂存数据,利用运算器完成数据运算,利用控制器控制数据与内存或外设的传输。由于我工作的关系,我深刻理解什么叫分层,什么叫抽象,什么叫协议,什么叫分工,什么叫转换,什么叫约定,什么叫专业,什么叫透明,什么叫物理,什么叫逻辑。其实计算机技术实现是哲学思想的体现,计算机技术较好的利用分层、抽象、模块化等思想使复杂的问题简单化。

人们常说“谢谢你的存在,世界因你而精彩”。然而我的世界是枯燥的,就是不断地正确执行这一百多个机器指令。也许专业就是简单的事情重复地做,我太过专业,人们赋予我计算专家头衔。

复杂的事情模块化,模块的事情简单化,简单的事情流程化,流程的事情自动化。计算机完成的功能的确非常复杂,要做好这项工作必须懂得分工和协作,每个人只做自己擅长的事情。所以我和我的伙伴们有明确的分工,由于责任明确,大家都完全遵守着各种协议和约定,所以我们配合得很默契,工作得很高效。我和我的同伴们是世界上最好的学生,从来规规矩矩,完全遵守各种协议和约定。内存完成工作数据的存储,硬盘完成长久数据的保存,键盘完成字符的输入,鼠标完成图形按钮的控制,显卡完成显示数据的转换,显示器完成图形显示,声卡完成数字声音向模拟声音的转换,音箱完成声音的播放。我和我的伙伴们总是呆在固定的地点,各自完成自己的工作,大家“鸡犬之声相闻,老死不相往来”,只是通过总线传递着数据进行交流。

许多人每天的工作和生活与我们联系在一起,人们与我们电脑从陌生到熟悉,然而许多人觉得我们太有内涵,难以弄懂我们复杂的工作原理和工作个性,又觉得从熟悉走向陌生。其实计算机的世界是按照一系列的协议和规则在运行,就像大自然按照一系列规则在运行一样。大自然是造物主创造的规则,而计算机则是全世界计算机专家们创造的协议与规则。了解计算机首先要了解各个硬件功能及硬件之间如何分工协作,然后再要知道软件的工作原理。软件是建立在硬件之上,就像精神建立物质之上一样。

许多人把我们电脑当做自我精神愉悦的朋友。许多人说想每天“听听你悦耳的声音,想看看你迷人的笑容,看见你我有一种莫名的快乐”。然而我的声音(声音格式)有各种格式,我的笑容(图像显示)有各种协议,我的声音和笑容凝聚着全球IT工程师辛勤的汗水和智慧的结晶,能为大家带来快乐我也感到很快乐。我们电脑被人们制造出来后,然后我们改变了世界上人们的工作与生活。有时我们分不清是世界改变了我们,还是我们改变了世界,也许这是相互促进的结果。在我看来,电脑和人脑有相通之处,人脑的思维结构是由天生注定的,人可以通过学习和思考来优化思维软件。电脑的物理结构是由硬件决定的,而软件则是由IT工程师编写出来的,软件通过硬件来实现电脑价值的提升。

由于我CPU是一板一眼,只认识数字世界,人们觉得我CPU很难沟通。大家觉得我CPU是冰冷的芯下面藏着热情的火焰,在计算机发展初期,只有能编写计算机机器指令的人才能点燃CPU芯片中的火焰。由于机器指令晦涩难懂,这不适应电脑的发展与普及,于是编译器产生了,编译器是我CPU与计算机语言之间的翻译官。计算机语言是面向人类思维的语言,而我CPU只认识机器指令,所以编译器架起了我们沟通的桥梁。人们的许多工作都是效率、成本、功用三者的平衡,编译器让人们编写软件更加高效、成本更低、功能更强,编译器推动了软件业的繁荣。不管人们用多少种软件语言,编写软件复杂程度有多高,但最终都要翻译成我能理解的为数不多的上百个或几百个机器指令。我用这数量极少的指令展现了色彩缤纷的世界、千变万化的声音。这一切的结果是基于一定的规则和协议的,科学就是认识万事万物的规则和规律,世界上只有有规则和规律的事情才有意义,美妙的声音是有规律的,而噪声是无规律的。我深深懂得规则和规律的重要,我的一切工作都是按照预定的规则和规律在进行。只有符合规则、约定、协议的数据,我和我的同伴们才能相互理解,理解是需要建立在协议、约定和规则的基础上。

在电脑世界里,标准的力量常常是无穷的,计算机业标准比任何其他行业都使用得广泛,顺标准则昌,逆标准则亡。由于全世界电脑各种协议需要统一的标准,才能更好地把世界软硬件厂商联系起来,推动计算机产业的进步,所以全世界许多IT公司都在认识标准、利用标准、制定标准、优化标准中博弈,如高清DVD标准之争。

计算机的世界,是数字(数据)的世界。计算机软硬件所做的一切只不过是为了数据的加工、转换、表现(显示、声音)、传输和存储。围绕数据在计算机中的处理产生了许多计算机学科,如数据结构是对数据的算法处理、数据库是对数据的关系处理、计算机网络是对数据的传输处理,而计算机存储是对数据的存储处理、计算机图形学是对数据的转换和显示处理。数据的加工、转换、表现、传输和存储都需要遵照一定的协议和规范。

4.2 常量与变量

C语言中数据由常量和变量组成,常量顾名思义是其值不可变的量,变量顾名思义是其值可以改变的量。C语言变量都有其数据类型,说明其在C语言中的类型。数据类型决定了数据占内存的字节数、数据的取值范围和其上可进行的操作。

1.数据基本类型的分类及特点

表4-1列出了C语言数据基本类型及其说明,使用时要注意这些基本类型的数值范围。

表4-1 C语言数据基本类型分类表

2.标识符

标识符是用来标识变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列。标识符只能由字母、数字、下画线组成,且第一个字母必须是字母或下画线;大小写敏感;不能使用关键字;长度最长32个字符;命名原则要见名知意,做到“顾名思义”。

3.常量和符号常量

在程序执行过程中,其值不发生改变的量称为常量。常量有直接常量和符号常量两种。

[1]直接常量

直接常量有整型常量、实型常量、字符常量、字符串常量、指针常量五种类型。下面是它们的举例说明。

整型常量:12、0、-3。

实型常量:4.6、-1.23。

字符常量:' a' 、' b' 。

[2]符号常量:用标识符代表一个常量。

在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。

符号常量在使用之前必须先定义,其一般形式为:

    #define 标识符 常量

其中#define是一条预处理命令(预处理命令都以“#”开头),称为宏定义命令,其功能是把该标识符定义为其后的常量值。

习惯上符号常量的标识符用大写字母,变量标识符用小写字母,以示区别。

【例4-1】 符号常量的使用。

price.c源代码如下:

    #include <stdio.h>
    #define PRICE 30
    void main()
    {
          int num, total;
          num=10;
          total=num* PRICE;
          printf("total=%d\n", total);
    }

由上面实例看出,符号常量有以下特点和好处:

[1]符号常量是用一个标识符代表一个常量。

[2]符号常量与变量不同,它的值在其作用域内不能改变,也不能再被赋值。

[3]使用符号常量的好处是含义清楚,能做到“一改全改”。

4.变量

(1)变量说明

其值可以改变的量称为变量,变量在内存中占据一定的存储单元。变量定义必须放在变量使用之前,一般放在函数体的开头部分,不能使用没有定义的变量。每个变量都有一个名字,称为变量名,其值存放在内存中,变量名是变量值的代号。

假设int a=0,图4-3给出了此变量的内存布局。此变量的变量名为a,其值为0,程序执行时分配内存空间的首地址为2012,即&a等于2012。

变量可以理解为内存相应位置所存数据的抽象,变量类型决定了变量所占空间的大小。字符型变量占用内存空间1个字节,整型变量占用内存空间4个字节。上述变量a可以理解为地址为2012-2009的4个字节内存空间的抽象,其存储的值为0。一个C语言源程序编译成可执行程序后,对变量a的操作实际转换为对内存地址2012-2009的存取操作。

图4-3 变量的内存布局

(2)变量初始化

初始化是指在变量定义的同时,给变量赋以初值的方法。

变量定义中赋初值的一般形式如下:

      类型说明符 变量1= 值1,变量2= 值2, ……;

例如:

      int a=3;
      int b, c=5;

(3)变量转换

变量转换分为显式转换和隐式转换两种。

其中,隐式转换分为运算转换、赋值转换、输出转换和函数调用转换。隐式转换时变量的转化顺序为char, short→int→unsigned→long→float→double。

显式转换的一般形式为:(类型名)(表达式),例如,(float)a是把a转换为实型。

5.整型数据

整型数据有前缀表示法和后缀表示法两种,具体说明如下。

[1]前缀表示法。以0开头表示八进制,以0x(或0X)表示十六进制,默认表示十进制。015 (十进制为13),0X2A(十进制为42)。

[2]后缀表示法。L(或l)表示长整型,U(或u)表示无符号数。

整型变量赋值要注意其取值范围,超过范围会造成变量的溢出。

6.转义字符

转义字符是一种特殊的字符常量。转义字符以反斜线“\”开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。例如,printf函数格式串中用到的“\n”就是一个转义字符,其意义是“回车换行”。转义字符主要用来表示用一般字符不便于表示的控制代码。表4-2列出了常用的转义字符及其含义。

表4-2 常用的转义字符及其含义

广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表中的\ddd和\xhh正是为此而提出的,ddd和hh分别为八进制和十六进制的ASCII代码。例如,\101表示字母A,\102表示字母B, \x0A表示换行等。

7.字符常量

字符常量是用单引号引起来的一个字符。

例如:' a' 、' b' 、' =' 、' +' 、' ? ’都是合法字符常量。

在C语言中,字符常量有以下特点:

[1]字符常量只能用单引号引起来,不能用双引号或其他括号。

[2]字符常量只能是单个字符,不能是字符串。

[3]字符可以是字符集中任意字符,但数字被定义为字符型之后就不能参与数值运算。例如,'5’和5是不同的,'5’表示字符5,对应ASCII码35,而5对应ASCII码5。

8.字符变量

字符变量用来存储字符常量,即单个字符,字符变量的类型说明符是char。

每个字符变量被分配一个字节的内存空间,字符值是以ASCII码的形式存放在变量的内存单元之中的。

C语言允许对整型变量赋以字符值,也允许对字符变量赋以整型值。在输出时,允许把字符变量按整型量输出,也允许把整型量按字符量输出。

短整型量为二字节量,字符量为单字节量,当整型量按字符型量处理时,只有低八位字节参与处理。

例如,x的十进制ASCII码是120,对字符变量a赋予’x’可以是a=' x’或a=120,这两条语句是等价的。

字符变量赋值还可以使用转义字符,如c=' \0’与c=0,这两条语句也是等价的。

【例4-2】 字符变量使用举例。

charint.c源代码如下:

    #include <stdio.h>
    void main()
    {
    char a, b;
    a=120;
    b=' y' ;
    printf("%c, %c\n", a, b);
    printf("%d, %d\n", a, b);
    }

编译gcc charint.c -o charint。

执行 ./charint,执行结果如下:

    x, y
    120,121

9.字符串常量

字符串常量是由一对双引号引起来的字符序列,如"CHINA"、"C program"、"$12.5" 等都是合法的字符串常量。

字符串常量和字符常量是不同的量。它们之间主要有以下区别:

[1]字符常量由单引号引起来,字符串常量由双引号引起来。

[2]字符常量只能是单个字符,字符串常量则可以包含一个或多个字符。

[3]字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加1。增加的一个字节中存放字符“\0”(ASCII码为0),这是字符串结束的标志。

例如,字符串"C program" 在内存中所占的字节为:

字符常量’a’和字符串常量"a"虽然都只有一个字符,但在内存中的情况是不同的,两者占用内存情况说明如下。

'a’在内存中占一个字节,可表示为:

"a"在内存中占两个字节,可表示为:

4.3 运算符

C语言中运算符和表达式数量之多,在高级语言中是少见的。正是丰富的运算符和表达式使C语言功能十分完善,这也是C语言的主要特点之一。

C言的运算符不仅具有不同的优先级,而且还有一个特点,就是它的结合性,使用时要注意运算符是右结合性还是左结合性的。

1.C语言的运算符分类

对C语言的运算符分类说明如下:

[1]算术运算符:用于各类数值运算,包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算%)、自增(++)、自减(--),共七种。

[2]关系运算符:用于比较运算,包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(! =)六种。

[3]逻辑运算符:用于逻辑运算,包括与(&&)、或(——)、非(! )三种。

[4]位操作运算符:参与运算的量,按二进制位进行运算,包括位与(&)、位或(—)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。

[5]赋值运算符:用于赋值运算,分为简单赋值(=)、复合算术赋值(+=、-=、*=、/=、%=)和复合位运算赋值(&=、—=、^=、>>=、<<=),三类共11种。

[6]条件运算符:这是一个三目运算符,用于条件求值(? :)。

[7]逗号运算符:用于把若干表达式组合成一个表达式(, )。

[8]指针运算符:用于取内容(*)和取地址(&)两种运算符。

[9]求字节数运算符:用于计算数据类型所占的字节数(sizeof)。

[10]特殊运算符:有优先级()、下标[]、结构成员(→、.)、取负运算符-、强制类型转换(类型)。

2.基本的算术运算符

对基本的算术运算符说明如下:

[1]加法运算符“+”:加法运算符为双目运算符,即应有两个量参与加法运算。如a+b、4+8等,加法运算符具有左结合性。

[2]减法运算符“-”:减法运算符为双目运算符。但“-”也可作为负值运算符使用,此时为单目运算,如-x、-5等具有左结合性。

[3]乘法运算符“*”:双目运算符,具有左结合性。

[4]除法运算符“/”:双目运算,具有左结合性。参与运算量均为整型时,结果也为整型,舍去小数。如果运算量中有一个是实型,则结果为双精度实型。

[5]求余运算符(模运算符)“%”:双目运算符,具有左结合性。要求参与运算的量均为整型,求余运算的结果等于两数相除后的余数。

算术单目运算符为右结合性,双目运算符为左结合性,两整数相除,结果为整数,%要求两侧均为整型数据。

3.表达式、运算符的优先级和结合性

表达式是由常量、变量、函数和运算符组合起来的式子。一个表达式有一个值及其类型,它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。单个的常量、变量、函数可以看做是表达式的特例。

算术表达式是由算术运算符和括号连接起来的式子。下面是对算术表达式和算术运算符的详细说明。

算术表达式:用算术运算符和括号将运算对象(也称操作数)连接起来的,符合C语法规则的式子。

运算符的结合性:C语言中运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左),如算术运算符的结合性是自左至右,即先左后右,现有表达式x-y+z,则y应先与“-”号结合,执行x-y运算,然后再执行+z的运算。这种自左至右的结合方向就称为“左结合性”,而自右至左的结合方向称为“右结合性”。最典型的右结合性运算符是赋值运算符,如x=y=z,由于“=”的右结合性,应先执行y=z再执行x=(y=z)运算。C语言运算符中有不少为右结合性,应注意区别,以避免理解错误。

运算符的优先级:C语言中,运算符的运算优先级共分为15级,1级最高,15级最低。在表达式中,优先级较高的先于优先级较低的进行运算。而当在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所规定的结合方向处理。

表4-3列出了C语言运算符优先级及其说明,此表了解即可,无须记忆,编程时优先级的确定时可使用圆括号“()”来解决。

表4-3 C语言运算符优先级表

强制类型转换运算符

强制类型转换运算符的形式为:

    (类型说明符)  (表达式)

其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。

例如:

    (float)a      把a转换为实型
    (int)(x+y)    把x+y的结果转换为整型

自增、自减运算符

自增1:自增1运算符记为“++”,其功能是使变量的值自增1。

自减1:运算符记为“--”,其功能是使变量值自减1。

自增1、自减1运算符均为单目运算,都具有右结合性,可有以下几种形式:

++i:i自增1后再参与其他运算

--i:i自减1后再参与其他运算

i++:i参与运算后,i的值再自增1

i--:i参与运算后,i的值再自减1

【例4-3】 自增、自减运算符举例。

varadd.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int i=8;
        int a ;
        printf("i=%d\n", ++i);
        printf("i=%d\n", i);
        a=++i ;
        printf("a=%d, i=%d\n", a, i);
        a=i++ ;
        printf("a=%d, i=%d\n", a, i);
        a=--i ;
        printf("a=%d, i=%d\n", a, i);
        a=i-- ;
        printf("a=%d, i=%d\n", a, i);
        printf("i=%d\n", --i);
        printf("i=%d\n", i++);
        printf("i=%d\n", i--);
        printf("i=%d\n", -i++);
        printf("i=%d\n", -i--);
    }

编译gcc varadd.c -o varadd。

执行 ./varadd,执行结果如下:

    i=9
    i=9
    a=10, i=10
    a=10, i=11
    a=10, i=10
    a=10, i=9
    i=8
    i=8
    i=9
    i=-8
    i=-9

4.赋值运算符和赋值表达式

(1)赋值运算符

简单赋值运算符号为“=”,由“=”连接的式子称为赋值表达式。其一般形式如下:

变量=表达式

例如:

    x=a+b;
      a=b=c=5可理解为 a=(b=(c=5))。

(2)类型转换

如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型换成左边的类型。具体规定如下:

[1]实型赋予整型,舍去小数部分。

[2]整型赋予实型,数值不变,但将以浮点形式存放,即增加小数部分(小数部分的值为0)。

[3]字符型赋予整型,由于字符型为一个字节,而整型为两个字节,故将字符的ASCII码值放到整型量的低八位中,高八位为0。整型赋予字符型,只把低八位赋予字符量。

(3)复合的赋值运算符

在赋值符“=”之前加上其他二目运算符可构成复合赋值运算符,如+=、-=、*=、/=、%=、<<=、>>=、&=、^=、—=都是复合赋值运算符。

构成复合赋值表达式的一般形式为:

变量 双目运算符=表达式

它等效于:

变量=变量 运算符 表达式

例如:

      a+=5       等价于a=a+5;
      x*=y+7     等价于x=x*(y+7);

复合赋值符这种写法,对初学者可能不习惯,但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。

5.逗号运算符和逗号表达式

在C语言中逗号“, ”也是一种运算符,称为逗号运算符,其功能是把多个表达式连接起来组成一个表达式,称为逗号表达式。

其一般形式为:

    表达式1,表达式2, …,表达式n

其求值过程是依次求表达式的值,并以表达式n的值作为整个逗号表达式的值。

6.关系运算符

关系表达式的语法形式如下:

      表达式 关系运算符  表达式

表4-4列出了C语言六种关系运算符。在C语言中,=表示赋值,==表示数学中的相等关系,使用时请注意=和==的区别。

表4-4 关系运算符表

对于关系表达式,如果表达式所表示的比较关系成立则值为真(True),否则为假(False),在C语言中分别用int型的1和0表示真和假。如果变量x的值是-1,那么x>0这个表达式的值为0, x>-2这个表达式的值为1。

关系运算符的两个操作数应该是相同类型的,两边都是整型或者都是浮点型时可以做比较,但两个字符串不能做比较,需要用字符串函数进行比较。

7.逻辑运算符

C语言中提供了三种逻辑运算符:&&是与运算(AND)符,——是或运算(OR)符,!是非运算(NOT)符。

逻辑表达式的语法形式如下:

表达式 逻辑运算符 表达式

(1)逻辑运算符优先级

逻辑运算符优先级顺序为:! (非)>&&(与)>——(或)。

图4-4给出了运算符优先级图,其中赋值运算符优先级最低,!优先级最高。

图4-4 运算符优先级

按照运算符的优先级顺序可以得出:

      a>b && c>d   等价于   (a>b)&&(c>d)
      !b==c——d<a   等价于   ((! b)==c)——(d<a)
      a+b>c&&x+y<b  等价于   ((a+b)>c)&&((x+y)<b)

(2)逻辑运算的值

逻辑运算的值分为“真”和“假”两种,分别用“1”和“0”来表示。

与运算&&:参与运算的两个量都为真时,结果才为真,否则为假。例如,5>0 && 4>2为真,即为1。

或运算——:参与运算的两个量只要有一个为真,结果就为真,两个量都为假时,结果为假。例如,5>0——5>8为真。

非运算!:参与运算量为真时,结果为假;参与运算量为假时,结果为真。例如,! (5>0)为假。

8.条件运算符和条件表达式

如果在条件语句中,只执行单个的赋值语句时,常可使用条件表达式来实现。这样不但使程序简洁,也提高了运行效率。

条件运算符为?和:,它是一个三目运算符,即有三个参与运算的量。

由条件运算符组成条件表达式的语法形式如下:

表达式1? 表达式2:表达式3

其求值规则为:如果表达式1的值为真,则以表达式2的值作为条件表达式的值,否则以表达式3的值作为整个条件表达式的值。条件表达式通常用在赋值语句之中。

例如下面的条件语句:

    if(a>b)  max=a;
        else max=b;

可用条件表达式写为:

    max=(a>b)? a:b;

该语句的语义为如果a>b为真,则把a赋予max,否则把b赋予max。

使用条件表达式时,还应注意以下几点:

[1]条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符。

因此:

    max=(a>b)? a:b

可以去掉括号而写为:

    max=a>b? a:b

[2]条件运算符?和:是一对运算符,不能分开单独使用。

[3]条件运算符的结合方向是自右至左。

例如,a>b? a:c>d? c:d应理解为a>b? a:(c>d? c:d)

【例4-4】 条件运算符使用举例。

expr.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int a, b, max;
        printf("\n input two numbers:   ");
        scanf("%d%d", &a, &b);
        printf("max=%d", a>b? a:b);
    }

编译gcc expr.c -o expr。

执行 ./expr,执行结果如下:

    input two numbers:   30 90
    max=90

4.4 C语言控制结构

C语言有三种基本流程结构,即顺序、选择、循环。选择结构通过if-else、case-switch、goto语句实现,循环结构通过while、for、do-while语句实现。

4.4.1 if语句

1.if语句三种形式

if语句用于分支条件判断,其语法形式有如下三种。

(1)第一种形式为基本形式,只有if关键字。

if语句基本形式语法如下:

    if(表达式) 语句

其语义是:如果表达式的值为真,则执行其后的语句,否则不执行该语句。其执行过程可表示为图4-5。

图4-5 if语句流程图

if基本形式应用举例,if1.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int a, b, max;
        printf("\n input two numbers:   ");
        scanf("%d%d", &a, &b);
        max=a;
        if (max<b) max=b;
        printf("max=%d", max);
    }

编译gcc if1.c -o if1。

执行 ./if1,执行结果如下:

    input two numbers:    3 9
    max=9

(2)第二种形式为if-else形式。

if-else语句语法形式如下:

    if(表达式)
        语句1;
    else
        语句2;

其语义是:如果表达式的值为真,则执行语句1,否则执行语句2。其执行过程可表示为图4-6。

图4-6 if-else语句流程图

if-else形式应用举例,if2.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int a, b;
        printf("input two numbers:    ");
        scanf("%d%d", &a, &b);
        if(a>b)
          printf("max=%d\n", a);
        else
          printf("max=%d\n", b);
    }

编译gcc if2.c -o if2。

执行 ./if2,执行结果如下:

    input two numbers:    10 0
    max=10

(3)第三种形式为if-else-if形式。

前两种形式的if语句一般都用于两个分支的情况。当有多个分支可供选择时,可采用if-else-if语句,其语法形式如下:

    if(表达式1)
          语句1;
        else  if(表达式2)
          语句2;
        else  if(表达式3)
          语句3;
          …
        else  if(表达式m)
          语句m;
        else
          语句n;

其语义是:依次判断表达式的值,当出现某个值为真时,则执行其对应的语句,然后跳到整个if语句之外继续执行程序。如果所有的表达式均为假,则执行语句n,然后继续执行后续程序。其执行过程可表示为图4-7。

图4-7 if-else-if语句流程图

if-else-if形式应用举例,if3.c源代码如下:

    #include  <stdio.h>
    void main()
    {
        char c;
        printf("input a character:   ");
        c=getchar();
        if(c<32)
          printf("This is a control character\n");
        else if(c>='0' &&c<='9' )
          printf("This is a digit\n");
        else if(c>=' A' &&c<=' Z' )
          printf("This is a capital letter\n");
        else if(c>=' a' &&c<=' z' )
          printf("This is a small letter\n");
        else
          printf("This is an other character\n");
    }

编译gcc if3.c -o if3。

执行 ./if3,执行结果如下:

    input a character:   X
    This is a capital letter
2.if语句的嵌套

当if语句中的执行语句又是if语句时,则构成了if语句嵌套的情形,为了避免这种二义性,C语言规定,else总是与它前面最近的if配对。

下面的if语句就构成了if语句的嵌套,使用if语句应尽量避免此种形式,应尽量使用“{}”来说明else匹配哪一个if。

    if(表达式1)
          if(表达式2)
            语句1;
          else
            语句2;

4.4.2 switch语句

1.switch语句说明

switch语句用于多分支选择,其语法形式如下:

    switch(表达式){
        case常量表达式1:  语句1;
        case常量表达式2:  语句2;
        …
        case常量表达式n:  语句n;
        default       :  语句n+1;
        }

其语义是:计算表达式的值,并逐个与其后的常量表达式值相比较,当表达式的值与某个常量表达式的值相等时,即执行其后的语句,然后不再进行判断,继续执行后面所有case后的语句。如表达式的值与所有case后的常量表达式均不相同时,则执行default后的语句。所以case语句需要用break进行跳出,否则将对后面的语句顺序执行。

2.switch语句注意事项

switch有如下注意事项:

[1]在case后的各常量表达式的值不能相同,否则会出现错误。

[2]在case后,允许有多个语句,可以不用{}括起来。

[3]各case和default子句的先后顺序可以变动,且不会影响程序执行结果。

[4]default子句可以省略不用,但不提倡省略。

[5]switch表达式结果只能为整型变量和字符型变量。

3.switch关键字应用举例

switch1.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int a;
        printf("input integer number:   ");
        scanf("%d", &a);
        switch (a){
          case 1:printf("Monday\n"); break;
          case 2:printf("Tuesday\n"); break;
          case 3:printf("Wednesday\n"); break;
          case 4:printf("Thursday\n"); break;
          case 5:printf("Friday\n"); break;
          case 6:printf("Saturday\n"); break;
          case 7:printf("Sunday\n"); break;
          default:printf("error\n");
        }
    }

编译gcc switch1.c -o switch1。

执行 ./switch1,执行结果如下:

    input integer number:   1
    Monday

switch2.c源代码如下:

    #include <stdio.h>
    void main()
    {
        float a, b;
        char c;
        printf("input expression: a+(-, *, /)b \n");
        scanf("%f%c%f", &a, &c, &b);
        switch(c){
          case ' +' : printf("%.2f\n", a+b); break;
          case ' -' : printf("%.2f\n", a-b); break;
          case ' *' : printf("%.2f\n", a*b); break;
          case ' /' : printf("%.2f\n", a/b); break;
          default: printf("input error\n");
        }
    }

编译gcc switch2.c -o switch2。

执行 ./switch2,执行结果如下:

    input expression: a+(-, *, /)b
    3*7
    21.00

4.4.3 goto语句

1.goto语句说明

goto语句是一种无条件转移语句,其语法形式如下:

    goto  语句标号;

标号是一个有效的标识符,这个标识符加上一个“:”一起出现在函数内某处,执行goto语句后,程序将跳转到该标号处并执行其后的语句。

标号必须与goto语句同处于一个函数中,但可以不在一个循环层中。

goto语句经典使用场合为程序处理多个错误时,跳转到结尾进行错误处理,达到错误处理代码复用的目的,并减少return语句,其他场合不建议使用。

2.goto语句应用举例

goto.c源代码如下:

    #include <stdio.h>
    int main()
    {
        int i=0;
        scanf("%d", &i) ;
        if ( i<0 )
        {
          goto err_end ;
        }
        if ( i >100 )
        {
          goto err_end ;
        }
        printf("input number validity:%d\n", i) ;
        return 0 ;
    err_end:
        printf("input number overflow, please guess again\n");
        return -1 ;
    }

编译gcc goto.c -o goto。

执行 ./goto,执行结果如下:

    900
    input number overflow, please guess again

4.4.4 while语句

1.while语句说明

while语句用来实现循环结构,其语法形式如下:

while(表达式) 语句

其中,表达式是循环条件,语句为循环体。

while语句的语义是:计算表达式的值,当值为真(非0)时,执行循环体语句。其执行过程可用图4-8表示。

图4-8 while语句流程图

2.while语句举例

用while语句求

while.c源代码如下:

    #include <stdio.h>
    void main()
    {
      int i, sum=0;
      i=1;
      while(i<=100)
          {
            sum=sum+i;
            i++;
            }
      printf("%d\n", sum);
    }

编译gcc while.c -o while。

执行 ./while,执行结果如下:

    sum=5050

4.4.5 do-while语句

1.do-while语句原型

do-while语句同样是用来实现循环结构的,其语法形式如下:

    do
      语句
    while(表达式);

这个循环与while循环的不同在于:它先执行循环中的语句,然后再判断表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。其执行过程可用图4-9表示。

图4-9 do-while语句流程图

2.do-while语句举例

用do-while语句求

dowhile.c源代码如下:

    #include <stdio.h>
    void main()
    {
    int i, sum=0;
        i=1;
        do
        {
            sum=sum+i;
            i++;
        }while(i<=100)
        printf("sum=%d\n", sum);
    }

编译gcc dowhile.c -o dowhile。

执行 ./dowhile,执行结果如下:

    sum=5050

4.4.6 for语句

1.for语句原型

for语句也是用来实现循环结构的,其语法形式如下:

    for(表达式1;表达式2;表达式3) 语句

它的执行过程如下:

1)先求解表达式1。

2)求解表达式2,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面第3步;若其值为假(0),则结束循环,转到第5步。

3)求解表达式3。

4)转回上面第2步继续执行。

5)循环结束,执行for语句下面的一个语句。

其执行过程可用图4-10表示。

图4-10 for语句流程图

for语句最简单的应用形式也是最容易理解的形式,其使用形式如下:

for(循环变量赋初值;循环条件;循环变量增量) 语句

注意for循环三个部分之间要用“; ”分开,举例说明如下。

for(i=1; i<=100; i++)sum=sum+i; /*利用for语句实现上述1到100的相加功能*/

2.for语句的多种使用方法

for语句有如下多种使用方法。

    for(i=1; i<=100; i++)/*常见使用方法*/
    for(i=1; ; i++)         /*死循环,语句中碰到合适条件时用break跳出*/
    for(i=1; i<=100; )      /*循环增加放到语句中*/
    for(; i<=100; )         /*初始化放到语句外,循环增量放到语句中*/
    for(; ; )               /*相当于while(1)语句*/
3.for语句应用举例

for.c源代码如下:

    #include <stdio.h>
    void main()
    {
        int i, j, k;
        printf("i j k\n");
        for (i=0; i<2; i++)
          for(j=0; j<2; j++)
          for(k=0; k<2; k++)
              printf("%d %d %d\n", i, j, k);
     }

编译gcc for.c -o for。

执行 ./for,执行结果如下:

    i j k
    i=0 j=0 k=0
    i=0 j=0 k=1
    i=0 j=1 k=0
    i=0 j=1 k=1
    i=1 j=0 k=0
    i=1 j=0 k=1
    i=1 j=1 k=0
    i=1 j=1 k=1
4.几种循环的比较

while、do-while、for三种循环都可以用来处理同一个问题,一般可以互相代替,for语句功能最强。

while和do-while循环,循环体中应包括使循环趋于结束的语句。

用while和do-while循环时,循环变量初始化的操作应在while和do-while语句之前完成,而for语句可以在表达式1中实现循环变量的初始化。

4.4.7 break和continue语句

1.语句说明

continue语句终止当前循环后,又回到循环体的开头准备执行下一次循环。

break语句跳出当前循环。

2.break和continue语句举例

breakcon.c源代码如下:

    #include <stdio.h>
    int is_prime(int n)
    {
        int i;
        for (i = 2; i < n; i++)
          if (n % i == 0)
              break;
        if (i == n)
          return 1;
        else
          return 0;
    }
    int main(void)
    {
        int i;
        for (i = 1; i <= 100; i++) {
          if (! is_prime(i))
              continue;
          printf("%d  ", i);
        }
        printf("\n") ;
        return 0;
    }

编译gcc breakcon.c -o breakcon。

执行 ./breakcon,执行结果如下:

    2  3  5  7  11  13  17  19  23  29  31  37  41  43  47  53  59  61  67  71  73  79
  83  89  97

程序说明如下:

[1]is_prime函数从2到n-1依次检查有没有能被n整除的数,如果有,就说明n不是素数,立刻跳出循环而不执行i++,所以is_prime函数中使用的是break。

[2]在主程序中,从1到100依次检查每个数是不是素数,如果不是素数,并不直接跳出循环,而是i++后继续执行下一次循环,因此用continue语句。