C语言语法体系(未放置指针)(摘录自蓝桥云课)(有意者蓝桥官网自取)

实验1 C语言简单剖析

C 语言的历史及地位

1972-1973 年,美国贝尔实验室为了描述和实现 UNIX 操作系统(一个具有强大影响力的操作系统,我们实验楼的实验环境 Linux 系统就是一种类 UNIX 系统),于是改良了 B 语言,这就是今天的大名鼎鼎的 C 语言。

C 语言相当灵活,用于执行几乎所有计算机可以完成的任务,既可以用于编写应用程序,还可用于编写操作系统,伟大的 Windows 和 Linux 操作系统的内核就是使用 C 语言开发的。C 语言包含了基本的编程元素,后来的很多语言(C++、Java 等)都参考了 C 语言,说 C 语言是现代编程语言的开山鼻祖毫不夸张,它改变了编程世界。

下面这张图是 2014 年 10 月份 TIOBE 根据编程语言在企业中的使用情况统计出来的编程语言排行榜,程序猿们可以了解自己的编程技能的价值,以便跟上编程潮流。这一排行榜亦可以作为初学者们对未来发展方向选择的参考依据。该数据每月都会更新,有兴趣的小伙伴可以去官网查看最新动态。

Alt text

通过该图大家可以看到 17.8% 的程序是使用 C 语言写的,虽然 C 语言是 70 后的老人,但它依然牢牢地占据在排行榜首位。

无可替代的 C 语言

Alt text

这张图同样来自 TIOBE,通过这张图大家可以发现从 2002 年至今,C 语言在编程中占有比例一直保持稳定,java 语言却呈现下跌态势,两者还会经常争夺霸主地位。 但是为什么 java 所占比例一直在下跌,而 C 语言却保持稳定呢?主要是因为能够代替 java 的语言不断涌现和上升,而 C 语言能保持稳定,是因为在很多领域 C 语言是唯一的选择,比如说做操作系统、做嵌入式系统。

美国一位资深软件专家写了一篇文章,题为“对计算机学生的建议”,可供参考。他说“大学生毕业前要学好 C 语言,C 语言是当前程序员共同的语言,比你在大学学到的现代语言(比如 ML,java,python 或者其它流行的语言)都更接近机器”。他指出“不管你懂多少延续、闭包、异常处理,只要你不能解释为什么 while(*s++=*t++) 的作用是复制字符串,那你就是在盲目无知的情况下编程,就像一个医生不懂最基本的解剖学就在开处方”。

如何学习 C 语言

勤动手

C 语言并不是一门纯理论课程,而是一门应用课程。学习过程中要通过大量的例题学习怎么设计一个算法,构造一个程序。初学时一定不要在语法细节上死记硬背,从一开始就应该编写简单程序,然逐步深入。语法细节需要通过长期的编程实践才能熟练掌握。

我们所给的习题,即使它们非常简单,也要自己动手输入。自己亲自输入,以后就不容易忘记。不要害怕用代码进行实验。犯错对编程而言非常有教育性!起初犯错越多,学到的东西就越多。

勤思考

学习程序设计,主要是掌握程序设计的思路和方法。学会使用一种计算机语言编程,在需要时改用另一种语言应当也不会太难。不要设想今后一辈子只使用大学学过的某一种语言。在学习时要善于思考,举一反三,完成一个程序需要什么样的算法、怎么去设计,要学会独立思考。

勤查找

很多同学在学习过程中稍微遇到点困难就想要放弃,遇到一点内容没有懂就不往下学习了,常常半途而废。在学习过程中我们难免遇到各种困难,遇到各种难懂的词汇,遇到各种半天解不出来的题目。当碰到实在不明白的概念,可以放一放往前走。如果这些概念或者题目很重要,不懂就没办法往前走,那就好好利用下百度和 Google 吧。一个勤奋的少年,即使只给他一个 Google 或者百度,他也能学好一门语言。一个不上进的屌丝你给他买好十本 C 语言的书,找个骨灰级的程序猿带他,他也学不会 C 语言编程。

剖析 C 语言

接下来我们将以上节课编写的名称为 shiyanlou 的 C 程序为材料,来讲解 C 程序的创建过程以及 C 语言的程序结构。

  • 创建 C 程序
    • 编辑
    • 编译
    • 链接
    • 执行
  • C 语言的简单结构
    • 预处理指令
    • main()函数
    • 程序框架
    • printf() 函数
创建 C 程序

C 程序的创建过程有四个基本步骤:

  1. 编写;
  2. 编译;
  3. 链接;
  4. 执行。

编写

编辑的过程就是创建和修改 C 程序的源代码——我们使用右侧环境的编辑器编写的程序便是源代码。其实在 Linux 上,Vim 编辑器也是很常用的文本编辑器,有兴趣的小伙伴也可以在我们蓝桥云课中学习一下 Vim 编辑器的相关课程。

编译

编译器可以将源代码转换成机器语言,在编译过程中,会找出错误并报告。这个阶段的输入是在编辑期间产生的文件,常称为源文件。

编译器能找出程序中很多无效的和无法识别的错误,包括结构错误,例如程序的某个部分永远不会执行。编译器输出的结构叫作对象代码,存放它们的文件叫作对象文件。在 Linux 中这些文件的扩展名通常是 .o,在 Windows 下面这些文件的扩展名通常是 .obj 。如果编译成功就会生成一个文件,它与源文件同名。但扩展名为 .o 或者 .obj。

其实我们在上一节编写第一个 C 语言程序的时候,使用的 gcc -o 1-1 1-1.c 这条命令既包含了编译也包含了链接,所以直接生成了可执行文件 1-1。 在 Linux 下编译是在源代码文件所在目录输入以下命令(假如源代码文件是上节课的 1-1.c):

gcc -c 1-1.c

这时源文件所在的目录将会生成 1-1.o 的文件。

链接

链接器将源代码文件中由编译器产生的各种对象模块组合起来,再从 C 语言提供的程序库中添加必要的代码模块,将它们组合成一个可执行文件。链接器也可以检测和报告错误,例如程序中引用了一个根本不存在的库组件。链接一旦成功,就会生成可执行文件,在 Windows 下面可执行文件的扩展名是 .exe,在 Linux 下面,可执行文件没有扩展名,但它的文件类型是可执行的。

在编译生成 .o 文件的基础上我们将会输入以下命令(以编译生成 shiyanlou.o 为例):

gcc -o 1-1 1-1.o

这时 1-1.o 所在的目录将会生成 1-1 可执行文件。

多数情况下,我们是通过 gcc -o 1-1 1-1.c 一次性完成编译和链接。

执行

执行阶段就是成功完成了前述的三个过程后,运行程序。但是这个阶段可能会出现各种错误,包括输出错误,计算机什么也不做哦,甚至是计算机崩溃。无论如何,都需要我们返回编辑阶段,检查并修改源代码。相信大家都还记得上一节课的执行命令,在文件所在目录执行:

./1-1

创建 C 程序的各个过程:

2-3.1.4-1

C 语言的简单结构

预处理指令

shiyanlou.c 的第一行代码如下:

#include <stdio.h>

严格地说,它不是可执行程序的一部分,但它很重要,它是 C 语言中的标准输入输出库,程序没有它将不能做任何输入输出。符号 # 表示这是一个预处理指令,告诉编译器在编译源代码之前,要先执行一些操作。编译器在编译过程开始之前的预处理阶段会处理这些指令。预处理指令的类型相当多,大多放于程序源文件的开头。

在这个例子中,编译器要将 stdio.h 文件的内容包含进来,这个文件被称为头文件,因为通常放在程序的开头处。在本例中,头文件定义了 C 标准库中一些函数的信息,本例要用到标准库中的 printf() 函数,所以必须包含 stdio.h 头文件。stdio 是 “standard input & output” 的缩写,包含了编译器理解 printf() 以及其它输入 / 输出函数所需要的信息。C 语言所有头文件的扩展名都是 .h 。在以后的学习过程中大家会看到很多其它的预处理指令。

main()函数

int main(){
  printf("Hello ShiYanLou");
  return 0;
}

main() 函数是“主函数”。每个 C 程序都由一个或多个函数组成,但每个 C 程序都必须有一个 main() 函数——因为每个程序总是从这个函数开始执行。

程序的几乎全部工作都是由各个函数分别完成的,函数是 C 程序的基本单位,在设计良好的程序中,每个函数都用来实现一个或多个特定的功能。

一个 C 语言程序由一个或者多个函数组成,其中必须包含一个 main() 函数(且只能有一个 main() 函数)。

一个函数包括两个部分:

一是 函数首部 即函数的第一行:

int main()

二是 函数体 即函数首部下面的花括号内的部分:

{
    ...
}

函数的内容我们会在以后的课程中涉及,这里只是简单的提及。

程序框架

#include <stdio.h>
int main(){
  XXXX
  return 0;
}

我们的课程的所有程序都需要写上这一段,直到学习函数之前,我们的代码都是放在 “xxxx” 的位置,所以以后你每次写程序的时候,可以先把这个框架写上去,再在这个框架中间写代码。

  • printf() 函数

printf() 是 C 编译系统提供的函数库中的输出函数。

printf() 函数中双引号内的字符串 “Hello ShiYanLou” 按照原样输出,每个语句最后都有一个分号,表示语句结束。

实验总结

实验2 数据类型

本节课我们只讲述了 C 语言的前世今生和它经久不衰的生命力,接下来我们将要正式进入 C 语言编程了。是不是想了解一下我们刚才编写的第一个 C 语言程序究竟是怎么执行的呢,接下来的课程将讲解编写 C 语言的开发环境以及对第一个 C 语言的剖析。

基本的输入输出函数

首先我们先举一个例子 3-1,下面我们建立一个程序 3-1.c,输入以下代码:

输入以下代码:

#include<stdio.h>
int main(){
  int a,b,c;
  printf("Please enter a value:");
  scanf("%d",&a);
  printf("\n");
  printf("Please enter b value:");
  scanf("%d",&b);
  c = a + b;
  printf("%d\n",c);
  return 0;
}

在终端输入以下命令,编译并运行:

gcc -o 3-1 3-1.c
./3-1

如果该程序成功运行,首先终端会显示 Please enter a value: 提示大家输入 a 的值,大家写一个整数(注意是整数),然后终端会显示 Please enter b value: 提示大家输入 b 的值,之后将会运算 c = a + b 的结果。如下图输入 4 和 5 将计算出 c 的值为 9:

3-2.1-1

接下来,我们依托这个程序讲解几个知识点,之后再做分析。

格式输出函数 printf()

一般形式:printf(格式控制,输出表列)。例如:printf("%d,%d",a,b); 括号内包含两个部分:

(1)格式控制是用双引号括起来的一个字符串,称“转换控制字符串”,简称“格式字符串”,它包括两个信息:

  • 格式声明:格式声明由 % 和格式字符组成,如 %d (%d 代表输出整数,%f 代表输出实数),它的作用是将输出的数据转换为指定的格式然后输出。格式声明总是由 % 字符开始。
  • 普通字符:普通字符即在需要输出时原样输出的字符。例如上例中的 printf("Please enter a value:");中的 Please enter a value: 即为原样输出。

(2)输出表列是程序需要输出的数据。看下面例子:

printf("I love %d and %d",x,s);

第一个 %d 对应的是 x 的值,第二个 %d 对应的是 s 的值。I loveand(注意这里包括空格)都是普通字符会原样输出。

假如 x 的值是 3,s 的值是 4,这条语句将会输出 I love 3 and 4

格式输入函数 scanf()

一般形式:scanf(格式控制,地址表列)。格式控制的含义同 printf() 函数。“地址表列”是由若干地址组成的表列,可以是变量的地址。

看下面的例子:

scanf("a=%d,b=%d",&a,&b);

在格式字符串中除了有格式声明的 %d 以外,其它普通字符在赋值时需要原样输入(如“ a= ”,“ b= ”和“,”),假如给 a 和 b 分别赋值 5 和 6,将输入 a=5,b=6。 建议大家不要在格式控制中加过多的普通字符,否则会发生不可预料的 BUG。

注意:scanf() 函数中的表列是地址表列。 scanf("a=%d,b=%d",&a,&b); 中 a 和 b 前面的 & 不能省掉,这一点要和 printf 作区分。

printf() 函数和 scanf() 函数我们会在后续深入讲解。

注释

位于 /* */ 中的和 // 后面的内容为注释,用来对代码进行说明,注释在编译时会被自动忽略。

3-1.c 是一个简单的计算程序,通过定义变量让用户可以自由设定 a 和 b 的值,之后通过 c = a + b; 这条语句实现把 a 和 b 的和计算出来并赋值给 c。究竟什么是变量,什么是常量呢?接下来我们来一一讲述。

常量

顾名思义,值不能被改变的量称为常量。如 5、7、19 或者 0.54、4.33 这些值,常见的常量分为以下类型:

整型常量

如 0、100、-30 等整数都是整型常量。

实型常量

就是我们通常所说的小数,如 12.34, -5.45, 143.342 等,小数还可以用指数形式表现,如 32.23e3(表示 32.23*10^3),-323.34e-6(表示 -323.34*10^-6),由于计算机无法表示上角和下角,所以规定以字母 e 或者 E 代表以 10 为底的指数。

注意:e 或者 E 之前必须有数字,且 e 或者 E 后面必须为整数,不能是 e3 或者 12e4.1 这种形式。

字符常量,字符常量有两种
  1. 普通字符:用单引号括起来的一个字符,如 ‘a’、‘E’、‘%’、‘3’。不能写成 ‘ab’、‘12’。字符常量只能是一个字符,不包括单引号。
  2. 转义字符:除了以上形式的字符常量外,C 语言还允许用一种特殊形式的字符常量,就是以字符 \ 开头的字符序列,比如我们本节课的 3-1.c 中,\n 代表的就是换行符,显示跳转到下一行。这是一种在屏幕上无法显示的“控制字符”。
  • 常用的控制字符:
转义字符含义转义字符含义
\n换行\t水平制表(右移 8 格)
\v垂直制表\b退格
\r回车(不换行)\f换页
\a响铃\反斜线
单引号‘’双引号
\add3 位 8 进制数代表的字符\xhh2 位 16 进制数代表的字符

变量

什么是变量

在例子 3-1 中的 a,b,c 都是变量。变量代表一个有名字的、具有特殊属性的存储单元。它可以用来保存数据。变量的值是可以改变的。变量在程序中定义的一般形式就是: <类型名称> <变量名称>。例如:

int a; int b; int a,b; int price; int amount;

  • int 代表定义的变量是整数类型。 我们在 3-1.c 的例子中便是直接定义了 a,b,c 三个变量为整数类型:
int a,b,c;
标识符

变量需要一个名字,变量的名字便是一种“标识符”,用来区别它和其它不同的变量。用来对变量、函数、数组等命名的字符序列统称为标识符,上面提到的 price 、amount 是标识符,函数名 printf 也是一种标识符。C 语言规定标识符只能由字母、数字和下划线构成,且第一个字符必须为字母或下划线。¥ qa,1ew,#22 这些都是非法的标识符。

变量的赋值与初始化
int price = 0;

变量必须先定义后使用。这一行定义了一个变量,变量的名字是 price,类型是 int,初始的值是 0。

注意:和数学不同,a=b 在数学中表示关系,即 a 和 b 的值一样;而在程序设计中,a=b 表示要求计算机做一个动作:将 b 的值赋值给 a。关系是静态的,而动作是动态的。在数学中,a=b 和 b=a 是等价的,而在程序设计中,两者意思相反。

数据类型

什么是数据类型

我们之前的案例中讲到了整数类型 int 定义整型变量,在程序中我们还会用到浮点类型(float)来表示具有小数点的实数,讲解数据类型之前我们先来看一个用到浮点型数据的例子 3-2:

一台拖拉机耕地一亩耗油 0.85kg,它的油箱的容积是 100 升(0.1m3),柴油的密度是 850kg/m3,该拖拉机装满油后最多耕地的亩数是多少?

我们在函数中首先要定义几个变量,定义油箱的容积的变量为浮点型类型 tank_volume,油的密度为浮点类型 oil_density,油箱装在油的总质量为浮点型 oil_kg,该拖拉机装满油最多耕地的亩数是浮点型 area。下面我们建立一个程序 3-2.c 并输入以下代码:

#include<stdio.h>
int main(){
  float tank_volume;                      // 定义油箱的容积为浮点类型变量 tank_volume
  float oil_density;                      // 定义油的密度为浮点类型变量 oil_density
  float oil_kg;
  float area;

  tank_volume = 0.1;                        // 给变量 tank_volume 赋值
  oil_density = 850;                        // 给变量 oil_density 赋值

  oil_kg = tank_volume*oil_density;         // 求 tank_volume 和 oil_density 的积并赋值给 oil_kg
  area = oil_kg/0.85;                       // 求 oil_kg/0.85 的商并赋值给 area
  printf("Most farming is %f mu",area);
  return 0;
}

代码说明: 下面我们来解释这个程序。下面的语句定义了四个变量:

float tank_volume;
float oil_density;
float oil_kg;
float area;

下面这两条语句是给 tank_volume 和 oil_density 这两个变量赋值:

tank_volume = 0.1;                       // 给变量 tank_volume 赋值
oil_density = 850;                       // 给变量 oil_density 赋值

其实 tank_volume=0.1;oil_density=850; 这两条语句可以和前面的 float tank_volume;float oil_density; 这两条语句放在一起,定义变量的同时初始化赋值,即:

float tank_volume = 0.1;
float oil_density = 850;

接下来我们使用乘法运算符 * 将油箱的容积和油的密度相乘,计算出油箱可以存放油的总质量:

oil_kg  = tank_volume*oil_density;        // 求 tank_volume 和 oil_density 的积并赋值给 oil_kg

之后我们利用除法运算符 / 求出最多可耕地的亩数,并赋值给 area:

area = oil_kg / 0.85;
  • 为什么在用计算机运算时,需要指定数据的类型呢?

    在数学中,数值是不区分类型的,数值的运算是绝对准确的,例如:1/3 的值是 0.33333…(循环小数)。数学是研究抽象的学科,数和数的运算都是抽象的。而在计算机中,数据是存储在计算机中的一个个单元里面,它是具体存在的。而且,存储单元是由有限的字节构成的,每一个存储单元存储的数据是有限的,不可能存放无限大的数,也不能存放无限循环小数,例如计算和输出 1/3:

printf("%f",1.0/3.0);

得到的结果是 0.333333,只能得到六位小数,而不是无穷位的小数。

3-2.4.1-1

大家可以大致浏览该图,不需要现在背下来这些数据类型。接下来我们将要讲述基本类型里面的整型和浮点型。

整数类型

为了方便大家理解数据在计算机中的存储方式,我们首先给大家讲述一下计算机内存。计算机在执行程序的时候,组成程序的指令和程序所操作的数据都必须存储在某个地方,这个地方就是计算机的内存,也称为 RAM。

可以将计算机的 RAM 想象成一排井然有序的盒子。每个盒子都有两个状态:满为 1,空为 0 ,因此每个盒子代表一个二进制数:0 或 1 。计算机有时用真和假表示它们:1 为真,0 为假。每个盒子称为一个位(bit)。每 8 个位组成一个字节,在计算机中,一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。

计算机中常用的单位是千字节(KB)、兆字节(MB)、千兆字节(GB)。 这些单位的意义如下: 1KB 是 1024 字节。其中 1024=2^10(2 的 10 次方),1MB=1024KB,1GB=1024MB 。如果大家对二进制、字节这些概念不是很熟悉,可以点击查看字节二进制学习了解相关概念。

基本类型(int 类型)

编译系统分配给 int 类型数据 2 个字节或者 4 个字节(由具体的编译系统自行决定)。我们使用的 gcc 编译器为每个整数类型分配四个字节(32 个二进位)。在存储单元中的存储方式是:用整数的补码形式存放。所以当 4 个字节的整数类型取值范围是 -2^31 到(2^31-1)。无符号的基本整型表示为 unsigned int,和 int 类型占有的字节数相同,取值范围是 0 到(2^32-1)。

短类型(short 类型)

短整型的类型名为 short,gcc 编译系统分配给 short 类型 2 个字节,存储方式和 int 类型一样,也是补码的形式存储,取值范围是 -2^15 到(2^15-1),无符号短整型 unsigned short 的取值范围是 0 到(2^16-1)。

长整型(long 类型)

gcc 编译系统分配给 long 类型 8 个字节,存储方式和 int 类型一样,也是补码的形式存储,取值范围是 -2^63 到(2^63-1),无符号长整型 unsigned long 的取值范围是 0 到(2^64-1)。

不同类型占用的空间

在这里大家可以通过 sizeof() 运算符查看各类型的常量占据多少字节。

创建 3-3.c 文件并输入以下代码:

#include<stdio.h>
int main(){
  printf("%d\n",sizeof(int));
  printf("%d\n",sizeof(short));
  printf("%d\n",sizeof(long));

  return 0;
}

保存后编译运行显示以下结果:

3-2.4.2.4-1

4,2,8 代表 int,short,long 占用的字节数。

由于 sizeof() 的值的类型不是 int 型,此处运行会出现了 warning。对于编译中出现的 warning 警告,我们需要加以注意,但依然可以生成可执行文件,在本次实验中,我们可以不处理,继续运行。

警告解决方案

要想解决上面的警告也不复杂,我们可以将 sizeof() 值的类型强制转换成 int 型,代码如下:

(int)sizeof(type);

所以,修改后,不报错的代码如下:

#include<stdio.h>
int main(){
  printf("%d\n",(int)sizeof(int));
  printf("%d\n",(int)sizeof(short));
  printf("%d\n",(int)sizeof(long));
  return 0;
}

图片描述

浮点型数据

浮点型数据是用来表示具有小数点的实数的。想知道为什么在 C 中把实数称为浮点数吗?

在 C 语言中,实数是以指数的形式存放在存储单元的。一个实数表示为指数可以不止一种形式,如 4.3242 可以表示为 4.3242*10^00.43242*10^10.043242*10^2432.42*10^-2 等,他们代表同一个值。可以看到小数点的位置是可以在 43242 几个数字之间浮动的,只要在小数点位置浮动的同时改变指数的值,就可以保证它的值不会改变。由于小数点的位置可以浮动,所以实数的指数形式称为浮点数。

规范化的指数形式:在指数形式的多种表示方式中把小数部分中小数点前的数字为 0,小数点后第 1 位数字不为 0 的表示形式称为规范化的指数形式,如 0.43242*10^1 就是 4.3242 的规范化的指数形式。一个实数只有一个规范化的指数形式。

浮点数类型包括 float(单精度浮点型)、double(双精度浮点型)、long double(长双精度浮点型)。

float 型(单精度浮点型)

gcc 编译系统为每一个 float 型变量分配 4 个字节,数值以规范化的二进制数指数形式存放在存储单元中。在存储时,系统将实型数据分成小数部分和指数部分两个部分、分别存储。如 3.14159 在内存中的存放形式如下图:

4-2.1.1-1

图中是用十进制来示意的,实际在计算机中是用二进制数来表示小数部分以及用 2 的幂次来表示指数部分的。在 4 个字节(32 位)究竟用多少位表示小数部分,多少位表示指数部分,是由 c 语言编译系统自定的。

如果想要知道 float 的取值范围,我们可以编写 4-1.c 程序:

#include<stdio.h>
#include<float.h>
int main(){
  printf("The size of the smallest positive non-zero value of type float is %.3e\n",FLT_MIN);
  printf("The size of the largest value of type float is %.3e\n",FLT_MAX);

  return 0;
}

如果程序你成功运行的话,你将会看到 float 数据类型的取值范围。现在我们试着编写一个计算题吧!

我们要做的是利用输入的直径计算一个圆桌的周长及面积。计算圆的周长或者面积时,数学公式要使用 pi(周长 = 2*pi*r,面积 = pi*r^2,其中 r 是半径)。如果不记得这些公式也不用担心。这不是数学课本,所以只要理解程序是如何运行的即可。

编写 4-2.c 程序:

#include<stdio.h>
int main(){
  float radius,diameter;
  float circumference,area;
  float pi = 3.1415926;

  printf("Input the diameter of the table:");
  scanf("%f",&diameter);

  radius = diameter / 2.0;
  circumference = 2.0 * pi * radius;
  area = pi * radius * radius;
  printf("\nThe circumference is %f",circumference);
  printf("\nThe area is %f",area);

  return 0;
}
double 型(双精度浮点型)

为了能扩大数字的范围,用 8 个字节(64 位)存储一个 double 型数据,可以得到 15 位有效数字,double 型的数值范围大家可以按照 4-1 案例查看,其中 double 极限值符号的下限为:DBL_MIN,上限为 DBL_MAX。double 型的存储方式和 float 的存储方式相同。

浮点型数据所占的内存空间以及取值范围: 4-2.1.2-1

在平时我们使用的浮点型数据的时候要注意这几个点哦! 超过有效位的数字被舍去,可能产生舍入误差

编写 4-3.c 程序,输入以下代码:

#include <stdio.h>
int main(){
  float  a , b ;
  a = 123456.789e5 ;
  /*   相当于 123456.789 * 10^5   */
  b = a + 20 ;
  /*   20加上无意义   */
  printf(" %f ", b) ;

  return 0;
}

编译运行结果为:

12345678848.000000

为什么计算出来不是正确的结果呢?是因为 float 数据的有效位是 7 位,a = 123456.789e5 这条语句中赋值给 a 的值超过了 float 的有效位;输出结果中 12345678848.000000 中只有前 7 位才是有效数字

现在我们再次编写 4-3.c,把程序修改如下:

#include <stdio.h>
int main(){
  double  a , b ;        //把 float 改为 double 类型
  a = 123456.789e5 ;
  b = a + 20 ;
  printf(" %f ", b) ;

  return 0;
}

程序修改完成后保存并再次编译,运行程序得到了以下结果:

12345678920.000000

这个时候我们发现运行的结果是一个正确的计算结果。因为 double 型的有效数字是 15~16 位,而 123456.789e5(12345678900)其有效数字是 11 位,把它赋值给 a 不会出现溢出。

在我们以后的项目编程中一定要小心数值溢出的问题,不要以为这种情况很难出现。举个简单的例子,我国很多城市的地铁造价每公里超过了 5 亿,我国 2014 年上半年国内生产总值 269044 亿元,这个时候如果我们定义变量为 float 甚至是 double 类型,都是很危险的。

字符型

C 语言中,字符型的基本类型符是 char。

上一节中我们讲到了字符常量,字符型常量是用单引号括起来的一个字符。如 ‘A’,‘a’,‘?’ 等等。 在所有的编译系统中都规定以 1 个字节(8 位)来存放一个字符。字符型数据在存储时,并不是把该字符本身存放到内存单元中,而是把该字符相应的 ASCII 码值存放到该存储单元中。(什么是 ASCII 码,为什么采用 ASCII 来存储,大家可以查看百度百科 ASCII) 如 x 的十进制 ASCII 码是 120,y 的十进制 ASCII 码是 121。对字符变量 a、b 赋予 ‘x’ 和 ‘y’ 值:

a = 'x';
b = 'y';

实际上是在 a、b 两个单元内存放 120 和 121 的二进制代码: 4-2.2-1

另外还有需要注意的是,字符常量是区分大小写的,例如,字符 ‘c’ 的 ASCII 码值是 99,‘C’ 的 ASCII 码值是 67,两者并不是同一个字符。大家可以通过 ASCII 码表查看字符对应的 ASCII 码值。

我们编写程序 4-4.c,代码如下:

#include<stdio.h>
int main(){
  char a,b;                       //定义a和b为字符型变量
  a = 'c';                      //把字符常量 'c' 赋值给变量 a
  b = 121;
  printf("%c,%c\n",a,b);          //%c 表示以字符的形式输出
  printf("%d,%d\n",a,b);          //%d 表示以有符号十进制形式输出整数型
  return 0;
}

编译并运行之后的结果是:

c,y
99,121

本程序中定义 a,b 为字符型,在赋值语句中给 a 赋以字符值,但是给 b 却赋以整型值。从结果看,a,b 值的输出形式取决于 printf 函数格式串中的格式符,当格式符为 ‘c’ 时,对应输出的变量值为字符,当格式符为 ‘d’ 时,对应输出的变量值为整数。

也就是说,一个在字符的数据既可以以字符的形式输出,也可以以整数的形式输出。以字符形式输出时,先将存储单元中的 ASCII 码转换成相应的字符再输出;以整数的形式输出时,直接输出其 ASCII 码。

我们还可以对字符型数据进行算术运算,此时相当于对他们的 ASCII 码进行运算,编写程序 4-5.c:

#include<stdio.h>
int main(){
  char a,x;
  int b;
  a = 'c';
  b = 1;
  x = a + b;
  printf("%c\n",x);
  printf("%d\n",x);
  return 0;
}

编译运行的结果是:

d
100

字符串常量

字符串常量是用一对双引号括起来的零个或多个字符组成的序列,如 “hello”,“China”,“b” 都是字符串常量。

字符串常量的存储与字符常量的存储是不同的。字符串中的每个字符占用一个字节,在存储字符串常量时还要自动在其末尾加上 ‘\0’ 作为字符串结束的标志。 我们先来一起看下 “How do you do.” 是如何存储的吧!

4-2.3-1

因此大家不要将字符常量和字符串常量混淆哦,‘b’ 和 “b” 是完全不同的。前者是字符常量,在内存中占用的字节数为 1;而后者是字符串常量,在内存中占用的字节数为 2,包含字符 ‘b’ 和 ‘\0’。

注意:在 C 语言中没有专门的字符串变量,如果你想要将一个字符串存放在变量中,必须使用字符数组,数组中每一个元素存放一个字符,数组的内容我们会在以后的课程中和大家详细讲述。

各位小伙伴,我们学到了字符串常量,这个时候有木有想起我们编写的第一个程序 Hello ShiYanLou!回忆起我们一起走过的日子真好!

实验总结

我们本节课讲述了常量与变量,数据类型。下一节课我们将讲述运算符与数据转换,以后的课程我们会逐步增多编程的数量,小伙伴们要做好心理准备,知识的海洋等着你去遨游。

回想一下所学知识,如果发现有所遗漏,不妨回头再看看,如果觉得自己已经完全掌握,那么就进入我们的课后练习阶段,希望每个学员都能给出自己的答案。

课后练习

习题一:计算距离

追风少年小明骑电瓶车的速度是 40km/h,他以这样的速度从家到公司花费了 1 小时 30 分钟,红绿灯时间忽略不计,小明家与公司的距离是多远?

程序中变量的命名需要采用骆驼命名法

习题二:制作中文图书管理系统的登录界面

问题分析:中文图书管理系统登录界面是进入图书管理系统的第一个界面,实现实现用户登录系统的功能,可据此对用户的合法性进行检查,本案例只是实现简单的登录功能。

用户输入:借书卡号,用户姓名 期望的输出:登录成功的提示信息

参考答案

习题一
#include <stdio.h>

int main(){
  int speed = 40;
  double time = 1.5;
  double len;
  len = speed * time;

  printf("小明家与公司的距离是%.2f公里\n",len);

  return 0;
}
习题二
#include<stdio.h>
int main(){
  int cardnum;
  char name[20];  //小伙伴注意了,这个就是定义了一个字符串数组,可以容纳 20 字符
  printf("**********************************************************\n");
  printf("********Welcome to the books management system************\n");
  printf("**********************************************************\n");
  printf("~~~~~~~~~\t\t\t\t~~~~~~~~~~~~~~\n");
  printf("Please input your card number:\n");
  scanf("%d",&cardnum);
  printf("Please input your name:\n");
  scanf("%s",name);
  printf("\nWelcome,%s!Your card number is:%d\n",name,cardnum);

  return 0;
}

实验3 运算符和数据转换

基本的算数运算符

常用的算术运算符表:

  • x + y:将 x 与 y 相加
  • x - y:将 x 与 y 相减
  • x * y:将 x 与 y 相乘
  • x / y:x 除以 y
  • x % y:求 x 除以 y 的余数

注意

  1. x/y 中,两个实数相除的结果是双精度实数,两个整数相除的结果为整数。如 5/3 的结果为 1,舍去小数部分。
  2. % 运算符要求参加运算的对象为整数,结果也是整数。如 7%3,结果为 1,除了%以外的运算符的操作数都可以是任何算数类型。

下面我们举例说明上注意事项。

创建 5-1.c 文件并输入以下代码:

# include<stdio.h>
int main(){
    int a=22;
    int b=3;
    printf("%d\n",a/b);
    return 0;
}

输入以下命令编译并运行:

gcc -o 5-1 5-1.c
./5-1

结果如下:

5-2.1-1

运行后之后,大家可以看到运行的结果是 7 而非 7.33333。

重新编写 5-1.c 作出以下修改:

#include<stdio.h>

int main(){
    float a = 22.5;
    int b = 3;
    printf("%d\n",a%b);
    return 0;
}

重新编译运行该程序,显示出以下结果:

5-2.1-2

编译出现 error 表示编译未成功(如果只出现 warning 表示警告,则其实编译已经成功生成了可执行文件),并提示了错误内容。

这也验证了我们在注意中提到的 % 运算符要求参加运算的对象为整数。

自增、自减运算符

作用是使变量的值加 1 或减 1,例如:++i--i(在使用 i 之前,先使 i 加(减)1);i++i--(在使用 i 之后,使 i 的值加(减)1)。

猛地一看,++ii++ 的作用相当于 i=i+1。但是 ++ii++ 的不同之处在于 ++i 是先执行 i=i+1 后,再使用 i 的值;而 i++ 是先使用 i 的值之后,再执行 i=i+1。我们看下面的例子:

编写源程序 5-2.c:

# include<stdio.h>
int main(){
    int a = 5;
    printf("%d\n",a++);
    printf("%d\n",++a);
    return 0;
}

编译运行后的结果是:

5-2.2-1

大家千万不要以为结果应该是:

5
6

因为 a++ 以后 a 的值已经成为了 6,而非是 5,经过 ++a 后的值是 7。 这个程序虽然简单,大家还是需要认真思考一番。

注意: 自增运算符 (++) 和自减运算符 (–) 只能用于变量,而不能用于常量或表达式。如 5++ 或者 (a+b)++ 都是不合法的。因为 5 是常量,常量的值是不能改变的。(a+b)++ 也是不可能实现,假如 a+b 的值是 5,自增后变为 6 放在什么地方呢?无变量可供存放。

现在大家可能还不能感受到自增和自减的用处,等我们学到循环语句和指针的时候,你将会在大量的程序中看到自增和自减。

不同数据之间的混合运算

在程序中经常会遇到不同类型的数据进行运算,比如 7*3.5。如果一个运算符的两侧数据类型不同,则先进行类型的转换,使两者具有同一种类型,然后进行运算。因此整型、浮点型、字符型数据之间可以进行混合运算。 数据类型计算的时候究竟是如何转换类型的呢?大家先看下表:

5-2.3-1

  1. 如果 int 类型的数据和 float 或 double 型数据进行运算时,先把 int 型和 float 型数据转换为 double 型数据,然后进行运算,结果为 double 型。其他的大家可以按照上图来做。
  2. 字符 (char) 型数据和整型数据进行运算,就是把字符的 ASCII 代码与整型运算。如 4+‘B’,由于字符 ‘B’ 的 ASCII 代码是 66,相当于 66+4=70。字符型数据可以直接和整型数据进行运算。如果字符型数据和浮点型数据运算,则将字符的 ASCII 码先转化为 double 型,然后再进行运算。

我们编写一个程序分析一下他们转换的编译过程,编写程序 5-3.c:

# include<stdio.h>
int main(){
  int i = 3;
  float f = 4.3;
  double d = 7.5;
  double sum;
  sum = 10 + 'a' + i*f - d/3;
  printf("%lf\n",sum);
    return 0;
}

sum=10+'a'+i*f-d/3; 这条语句中,右边的表达式从左到右扫描,运算次序为:

10 + ‘a’ 的运算,‘a’ 的值是整数 97,运算结果为 107。 i*f 的运算。先将 i 与 f 都转换为 double 型,两者运算 12.9,double 型。 整数 107 与 i * f 的值相加,结果为 119.9,double 型。 d/3 的运算,现将 3 转换 double 类型,d/3 的结果为 2.5,double 型。 将 119.9 与 2.5 相减,117.4,double 型。

编译运行结果如下:

5-2.3-1

要求给定一个大写字母得到其小写字母输出,编写 5-4.c。

解题思路:我们前面已经介绍过,字符数据以 ASCII 码存储在内存中,形式与整数的存储形式相同,所以字符型数据和其他算数型数据之间可以相互赋值和运算。

通过 ASCII 码表我们可以找到同一个字母的大写形式和小写形式之间有什么内在的联系。那就是同一个字母,用小写表示的字符的 ASCII 码比用大写表示的 ASCII 码大 32。

代码如下:

#include<stdio.h>
int main(){
    char c1,c2;
    printf("Please enter a capital letter:");
    scanf("%c",&c1);
    c2 = c1 + 32;
    printf("%c\n",c2);
    return 0;
}

程序运行结果如下:

5-2.3-2

强制类型转换

可以利用强制类型转换运算符将一个数据类型转换为所需的类型,例如:

(double)a            //将 a 转换成为 double 型
(int)(x + y)           //将 x+y 的值转换成为 int 类型

其一般形式就是

(类型名)(表达式)

上述例子中如果写成 (int)x + y,则只将 x 转换成为整型再与 y 相加。

需要说明的是,在强制类型转换时,得到一个所需类型的中间数据,而原来变量的类型未发生变化。例如 a = (int)x

如果已经定义了 x 为 float 型变量,a 为整型变量,进行强制类型运算 (int)x 后得到一个 int 类型的临时值,它的值等于 x 的整数部分,把它赋值给 a,注意 x 的值和类型都未变化,仍为 float 型,该临时值在赋值后就不再存在了。

数据的输入和输出

之前为了方便大家学习,我们粗略的讲述过 scanf()printf() 函数。这一小节内容,我们系统的讲述数据的输入和输出。

举例说明

求 ax^2+bx+c=0 方程的根。a,b,c 由键盘输入,设 b^2-4ac>0。

我们知道:

5-2.5.1-1

创建 5-5.c,编写以下代码。

#include<stdio.h>
#include<math.h>               //程序中要调用求平方根函数 sqrt

int main(){
    double a,b,c,disc,x1,x2,p,q;
    scanf("%lf%lf%lf",&a,&b,&c);
    disc = b * b - 4 * a * c;
    p = -b / (2.0 * a);
    q = sqrt(disc) / (2.0 * a);
    x1 = p + q,x2 = p - q;
    printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
    return 0;
}

编译时需要注意,在 Linux 系统下,C 源文件若调用了 math 库里的函数,则编译时要加上 -lm (是字母 l ,不是数字 1),表示链接到 math 库。

gcc -o 5-5 5-5.c -lm

这里我们说明一下:因为数学函数位于 libm.so 库文件中(这些库文件通常位于 /lib 目录下),-lm 选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。而 printf 为什么不需要呢?这是因为它位于 libc.so 库文件中,使用 libc.so 中的库函数在编译时不需要加 -lc 选项,当然加了也不会错,因为这个选项是 gcc 的默认选项。

结果如下:

5-2.5.1-2

程序分析
  1. scanf 函数中,格式声明为 “%lf”,表示输入的是双精度型实数。从上面的运行结果来看输入 “1 3 2” 时,两个数字之间是用空格分开,如果用其他符号(如逗号)会出错。我们输入的 “1 3 2” 虽然是整数,但是由于指定了用 %lf 的格式输入,因此系统会先把这 3 个整数装换成为实数 1.0,3.0,2.0,然后赋值给变量 a,b,c。
  2. printf 函数中,不是简单地用 “%f” 格式声明,而是在格式符 “f” 的前面加了 “7.2”。表示的意思是在输出 x1,x2 时,指定数据占 7 列,其中小数占 2 列。这样做有两个好处。
    • 可以根据实际需求来输出小数的位数,因为并不是任何时候都需要 6 位小数的。
    • 如果输出多个数据各占一行,用同一个格式输出,即使输出的数据整数部分不同,但输出时上下行必然按小数点对齐,使输出数据整齐美观。
  3. 因为我们假设了 a,b,c 的值满足 b^2-4ac>0。但是实际我们输入的 a,b,c 三个值并不一定满足要求,所以我们在程序中先判断 a,b,c 的值是否满足条件。只有满足,我们采用使用上述方法计算,判断我们将会在下一节课程中讲述。
printfscanf 中的格式字符

前面我们已经介绍了,在输入输出时,对不同的数据要指定不同的格式声明,而格式声明最重要的就是格式字符,我们在此做下总结:

5-2.5.3-1

这个表不用死记,开始时会用比较简单的形式输入数据即可。在这里我们也不会详细的阐述,如果想要学习相关内容,可以买相关书籍。

小知识点:

  1. 在输入函数时,用 %c 格式声明输入字符时,空格字符和转义字符都是作为有效字符输入,例如:scanf("%c%c%c",&c1,&c2,&c3); 在执行这个程序时,需要我们连续输入 3 个字符,中间不要有空格,如下: abc 下面插入空格的形式是错误的: a b c
  • 这种形式第一个字符 ‘a’ 送给 c1,第二个字符是空格字符’ '送给了 c2,第三个字符 ‘b’ 送给了 c3。
  1. 在使用 %d 输出时,我们可以指定输出的宽度。具体用法:
  • %d:按照整型数据的实际长度输出。
  • %md:以 m 为指定的最小字段宽度输出,右对齐。
  • %ld:输出长整型数据。
  • %mld:输出指定宽度的长整型数据。

我们编写 5-6.c 程序可以体验一下:

#include<stdio.h>
int main(){
    int a = 12,b = -3456;
    long int c = 123456;
    printf("%5d\n",a);
    printf("%d\n",b);
    printf("%ld\n",c);
    printf("%6ld\n",c);
  return 0;
}

会得到输出结果:

5-2.5.3-2

字符数据的输入输出

除了可以用 printf 函数和 scanf 函数输出和输入的字符外,C 语言的库函数还提供一些专门用于输入和输出的字符的函数。

putchar() 函数输出一个字符

一般形式:putchar(c); 功能:输出变量 c 所代表的一个字符; 说明:c 为字符型变量或整型变量。

我们建立 5-7.c 程序,输入以下内容:

#include <stdio.h>
void main(){
    char a,b,c;
    a = 'O';b = 'K';c = '\n';
    putchar(a);
    putchar(b);
    putchar(c);
}

输出结果为:

5-2.5.4.1-1

通过此例我们可以看到:用 putchar() 函数既可以输出能在显示器屏幕上显示的字符,也可以输出屏幕控制字符,如 \n 的作用就是输出一个换行符,使输出的当前位置移到下一行的开头。

getchar 函数输入一个字符
  • 一般形式:getchar();
  • 功能:要求用户从终端(键盘)输入单个字符;
  • 说明:返回值为从输入设备上得到的字符。

我们现在改写之前的一个练习,实现在键盘上输入一个大写字母,显示一个对应的小写字母。建立 5-8.c,输入一下程序:

#include <stdio.h>
int main(){
    char c;
    printf("Input an uppercase letter:\n");
    c = getchar();
    putchar(c + 32);
    return 0;
}

注意:运行程序时,系统等待用户输入,注意回车也是一个合法字符。

输出结果:

5-2.5.4.2-1

实验总结

到这里我们本节课程就已经结束了,在这节课我们讲述了运算符,数据类型转换以及数据的输入和输出。也是到这里我们就可以说是大家已经了解了 C 语言的基础知识,从下一节课,我们将要讲述 C 语言的选择结构。

课后练习

习题一

请编写程序将 ShiYanLou 译成密码,用原来的字母后面第一个字母替代原来的字母。例如 A 后面的第二个字母是 B,因此 ShiYanLou 应该译成 TijZboMpv。请大家编写一个程序实现这个过程,并且分别用 putchar 函数和 printf 函数打印这些字符。

习题二
  1. 设一个圆柱底面圆的半径 r=2.5,圆柱的高 h=3,求底面圆周长、圆面积以及圆柱的体积。用 scanf 输入数据,输出计算结果,要求保留小数点后两位。

参考答案

习题一
#include <stdio.h>

int main(){
    //初始化字符
    char str[9] ={'S','h','i','Y','a','n','L','o','u'};

    //字符加1
    for(int i=0;i<9;i++)
        str[i] += 1;

    //printf打印
    for(int i=0;i<9;i++)
        printf("%c",str[i]);

    printf("\n");

    //putchar打印
    for(int i=0;i<9;i++)
        putchar(str[i]);
    return 0;
}
习题二
#include <stdio.h>

#define PI 3.14159

int main(){
    //初始化数据
    float r;
    // 需要输入 r =2.5
    scanf("%f", &r);
    int h;
    // 需要输入 h =3
    scanf("%d", &h);
    float area,cir,vol;
    //计算
    cir = PI * r * 2;
    area = PI * r * r;
    vol = area * h;

    printf("cir=%.2f,area=%.2f,vol=%.2f",cir,area,vol);
    return 0;
}

实验4 选择程序设计

条件判断案例

大家应该还记得我们上一节课做过一个题目,这个题目是用来求一元二次方程的根的,原来的程序如下:

#include<stdio.h>
#include<math.h>               //程序中要调用求平方根函数 sqrt

int main(){
    double a,b,c,disc,x1,x2,p,q;
    scanf("%lf%lf%lf",&a,&b,&c);
    disc = b * b - 4 * a * c;
    p = -b / (2.0 * a);
    q = sqrt(disc) / (2.0 * a);
    x1 = p + q,x2 = p - q;
    printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
    return 0;
}

这个程序我们并没有加入一个 b^2-4ac 是否大于等于 0 的一个判断,现在我们对这个程序做出一些改动,加入判断语句,输入以下代码:

#include<stdio.h>
#include<math.h>               //程序中要调用求平方根函数 sqrt
int main(){
    double a,b,c,disc,x1,x2,p,q;
    scanf("%lf%lf%lf",&a,&b,&c);
    disc = b * b - 4 * a * c;
    if(disc<0)
        printf("This equation hasn't real root!\n");
    else{
        p = -b / (2.0 * a);
        q = sqrt(disc) / (2.0 * a);
        x1 = p + q,x2 = p - q;
        printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
    }
    return 0;
}

输入以下命令编译并运行:

gcc -o 6-1 6-1.c -lm
./6-1

我们运行两次,分别输入 a,b,c 不同的值。

会看到以下结果:

6-2.1-1

程序分析

这就是一个选择结构,if 对给定的条件 disc<0 进行判断后,形成了两条路径,如果 disc<0 成立,执行 printf("This equation hasn't real root!\n");,如果 disc<0 不成立,执行 else{} 中的内容。

知识点:

  • 可以把几个语句放在一个 {} 中,这样如果 disc<0 不成立,就会执行 else{} 里面所有的内容;
  • 假如没有 {} 同时 disc<0,这个时候执行完 printf("This equation hasn't real root!\n");
  • 语句以后会从 q=sqrt(disc)/(2.0*a);
  • 接着往下执行,大家可以自己思考下加不加 {} 的影响。

用 if 语句实现选择结构

在 C 语言中选择结构最常用的就是 if 语句,为了了解 if 语句的应用,我们举一个例子进行说明。

编写源程序 6-2.c:

#include<stdio.h>
int main(){
    int number=0;
    printf("\nPlease enter an integer between 1 and 10:");
    scanf("%d",&number);
    if(number>5)
        printf("You entered %d which is greater than 5\n",number);

    if(number<6)
        printf("You enter %d which is less than 6\n",number);

    return 0;
}

运行两次,分别输入 6 和 2.结果如下:

6-2.2-1

程序分析
  1. main() 函数体的前三个语句如下:
int number=0;
printf("\nPlease enter an integer between 1 and 10:");
scanf("%d",&number);

这段代码声明一个整型变量 number,并初始化为 0,接着提示用户输入一个 1~10 的数字。使用 scanf() 函数读取这个数值,并存储在变量 number 中。

  1. 下一条语句就是一条测试输入值的 if 语句
if(number>5)
  printf("You entered %d which is greater than 5\n",number);

比较 number 变量的值和 5,如果 number 大于 5,就执行下一条语句,然后进入程序的下一个部分。如果 number 不大于 5,就跳过 printf()

  1. 接下来这条语句和上一条语句意义相同
if(number<6)
  printf("You enter %d which is less than 6\n",number);

if 语句的一般形式

通过上面的两个例子,我们可以初步的了解怎样使用 if 语句去实现选择结构了。

例如:

if (number>500)  cost=0.15;
else  if (number>300)  cost=0.10;
else  if (number>100)  cost=0.07;
else  if (number>50)   cost=0.05;
else                   cost=0;
程序分析
  1. 我们 6-1.c 的例子便是上面你的第二种形式,6-2.c 的例子便是第一种形式。
  2. 整个 if 语句可以写在多行上,也可以写在一行上面。如:
if (x>0) y=1;else y=-1;

但是为了程序的清晰,我们很少用上面的形式,而是采用锯齿的形式。

关系运算符和关系表达式

C 语言提供 6 种关系运算符,如下所示:

运算符名称示例功能
<小于a<ba 小于 b 时返回真;否则返回假
<=小于等于a<=ba 小于等于 b 时返回真;否则返回假
>大于a>ba 大于 b 时返回真;否则返回假
>=大于等于a>=ba 大于等于 b 时返回真;否则返回假
==等于a==ba 等于 b 时返回真;否则返回假
!=不等于a!=ba 不等于 b 时返回真;否则返回假

关系运算符的值只能是 0 或 1。 关系运算符的值为真时,结果值都为 1。 关系运算符的值为假时,结果值都为 0。

逻辑运算符和逻辑表达式

有时要求判断的条件不是一个简单的条件,是有几个条件组合而成的复合条件。比如“如果周六下雨,我在实验楼做一个项目课”,这就是有两个条件组合而成的符合条件,需要同时满足两个条件:(1)是否周六(2)是否下雨,只有满足是周六又在下雨,才来实验楼做项目课。

C 语言中提供了三种逻辑运算符:

运算符含义举例说明
&&逻辑与a&&b如果 a 和 b 都为真,则为真,否则为假
||逻辑或a||b如果 a 和 b 有一个或一个以上为真,则为真,二者都是为假,结果为假
!逻辑非!a如果 a 为假,则!a 为真,如果 a 为真,则!a 为假

之前我们做过用户输入一个大写字母,命令行显示与之对应小写字母的程序。但是在当时我们并没有去判断输入的字符是否是一个大写字母,现在我们从新编写程序。

编写源程序 6-3.c:

#include<stdio.h>
int main(){
    char letter;
    printf("Enter an upper case letter:");
    scanf("%c",&letter);

    if((letter>='A') && (letter<='Z'))
    {
        letter += 'a' - 'A';   //等同于 letter=letter+'a'-'A';
        printf("You entered an uppercase %c.\n",letter);
    }
    else
        printf("You did not enter an uppercase letter.\n");

    return 0;
}

运行结果如下:

6-2.5-2

程序分析
if((letter>='A')&&(letter<='Z')){
    letter += 'a' - 'A';   //等同于 letter=letter+'a'-'A';    printf("You entered an uppercase %c.\n",letter);
}
else
    printf("You did not enter an uppercase letter.\n");

在这段程序中 if((letter>='A')&&(letter<='Z')) 检查输入的字符是否大于等于 ‘A’,且小于等于 ‘Z’。两者同时满足才会执行后面的语句。

条件运算符和条件表达式

有一种 if 语句,当被判别的表达式的值为“真”或“假”时,都执行一个赋值语句且向同一个变量赋值。如:

if(a>b)
  max=a;
else
  max=b;
程序分析

a>b 时将 a 的值赋值给 max,当 a<=b 时将 b 的值赋给 max,可以看到无论 a>b 是否满足,都是给同一个变量赋值。

C 语言提供的条件运算符和条件表达式来处理这类问题可以把上面的 if 语句改写为 max=(a>b)?a:b;(a>b)?a:b;是一个“条件表达式”。?是条件运算符。

如果 (a>b) 为真,则表达式的值等于 a,否则取值为 b。条件运算符由两个符号(?和:)组成,必须一起使用。要求三个对象,成为三目运算符,它是 C 语言唯一的一个三目运算符。

条件表达式的一般形式为:

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

6-2.6-1

这次我们列举一个打折的例子。

假定产品的单价是 3.5,提供 3 个级别的折扣:数量超过 50,折扣为 15%;数量超过 20,折扣为 10%;数量超过 10,折扣为 5%。下面是代码。

编写源程序 6-4.c:

#include<stdio.h>
int main(){
    double unit_price = 3.5;
    double discount1 = 0.05;
    double discount2 = 0.1;
    double discount3 = 0.15;
    double total_price = 0.0;
    int quantity = 0;

    printf("Enter the number that you want to buy:");
    scanf("%d",&quantity);

    total_price=quantity*unit_price*(1.0-
        (quantity>50?discount3:(
            quantity>20?discount2:(
                quantity>10?discount1:0.0))));

    printf("The price for %d is %7.2f\n",quantity,total_price);
    return 0;
}

运行结果如下:

6-2.6-2

程序分析

比较有趣的是根据输入的数量计算产品的总价的语句。该语句包含额三个条件运算符,所以有点难以理解:

total_price = quantity*unit_price*(1.0-
                   (quantity>50?discount3:(
                       quantity>20?discount2:(
                            quantity>10?discount1:0.0))));

把它分解为各个部分,就容易理解它是如何得出正确结果的。

总价是用表达式 quantity*unit_price 计算出来的,它只是将单价乘以订购数量。其数量必须乘以由数量决定的折扣因子

  • 如果数量超过 50,总价必须乘以 (1.0-discount3),这用下面的表达式 (1.0-quantity>50?discount3:something_else)
  • 如果 quantity>50 表达式就乘以 (1.0-discount3),完成赋值运算符右边的运算
  • 否则,表达式乘以 (1.0-something_else),其中 something_else 是另一个条件运算符的结果。

用 switch 语句实现多分支选择结构

if 语句只有两个分支可供选择,而实际中常常用到多分支选择。例如成绩分类(85 分以上为 A,70-85 为 B,60-69 为 C 等),人口的分类(可以分为老、中、青、少、儿童),工资统计分类等等,如果我们使用 if 语句就会嵌套很多层数,影响可读性。C 语言提供的 switch 语句直接处理多分支选择。

我们首先用一个例子说明,编写 6-5.c 程序:

#include<stdio.h>
int main(){
    char grade;
    scanf("%c",&grade);
    printf("you score:");
    switch(grade){
      case 'a':printf("85~100\n");break;
      case 'b':printf("70~84\n");break;
      case 'c':printf("60~69\n");break;
      case 'd':printf("<60\n");break;
      default:printf("error!\n");
    }

   return 0;
}

运行结果:

6-2.7-1

程序分析
  1. 等级 grade 定义为字符变量,从键盘输入一个小写字母,赋给变量 grade,switch 得到 grade 的值并和各 case 中给定的值(‘a’‘b’‘c’‘d’之一)相比较,如果和其中之一相同,则执行该 case 后面的语句(即 printf 语句)。如果输入的字符与’a’‘b’‘c’'d’都不相同,就执行 default 后面的语句,输出 error!信息。
  2. 注意每个 case 语句中,后面都有一个 break 语句,该语句的作用是使得当前流程跳转到闭括号 ‘}’ 后面的语句。

假如去掉程序中所有 break 语句,用户从键盘输入 b 以后,输出 70 ~ 84 并换行以后,程序将执行 case 'c':printf("60~69\n"); 语句,并且还要执行下去,结果如下:

6-2.7-2

通过上例我们已经基本了解了 switch 语句。其一般形式如下:

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

说明:

  • switch 括号内的“表达式”,其值的类型应为整数类型或者字符类型,不可以是其他数据类型。
  • default 语句为选择项,可有可无。另外,default 后面可以不加 break 语句。

实验总结

我们本节课基本上学习了 C 语言选择结构程序设计的大部分内容,但是并没给大家更多的案例和习题,接下来我们写一个小的项目课来巩固本节课的学习。

课后练习题

实验5 循环程序设计

用 for 语句实现循环

除了可以用 while 语句和 do...while 语句实现循环外。C 语言还提供 for 语句实现循环,for 语言十分灵活,很多情况下他完全可以替代 while 语句。

for 语句的一般形式如下:

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

3 个表达式的作用如下:

  • 表达式 1:设置初始条件,只执行一次。可以为零个、一个或者多个表达式赋初值。
  • 表达式 2:是循环条件表达式,用来判定是否继续循环。在每次执行循环体前先执行此表达式,决定是否继续执行循环。
  • 表达式 3:作为循环的调整,例如循环变量的增值,是在执行完循环体后才进行的。

我们依然用 1 到 100 的和这个计算题来理解 for 语句。

编写源程序 7-5.c:

#include<stdio.h>

int main(){
    int i,sum = 0;
    for(i=1;i<=100;i++)
        sum = sum + i;
    printf("sum=%d\n",sum);

    return 0;
}

运行结果如下:

7-2.3-1

for 语句的执行过程如下:

  1. 先求解表达式 1。本例中的表达式 1 就是 i=1,即把整数 1 赋值给 i。
  2. 求解表达式 2。若此条件表达式的值为真(非 0),则执行 for 语句的循环体,然后执行第三步。若为假(0),则结束循环,转到第(5)步。
  3. 求解表达式 3。在本例中,执行 i++,使 i 的值加 1,i 的值变成 2。
  4. 转回步骤 2 继续执行。 由于此时 i=2,表达式 i<=100 的值为真,再次执行循环体中的语句。然后再执行步骤(3)。如此反复,直到 i 的值变到了 101,此时表达式 i<=100 的值为假,不再执行循环体,而转到步骤(5)。
  5. 循环结束,执行 for 语句下面的一个语句。

上面的 for 语句:

for(i=1;i<=100;i++)
sum = sum + i;

其执行过程和下面语句相当:

i = 1;
while(i<=100){
   sum = sum + i;
   i++;
}

显然用 for 语句更简单、方便。

关于 for 语句的一些说明:

  1. for 语句的一般形式:

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

    可以改写成为 while 循环的形式:

    表达式 1;

    while (表达式 2)

    {

    语句

    表达式 3

    }

    两者是无条件等价的。

  2. 表达式 1 可以省略,即为不初值。但是表达式 1 后面的分号不能省略。例如:

    i = 1;
    for(;i<=100;i++)
        sum = sum + i;
    
  3. 表达式 2 也可以省略。代码如下:

    for(i=1;;i++)
        sum = sum + i;
    

    此时循环将会无终止的进行下去,默认为表达式 2 是真。

  4. 表达式 3 也可以省略,代码如下:

    for(i=1;i<=100;)
    {
      sum = sum + i;
      i++;
    }
    

    i++操作不放在表达式 3 原本的位置,放到循环体中效果是一样的。

通过以上介绍,我们知道了 C 语言的 for 循环语句变化多端,十分灵活。但是我们应该注意的是不要过分利用这一特点,这样会使的 for 语句显得杂乱,可读性差。

改变循环的执行状态

我们上一章节在 switch 语句中使用过 break 语句。它的作用是终止 switch 块中代码的执行,并继续执行跟在switch 后的第一行语句。break 语句在循环体内的作用和 switch 基本相同。

例:某电商网站某天做促销活动,当天新注册的用户(1000 人)购物全部 9 折,当新注册用户购买商品总金额超过 10 万,停止活动。统计此时购物的新注册用户人数以及平均每人购物的金额。

for 语句本来循环 1000 次。在每一次循环中,输入一个新注册用户的购买金额,然后把它累加到 total 中,如果没有 if 语句,则执行循环体 1000 次。现在设置一个 if 语句,在每一次累加了购买金额 amount 后,立即检查累加和 total 是否达到或者超过 SUM ,当 total>=100000 时,就执行 break 语句,流程跳转到循环体的花括号外,即不再继续执行其余的几次循环,提前结束循环。此时的 i 是什么呢?是已经购买商品的新注册用户。

实现中的核心代码如下:

for (i=0; i < 1000; i++) {
  // 初始化购买金额
  int money = 0;

  // 输入新的购买金额
  scanf("%d", &money);

  // 增加到总金额里
  total += money;

  // 判断是否达到停止活动条件
  if (total > 100000)
      break;
}

continue 语句与 break 语句不同,并不停止循环,而是跳出当前循环的状态继续执行下一轮的循环。求取 100-200 之间所有不可以被 3 整除的数,并打印出来。实现过程中凡是遇到可以被 3 整除的数则跳过循环内的后面所有的代码,继续执行下一轮循环。

编写源程序 7-6.c:

#include<stdio.h>

int main(){
    int n;
    for(n=100;n<=200;n++){
        if(n%3==0)
            continue;
        printf("%d\t",n);
    }
    printf("\n");

    return 0;
}

运行结果如下:

7-2.4-1

continue 的作用为结束本次循环,就是跳过循环体中下面尚未执行的语句,然后接着下一次的循环操作。 continue 语句是结束本次循环,而不是终止整个循环的执行。而 break 语句则是结束整个循环过程,不在判断执行循环的条件是否成立。

循环的嵌套

一个循环体内又包含另外一个完整的循环结构,称为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。while 循环、do...while 循环和 for 循环可以互相嵌套。我们在这里不敞开一一讲述,举一个 for 循环的嵌套供大家理解,以后的学习中我们还会经常遇到其他嵌套。输出下面的 4*5 的矩阵:

1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20

我们可以使用嵌套来处理此问题,用内循环来输出一行数据,用外循环来输出一列数据。要注意设法输出以上矩阵的格式(每行 5 个数据),即每输出完 5 个数据后换行。

编写源程序 7-7.c:

#include<stdio.h>

int main(){
    int i,j,n=0;
    for(i=1;i<=4;i++)
        for(j=1;j<=5;j++,n++){
            if(n%5==0) printf("\n");
            printf("%d\t",i*j);
        }
    printf("\n");

    return 0;
}

代码 for(j=1;j<=5;j++,n++) 中的 j++,n++ 是一个逗号表达式。在这里,我们不关心表达式的值(感兴趣的同学可以自己下来查阅资料)。我们关心的是这个表达式怎么执行,它的流程非常简单:它先执行 j++(等同于 j=j+1 再执行 n++(等同于 n=n+1),这里我们可以就看作是 jn 都步进 1,与之前只有一个变量做步进操作是一样的。

运行结果如下:

7-2.5-1

该程序包含一个双重循环,是 for 循环的嵌套。外循环变量 i 由 1 变到 4,用来控制输出的 4 行数据,内循环变量 j 由 1 变到 5,用来控制输出中的 5 个数据。输出的值是 ij。在执行第 1 次外循环体时,i=1,j 由 1 变到 5,因此 ij 就是 1,2,3,4,5。在执行第二次外循环时,i=2,j 由 1 变到 5,因此 i*j 的值就是 2,4,6,8,10。以此类推。

循环的嵌套

一个循环体内又包含另外一个完整的循环结构,称为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。while 循环、do...while 循环和 for 循环可以互相嵌套。我们在这里不敞开一一讲述,举一个 for 循环的嵌套供大家理解,以后的学习中我们还会经常遇到其他嵌套。输出下面的 4*5 的矩阵:

1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20

我们可以使用嵌套来处理此问题,用内循环来输出一行数据,用外循环来输出一列数据。要注意设法输出以上矩阵的格式(每行 5 个数据),即每输出完 5 个数据后换行。

编写源程序 7-7.c:

#include<stdio.h>

int main(){
    int i,j,n=0;
    for(i=1;i<=4;i++)
        for(j=1;j<=5;j++,n++){
            if(n%5==0) printf("\n");
            printf("%d\t",i*j);
        }
    printf("\n");

    return 0;
}

代码 for(j=1;j<=5;j++,n++) 中的 j++,n++ 是一个逗号表达式。在这里,我们不关心表达式的值(感兴趣的同学可以自己下来查阅资料)。我们关心的是这个表达式怎么执行,它的流程非常简单:它先执行 j++(等同于 j=j+1 再执行 n++(等同于 n=n+1),这里我们可以就看作是 jn 都步进 1,与之前只有一个变量做步进操作是一样的。

运行结果如下:

7-2.5-1

该程序包含一个双重循环,是 for 循环的嵌套。外循环变量 i 由 1 变到 4,用来控制输出的 4 行数据,内循环变量 j 由 1 变到 5,用来控制输出中的 5 个数据。输出的值是 ij。在执行第 1 次外循环体时,i=1,j 由 1 变到 5,因此 ij 就是 1,2,3,4,5。在执行第二次外循环时,i=2,j 由 1 变到 5,因此 i*j 的值就是 2,4,6,8,10。以此类推。

循环的嵌套

一个循环体内又包含另外一个完整的循环结构,称为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。while 循环、do...while 循环和 for 循环可以互相嵌套。我们在这里不敞开一一讲述,举一个 for 循环的嵌套供大家理解,以后的学习中我们还会经常遇到其他嵌套。输出下面的 4*5 的矩阵:

1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20

我们可以使用嵌套来处理此问题,用内循环来输出一行数据,用外循环来输出一列数据。要注意设法输出以上矩阵的格式(每行 5 个数据),即每输出完 5 个数据后换行。

编写源程序 7-7.c:

#include<stdio.h>

int main(){
    int i,j,n=0;
    for(i=1;i<=4;i++)
        for(j=1;j<=5;j++,n++){
            if(n%5==0) printf("\n");
            printf("%d\t",i*j);
        }
    printf("\n");

    return 0;
}

代码 for(j=1;j<=5;j++,n++) 中的 j++,n++ 是一个逗号表达式。在这里,我们不关心表达式的值(感兴趣的同学可以自己下来查阅资料)。我们关心的是这个表达式怎么执行,它的流程非常简单:它先执行 j++(等同于 j=j+1 再执行 n++(等同于 n=n+1),这里我们可以就看作是 jn 都步进 1,与之前只有一个变量做步进操作是一样的。

运行结果如下:

7-2.5-1

该程序包含一个双重循环,是 for 循环的嵌套。外循环变量 i 由 1 变到 4,用来控制输出的 4 行数据,内循环变量 j 由 1 变到 5,用来控制输出中的 5 个数据。输出的值是 ij。在执行第 1 次外循环体时,i=1,j 由 1 变到 5,因此 ij 就是 1,2,3,4,5。在执行第二次外循环时,i=2,j 由 1 变到 5,因此 i*j 的值就是 2,4,6,8,10。以此类推。

循环的嵌套

一个循环体内又包含另外一个完整的循环结构,称为循环的嵌套。内嵌的循环中还可以嵌套循环,这就是多层循环。while 循环、do...while 循环和 for 循环可以互相嵌套。我们在这里不敞开一一讲述,举一个 for 循环的嵌套供大家理解,以后的学习中我们还会经常遇到其他嵌套。输出下面的 4*5 的矩阵:

1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20

我们可以使用嵌套来处理此问题,用内循环来输出一行数据,用外循环来输出一列数据。要注意设法输出以上矩阵的格式(每行 5 个数据),即每输出完 5 个数据后换行。

编写源程序 7-7.c:

#include<stdio.h>

int main(){
    int i,j,n=0;
    for(i=1;i<=4;i++)
        for(j=1;j<=5;j++,n++){
            if(n%5==0) printf("\n");
            printf("%d\t",i*j);
        }
    printf("\n");

    return 0;
}

代码 for(j=1;j<=5;j++,n++) 中的 j++,n++ 是一个逗号表达式。在这里,我们不关心表达式的值(感兴趣的同学可以自己下来查阅资料)。我们关心的是这个表达式怎么执行,它的流程非常简单:它先执行 j++(等同于 j=j+1 再执行 n++(等同于 n=n+1),这里我们可以就看作是 jn 都步进 1,与之前只有一个变量做步进操作是一样的。

运行结果如下:

7-2.5-1

该程序包含一个双重循环,是 for 循环的嵌套。外循环变量 i 由 1 变到 4,用来控制输出的 4 行数据,内循环变量 j 由 1 变到 5,用来控制输出中的 5 个数据。输出的值是 ij。在执行第 1 次外循环体时,i=1,j 由 1 变到 5,因此 ij 就是 1,2,3,4,5。在执行第二次外循环时,i=2,j 由 1 变到 5,因此 i*j 的值就是 2,4,6,8,10。以此类推。

实验总结

我们本节课讲述了 C 语言的循环语句,大家可以通过练习体验 whiledo...whilefor 语句之间的区别和联系,同时讲述循环的中断与嵌套。循环语句想要真正的掌握就必须大量的进行编程练习。

课后练习题

输出下面图案: 提示:找到跳出循环的判断条件是关键。

    *
   ***
  *****
 *******
  *****
   ***
    *

参考答案

#include<stdio.h>

int main(){
    int i,j;
    //正金字
    for (i=1;i<=4;i++){          // 控制金字塔的层数,输出4层,可以改变输出任意层
        for (j=1;j<=4-i;j++)    // 控制金字塔每层前面输出的空格数
            printf(" ");
        for (j=1;j<=2*i-1;j++)  // 控制金字塔每层需要打印'*'的个数
            printf("*");
        printf("\n");           // 一层金字塔输出完毕,换行继续输出下一层
    }
      //倒金字
    for(i=3;i>0;i--){            // 控制金字塔的层数,输出3层,可以改变输出任意层
        for(j=1;j<=4-i;j++)
            printf(" ");
        for (j=1;j<=2*i-1;j++)
            printf("*");
        printf("\n");
    }
    return 0;
}

章节

步骤

报告

讨论

实验6 C语言数组

一维数组

定义一维数组

定义一维数组的一般形式为:

类型符 数组名[常量表达式]

例如定义以下数组:

int student[10];

注意:

  • 数组名的命名规则和变量名相同,遵循标识符命名规则。
  • 在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。例如,在定义时定义 student[10],表示数组 student[] 有 10 个元素。注意,下标是从 0 开始的,这 10 个元素分别是 student[0]…student[9]。一定要注意这里面不会有 student[10]。
  • 常量表达式中可以包括常量和符号变量,如 int a[4*2] 是合法的。但是不能包括变量,如下面的就是不合法的:
int n;
scanf("%d",&n);
int b[n];

经过上面的定义,在内存中划出一片存储空间(空间大小:4*10 = 40 字节),存放一个含有 10 个整形元素的数组。

8-2.1.1-1

一维数组的初始化

为了让程序简洁,我们常常在定义数组的同时,给各数组元素赋值,这就是数组的初始化。

  1. 在定义数组时对全部数组元素赋予初值。例如:
int a[10] = {0,1,2,3,4,5,6,7,8,9}

经过上面的定义并初始化之后,a[0]=0,a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5,a[6]=6,a[7]=7,a[8]=8,a[9]=9。

  1. 给数组中的部分元素赋值,例如:

    int a[10] = {0,1,2,3};
    

定义 a 数组有 10 个元素,但是花括号内只给了 4 个初值,这表示只给前 4 个元素赋初值,其余元素系统自动给其赋初值为 0。

一维数组举例
  1. 定义一个含有 10 个元素的数组,依次赋值为 1,2,3,4,5,6,7,8,9,10,然后按逆序输出。

解题思路:

要赋的值是 1~10,有一定规律,我们可以用循环来赋值。同样,用循环来输出这 10 个值,在输出时,先输出最后的元素,按下标从大到小输出这 10 个元素。

创建 8-1.c 文件并输入以下代码:

#include<stdio.h>
int main(){
    int i,a[10];

    for(i=0;i<10;i++)
        a[i] = i + 1;

    for(i=9;i>=0;i--)
        printf("%d\t",a[i]);

    return 0;
}

输入以下命令编译并运行:

gcc -o 8-1 8-1.c
./8-1

结果如下: 8-2.1.3-1

  1. 现在输入 10 个用户的有效学习时间,要求对它们按由小到大的顺序排序。

解题思路:这种问题是一个典型的排序问题,排序方法是一种重要且基本的算法,我们在此使用“冒泡排序法”,其思路为:每次将相邻两个数比较,将小的调到前面,如果有 6 个数:8,7,5,4,2,0。第一次先将最前面的两个数 8 和 7 对调(看下图)。第二次将第二个数和第三个数(8 和 5)对调。如此总计进行了 5 次,得到 7-5-4-2-0-8 的顺序,可以看到:最大的数 8 已经沉底,成为最下面的一个数,而小的数上升。经过第一轮(5 次比较)后,得到了最大的数 8。

8-2.1.3-2

然后进行第二轮的比较(见下图),对余下的 7,5,4,2,0 进行新一轮的比较,以便使次大的数沉底。经过这一轮 4 次的比较与交换,得到次大的数 7。

8-2.1.3-3

按照此规律进行下去,可以推知 6 个数需要比较 5 轮,其中第一轮需要比较 5 次,第二轮需要比较 4 次,以此类推,第五轮只需比较一次。

如果有 n 个数,则需要比较 n-1 轮,在第一轮需要进行 n-1 次两两比较,在第 j 轮中要进行 n-j 次的两两比较。

这种如水底的气泡逐步冒出水面一样,故称之为冒泡法。

编写源程序 8-2.c:

#include<stdio.h>

int main(){
  int i,j,t,LearnTime[10];
  printf("please enter 10 number:\n");

  for(i=0;i<10;i++)
      scanf("%d",&LearnTime[i]);

  for(j=0;j<9;j++)    //9次外循环,9次比较
      for(i=0;i<9-j;i++)    //9-j次内循环
          if(LearnTime[i]>LearnTime[i+1])    //交换值
          {
              t=LearnTime[i];
              LearnTime[i] = LearnTime[i+1];
              LearnTime[i+1] = t;
          }
   printf("the sorted number:\n");
   for(i=0;i<10;i++)
       printf("%d\t",LearnTime[i]);

   return 0;
}

程序运行结果如下:

8-2.1.3-4

二维数组

很多问题需要我们用到二维数组,比如一个年级有 4 个班,每个班有 30 个人,需要统计每个班的学生成绩,这个时候就要用的二维数组。如果建立一个数组 student[][],第一维用来表示第几班,二维用来表示第几号,比如 student[2][3] 表示 2 班的 3 号同学。

定义二维数组

二维数组的定义方法和一维数组类似,其一般形式为:

类型说明符 数组名[常量表达式][常量表达式]

例如:

float a[3][4];

C 语言中,二维数组在内存中存放的顺序是按行存放的,即在内存中先顺序存放第一行元素,接着再来存放第二行元素。

假设数组存放是从 2000 开始的,一个元素占用 4 个字节,下图就是 a[3][4] 在内存中存放的具体形式。

8-2.2.1-1

二维数组的初始化
  1. 分行给二维数组赋初值。例如:

    int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    

    这种赋值方法比较直观,把第一个花括号的值给第 0 行元素,第 2 个花括号的值给第一行元素

  2. 可以将所有的数据放在一个花括号内,例如:

    int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    

    效果和第一种相同。但是很显然第一种方式较好,一行对一行,界限很清楚。用第二种方法如果数据多,则会写成一大片,容易遗漏。

  3. 可以对部分元素赋初值。例如:

    int a[3][4]={{1},{3},{5}};
    

    它的作用是只给各行第 0 列的元素赋初值,其他元素默认为 0。赋初值后的结果为:

    1 0 0 0
    3 0 0 0
    5 0 0 0
    
二维数组的举例

有一个 3*4 的矩阵,求出其中的最大值并输出最大值和其所在的行号和列号。

解题思路:先思考一下在打擂台的时候怎样确定最后的优胜者。先找出任一个人站在台上,第 2 个人上去与之比武,胜者留在台上。再上去第 3 个人,与台上的人比武,胜者留在台上,败者下台。以后每一个人都和当时留在台上的人比武。直到所有人都上台比过为止,最后留在台上的就是冠军,这中方法就是打擂台法。

我们本题目也采用打擂台算法。先让 a[0][0] 做“擂主”,把它的值赋给变量 max,max 用来存放当前已知的最大值,在开始时还未进行比较,把最前面的元素认为是当前最大值,然后让下一个元素 a[0][1] 和 max 比较,如果 a[0][1]>max,则把 a[0][1] 赋值给 max,取代 max 的原值。以后以此处理,直到全部比完之后,max 就是最大值。

编写源程序 8-3.c:

#include<stdio.h>

int main(){
    int i,j,row = 0,colum = 0,max;
    int a[3][4] = {{3,4,16,2},{7,5,1,9},{11,23,3,8}};

    max = a[0][0];
    for(i=0;i<3;i++)
        for(j=0;j<4;j++)
            if(a[i][j]>max)
            {
                max = a[i][j];
                row = i;
                colum = j;
            }

    printf("max=%d\nrow=%d\ncolum=%d\n",max,row,colum);

    return 0;
}

运行结果为:

8-2.2.3-1

字符数组

前面我们已经讲解过,字符型数据是以字符的 ASCII 代码存储在存储单元中的,一般占一个字节。由于 ASCII 代码属于整数形式,因此,把字符型归纳为整数类型中的一种。 由于字符数据的应用十分广泛,尤其是作为字符串形式的使用,有其自己的特点,我们单独成节来讲述该内容。

注意:C 语言中没有字符串类型,字符串都是存储在字符型数组中的。

字符数组的定义

定义字符数组的方法与定义数值型数组的方法类似:例如:

char c[10];

以上就定义了 c[] 为字符数组,包含 10 个元素。

字符数组的初始化

把各个字符以此赋给数组中各元素。例如:

char c[10]={'I',' ','a','m',' ','h','a','p','p','y'};

把这 10 个字符以此赋给 c[0]~c[9] 这 10 个元素。

注意:上面例子中花括号提供的初值个数(即字符个数)等于数组长度,倘若花括号中的提供初值个数大于数组长度,则会出现语法错误。倘若初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余元素自动定为空字符(即’\0’)。例如:

char c[10]={'I',' ','l','o','v','e',' ','c'};

8-2.3.2-1

在定义数组时可以省略数组长度,系统会自动根据数组个数确定数组长度。例如:

char c[]={'I',' ','a','m',' ','h','a','p','p','y'};

数组 c[] 的长度自动定为 10。这种方式不用人工去数字符的个数,尤其在赋初值字符个数不较多的情况下,比较方便。

例如,输出一个正方形。

解题思路:

先画出一个平面正方形图案,每行包含 5 个字符,其中有的是空白字符,有的是 ‘*’ 字符,定义一个字符型的二维数组并初始化,用 for 循环嵌套输出。

编写源程序 8-4.c:

#include<stdio.h>

int main(){
    char c[ ][9]={
    {'*',' ','*',' ','*',' ','*',' ','*'},
    {'*',' ',' ',' ',' ',' ',' ',' ','*'},
    {'*',' ',' ',' ',' ',' ',' ',' ','*'},
    {'*',' ',' ',' ',' ',' ',' ',' ','*'},
    {'*',' ','*',' ','*',' ','*',' ','*'},
    };
    int i,j;

    for(i=0;i<5;i++)
    {
        for(j=0;j<9;j++)
            printf("%c",c[i][j]);
        printf("\n");
    }

    return 0;
}

补充一点:C 语言中,二维数组初始化的语句中可以不指定行数进行初始化,比如 char c[ ][9] 就省略了行数,编译器自动将其识别为 char c[5][9]

运行结果:

8-2.3.2-2

字符串和字符串结束标志

在 C 语言中,是将字符串作为字符数组来处理的。在平时的应用中,人们往往关心的是字符串的有效长度而不是字符数组的长度。例如,定义一个字符数组长度 50,而实际有效字符只有 30 个。为了测定字符串的实际长度,C 语言规定了一个“字符串结束标志(‘\0’)”,以字符 ‘\0’ 作为结束标志。

如果字符数组中存在若干字符,前面 9 个字符都不是空字符(‘\0’),而第 10 个字符是 ‘\0’,则认为数组中有一个字符串,其有效字符为 9 个。 系统在用字符数组存储字符串常量时会自动加一个 ‘\0’ 作为结束符。例如:

printf("Shiyanlou");

在执行次语句时系统怎么知道该输出到哪里为止呢?实际上,在向内存中存储时,系统自动在最后一个字符 ‘u’ 后面加一个 ‘\0’ 作为字符串结束标志。在执行 printf() 函数时,每输出一个字符检查一次,看下一个字符是否为 ‘\0’,遇到 ‘\0’ 就停止输出。

8-2.3.3-1

有了上述理解,在对字符数组初始化的方法上面我们做出一些补充。例如:

char[] = {"I love shiyanlou"};

我们也可以直接省去花括号:

char[] = "I love shiyanlou";

这里不像之前单个字符作为字符数组的初值,而是用字符串作为初值(字符串做初值是用双撇号而不是单撇号)。这种方法更直观、更方便。

注意:此时数组的长度是 17,而不是 16,因为字符串常量的最后系统自动加了一个 ‘\0’。上面的初始化与下面的初始化等价的。

char c[] = {'I',' ','l','o','v','e',' ','s','h','i','y','a','n','l','o','u','\0'};

和下面的这个不等价:

char c[] = {'I',' ','l','o','v','e',' ','s','h','i','y','a','n','l','o','u'};
字符数组的输入输出
  • 字符数组的输入输可以像前面的输入输出数字一样,用格式输入输出控制符。
  1. 逐个字符输入输出,用格式“%c”输入或者输出一个字符。

  2. 将整个字符串一次输入或者输出,用“%s”格式符,例如:

    char c[] = "shiyanlou";
    printf("%s", c);     //用%s格式符输入或输出字符串时,接受项是字符数组名
    

    内存中数组 c 的存储情况:

    8-2.3.4-1

  • 也可以用putsgets函数。其一般形式如下:

    puts(字符数组):其作用是将一个字符串输出到终端,因此该函数用的不是很多,我们可以编写小程序来体验。

    gets(字符数组):其作用是从终端输入一个字符串到字符数组,并且得到一个函数值。

编写源程序 8-5.c:

#include<stdio.h>
#include<string.h> //在使用字符串处理函数时,在程序文件的开头用 #include<string.h>
int main(){
   char str[] = "China\nChengdu";

   puts(str);
   printf("Enter a new string:");
   gets(str);    //有的编译器会报 warning,提示 gets() 函数不安全;
                   //这里说明一下,这个warning是编译器针对这个函数的,不影响实验。感兴趣的同学可以作为课后自学。
   puts(str);

   return 0;
}

运行结果为:

8-2.3.4-2

使用字符串处理函数

C 语言函数库中提供了一些用来专门处理字符串的函数,使用比较方便。

strcat 函数-字符串连接函数

其一般形式如下:

strcat(字符数组 1,字符数组 2)

其作用是把两个字符数组中的字符串连接起来,把字符串 2 接到 1 后面,结果放到字符串 1 中。

编写源程序 8-6.c:

#include<stdio.h>
#include<string.h> //在使用字符串处理函数时,在程序文件的开头用 #include<string.h>
int main(){
   char str1[30] = "People's Republic of'";
   char str2[] = "China";

   printf("%s",strcat(str1,str2));

   return 0;
}

运行结果:

8-2.4.1-1

注意:

  1. 连接前两个字符串后面都有 ‘\0’,连接时将字符串 1 后面的 ‘\0’ 取消,只在新串后面保留 ‘\0’。
  2. 字符串 1 必须足够大,以便于容纳字符串 2。如果在定义是定义为 char str1[]="People's Republic of"; 就会出现问题,因为长度不够。
strlen 函数-测字符串长度的函数

其一般形式如下:

strlen(字符数组)

它是测量字符串长度的函数。函数的值为字符串中的实际长度。例如:

char str[] = "China";
printf("%d",strlen(str));

注意:strlen() 返回的值比实际占用的长度要小,因为不包含 ‘\0’。

strcpy ——字符串复制函数

其一般形式如下:

strcpy(字符串 1,字符串 2)

作用是将字符串 2 复制到字符串 1 中。例如:

char str1[10],str2[] = "China";
strcpy(str1,str2);

注意:

  • 字符数组 1 必须定义的足够大,以便容纳被复制的字符串 2。

  • “字符数组 1” 必须写成数组名形式(如 str1),“字符串 2”可以使字符数组名,也可以是一个字符串常量。例如:strcpy(str1,"China"); 作用与前面的相同。

  • 不能用赋值语句将一个字符串常量直接给一个字符数组。如下面两行是错误的:

    str1 = "shiyanlou";      //错误,企图用赋值语句将一个字符串常量直接赋值给一个数组
    str1 = str2;              //错误,企图用赋值语句将一个字符数组直接赋给另一个字符数组
    
strcmp——字符串比较函数

其一般形式如下:

strcmp(字符串 1,字符串 2)

它的作用是比较字符串 1 和字符串 2。例如:

strcmp(str1,str2);
strcmp("Chengdu","Beijing");

比较规则

将两个字符串自左向右逐个字符比较(按照 ASCII 码值大小比较),直到出现不同的字符或者遇到 '\0 '为止。

  1. 如果全部字符相同,则认为两个字符串相同。
  2. 若出现不同的字符,则以第 1 对不相同的字符的比较结果为准。例如:“A”<“D”,“e”>“E”,“these”>“that”,“computer”>“compare”。
  3. 比较结果由函数值带回。
    • 字符串 1=字符串 2,则函数值为 0
    • 字符串 1>字符串 2,则函数值为一个正整数
    • 字符串 1<字符串 2,则函数值为一个负整数

关于字符串处理函数,还有 strlwr 函数(转换为小写的函数)、strupr 函数(转换为大写的函数)strncpy 函数等其它函数,我们这里不再一一阐述,有兴趣的小伙伴可以查询相关内容。

字符数组应用举例

任意键入 3 个字符串,编程找出最小的一个。

编写源程序 8-7.c:

#include<stdio.h>
#include<string.h>

int main(){
    char string[30];
    // 定义一个二维数组
    // ch[1]、ch[2]、ch[3] 都是一个字符数组,相当于三个字符串
    char ch[3][30];
    int i;
    // 循环读取三行,并且将每行存到 ch[i] 中
    for(i=0;i<3;i++)
        gets(ch[i]);  // 有的编译器会报 warning,提示 gets() 函数不安全,不影响实验。
    strcpy(string,ch[0]);
    for(i=1;i<3;i++)
        if(strcmp(ch[i],string)<0)
            strcpy(string,ch[i]);
    printf("The result is :\n%s",string);

    return 0;
}

这里要注意的是,我们定义的 ch[1]、ch[2]、ch[3] 都只有 30 个字符大小,gets() 函数还会自动补 \0,因此我们每行的输入不要超过 29 个字符。

运行结果为:

8-2.4.4-1

实验总结

本节课讲述的内容较多,需要大家多多练习方可掌握,接下来的练习题大家也务必自己独立思考并完成程序编写才能融会贯通,掌握数组知识。

课后练习题

习题一

输入 20 个实数存放在一维数组中,输出它们的平均值以及高于平均的数的个数。

习题二

围绕着山顶有 10 个洞,一只兔子和一只狐狸住在各自的洞里,狐狸总想吃掉兔子,一天兔子对狐狸说,你想吃我有一个条件,你先把洞编号 1 到 10,你从第 10 洞出发,先到第 1 号洞找我,第二次隔一个洞找我,第三次隔两个洞找我,以后依次类推,次数不限,若能找到我你就可以饱餐一顿,在没找到我之前不能停止,狐狸一想只有 10 个洞,寻找的次数又不限,哪有找不到的道理,就答应了条件,结果狐狸跑得昏了过去也没找到兔子,请问兔子躲在哪个洞里。程序中可假定狐狸找了 1000 次。

参考答案

习题一
#include<stdio.h>

int main(){
    int num[20];
    int ave,sum;
    int flag = 0;               //大于平均数的个数

    //输入
    for(int i=0;i<20;i++)
        scanf("%d",&num[i]);

    //求和
    for(int i=0;i<20;i++)
        sum += num[i];

    ave = sum / 20;

    for(int i=0;i<20;i++)
       if(num[i]>ave)
            flag++;

    printf("ave=%d,num bigger than ave is %d",ave,flag);

    return 0;
}
习题二
#include<stdio.h>

int main(){
    int hole[10],i,n = 0;

    for(i=0;i<10;i++)
        hole[i] = 0;                    //为每个洞赋值为0,代表狐狸未找过该洞

    for(i=0;i<1000;i++)                 //用循环实现狐狸找1000次洞
    {
        n = n % 10;                      //n为狐狸当前寻找的洞的编号(实际编号应为n+1)
        hole[n] = 1;                    //为找过的洞赋值为1,代表狐狸已经找过该洞
        n = n + i + 2;                     //设置下一次寻找的编号
    }

    printf("The hole's numbers are:");

    for(i=0;i<10;i++)                   //逐个输出洞的编号(实际编号)
    {
        if(hole[i]==0)
        {
            printf("%d ",i + 1);
        }
    }

    return 0;
}

实验7 模块化程序设计

实验步骤

一个 C 语言程序可由一个主函数和若干其它函数构成,每个函数实现一个特定的功能。主函数调用其他函数,其它函数也可以互相调用。同一个函数可以被一个或者多个函数调用多次。下图是一个程序中函数调用的示意图。

9-2-1

模块化程序设计

我们通过例子来理解模块化程序设计,要求用函数调用实现输出以下的结果:

*************
how do you do!
*************

解题思路

在输出的文字上下分别有一行 * 号,显然不必重复写这段代码,用一个函数 printstar 来实现输出一行 * 的功能。再写一个 print_message 函数来输出中间一行文字信息,用主函数分别调用这两个函数即可。

创建 9-1.c 文件并输入以下代码:

#include<stdio.h>
#include<string.h>

int main(){
    void printstar();        //声明 printstar 函数
    void print_message();    //声明 print_message 函数

    printstar();            //调用 printstar 函数
    print_message();        //调用 print_message 函数
    printstar();            //调用 printstar 函数

    return 0;
}

void printstar(){    //定义 printstar 函数
    printf("**************\n");
}

void print_message(){    //定义 print_message 函数
    printf("how do you do!\n");
}

输入以下命令编译并运行:

gcc -o 9-1 9-1.c
./9-1

程序运行结果如下:

9-2.1-1

程序分析

printstarprint_message 都是用户自定义的函数名,分别用来输出一排 “*” 号和一行文字信息。在定义这两个函数时指定函数的类型为 void,表示函数无类型,即无函数值,也就是说,执行这两个函数不会返回任何值到 main 函数。

在程序中,定义 printstarprint_message 函数的位置是在 main 函数后面,在这种情况下,应该在 main 函数之前或 main 函数中的开头部分,对以上两个函数进行“声明”。

函数声明的作用是把有关函数的信息(函数名、函数类型、函数参数的个数与类型)通知编译系统,以便于在编译系统对程序进行编译时,在进行到 main 函数调用 printstarprint_message 时知道他们是函数而不是变量或其他对象。

函数的定义

C 语言要求,在程序中用到的所有函数,必须“先定义,后使用”。例如想用 max 求两个数的最大值。必须先按规范对它进行定义,指定他的名字、函数返回类型、函数实现的功能以及参数的个数和类型,将这些信息通知给编译系统。这样程序执行 max 函数的时,编译系统就会按照定义时所指定的功能去执行。如果事先不定义,编译系统是无从知晓 max 是什么,要实现什么功能的。

对于 C 编译系统提供的库函数,是由编译系统事先定义好的,库文件中包含了对各函数的定义。程序设计者不必自己定义,只需要用 #include 指令把有关的头文件包含到本文件中即可。例如在程序中用到 sqrt,fabs,sin 等数学函数,就必须在文件模块开头写上:#include<math.h>

库函数只提供了最基本、最通用的一些函数,而不可能包括人们在实际应用中所用到的所有函数。我们往往需要自己定义想要用的而库函数并没有提供的函数。

定义无参函数

我们在 9-1.c 中的 printstarprint_message 函数都是无参函数,大家可以看到函数名后的括号中是空的,没有任何参数。

其一般形式为

类型名 函数名(){
    函数体
}

说明:

类型名表示的是返回值的类型,比如 9-1.c 中在定义 printstar 函数为:

void printstar (){

}

函数没有返回值所以定义为 void。

定义有参函数

一般形式为:

类型名 函数名(形式参数列表){
    函数体
}

求出两个数的最大值。

编写源程序 9-2.c:

#include<stdio.h>

int main(){
    int max(int x,int y);    //声明 max 函数
    int a = 10,b = 20;
    int c;
    c = max(a,b);                //a, b 为实际参数;调用max函数,传递的是a,b的值给 max 函数。
    printf("%d",c);

    return 0;
}

int max(int x,int y){         //int 代表的是返回值是 int 型
    int z;
    z = x>y ? x:y;
    return (z);                //返回 z
}

程序运行结果如下:

9-2.3-1

说明

  1. 在上述函数调用过程中,主调函数和被调函数之间存在数据传递的关系。在定义函数的时候函数名后面括号中的变量名称为“形式参数”(简称“形参”)(上例中为 x 和 y),在调用语句 c=max(a,b); 中,a 和 b 称为“实际参数”(简称“实参”)。
  2. 函数的调用过程:
    • 在定义函数中指定的形参,在未出现函数调用时,他们并不占内存中的存储单元。在发生函数调用时,函数 max 的形参被临时分配内存单元;
    • 将实参对应的值传递给形参。9-2.c 例题中,实参的值 a 为 10,b 为 20 把 10 和 20 传递给相应的形参 x 和 y,这是形参 x 就得到了 10,形参 y 得到值 20;
    • 在执行 max 函数期间,由于形参已经有值,就可以利用形参进行有关运算了;
    • 通过 return 语句将运算得到的 z 的值带回主调函数。应当注意返回值的类型和函数类型一致。如 max 函数为 int 型,返回值是变量 z,也是 int 类型,两者一致;
    • 调用结束,形参单元被释放。注意:实参单元仍保持原值和存储单元,自始至终未发生变化。形参会临时占用内存存储单元,但是实参和形参在内存中占用的存储单元是不同的。
函数的分类
  • 用户使用角度分类
    1. 库函数。它是由系统提供的,用户不必去定义,可以直接使用它。比如 printf 函数、puts 函数等等。
    2. 用户自定义函数。比如上例中的 printstar 函数和 print_message 函数。
  • 函数的形式角度分类
    1. 无参函数。上例中的 printstarprint_message 都是无参函数。在调用无参函数的时候,主调函数不向被调函数传递数据。无参函数一般用来执行指定的一组操作,比如 printstar 就是输出一行 “*”。无参函数可以带回也可以不带回函数值。
    2. 有参函数。在调用函数时,主调函数通过参数向被调函数传输数据,例如上一节的 strlen 函数就是有参函数,主函数传递一个字符串到 strlen 函数,strlen 返回值是这个字符串的长度。

函数的嵌套调用

C 语言的函数定义是互相平行、独立的,也就是说,在定义函数时,一个函数内不能定义另一个函数,也就是不能嵌套定义,但是可以嵌套调用函数,也就是说,在调用一个函数的过程中,又可以调用另一个函数。

如下图:

9-2.4-1

上图的执行的过程为:

  1. 执行 main 函数的开头部分;
  2. 遇到函数调用语句,调用 a 函数,流程转去 a 函数;
  3. 执行 a 函数的开头部分;
  4. 遇到函数调用语句,调用函数 b,流程转去函数 b
  5. 执行 b 完成,返回 a 函数中调用 b 函数的位置继续执行 a 函数尚未执行的部分。直到 a 函数结束;
  6. 返回 main 函数中调用 a 函数的位置,继续执行剩余的 main 函数部分。

输入四个整数,找出其中最大的整数。

解题思路

本题目我们就是使用函数嵌套的方法来处理。 在 main 函数中调用 max4 函数,max4 函数的作用是找出 4 个数中的最大值。在 max4 中调用 max2 函数,max2 是用来找出两个数中的最大值,max4 多次调用 max2 函数来达到找出 4 个数中最大值的目的。

编写源程序 9-3.c:

#include<stdio.h>
int main(){
    int max4(int a,int b,int c,int d);
    int a,b,c,d,max;

    printf("please enter 4 integer number:");
    scanf("%d%d%d%d",&a,&b,&c,&d);
    max=max4(a,b,c,d);
    printf("max=%d\n",max);

    return 0;
}

int max4(int a,int b,int c,int d){
    int max2(int a,int b);      //声明 max2
    int m;

    m = max2(a,b);
    m = max2(m,c);
    m = max2(m,d);

    return(m);
}

int max2(int a,int b){
    if(a>=b)
      return a;
    else
      return b;
}

程序运行结果如下:

9-2.4-2

函数的递归调用

递归调用:在调用一个函数的过程中又出现直接或间接的调用该函数本身,称为函数的递归调用。例如:

int fun(int a){
    int b,c;
    c = fun(b);   //在执行的过程中又调用了函数 fun
    return(2*c);
}

在调用函数 fun 的过程中,又调用 fun 函数,这是在函数内部调用函数本身,见下图:

9-2.5-1

这种调用都是无终止的调用,显然程序中不可能出现这种状况,而是应该出现有限次数的、有终止的递归调用,这可以用 if 语句来控制。

即:递归函数的两个要素为递归表达式和递归出口。

用递归的方法求 n 的阶乘。

解题思路

n!=1*2*3*4...*(n-1)*n。采用递归的公式可表示为:

9-2.5-2

5! = 4! * 5,而 4! = 3! * 4,…,1! = 1,具体计算过程如下:

9-2.5-3

编写源程序 9-4.c:

#include<stdio.h>
int main(){
    int fac(int n);
    int n,y;

    printf("please input an integer number:");
    scanf("%d",&n);
    if(n<0)
        printf("data error");   // n 不能小于 0
    else{
        y = fac(n);
        printf("%d!=%d\n",n,y);
    }

    return 0;
}

int fac(int n){
    int f;

    if(n==0||n==1)
        f = 1;
    else
        f = fac(n-1) * n;

    return f;
}

程序运行结果如下:

9-2.5-4

这里为了照顾刚入门的学员,尽量直接使用图的方式来讲解,但是还是各位能够自己独立思考,自己画图或脑子里构思这个程序递归调用的流程图。

数组与函数

数组元素作为函数实参

数组元素可以作为函数实参,不能作为函数形参。因为形参是在函数被调用时临时分配的存储单元,不可能作为一个数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。在用数组元素作为函数实参时,把实参的值传给形参,是“值传递”的方式。

输入 10 个数,要求输出其中值最大的元素和该数是第几个数。

解题思路

可以定义一个长度为 10 的数组。设计一个函数 max,用来求两个数中的大者。在主函数中定义一个变量 m,m 的初值为 a[0],每次调用 max 函数后的返回值存放在 m 中。用打擂台算法,将数组元素 a[1] 到 a[9] 与 m 比较,最后的到的 m 就是最大值。

编写源程序 9-5.c:

#include<stdio.h>
int main(){
    int max(int x,int y);
    int a[10],m,n,i;

    printf("please enter 10 integer number:");
    for(i=0;i<10;i++)
        scanf("%d",&a[i]);
    printf("\n");

    for(i=1,m=a[0],n=0;i<10;i++){
        if(max(m,a[i])>m){          //若 max 函数返回值大于 m
            m = max(m,a[i]);        //max 函数的返回值取代 m
            n = i;                  //把数组元素的序号记下来,放到 n 中
        }
    }
    printf("the largest number is %d\n it is the %dth number \n",m,n+1);

    return 0;
}

int max(int x,int y){
    return(x>y?x:y);
}

程序运行结果如下:

9-2.6-1

请注意分析怎样得到最大数是 10 个数中第几个数的。

数组名作为函数参数

除了可以用数组元素作为函数的参数外,还可以用数组名做函数参数(包括实参和形参)。应当注意的是:用数组元素作为实参时,向形参变量传递的是数组所对应数组元素的值,而用数组名做函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址,下一节指针大家会更加深入的理解该内容。

有一个一维数组 score,内放 10 个学生成绩,求平均成绩。

解题思路

用一个函数 average 来求平均成绩,不用数组元素作为函数实参,而是用数组名作为函数的实参,形参也用数组名。在 average 函数中引用各数组的元素,求平均成绩并返回 main 函数。

编写源程序 9-6.c:

#include<stdio.h>
int main(){
    float average(float array[10]);
    float score[10],aver;
    int i;

    printf("please enter 10 scores:");
    for(i=0;i<10;i++)
        scanf("%f",&score[i]);
    printf("\n");

    aver = average(score);   //注意这里调用函数时,向形参传递的是数组首元素的地址
    printf("average score is %5.2f\n ",aver);

    return 0;
}

float average(float array[10]){
    int i;
    float aver,sum = 0;

    for(i=0;i<10;i++)
        sum = sum + array[i];
    aver = sum / 10;

    return(aver);
}

程序运行结果如下:

9-2.6-2

注意:用数组名作为函数参数,应该在主调函数和被调用函数分别定义数组,例如 array 是形参的数组名,score 是实参数组名,分别在其所在函数中定义。

实验总结

该章节接触到一些重要的概念和方法,这些对于一个程序猿来讲,是必须了解和掌握的。模块化程序设计在一定规模和深度的程序设计时是必不可少的。本章只介绍了最基本的内容,希望大家能认真消化,在实验楼环境中完成每一道编程题,为以后的深入学习打下良好的基础。

课后练习题

习题一

编写函数 fun,其功能是:用户输入一个数 m,打印 1~m 能被 7 或 11 整除的所有整数。

例如,如果传给 m 的值是 50,则程序输出:

7 11 14 21 22 28 33 35 42 44 49
习题二

编写函数 fun,其功能是将两个两位数的正整数 a、b 合并成一个整数放在 c 中。合并的方式是:将 a 数的十位和个位依次放在 c 数的十位和千位上,b 数的十位和个位依次放在 c 数的百位和个位上。

例如,当 a=45,b=12 时,调用该函数后,c=5142。

参考答案

习题一
#include<stdio.h>

int fun(int num){
    if((num%7)==0 || (num%11)==0)
        printf("%d ",num);
}

int main(){
    int m;
    scanf("%d",&m);
    //调用函数
    for(int i=0;i<m;i++)
        fun(i+1);

    return 0;
}
习题二
#include<stdio.h>

int fun(int a,int b){
    int c;
    c = a%10*1000 + a/10*10 + b/10*100 + b%10;
    printf("%d",c);
}

int main(){
    int a,b;
    scanf("%d%d",&a,&b);

    //a,b必须是两位数
    if(a<10 || a>99 || b<10 || b>99){
        printf("Enter error!");
        return 0;
    }

    //调用函数
    fun(a,b);

    return 0;
}
作为函数实参时,把实参的值传给形参,是“值传递”的方式。

输入 10 个数,要求输出其中值最大的元素和该数是第几个数。

**解题思路**

可以定义一个长度为 10 的数组。设计一个函数 max,用来求两个数中的大者。在主函数中定义一个变量 m,m 的初值为 a[0],每次调用 max 函数后的返回值存放在 m 中。用打擂台算法,将数组元素 a[1] 到 a[9] 与 m 比较,最后的到的 m 就是最大值。

**编写源程序 9-5.c:**

```c
#include<stdio.h>
int main(){
    int max(int x,int y);
    int a[10],m,n,i;

    printf("please enter 10 integer number:");
    for(i=0;i<10;i++)
        scanf("%d",&a[i]);
    printf("\n");

    for(i=1,m=a[0],n=0;i<10;i++){
        if(max(m,a[i])>m){          //若 max 函数返回值大于 m
            m = max(m,a[i]);        //max 函数的返回值取代 m
            n = i;                  //把数组元素的序号记下来,放到 n 中
        }
    }
    printf("the largest number is %d\n it is the %dth number \n",m,n+1);

    return 0;
}

int max(int x,int y){
    return(x>y?x:y);
}

程序运行结果如下:

[外链图片转存中…(img-UqdE7gnJ-1673100540980)]

请注意分析怎样得到最大数是 10 个数中第几个数的。

数组名作为函数参数

除了可以用数组元素作为函数的参数外,还可以用数组名做函数参数(包括实参和形参)。应当注意的是:用数组元素作为实参时,向形参变量传递的是数组所对应数组元素的值,而用数组名做函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址,下一节指针大家会更加深入的理解该内容。

有一个一维数组 score,内放 10 个学生成绩,求平均成绩。

解题思路

用一个函数 average 来求平均成绩,不用数组元素作为函数实参,而是用数组名作为函数的实参,形参也用数组名。在 average 函数中引用各数组的元素,求平均成绩并返回 main 函数。

编写源程序 9-6.c:

#include<stdio.h>
int main(){
    float average(float array[10]);
    float score[10],aver;
    int i;

    printf("please enter 10 scores:");
    for(i=0;i<10;i++)
        scanf("%f",&score[i]);
    printf("\n");

    aver = average(score);   //注意这里调用函数时,向形参传递的是数组首元素的地址
    printf("average score is %5.2f\n ",aver);

    return 0;
}

float average(float array[10]){
    int i;
    float aver,sum = 0;

    for(i=0;i<10;i++)
        sum = sum + array[i];
    aver = sum / 10;

    return(aver);
}

程序运行结果如下:

[外链图片转存中…(img-65LZ9y6L-1673100540980)]

注意:用数组名作为函数参数,应该在主调函数和被调用函数分别定义数组,例如 array 是形参的数组名,score 是实参数组名,分别在其所在函数中定义。

实验总结

该章节接触到一些重要的概念和方法,这些对于一个程序猿来讲,是必须了解和掌握的。模块化程序设计在一定规模和深度的程序设计时是必不可少的。本章只介绍了最基本的内容,希望大家能认真消化,在实验楼环境中完成每一道编程题,为以后的深入学习打下良好的基础。

课后练习题

习题一

编写函数 fun,其功能是:用户输入一个数 m,打印 1~m 能被 7 或 11 整除的所有整数。

例如,如果传给 m 的值是 50,则程序输出:

7 11 14 21 22 28 33 35 42 44 49
习题二

编写函数 fun,其功能是将两个两位数的正整数 a、b 合并成一个整数放在 c 中。合并的方式是:将 a 数的十位和个位依次放在 c 数的十位和千位上,b 数的十位和个位依次放在 c 数的百位和个位上。

例如,当 a=45,b=12 时,调用该函数后,c=5142。

参考答案

习题一
#include<stdio.h>

int fun(int num){
    if((num%7)==0 || (num%11)==0)
        printf("%d ",num);
}

int main(){
    int m;
    scanf("%d",&m);
    //调用函数
    for(int i=0;i<m;i++)
        fun(i+1);

    return 0;
}
习题二
#include<stdio.h>

int fun(int a,int b){
    int c;
    c = a%10*1000 + a/10*10 + b/10*100 + b%10;
    printf("%d",c);
}

int main(){
    int a,b;
    scanf("%d%d",&a,&b);

    //a,b必须是两位数
    if(a<10 || a>99 || b<10 || b>99){
        printf("Enter error!");
        return 0;
    }

    //调用函数
    fun(a,b);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值