C++ 编程学习手册(一)
一、基本编程概念
Electronic supplementary material The online version of this chapter (doi:10.1007/978-1-4842-1371-1_1) contains supplementary material, which is available to authorized users.
在本章中,我们将解释以下内容:
- 计算机如何解决问题
- 计算机程序开发的各个阶段:从问题定义到完成程序
- 计算机如何执行程序
- 什么是“数据类型”及其在编写程序中的基本作用
- 角色——所有程序的基本构件
- 常量和变量的概念
- 语法错误和逻辑错误的区别
- 如何使用
printf语句产生 C 语言的基本输出 - 什么是转义序列
- 如何在你的程序中加入描述性或解释性的注释
- 什么是赋值语句以及如何用 C 语言编写一个赋值语句
1.1 程序、语言和编译器
我们都熟悉计算机执行各种任务的能力。例如,我们可以用它来玩游戏、写信或写书、为公司做会计工作、学习外语、听 CD 上的音乐、发传真或在互联网上搜索信息。这怎么可能,都在同一台机器上?答案在于编程——创建一系列计算机可以执行的指令(我们称之为“执行”)来完成每项任务。这个指令序列被称为程序。每个任务需要不同的程序:
- 要玩游戏,我们需要一个玩游戏的程序。
- 要写一封信或一本书,我们需要一个文字处理程序。
- 要做账,我们需要一个会计程序。
- 为了学习西班牙语,我们需要一个教授西班牙语的项目。
- 要听 CD,我们需要一个音乐播放程序。
- 要发送传真,我们需要一个传真发送程序。
- 要使用互联网,我们需要一个叫做“网络浏览器”的程序
对于我们想要执行的每一项任务,我们都需要一个合适的程序。为了让计算机运行一个程序,这个程序必须被存储(我们有时称之为加载)在计算机的内存中。
但是程序的本质是什么呢?首先,我们需要知道计算机是用来执行用所谓的机器语言编写的指令的。在机器语言中,一切都用二进制数字系统来表示——1 和 0。每台计算机都有自己的机器语言,并且计算机只能执行用那种语言编写的指令。
指令本身非常简单:例如,将两个数字相加或相减,将一个数字与另一个数字进行比较,或者将一个数字从一个地方复制到另一个地方。那么,计算机怎么能用如此简单的指令完成如此多种多样的任务,解决如此多种多样的问题呢?
答案是,无论一项活动看起来有多复杂,通常都可以分解成一系列简单的步骤。分析一个复杂问题并以简单的计算机指令表达其解决方案的能力是优秀程序员的标志之一。
机器语言被认为是一种低级编程语言。在计算的早期(20 世纪 40 年代和 50 年代),程序员必须用机器语言编写程序,也就是说,用 1 和 0 来表达他们所有的指令。
为了让他们的生活稍微轻松一点,汇编语言被开发出来。这与机器语言密切相关,但它允许程序员使用助记指令代码(如ADD和存储位置名称(如sum)而不是二进制数字串(位)。例如,程序员可以通过sum引用一个数字,而不必记住这个数字存储在内存位置1000011101101011。
一种叫做汇编程序的程序被用来把汇编语言程序转换成机器语言。然而,这种编程方式有几个缺点:
- 这非常乏味,而且容易出错。
- 它迫使程序员从机器的角度去思考,而不是从他的问题的角度去思考。
- 用一台计算机的机器语言编写的程序不能在使用不同机器语言的计算机上运行。更换你的电脑可能意味着你必须重写所有的程序。
为了克服这些问题,在 20 世纪 50 年代末和 60 年代开发了高级语言或面向问题的语言。其中最流行的是 FORTRAN(公式翻译)和 COBOL(面向商业的通用语言)。FORTRAN 是为解决涉及大量数值计算的科学和工程问题而设计的。COBOL 是为解决商业团体的数据处理问题而设计的。
这个想法是让程序员用他熟悉的和与问题相关的术语来思考问题,而不是担心机器。例如,如果他想知道两个量中较大的一个,A和B,他可以写
IF A IS GREATER THAN B THEN BIGGER = A ELSE BIGGER = B
而不是摆弄几个机器或汇编语言指令来得到相同的结果。因此,高级语言使程序员能够集中精力解决手头的问题,而不必担心特定机器的特性。
然而,计算机仍然只能执行用机器语言编写的指令。一种叫做编译器的程序被用来把用高级语言编写的程序翻译成机器语言。
因此,我们说 FORTRAN 编译器或 COBOL 编译器分别用于翻译 FORTRAN 和 COBOL 程序。但这还不是故事的全部。由于每台计算机都有自己的机器语言,我们必须有,比如说,一台 FORTRAN 编译器用于联想 ThinkPad 计算机,一台 FORTRAN 编译器用于 MacBook 计算机。
1.2 计算机如何解决问题
在计算机上解决问题涉及以下活动:
Define the problem. Analyze the problem. Develop an algorithm (a method) for solving the problem. Write the computer program that implements the algorithm. Test and debug (find the errors in) the program. Document the program. (Explain how the program works and how to use it.) Maintain the program.
这些活动通常有些重叠。例如,对于一个大的程序,一部分可以在另一部分被写入之前被写入和测试。此外,文件应与所有其他活动同时进行;每项活动都有自己的文档,这些文档将成为最终项目文档的一部分。
定义问题
假设我们想帮助一个孩子算出正方形的面积。这就定义了一个要解决的问题。然而,一个简短的分析表明,这个定义不够完整或具体,不足以继续开发一个程序。与孩子交谈可能会发现她需要一个程序,要求她输入正方形一边的长度;然后程序打印出正方形的面积。
1.2.2 分析问题
我们进一步分析这个问题,以
- 确保我们对它有最清晰的理解。
- 确定一般要求,如程序的主要输入和程序的主要输出。例如,对于更复杂的程序,我们还需要决定可能需要的文件种类。
如果有几种解决这个问题的方法,我们应该考虑这些选择,并选择最好或最合适的一个。
在这个例子中,程序的输入是正方形一边的长度,输出是正方形的面积。我们只需要知道如何计算面积。如果边长为s,则面积a计算如下:
a = s × s
1.2.3 开发解决问题的算法
算法是一组指令,如果忠实地执行,将产生给定问题的解决方案或执行一些特定的任务。当一个指令被遵循时,我们说它被执行了。我们可以谈论在字典中查找单词、更换扎破的轮胎或玩视频游戏的算法。
对于任何问题,通常都有不止一种算法来解决。每种算法都有自己的优缺点。当我们在字典中查找一个单词时,一种方法是从开头开始,依次查看每个单词。第二种方法是从末尾开始向后搜索。这里,第一种方法的优点是,如果单词在开头,它会更快地找到该单词,而如果单词接近结尾,第二种方法会更快。
另一种搜索单词的方法是利用字典中的单词是按字母顺序排列的这一事实,这是我们在字典中查找单词时都使用的方法。在任何情况下,程序员通常都可以选择算法,而决定哪种算法是最好的,以及为什么这样,是她更重要的工作之一。
在我们的例子中,我们必须以这样一种方式编写算法中的指令,使得它们可以容易地转换成计算机可以遵循的形式。计算机指令分为三大类:
Input instructions, used for supplying data from the “outside world” to a program; this is usually done via the keyboard or a file. Processing instructions, used for manipulating data inside the computer. These instructions allow us to add, subtract, multiply, and divide; they also allow us to compare two values, and act according to the result of the comparison. Also, we can move data from one location in the computer’s memory to another location. Output instructions, used for getting information out of the computer to the outside world.
1.2.3.1 数据和变量
所有的计算机程序,除了最琐碎的程序,都是用来操作数据的。例如:
- 动作游戏的数据可能是按下的键或鼠标点击时光标的位置。
- 文字处理程序的数据是你打字时按下的键。
- 会计程序的数据包括支出和收入等。
- 教授西班牙语的程序的数据可以是你在回答问题时键入的一个英语单词。
回想一下,程序必须存储在计算机的内存中才能运行。当数据被提供给程序时,该数据也被存储在存储器中。因此,我们认为内存是保存程序和数据的地方。用高级语言(相对于机器语言)编程的一个好处是,你不必担心哪些内存位置用于存储数据。但是,假设内存中可能有许多数据项,我们如何引用一个数据项呢?
把内存想象成一组盒子(或存储位置)。每个盒子可以容纳一项数据,例如一个数字。我们可以给一个盒子起一个名字,并且我们可以通过这个名字来引用这个盒子。在我们的例子中,我们将需要两个盒子:一个用来保存正方形的边,一个用来保存面积。我们将分别称这些盒子为s和a。
如果我们愿意,我们可以随时改变盒子里的值;由于值可以变化,s和a被称为变量名,或简称为变量。因此,变量是一个与特定内存位置相关的名称,或者,如果你愿意,它是一个内存位置的标签。我们可以说给一个变量一个值,或者给一个变量设置一个特定的值,比如1。需要记住的要点是:
- 一个盒子一次只能容纳一个值;如果我们输入一个新值,旧值就会丢失。
- 除非我们明确地在盒子中存储一个值,否则我们不能假设盒子中包含任何值。特别是,我们不能假定盒子里装的是零。
变量是计算机程序的一个常见特征。很难想象没有它们编程会是什么样子。在日常生活中,我们经常使用变量。例如,我们说一个“地址”在这里,“地址”是一个变量,其值取决于所考虑的人。其他常见的变量有电话号码、学校名称、学科、人口规模、汽车类型、电视型号等。(这些变量有哪些可能的值?)
1.2.3.2 示例—开发算法
使用算法的概念和变量的概念,我们开发了以下算法,用于计算给定一边的正方形的面积:
给定一条边,计算正方形面积的算法:
Ask the user for the length of a side. Store the value in the box s. Calculate the area of the square (s × s). Store the area in the box a. Print the value in box a, appropriately labeled. Stop.
当一个算法被开发出来时,必须对它进行检查,以确保它能正确地完成预期的工作。我们可以通过“玩电脑”来测试一个算法,也就是说,我们用适当的数据值手工执行指令。这个过程被称为模拟运行或案头检查算法。它用于在计算机程序实际编写之前查明任何逻辑错误。除非我们确信算法是正确的,否则我们永远不应该开始编写编程代码。
1.2.4 为算法编写程序
我们已经使用英语语句指定了算法。然而,这些语句足够“面向计算机”,计算机程序可以直接从它们中编写。在我们这样做之前,让我们从用户的角度看看我们期望程序如何工作。
首先,程序将输入一条边的长度要求;我们说程序提示用户提供数据。屏幕显示可能如下所示:
Enter length of side:
然后,计算机将等待用户输入长度。假设用户输入12。显示将如下所示:
Enter length of side: 12
然后,程序将接受(我们称之为读取)输入的数字,计算面积,并打印结果。显示可能如下所示:
Enter length of side: 12
Area of square is 144
这里我们指定了程序的输出应该是什么样子。例如,在提示行和给出答案的行之间有一个空行;我们还指定了答案的确切形式。这是一个简单的输出设计的例子。这是必要的,因为程序员无法编写程序,除非他知道所需的精确输出。
为了根据算法编写计算机程序,必须选择合适的编程语言。我们可以把一个程序想象成一套用编程语言编写的指令,当执行时,它将产生一个给定问题的解决方案或执行一些特定的任务。
算法和程序之间的主要区别是,算法可以用非正式语言编写,而不必遵循任何特殊规则(尽管通常遵循一些约定),而程序是用编程语言编写的,必须遵循该语言的所有规则(语法规则)。(同样,如果我们希望写出正确的英语,我们必须遵循英语的语法规则。)
在本书中,我们将向您展示如何用 C 语言编写程序,C 语言是由贝尔实验室的 Ken Thompson 和 Dennis Ritchie 开发的编程语言,也是当今最流行和广泛使用的语言之一。
程序 P1.1 是一个 C 程序,要求用户输入一条边的长度并打印正方形的面积:
Program P1.1
#include <stdio.h>
int main() {
int a, s;
printf("Enter length of side: ");
scanf("%d", &s); //store length in s
a = s * s; //calculate area; store in a
printf("\nArea of square is %d\n", a);
}
此时,你是否了解这个项目并不太重要。但是你可以观察到一个 C 程序有一个叫做main的东西(一个函数)后面跟有开括号和闭括号。在左括号{和右括号}之间,我们有所谓的函数体。声明
int a, s;
叫做宣言。//后的部分是帮助解释程序但在程序运行时没有作用的注释。而*用来表示乘法。
所有这些术语将在适当的时候详细解释。
最后,用高级语言编写的程序通常被称为源程序或源代码。
1.2.5 测试和调试程序
写完程序后,接下来的工作是测试它,看看它是否完成了预定的工作。测试程序包括以下步骤:
Compile the program: recall that a computer can execute a program written in machine language only. Before the computer can run our C program, the latter must be converted to machine language. We say that the source code must be converted to object code or machine code. The program that does this job is called a compiler. Appendix D tells you how you can acquire a C compiler for writing and running your programs. Among other things, a compiler will check the source code for syntax errors—errors that arise from breaking the rules for writing statements in the language. For example, a common syntax error in writing C programs is to omit a semicolon or to put one where it is not required. If the program contains syntax errors, these must be corrected before compiling it again. When the program is free from syntax errors, the compiler will convert it to machine language and we can go on to the next step. Run the program: here we request the computer to execute the program and we supply data to the program for which we know the answer. Such data is called test data. Some values we can use for the length of a side are 3, 12, and 20. If the program does not give us the answers 9, 144, and 400, respectively, then we know that the program contains at least one logic error. A logic error is one that causes a program to give incorrect results for valid data. A logic error may also cause a program to crash (come to an abrupt halt). If a program contains logic errors, we must debug the program; we must find and correct any errors that are causing the program to produce wrong answers.
举例来说,假设计算面积的语句被(不正确地)写成:
a = s + s;
当程序运行时,输入10作为长度。(下面,10加下划线表示是用户键入的。)假设我们知道这个地区应该是100。但是当程序运行时,它会打印如下内容:
Enter length of side: 10
Area of square is 20
由于这不是我们期望的答案,我们知道程序中有一个错误(可能不止一个)。因为面积是错误的,所以从计算面积的语句开始查找错误是合乎逻辑的。如果我们仔细观察,应该会发现输入的是+而不是*。当这个修正完成后,程序运行良好。
记录该计划
最后的工作是完成程序的文档。到目前为止,我们的文档包括以下内容:
- 问题的陈述。
- 解决问题的算法。
- 节目单。
- 测试数据和程序产生的结果。
这些是构成程序技术文档的一些项目。这是对程序员有用的文档,也许是为了在以后的阶段修改程序。
另一种必须编写的文档是用户文档。这使得非技术人员无需了解程序的内部工作就能使用程序。其中,用户需要知道如何在计算机中加载程序,以及如何使用程序的各种功能。如果合适的话,用户还需要知道如何处理程序使用过程中可能出现的异常情况。
维护程序
除了像课堂作业这样的事情,程序通常意味着要使用很长一段时间。在此期间,可能会发现以前没有注意到的错误。错误也可能因为以前从未出现过的条件或数据而出现。不管什么原因,这种错误必须纠正。
但是程序可能因为其他原因需要修改。也许编写程序时所做的假设现在已经由于公司政策的改变或者甚至由于政府法规的改变(例如,所得税税率的改变)而改变了。也许公司正在改变它的计算机系统,程序需要“移植”到新系统。我们说程序必须被“维护”
这是否容易做到很大程度上取决于原始程序是如何编写的。如果它设计良好并有适当的文档记录,那么维护程序员的工作就会变得容易得多。
1.3 计算机如何执行程序
首先,回想一下,计算机只能执行用机器语言编写的程序。为了让计算机执行这样一个程序的指令,这些指令必须被载入计算机的内存(也称为主存储器),就像这样:
你可以把内存想象成一系列的存储单元,从0开始连续编号。因此,你可以说内存位置27或内存位置31548。与一个存储单元相关的数字叫做它的地址。
计算机通过执行第一条指令来运行程序,然后是第二条,第三条,依此类推。一条指令可能会说要跳过几条指令,跳到一条特定的指令,然后从那里继续执行。另一个人可能会说回到上一条指令并再次执行它。
无论指令是什么,计算机都会忠实地按照指定的方式执行它们。这就是为什么程序精确地指定必须做什么是如此重要。计算机无法知道你的意图,它只能执行你实际写的东西。如果你给计算机下了错误的指令,它就会盲目地按照你指定的方式执行。
1.4 数据类型
每天我们都会遇到名字和数字——在家里,在工作中,在学校,或者在玩耍中。人名是一种数据类型;一个数字也是。因此,我们可以称这两种数据类型为“名称”和“数字”考虑以下陈述:
Caroline bought 3 dresses for $199.95
在这里,我们可以发现:
- 一个名字的例子:
Caroline。 - 数字的两个例子:
3和199.95.
通常,我们发现把数字分成两种很方便:
Whole numbers, or integers. Numbers with a decimal point, so-called real or floating-point numbers.
在示例中,3是整数,199.95是实数。
Exercise: Identify the data types—names, integers, and real numbers—in the followingBill’s batting average was 35.25 with a highest score of 99. Abigail, who lives at 41 Third Avenue, worked 36 hours at $11.50 per hour. In his 8 subjects, Richard’s average mark was 68.5.
一般来说,编写程序是为了处理各种类型的数据。我们使用数字这个术语来指代数字(整数或浮点)。我们使用术语字符串来指代非数字数据,如姓名、地址、工作描述、歌曲名称或车辆号码(就计算机而言,这并不是真正的数字,它通常包含字母,例如PAB6052)。
一般来说,编程语言,特别是 C,精确地定义了用这些语言编写的程序可以操作的各种类型的数据。整数、实数(或浮点)、字符(由单个字符组成的数据,如'K'或'%')和字符串数据类型是最常见的。
每种数据类型都定义了该类型的常量。例如,
- 一些整数常量有
3、-52、0和9813。 - 一些实(或浮点)常量是
3.142、-5.0、345.21和1.16。 - 一些字符常量有
't'、'+'、'8'和'R'。 - 一些字符串常量是
"Hi there", "Wherefore art thou, Romeo?"和"C World"。
请注意,在 C 中,字符常量由单引号分隔,字符串常量由双引号分隔。
当我们在程序中使用一个变量时,我们必须说明我们打算在这个变量中存储什么类型的数据(常量的种类)——我们说我们必须声明这个变量。如果我们将一个变量声明为一种类型,然后试图在其中存储不同类型的值,这通常是错误的。例如,试图在整数变量中存储字符串常量是错误的。c 数据类型在第二章中详细讨论。
1.5 个字符
在计算机术语中,我们使用术语“字符”来指代以下任一项:
- 从
0到9的一个数字。 - 从
A到Z的一个大写字母。 - 从
a到z的小写字母。 - 特殊符号如(,,$,=,,+,-,/,*等。
以下是常用术语:
- 字母–从
a到z或从A到Z中的一个 - 小写字母–从
a到z中的一个 - 大写字母–从
A到Z中的一个 - 数字–为
0、1、2、3、4、5、6、7、8、9中的一种 - 特殊字符–除字母或数字之外的任何符号,例如
+、<、$、&、*、/、= - 字母——用来指一个字母
- 数字–用来指一个数字
- 字母数字–用于指代字母或数字
字符是编写程序时使用的基本构件。
我们把字符放在一起形成变量和常量。
我们将变量、常量和特殊字符组成表达式,如
(a + 2.5) * (b – c);
我们添加一些特殊的词,如if、else和while来形成陈述,如
if (a > 0) b = a + 2; else b = a – 2;
我们把语句放在一起组成程序。
1.6 欢迎学习 C 编程
我们通过编写一个打印消息的程序来快速浏览一下 C 编程语言
Welcome to Trinidad & Tobago
一个解决方案是程序 P1.2。
Program P1.2
#include <stdio.h>
int main() {
printf("Welcome to Trinidad & Tobago");
}
The statement
#include <stdio.h>
称为编译器指令。这仅仅意味着它提供了编译器编译你的程序所需的信息。在 C 语言中,输入输出指令是通过存储在标准库中的标准函数来提供的。这些函数使用存储在名为stdio.h的特殊头文件中的变量(和其他)声明。任何使用输入/输出指令(如printf)的程序必须通知编译器将声明包含在程序的文件stdio.h中。如果不这样做,编译器将不知道如何解释程序中使用的输入/输出语句。
一个 C 程序由一个或多个函数(或子程序)组成,其中一个函数必须被称为main。我们的解决方案只包含一个函数,所以它必须被称为main。main后面的(圆)括号是必要的,因为在 C 中,函数名后面是一列参数,用括号括起来。如果没有参数,括号必须仍然存在。这里,main没有参数,所以只有括号。main前的int表示main返回的值的类型。稍后我们将对此进行更详细的解释。
每个函数都有一个叫做函数体的部分。身体是执行功能工作的地方。左右括号{和}分别用于定义主体的起点和终点。在 C 语言中,由{和}括起来的一条或多条语句称为块语句或复合语句。
main的正文包含一条语句:
printf("Welcome to Trinidad & Tobago");
printf是一个标准的输出函数,在这个例子中,它接受一个参数,一个字符串常量"Welcome to Trinidad & Tobago"。请注意,与所有函数一样,该参数用圆括号括起来。分号用于表示语句的结束。我们说分号终止语句。执行时,该语句将打印
Welcome to Trinidad & Tobago
关于“标准输出”现在,把这个理解为屏幕。
Programming Note
正如在序言中提到的,生活中令人兴奋的事情之一是编写你的第一个程序,并让它在计算机上成功运行。不要错过它。有关如何获得 C 编译器的说明,请参见附录 D。
运行程序
把程序写在纸上后,下一个任务是让它在真正的计算机上运行。如何做到这一点在不同的计算机系统之间有所不同,但一般来说,必须执行以下步骤:
Type the program to a file. The file could be named welcome.c; it is good practice to use .c as the filename extension to those files that contain C source code. Invoke your C compiler to compile the program in the file welcome.c. For instance, you may have to start up your C compiler and open the file welcome.c from the File menu or you may simply have to double-click on the file welcome.c to start up the compiler. Once the file is open, typically there will be a menu command to Compile or Run the program. (Generally, Run implies Compile and Run). If any (syntax) errors are detected during the compile phase, you must correct these errors and try again. When all errors have been corrected and the program is Run, it will print
Welcome to Trinidad & Tobago
1.6.2 程序布局概述
c 不要求程序像示例中那样进行布局。一个等效的程序是
#include <stdio.h>
int main() { printf("Welcome to Trinidad & Tobago"); }
或者
#include <stdio.h>
int main()
{
printf("Welcome to Trinidad & Tobago");
}
对于这个小程序,我们使用哪个版本可能并不重要。然而,随着程序大小的增加,程序的布局突出程序的逻辑结构变得非常必要。
这提高了它的可读性,使它更容易理解。缩进和清楚地指出哪个{匹配哪个}在这方面会有帮助。随着我们的项目越来越大,我们将会看到这一原则的价值。
1.7 用printf写输出
假设我们想写一个程序来打印罗宾德拉纳特·泰戈尔的《吉檀迦利》中的以下几行:
Where the mind is without fear
And the head is held high
我们最初的尝试可能是这样的:
#include <stdio.h>
int main() {
printf("Where the mind is without fear");
printf("And the head is held high");
}
但是,当运行时,该程序将打印:
Where the mind is without fearAnd the head is held high
请注意,这两个字符串是连接在一起的(我们说字符串是连接在一起的)。发生这种情况是因为printf不会将输出放在新行上,除非明确指定。换句话说,printf在打印其参数后不会自动提供换行符。换行符会导致后续输出从下一行的左边开始。
在这个例子中,在打印出fear之后没有提供换行符,因此And the head...与fear打印在同一行,并且紧接在它之后。
1.7.1 换行符,\n(反斜杠 n)
为了获得想要的效果,我们必须告诉printf在打印...without fear之后提供一个换行符。我们通过使用程序 P1.3 中的字符序列\n(反斜杠n)来做到这一点
Program P1.3
#include <stdio.h>
int main() {
printf("Where the mind is without fear\n");
printf("And the head is held high\n");
}
第一个\n表示终止当前输出线;后续输出将从下一行的左边距开始。这样,And the...将被打印在新的一行上。第二个\n具有终止第二行的效果。如果它不存在,输出仍然正确,但只是因为这是最后一行输出。
一个程序在终止前打印所有的挂起输出。(这也是我们第一个程序没有\n也能工作的原因。)
作为修饰,假设我们想要在两行输出之间放置一个空行,如下所示:
Where the mind is without fear
And the head is held high
下面的每一组语句都将实现这一点:
printf("Where the mind is without fear\n\n");
printf("And the head is held high\n");
printf("Where the mind is without fear\n");
printf("\nAnd the head is held high\n");
printf("Where the mind is without fear\n");
printf("\n");
printf("And the head is held high\n");
我们只需要确保在fear和And之间打印两个\n。第一个\n结束第一行;第二行结束第二行,实际上是打印一个空行。在如何编写语句以产生预期效果方面,c 语言给了我们很大的灵活性。
练习:写一个程序打印你最喜欢的歌曲的歌词
转义序列
在printf的字符串参数中,反斜杠(\)表示此时需要一个特殊效果。反斜杠后面的字符指定要做什么。这种组合(\后跟另一个字符)被称为转义序列。以下是一些可以在printf语句的字符串中使用的转义序列:
\n issue a newline character
\f issue a new page (form feed) character
\t issue a tab character
\" print "
\\ print \
例如,使用转义序列是将双引号作为输出的一部分打印出来的唯一方法。假设我们想打印这一行
Use " to begin and end a string
如果我们打字
printf("Use " to begin and end a string\n");
然后 C 会假设Use后的双引号结束字符串(当它想不出如何处理to时会导致后续错误)。使用转义序列\",我们可以正确地打印出这样一行:
printf("Use \" to begin and end a string\n");
练习:编写一条语句来打印该行:转义序列以\
打印变量的值
到目前为止,我们已经使用了printf来打印一个字符串常量的值(即字符串中不包括引号的字符)。我们现在展示如何打印变量的值,暂时忽略变量是如何获得其值的。(我们将在第二章中看到。)假设整数变量m的值为52。声明:
printf("The number of students = %d\n", m);
将打印以下内容:
The number of students = 52
这个printf和我们目前看到的有些不同。这个有两个参数——一个字符串和一个变量。这个字符串称为格式字符串,包含一个格式规范%d。(在我们前面的例子中,格式字符串不包含格式规范。)在这种情况下,除了用第二个参数m的值替换了%d之外,格式字符串的输出和以前一样。因此,%d被替换为52,给出如下结果:
The number of students = 52
我们将在第二章中更详细地解释printf和格式规范,但是,现在请注意,如果我们想要打印一个整数值,我们将使用规范%d。
如果我们想要打印多个值呢?只要每个值都有相应的格式规范,就可以做到这一点。例如,假设a的值为14,而b的值为25。考虑以下陈述:
printf("The sum of %d and %d is %d\n", a, b, a + b);
这个printf有四个参数——格式字符串和三个要打印的值:a、b,和a+b。格式字符串必须包含三个格式说明:第一个对应a,第二个对应b,第三个对应a+b。当打印格式字符串时,每个%d将被其相应参数的值替换,给出如下:
The sum of 14 and 25 is 39
Exercise: What is Printed by the Following Statement?
printf("%d + %d = %d\n ",a,b,a+b);
1.8 意见
所有的编程语言都允许你在程序中加入注释。注释可以用来提醒你自己(和其他人)正在进行什么处理,或者某个特定变量的用途。它们可以用来解释或阐明一个程序的任何方面,这些方面可能仅仅通过阅读编程语句是难以理解的。这非常重要,因为程序越容易理解,你就越有信心它是正确的。添加任何使程序更容易理解的东西都是值得的。
请记住,注释(或没有注释)对程序的运行没有任何影响。如果你从一个程序中删除所有的注释,它将会以和注释完全一样的方式运行。
每种语言都有自己的方式来指定注释的书写方式。在 C 语言中,我们通过将注释包含在/*和*/中来编写注释,例如:
/* This program prints a greeting */
一个注释从/*延伸到下一个*/,可能跨越一行或多行。以下是有效的注释:
/* This program reads characters one at a time
and counts the number of letters found */
c 也允许你使用//来写一行注释。注释从//延伸到行尾,例如:
a = s * s; //calculate area; store in a
在本书中,我们将主要使用单行注释。
1.9 使用变量编程
为了加强到目前为止所讨论的观点,让我们编写一个程序,将数字14和25相加并打印出总和。
我们需要两个数和总和的存储位置。存储在这些位置的值是整数值。为了指代这些地点,我们编造了一些名字,比如说a、b和sum。(其他名字都可以。与所有编程语言一样,在 C 语言中,组成变量名也有一些规则要遵循,例如,名称必须以字母开头,不能包含空格。我们将在下一章看到 C 规则。)
一种可能的算法如下所示:
set a to 14
set b to 25
set sum to a + b
print sum
该算法由四条语句组成。下面解释了每个语句的含义:
set a to 14:将数字14存储在存储位置a;这是一个赋值语句的例子。set b to 25:将数字25存储在b的存储位置。set sum to a + b:将存储位置a和b中的数字相加,并将总和存储在位置sum中。结果是39被存储在sum中。print sum:打印(在屏幕上)出sum中的数值,即39。
程序 P1.4 展示了我们如何将这个算法写成一个 C 程序。
Program P1.4
//This program prints the sum of 14 and 25\. It shows how
//to declare variables in C and assign values to them.
#include <stdio.h>
int main() {
int a, b, sum;
a = 14;
b = 25;
sum = a + b;
printf("%d + %d = %d\n", a, b, sum);
}
运行时,该程序将打印以下内容:
14 + 25 = 39
在 C 中,变量被声明为整数,使用必需的字int。(在编程术语中,我们说int是保留字。)因此,声明
int a, b, sum;
声明a、b和sum是整数变量。在 C 语言中,所有变量在程序中使用之前都必须声明。请注意,变量由逗号分隔,最后一个变量后有分号。如果我们只需要声明一个变量(a),我们将编写
int a;
声明
a = 14;
是 C 写赋值语句的方式
set a to 14
有时会读作“a 变成 14。”在 C 语言中,赋值语句包括一个变量(【示例中的 ),后面是一个等号(=),后面是要赋给变量的值(示例中的14),再后面是一个分号。一般来说,值可以是常量(如14)、变量(如b)或表达式(如a + b)。因此,
set b to 25
被写成
b = 25;
和
set sum to a + b
被写成
sum = a + b;
最后一点:您可能已经从之前的练习中了解到,对于这个问题,变量sum并不是真正必要的。例如,我们可以从程序中完全省略掉sum,使用下面的代码:
int a, b;
a = 14;
b = 25;
printf("%d + %d = %d\n", a, b, a + b);
给出相同的结果,因为 C 让我们使用一个表达式(例如,a + b)作为printf的参数。然而,如果程序比较长,我们需要在其他地方使用这个总数,明智的做法是计算并存储一次总数(比如说在sum)。每当需要求和时,我们就使用sum,而不是每次都重新计算a + b。
现在我们已经对编写程序涉及的内容有了一个大致的概念,我们准备开始深入 C 编程的本质。
Exercises 1What makes it possible to do such a variety of things on a computer? Computers can execute instructions written in what language? Give two advantages of assembly language over machine language. Give two advantages of a high-level language over assembly language. Describe two main tasks performed by a compiler. Describe the steps required to solve a problem on a computer. Distinguish between an algorithm and a program. Programming instructions fall into three main categories; what are they? Distinguish between a syntax error and a logic error. What is meant by “debugging a program”? Name five data types commonly used in programming and give examples of constants of each type. What are the different classes into which characters can be divided? Give examples in each class. What is the purpose of comments in a program? Write a program to print Welcome to C on the screen. Write a program to print the following: There is a tide in the affairs of men Which, taken at the flood, leads on to fortune Write a program to print any four lines of your favorite song or poem. Same as exercise 16, but print a blank line after each line. If a is 29 and b is 5, what is printed by each of the following statements? printf("The product of %d and %d is %d\n", a, b, a * b); printf("%d + %d = %d\n", a, b, a + b); printf("%d - %d = %d\n", a, b, a - b); printf("%d x %d = %d\n", a, b, a * b); If a is 29 and b is 14, what is printed by the following statements? printf("%d + \n", a); printf("%d\n", b); printf("--\n"); printf("%d\n", a + b); If rate = 15, what is printed by (a) printf("rate\n")? (b) printf("%d\n", rate)?
二、C 基础知识
在本章中,我们将解释以下内容:
- 什么是字母表、字符集和令牌
- 什么是语法规则和语法错误
- 什么是保留字
- 如何在 C 中创建标识符
- 什么是符号常量
- C 数据类型—
int、float和double - 如何写
int和double表达式 - 如何使用字段宽度打印整数
- 如何将浮点数打印到所需的小数位数
- 当
int和double值混合在同一个表达式中时会发生什么 - 当我们将
int分配给double并将double分配给int时会发生什么 - 如何声明变量来保存字符串
- 如何将字符串值赋给字符串变量
- 使用赋值语句时要避免的一些问题
2.1 导言
在这一章中,我们将讨论一些用 C 语言编写程序时你需要知道的基本概念。
编程语言在许多方面类似于口语。它有一个字母表(通常称为字符集),语言中的所有内容都是从这个字母表构建的。它有构成单词(也称为记号)的规则、构成语句的规则和构成程序的规则。这些被称为语言的语法规则,在编写程序时必须遵守。如果你违反了规则,你的程序将包含一个语法错误。当你试图编译程序时,编译器会通知你错误。您必须更正并重试。
成为优秀程序员的第一步是学习编程语言的语法规则。这是容易的部分,许多人错误地认为这使他们成为程序员。这就像说,学习一些英语语法规则,并能够写出一些正确形成的句子,使一个人成为小说家。小说写作技巧需要的不仅仅是学习一些语法规则。除此之外,它还需要洞察力、创造力和在特定情况下使用正确词汇的诀窍。
同样,一个好的程序员必须能够创造性地使用语言的特性,以优雅而高效的方式解决各种各样的问题。这是困难的部分,只有通过长期、艰苦地研究解决问题的算法和编写程序来解决广泛的问题才能实现。但是我们必须从小步开始。
2.2 字母表
在 1.4 节中,我们介绍了角色的概念。我们可以把 C 字母表看作是由所有可以在标准英语键盘上输入的字符组成的:例如,数字;大写和小写字母;以及+、=、<、>、&、%等特殊字符。
更正式的说法是,C 使用 ASCII(美国信息交换标准代码,发音为ass-key)字符集。这是一种字符标准,包括标准键盘上的字母、数字和特殊字符。它还包括控制字符,如退格、制表符、换行符、换页符和回车符。每个字符被分配一个数字代码。ASCII 码从 0 到 127。
本书中的程序将使用 ASCII 字符集编写。ASCII 字符集中的字符如附录 b 所示。
角色处理将在第六章中详细讨论。
2.3 C 代币
语言的标记是可以放在一起构造程序的基本构件。令牌可以是保留字(如int或while)、标识符(如b或sum)、常量(如25或"Alice in Wonderland")、分隔符(如}或;)或运算符(如+或=)。
例如,考虑上一章末尾给出的程序 P1.4 的以下部分:
int main() {
int a, b, sum;
a = 14;
b = 25;
sum = a + b;
printf("%d + %d = %d\n", a, b, sum);
}
从头开始,我们可以按顺序列出令牌:
| 代币 | 类型 | | --- | --- | | `int` | 预定字 | | `main` | 标识符 | | `(` | 左括号,分隔符 | | `)` | 右括号,分隔符 | | `{` | 左大括号,分隔符 | | `int` | 预定字 | | `a` | 标识符 | | `,` | 逗号,分隔符 | | `b` | 标识符 | | `,` | 逗号,分隔符 | | `sum` | 标识符 | | `;` | 分号,分隔符 | | `a` | 标识符 | | `=` | 等号,分隔符 | | `14` | 常量 | | `;` | 分号,分隔符 |等等。因此,我们可以把一个程序想象成一串符号,这正是编译器看待它的方式。因此,就编译器而言,上面的代码可以写成这样:
int main() { int a, b, sum;
a = 14; b = 25; sum = a + b;
printf("%d + %d = %d\n", a, b, sum); }
令牌的顺序完全相同;对编译器来说,它是同一个程序。对于计算机来说,只有令牌的顺序才是重要的。然而,布局和间距对于提高程序的可读性是很重要的。
2.3.1 程序内的间距
一般来说,C 程序可以用“自由格式”编写。例如,这种语言不要求我们在一行上写一个语句。甚至一个简单的声明,如
a = 14;
可以写成四行,像这样:
a
=
14
;
只有标记的顺序是重要的。然而,由于14是一个令牌,所以1不能与4分开。你甚至不能在1和4之间留一个空格。
除了在字符串或字符常量中,空格在 c # 中并不重要。但是,合理使用空格可以极大地提高程序的可读性。一般的经验法则是,只要你能放一个空格,你就可以放任意数量的空格,而不影响你程序的意思。该声明
a = 14;
可以写成
a=14;
或者
a = 14 ;
或者
a= 14;
声明
sum = a + b;
可以写成
sum=a+b;
或者
sum= a + b ;
或者
sum = a+b;
当然,请注意,变量sum中不能有空格。写s um或者su m就不对了。一般来说,一个令牌的所有字符必须在一起。
保留字
C 语言使用了许多关键字,如int, char和while。关键字在 C 程序的上下文中有特殊的含义,并且只能用于该目的。比如,int 只能用在那些我们需要指定某个项的类型是整数的地方。所有关键字都只用小写字母书写。因此int是一个关键字,而Int和INT不是。关键字是保留的,也就是说,不能将它们用作标识符。因此,它们通常被称为保留字。附录 a 中给出了 C 关键字列表。
标识符
C 程序员需要为诸如变量、函数名(第七章)和符号常量(见下页)等事物起名字。他编造的名字被称为用户标识符。在命名标识符时,需要遵循一些简单的规则:
- 它必须以字母或下划线开头。
- 如果需要其他字符,它们可以是字母、数字或下划线的任意组合。
标识符的长度不能超过 63 个字符。
有效标识符的示例:
r
R
sumOfRoots1and2
_XYZ
maxThrowsPerTurn
TURNS_PER_GAME
R2D2
root1
无效标识符的示例:
2hotToHandle // does not start with a letter
Net Pay // contains a space
ALPHA;BETA // contains an invalid character ;
需要注意的要点:
- 标识符中不允许有空格。如果你需要一个由两个或更多单词组成的单词,使用大小写字母的组合(如
numThrowsThisTurn)或使用下划线来分隔单词(如num_throws_this_turn)。我们更喜欢大写/小写的组合。 - 通常,C 区分大小写(大写字母被认为不同于相应的小写字母)。因此
r是不同于R的标识符。而且sum不同于Sum不同于SUM不同于SuM。 - 不能使用 C 保留字作为标识符之一。
2.3.4 一些命名约定
除了创建标识符的规则之外,C 对于使用什么名字,或者使用什么格式(例如,大写或小写)没有任何限制。然而,良好的编程实践表明应该遵循一些常识性的规则。
标识符应该是有意义的。例如,如果它是一个变量,它应该反映存储在变量中的值;对于存储某人的净工资,netPay是比x更好的变量,尽管两者都有效。如果是一个函数(第七章),它应该给出这个函数应该做什么的一些指示;playGame是比plg更好的标识符。
使用大写字母和小写字母的组合来表示由标识符命名的项的种类是一个好主意。在本书中,我们使用以下约定:
- 变量通常用小写字母书写:例如,
sum。如果我们需要一个由两个或更多单词组成的变量,我们用一个大写字母开始第二个和随后的单词:例如,voteCount或sumOfSeries。 - 符号(或命名)常量是一个标识符,可以用来代替常量,如
100。假设100代表我们希望在某个程序中处理的项目的最大数量。我们可能需要在程序的不同地方使用数字100。但是假设我们改变主意,想要供应 500 件商品。我们必须把所有出现的100都改成500。然而,我们必须确保不改变除了最大项目数(在类似于principal*rate/100的计算中)之外的其他用途的100的出现。 - 为了便于改变我们的想法,我们可以将标识符
MaxItems设置为100,并在需要引用最大项数时使用MaxItems。如果我们改变主意,我们只需要将MaxItems设置为新值。我们将以大写字母开始一个符号常量。如果它由一个以上的单词组成,我们将以大写字母开始每个单词,如MaxThrowsPerTurn。 - 我们将在 4.6 节看到如何使用符号常量。
2.4 基本数据类型
在 1.4 节中,我们简要地谈到了数据类型的概念。对于本书的大部分内容,我们将使用以下数据类型:
int, double, and char
其中,这些被称为原始数据类型。
每种数据类型都定义了该类型的常量。当我们声明一个变量为特定类型时,我们实际上是在说什么样的常量(值)可以存储在那个变量中。例如,如果我们将变量num声明为int,我们就是说num的值在任何时候都可以是一个整数常量,如25、-369或1024。
2.5 整数- int
int 变量用于存储一个整数值。整数值是 0、1、2、3、4 等中的一个。然而,在计算机上,可以存储的最大和最小整数是由用于存储整数的位数决定的。附录 C 展示了如何在计算机上表示整数。
通常,int变量占用 16 位(2 字节),可用于存储-32,768 到+32,767 范围内的整数。但是,请注意,在某些机器上,int可以占用 32 位,在这种情况下,它可以存储从-2,147,483,648 到+2,147,483,647 的整数。一般来说,如果用 n 位来存储一个int,可以存储的数的范围是-2 n-1 到+2 n-1 - 1。
作为练习,找出计算机上最大和最小的int值。
声明变量
在 C # 中,通过指定类型名后跟变量来声明变量。例如,
int h;
将h声明为int类型的变量。该声明为h分配了空间,但没有将其初始化为任何值。除非明确地为变量赋值,否则不能假定变量包含任何值。
您可以在一个语句中声明几个相同类型的变量,如下所示:
int a, b, c; // declares 3 variables of type int
变量之间用逗号分隔,最后一个变量后用分号隔开。
您可以在一条语句中声明一个变量并赋予它一个初始值,如:
int h = 14;
这将h声明为int,并给它一个值14。
整数表达式
整数常量的写法我们都很熟悉:比如354、639, -1、30705、-4812。请注意,您只能使用一个可能的符号,后跟从0到9的数字。特别是,您不能像分隔千位那样使用逗号;因此32,732是一个无效的整数常量——您必须将其写成32732。
可以使用以下算术运算符来编写整数表达式:
| `+` | 增加 | | − | 减去 | | `*` | 多样地 | | `/` | 划分 | | `%` | 查找余数 |例如,假设我们有以下声明:
int a, b, c;
那么以下都是有效的表达式:
a + 39
a + b - c * 2
b % 10 //the remainder when b is divided by 10
c + (a * 2 + b * 2) / 2
操作符+、-和*都给出了预期的结果。但是,/执行整数除法;如果有剩余,就扔掉。我们说整数除法截断。因此19/5给出值3;剩余部分4被丢弃。
但是-19/5的值是多少呢?这里的答案是–3。规则是,在 C 中,整数除法向零截断。因为–19 ÷ 5的精确值是–3.8,向零截断得到–3。(在下一节中,我们将展示如何获得一个整数除以另一个整数的精确值。)
当一个整数被另一个整数除时,%运算符给出余数。举个例子,
19 % 5 evaluates to 4;
h % 7 gives the remainder when h is divided by 7;
例如,你可以用它来测试一个数字h是偶数还是奇数。如果h % 2是0,那么h是偶数;如果h % 2是1,那么h就是奇数。
2.5.3 运算符优先级
c # 基于运算符的通常优先级对表达式求值:乘法和除法在加法和减法之前完成。我们说乘除法的优先级高于加减法。例如,表达式
5 + 3 * 4
首先将3乘以4(给出12,然后将5加到12,给出17作为表达式的值。
像往常一样,我们可以使用括号来强制表达式按照我们想要的顺序求值。例如,
(5 + 3) * 4
先将5和3相加(给出8,再将8乘以4,给出32。
当两个具有相同优先级的运算符出现在一个表达式中时,它们从左到右进行计算,除非用括号另行指定。例如,
24 / 4 * 2
被评估为
(24 / 4) * 2
(给予12)和
12 - 7 + 3
被评估为
(12 - 7) + 3
给予8。然而,
24 / (4 * 2)
按照预期进行评估,给出3,并且
12 - (7 + 3)
如预期被评估,给出2。
在 C 语言中,余数运算符%与乘法(*)和除法(/)具有相同的优先级。
Exercise: What is printed by the following program? Verify your answer by typing and running the program
#include <stdio.h>
int main() {
int a = 15;
int b = 24;
printf("%d %d\n", b - a + 7, b - (a + 7));
printf("%d %d\n", b - a - 4, b - (a - 4));
printf("%d %d\n", b % a / 2, b % (a / 2));
printf("%d %d\n", b * a / 2, b * (a / 2));
printf("%d %d\n", b / 2 * a, b / (2 * a));
}
2.5.4 使用“字段宽度”打印整数
我们已经看到,我们可以通过在printf语句中指定值(通过变量或表达式)来打印整数值。当我们这样做时,C 使用所需的“打印列”打印值。例如,如果值是 782,则使用 3 列打印,因为 782 有 3 个数字。如果该值为-2345,则使用 5 个打印列(一个用于负号)进行打印。
虽然这对于大多数目的来说已经足够了,但是有时候能够告诉 C 要使用多少个打印列是很有用的。例如,如果我们想在5打印列中打印n的值,我们可以通过指定字段宽度5来实现,如下所示:
printf("%5d", n);
我们现在用%5d代替规格%d。字段宽度位于%和d之间。n的值被打印在“字段宽度5”中。
假设n是279;有 3 个数字要打印,因此需要 3 个打印列。由于字段宽度为5,所以数字279前会打印 2 个空格,因此:◎◎279(表示空格)。我们也说“打印时有两个前导空格”和“打印时在左边填充两个空格”
更专业的说法是“n在字段宽度5中右对齐打印。”“右对齐”是指将数字尽可能地放在字段的右侧,并在其前面添加空格以构成字段宽度。如果数字放在尽可能靠左的位置,并在其后添加空格以构成字段宽度,则数字是左对齐的。例如,279在字段宽度5中左对齐。
减号可用于指定左对齐;%-wd将在w的字段宽度中打印一个左对齐的值。例如,要打印一个在5的字段宽度中左对齐的整数值,我们使用%-5d。
再比如,假设n是-7,字段宽度是5。打印n需要两个打印列(一个用于-,一个用于7);由于字段宽度为5,打印时有 3 个前导空格,因此:◎◎-7。
你可能会问,场宽太小会怎么样?假设要打印的值是23456,字段宽度是3。打印该值需要 5 列,这大于字段宽度 3。在这种情况下,C 忽略字段宽度,并根据需要使用尽可能多的列(本例中为 5 列)来打印值。
一般情况下,假设用规格%wd打印整数值v,其中w为整数,假设需要n列打印v。有两种情况需要考虑:
If n is less than w (the field width is bigger), the value is padded on the left with (w - n) spaces. For example, if w is 7 and v is -345 so that n is 4, the number is padded on the left with (7-4) = 3 spaces and printed as ◊◊◊-345. If n is greater than or equal to w (field width is the same or smaller), the value is printed using n print columns. In this case, the field width is ignored.
当我们想要一个接一个地排列数字时,字段宽度很有用。假设我们有三个int变量a、b和c,分别具有值9876、-3和501。声明
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
将打印
9876
-3
501
每个数字仅使用所需的列数打印。由于这一数字与下一个数字不同,所以它们不会排成一行。例如,如果我们愿意,我们可以使用字段宽度5来排列数字。声明
printf("%5d\n", a);
printf("%5d\n", b);
printf("%5d\n", c);
将打印(◊表示空格)
◊9876
◊◊◊-3
◊◊501
那会是这样的(不带◎):
9876
-3
501
一切都井然有序。
有趣的是,我们并不真的需要三个printf语句。我们可以将最后三个printf语句替换为
printf("%5d\n%5d\n%5d\n", a, b, c);
每个\n强制下面的输出到新的一行。
2.6 浮点数—float和double
浮点数是可能有小数部分的数字。浮点常量可以用两种方法之一来编写:
- 正常方式,带有一个可选符号,包括一个小数点;比如-3.75,0.537,47.0。
- 使用科学记数法,带有可选符号,包括小数点和“指数”部分;比如-0.375E1,意思是“-0.375 乘以 10 的 1 次方”,也就是-3.75。同样,0.537 可以写成 5.37e-1,即 5.37 x 10-1。指数可以用 e 或 e 来指定。
- 注意,同一个数有几种写法。例如,以下都表示同一个数字 27.96:
27.96E00 2.796E1 2.796E+1 2.796E+01 0.2796E+02 279.6E-1
在 C 中,我们可以使用float或double来声明浮点变量。一个float值通常存储为一个 32 位浮点数,给出大约 6 或 7 个有效数字。一个double值被存储为一个 64 位浮点数,给出大约 15 个有效数字。
浮点常量的类型是double,除非它后面跟有f或F,在这种情况下,它的类型是float。因此3.75属于double类型,但3.75f或3.75F属于float类型。大多数计算都是使用double精度完成的。如果您需要存储大量浮点数,并且希望使用尽可能少的存储空间(并且不介意只有 6 或 7 位精度),那么类型float非常有用。
在本书中,我们将主要使用double来处理浮点数。
2.6.1 打印double和float变量
我们已经在一个printf语句中使用了格式规范%d来打印一个整型变量的值。如果我们希望打印一个double或float变量的值,我们可以使用%f。例如,考虑以下情况:
double d = 987.654321;
printf("%f \n", d);
d的值将被打印到预定义的小数位数(通常是 6 位,但可能因编译器而异)。在这种情况下,打印的值将是987.654321。然而,如果d被赋值为987.6543215,打印出的值将是987.654322(四舍五入到小数点后六位)。
类似地,如果x的类型是float,那么它的值可以使用以下方式打印:
printf("%f \n", x);
我们刚刚看到规范%f将数字打印到预定义的小数位数。然而,大多数时候,我们想说要打印多少个小数位,有时,要使用多少列。例如,如果我们想在宽度为 6 的字段中打印上面的d,精确到 2 位小数,我们可以使用:
printf("%6.2f \n", d);
在%和f之间,我们写6.2,也就是字段宽度,后面是一个.(点),后面是小数位数。该值四舍五入到指定的小数位数,然后打印出来。这里,打印的值将是987.65,正好占据 6 个打印列。如果字段宽度更大,数字将在左边用空格填充。如果字段宽度较小,则会被忽略,并根据需要使用尽可能多的列来打印数字。
作为另一个例子,考虑
b = 245.75;
printf("%6.1f \n", b);
在规范%6.1f中,1表示将数字四舍五入到小数点后 1 位;这给出了245.8,它需要 5 列来打印。
6表示打印第 6 列245.8;由于打印数字只需要 5 列,所以在开头加了一个空格组成 6 列,所以数字打印为◎245.8(¢表示空格)。
同样的,
printf("%6.0f \n", b);
将b打印为◎◎246(四舍五入到0小数位,打印在字段宽度6)。
如果规格是%3.1f并且要打印的值是245.8,它将使用5打印列来打印,即使字段宽度是3。同样,当指定的字段宽度小于所需的打印列数时,C 会忽略字段宽度,并根据需要使用尽可能多的列来打印值。
我们有时可以利用这一点。如果我们不知道一个值可能有多大,我们可以故意使用一个小的字段宽度,以确保使用打印该值所需的精确的打印列数来打印它。
一般来说,假设float或double值v要用规格%w.df打印,其中w和d是整数。首先,将值v四舍五入到d位小数。假设打印v需要的打印列数,包括一个可能的点(d = 0 就没有点;该值将被四舍五入为整数)和一个可能的符号,是n。有两种情况需要考虑:
If n is less than w (the field width is bigger), the value is padded on the left with (w - n) spaces. For example, suppose w is 7 and the value to be printed is -3.45 so that n is 5. The number is padded on the left with (7-5) = 2 spaces and printed as ◊◊-3.45. If n is greater than or equal to w (field width is the same or smaller), the value is printed using n print columns. In this case, the field width is ignored.
与整数一样,当我们想要一个接一个地排列数字时,字段宽度很有用。假设我们有三个double变量a、b,和c,分别具有值419.563、-8.7,和3.25。假设我们要将值打印到小数点后两位,按小数点对齐,如下所示:
419.56
-8.70
3.25
因为最大的数字需要 6 个打印列,我们可以使用至少 6 的字段宽度来排列它们。下面的语句会将它们按上述方式排列起来:
printf("%6.2f \n", a);
printf("%6.2f \n", b);
printf("%6.2f \n", c);
如果我们使用大于 6 的字段宽度,数字仍然会排列,但是会有前导空格。
例如,如果我们使用字段宽度8,我们将得到(◊表示一个空格)
◊◊419.56
◊◊◊-8.70
◊◊◊◊3.25
同样,我们可以使用一个printf而不是三个来达到相同的效果:
printf("%6.2f \n%6.2f \n%6.2f \n", a, b, c);
每个\n强制下面的输出到新的一行。
2 . 6 . 2double和float之间的分配
正如所料,您可以在一个float变量中存储一个float值,在一个double变量中存储一个double值。由于float比double小,C 允许你在double变量中存储一个float值,没有任何问题。但是,如果将一个double赋值给一个float,可能会损失一些精度。请考虑以下几点:
double d = 987.654321;
float x = d;
printf("%f \n", x);
由于一个float变量只允许大约 7 位数的精度,我们应该预料到d的值可能不会精确地分配给x。实际上,当使用一个编译器运行时,会为x打印出值987.654297。当d改为987654321.12345时,打印的数值为987654336.000000。在这两种情况下,保留了大约 6 或 7 位数的精度。
作为一个练习,看看用你的编译器打印出了什么值。
浮点表达式
浮点表达式可以使用以下运算符编写:
| `+` | 添加 | | − | 减法 | | `*` | 增加 | | `/` | 分开 |这些按预期运行;特别地,除法以通常的方式执行,例如,19.0/5.0给出值3.8。
如果op1和op2是一个运算符的两个操作数,下面显示了所执行的计算类型:
因此,只有当两个操作数都是float时,才会执行float;否则执行double。
2.6.4 具有整数和浮点值的表达式
使用包含整数值和浮点值的表达式是很常见的,例如,
a / 3 where a is float
n * 0.25 where n is int
在 C 语言中,这种表达式的规则是这样的:
If any operand of arithmetic operator is floating-point, the calculation is completed in floating-point arithmetic. Unless at least one operand is double, the calculation is of floating-point type, in which case the calculation is of double type.
在上面的第一个例子中,整数3被转换为float,计算在float中完成。在第二个例子中,n被转换为double(因为0.25是double),计算在double中完成。
比方说,我们如何得到整数除法19/5的精确值?我们可以通过将一个或两个常量写成double来强制进行双精度计算,因此:19/5.0、19.0/5,或19.0/5.0。我们也可以使用石膏,比如
(double) 19 / 5
强制转换由括号中的类型名组成,并允许我们强制将一种类型转换为另一种类型。这里,19被转换为double,迫使5转换为double,并执行双精度除法。
然而,我们必须小心像这样的构造
(double) (19 / 5)
这可能不是我们想的那样。这不做浮点除法。由于两个常量都是整数,括号内的表达式计算为整数除法,给出3;这个值转换成double,给出3.0。
2.6.5 将double/float分配给int
考虑一下:
double d = 987.654321;
int n = d;
printf("%d \n", n);
打印数值987。当我们将一个浮点值赋给一个int时,小数部分(如果有的话)将被丢弃(不舍入)并且最终的整数值被赋值。我们有责任确保获得的整数足够小,能够适合一个int。否则,结果值是不可预测的。
在一个编译器上,一个int的最大值是32767,当d被改为987654.321时,输出的值是4614,与预期相差甚远,看起来不可预测。(不完全不可预测;赋值为987654 % 32768,也就是4614。一般来说,如果big代表一个太大而无法存储的值,那么对于 16 位存储的整数,实际存储的值就是big % 32768。)这是因为d的截断值是987654,它太大了,放不进一个int变量。作为一个练习,看看你的编译器会输出什么样的值。
如果我们想要将d的舍入值存储在n中,我们可以用
n = d + 0.5;
如果d中小数点后的第一个数字是5或更多,添加0.5会将1添加到整个数字部分。如果点后的第一个数字小于5,添加0.5不会改变整个数字部分。
比如d是245.75,加0.5会给246.25,246会赋给n。但如果d是245.49,加0.5会给245.99,245会赋给n。
2.7 弦
到目前为止,我们已经看到了几个printf语句中字符串常量的例子。
字符串常量是任何用双引号括起来的字符序列。例如:
"Once upon a time"
"645-2001"
"Are you OK?"
"c:\\data\\castle.in"
左引号和右引号必须出现在同一行。换句话说,C 不允许字符串常量延续到另一行。然而,一个长的字符串可以被分成几个部分,每个部分占一行。当程序被编译时,C 将把这些片段连接起来,形成一个字符串。例如,
printf("Place part of a long string on one line and "
"place the next part on the next line. The parts are "
"separated by whitespace, not comma or ; \n");
字符串常量的值是没有开始和结束引号的字符序列。因此,"Are you OK?"的值就是Are you OK?。
如果您希望双引号成为字符串的一部分,您必须使用转义序列\"来编写它,如
"\"Don’t move!\", he commanded"
该字符串的值为
"Don’t move!", he commanded
每个\"都被替换为",并且开始和结束的引号都被删除。
C 语言没有预定义的string类型。这给初学编程的人带来了困难,因为他不能像处理数值变量那样处理字符串变量。
在 C 语言中,字符串存储在“字符数组”中既然我们在第六章的中讨论了字符,在第八章的中讨论了数组,我们可以耐心等待,直到理解数组是什么,字符串是如何存储的,以及我们如何用它们来存储一个名字。或者,我们可以凭着信心接受一些东西,并以一种有限的方式,比我们通常更快地收获能够与弦一起工作的好处。我们会不耐烦,选择后者。
假设我们希望在某个变量name中存储一个人的名字。我们可以声明name如下:
char name[50];
这声明了name是一个大小为50的“字符数组”。正如我们将在第八章中解释的,这允许我们在name中存储最多49个角色。如果您发现这对于您的目的来说太多(或太少),您可以使用不同的数字。
如果我们愿意,我们可以在声明中给name赋一个字符串常量,这样:
char name[50] = "Alice Wonder";
这在name中存储了从A到r的字符,包括空格。报价不会被存储。一旦完成,我们可以使用printf中的规格%s打印name的值,因此:
printf("Hello, %s\n", name);
这会打印出来
Hello, Alice Wonder
name的值替换%s。
不幸的是,除了在name的声明中,我们不能给name赋值一个字符串常量。c 不允许我们写一个赋值语句,比如
name = "Alice in Wonderland"; // this is not valid
给name赋值。我们可以使用标准函数strcpy(用于字符串复制),如下所示:
strcpy(name, "Alice in Wonderland"); // this is valid
但是为了使用strcpy(和其他字符串函数),我们必须在程序前加上指令:
#include <string.h>
我们在程序 P2.1 中总结了所有这些。
Program P2.1
#include <stdio.h> // needed for printf
#include <string.h> // needed for strcpy
int main() {
char name[50];
strcpy(name, "Alice in Wonderland");
printf("Hello, %s\n", name);
}
运行时,该程序将打印
Hello, Alice in Wonderland
在 3.4 和 5.9 节中,我们将看到如何将一个字符串值读入一个变量。
连接两个字符串是我们有时想要执行的操作。我们说我们想要连接两个字符串。我们可以用标准的字符串函数strcat(字符串连接)来实现。例如,假设我们有:
char name[30] = "Alice";
char last[15] = "Wonderland";
声明
strcat(name, last);
会将last中的字符串添加到name中的字符串。我们有责任确保name足够大,能够容纳连接的字符串。结果是name现在会按住AliceWonderland;last中的值不变。以下语句将 name 设置为Alice in Wonderland。
strcat(name, " in "); //one space before and after "in"
strcat(name, last);
2.8 转让声明
在 1.9 节中,我们介绍了赋值语句。回想一下,赋值语句包括一个变量,后面跟一个等号(=),后面跟要赋给变量的值,再后面跟一个分号。我们可以这样写:
<variable> = <value>;
<value>必须与<variable>兼容,否则会出错。例如,如果<variable>是int,我们必须能够从<value>中导出一个整数。如果<variable>是double,我们必须能够从<value>中导出一个浮点值。例如,如果n是int而x是double,我们就不能写
n = "Hi there"; //cannot assign string to int
x = "Be nice"; //cannot assign string to double
将赋值语句想象成如下执行是很有用的:计算=右边的值。获得的值存储在左侧的变量中。变量的旧值(如果有)会丢失。例如,如果 s core的值为25,那么在语句之后
score = 84;
score的值将是84;旧的价值25丢失了。我们可以把这想象成:
一个变量可以取几个值中的任何一个,但一次只能取一个。作为另一个例子,考虑这个陈述:
score = score + 5;
假设score在这个语句执行之前有值84。执行后的价值是什么?
首先,使用score、84的当前值评估右侧score + 5。计算得出89—该值存储在左侧的变量中;恰好是score。最终结果是score的值增加了5到89。旧值84丢失。
即使赋值语句是有效的,它也可能在程序运行时产生错误。考虑以下情况(a、b、c、d、e为int):
a = 12;
b = 5;
c = (a – b) * 2;
d = c + e;
其中每一个都是格式正确的赋值语句。但是,当执行这些语句时,将会导致错误。你能看出是怎么回事吗?
第一条语句将12赋值给a;第二个将5分配给b;第三个将14分配给c;目前没有问题。然而,当计算机试图执行第四条语句时,它遇到了问题。e没有值,所以表达式c + e不能求值。我们说e是未定义的——它没有值。
在我们可以在表达式中使用任何变量之前,它必须已经被某个先前的语句赋值。如果没有,我们将得到一个“未定义变量”的错误,我们的程序将暂停。
这个故事的寓意是:有效的程序不一定是正确的程序。
Exercise: What is printed by the following?
a = 13;
b = a + 12;
printf("%d %d\n", a, b);
c = a + b;
a = a + 11;
printf("a = %d b = %d c = %d\n", a, b, c);
2.9 printf
我们已经看到了几个printf语句的例子。我们用它来打印字符串常量、整数值和浮点值。我们打印了有和没有字段宽度的值。我们还看到了如何使用转义序列\n来强制输出到新的一行。
值得强调的是,格式字符串中的字符完全按照它们出现时的样子打印,只是格式规范被其相应的值所替换。例如,如果a是25,b是847,考虑以下语句
printf("%d%d\n", a, b);
这会打印出来
25847
这些数字粘在一起,我们分不清什么是a什么是b!这是因为规范%d%d说要打印相邻的数字。如果我们想用一个空格将它们分开,比如说,我们必须在%d和%d之间放一个空格,就像这样:
printf("%d %d\n", a, b);
这会打印出来
25 847
为了在数字之间获得更多的空格,我们只需在%d和%d之间输入我们想要的数字。
Exercise: What is printed by the following?
printf("%d\n %d\n", a, b);
下面是一些关于格式规范的有用信息。
假设num为int,其值为75:
- 规格
%d将使用2打印列75打印75 - 规格
%5d将打印带有 3 个前导空格的75:◊◊◊75 - 规格
%-5d将打印带有 3 个尾随空格的75:75◊◊◊ - 规格
%05d将打印带有 3 个前导零的75:00075
对于前导0可能有用的示例,考虑以下语句
printf("Pay this amount: $%04d\n", num);
这会打印出来
Pay this amount: $0075
这比印刷好
Pay this amount: $ 75
因为有人可以在$和7之间插入数字。
通常,减号指定左对齐,字段宽度前面的0指定0(零,而不是空格)作为填充字符。
Exercises 2In the ASCII character set, what is the range of codes for (a) the digits (b) the uppercase letters and (c) the lowercase letters? What is a token? Give examples. Spaces are normally not significant in a program. Give an example showing where spaces are significant. What is a reserved word? Give examples. Give the rules for making up an identifier. What is a symbolic constant and why is it useful? Give examples of integer constants, floating-point constants, and string constants. Name five operators that can be used for writing integer expressions and give their precedence in relation to each other. Give the value of (a) 39 % 7 (b) 88 % 4 (c) 100 % 11 (d) -25 % 9 Give the value of (a) 39 / 7 (b) 88 / 4 (c) 100 / 11 (d) -25 / 9 Write a statement that prints the value of the int variable sum, right justified in a field width of 6. You are required to print the values of the int variables b, h, and n. Write a statement that prints b with its rightmost digit in column 10, h with its rightmost digit in column 20, and n with its rightmost digit in column 30. Write statements that print the values of b, h, and n lined up one below the other with their rightmost digits in column 8. Using scientific notation, write the number 345.72 in four different ways. Write a statement that prints the value of the double variable total to 3 decimal places, right justified in a field width of 9. You need to print the values of the float variables a, b, and c to 1 decimal place. Write a statement that prints a with its rightmost digit in column 12, b with its rightmost digit in column 20, and c with its rightmost digit in column 32. What kind of variable would you use to store a telephone number? Explain. Write statements to print the values of 3 double variables a, b, and c, to 2 decimal places, The values must be printed one below the other, with their rightmost digits in column 12. How can you print the value of a double variable, rounded to the nearest whole number? What happens if you try to print a number (int, float, or double) with a field width and the field width is too small? What if the field width is too big? Name some operators that can be used for writing floating-point expressions. Describe what happens when we attempt to assign an int value to a float variable. Describe what happens when we attempt to assign a float value to an int variable. Write a statement to print the following: Use \n to end a line of output. Write a statement to increase the value of the int variable quantity by 10. Write a statement to decrease the value of the int variable quantity by 5. Write a statement to double the value of the int variable quantity. Write a statement to set a to 2 times b plus 3 times c. The double variable price holds the price of an item. Write a statement to increase the price by (a) $12.50 (b) 25%. What will happen when the computer attempts to execute the following: p = 7; q = 3 + p; p = p + r; printf("%d\n", p); Suppose rate = 15. What is printed by each of the following? printf("Maria earns rate dollars an hour\n"); printf("Maria earns %d dollars an hour\n", rate); If m is 3770 and n is 123, what is printed by each of the following? (a) printf("%d%d\n", n, m); (b) printf("%d\n%d\n", n, m);
三、具有顺序逻辑的程序
在本章中,我们将解释以下内容:
- 读取用户提供的数据的想法
scanf语句如何工作- 如何使用
scanf读取数字数据 - 如何使用
gets读取字符串数据 - 用几个例子说明程序编写的重要原则
3.1 导言
在上一章中,我们介绍了一些 C 语言的基本数据类型——int、double和float——并用简单的语句来说明它们的用法。我们现在更进一步,通过使用这些类型编写程序来介绍几个编程概念。
本章中的程序将基于顺序逻辑——简单地说就是程序中的语句一个接一个地执行,从第一个到最后一个。这是最简单的一种逻辑,也叫直线逻辑。在下一章中,我们将编写使用选择逻辑的程序——程序测试某些条件并根据条件的真假采取不同行动的能力。
3.2 读取用户提供的数据
再次考虑程序 P1.3。
Program P1.3
// This program prints the sum of 14 and 25\. It shows how
// to declare variables in C and assign values to them.
#include <stdio.h>
int main() {
int a, b, sum;
a = 14;
b = 25;
sum = a + b;
printf("%d + %d = %d\n", a, b, sum);
}
c 允许我们在一条语句中声明一个变量并给它一个初始值,这样我们就可以更简洁地编写程序(没有注释),如程序 P3.1:
Program P3.1
#include <stdio.h>
int main() {
int a = 14;
int b = 25;
int sum = a + b;
printf("%d + %d = %d\n", a, b, sum);
}
因为,如前所述,我们并不真正需要变量sum,这个程序可以写成程序 P3.2。
Program P3.2
#include <stdio.h>
int main() {
int a = 14;
int b = 25;
printf("%d + %d = %d\n", a, b, a + b);
}
这个项目非常严格。如果我们希望添加另外两个数字,我们必须将程序中的数字14和25更改为所需的数字。然后我们必须重新编译程序。每次我们想增加两个不同的数字,我们就必须改变程序。这可能会变得非常乏味。
如果我们能够以这样一种方式编写程序,当我们运行程序时,我们将有机会告诉程序我们希望添加哪些数字,那就太好了。这样,数字就不会与程序捆绑在一起,程序也会更加灵活。当我们“告诉”程序这些数字时,我们说我们在向程序提供数据。但是我们如何让程序向我们“询问”这些数字,我们如何“告诉”程序这些数字是什么?
我们可以让程序通过打印如下消息来提示我们输入一个数字:
Enter first number:
使用printf语句。然后,程序必须等待我们键入数字,当数字被键入时,就读取它。这可以通过scanf语句来完成。(严格来说,printf和scanf都是函数,但区别对我们来说并不太重要。)在我们看这个语句之前,让我们用这些新的想法重写这个算法:
prompt for the first number
read the number
prompt for the second number
read the number
find the sum
print the sum
我们可以用 C 语言将这个算法实现为程序 P3.3。
Program P3.3
//prompt for two numbers and find their sum
#include <stdio.h>
int main() {
int a, b;
printf("Enter first number: ");
scanf("%d", &a);
printf("Enter second number: ");
scanf("%d", &b);
printf("%d + %d = %d\n", a, b, a + b);
}
运行时,第一个printf语句将打印出来:
Enter first number:
简单解释一下,scanf语句会让计算机等待用户输入一个数字。
假设她输入23;屏幕将如下所示:
Enter first number: 23
当她按下键盘上的“Enter”或“Return”键时,scanf读取数字并将其存储在变量a中。
然后,下一个printf语句提示:
Enter second number:
再次,scanf使计算机等待用户输入一个数字。假设她进入18;scanf读取数字,并存储在变量b中。在这个阶段,数字23存储在a中,而18存储在b中。我们可以这样描述:
然后,程序执行最后一条printf语句,并打印以下内容:
23 + 18 = 41
最后,屏幕将如下所示。下划线部分由用户输入,其他部分由计算机打印:
Enter first number: 23
Enter second number: 18
23 + 18 = 41
由于用户可以自由输入任何数字,只要数字足够小,可以存储在一个int变量中,程序就可以处理任何输入的数字。否则,将会打印出奇怪的结果。
3.3 scanf
在程序 P3.3 中,语句
scanf("%d", &a);
使计算机等待用户键入数字。由于a是一个整数变量,scanf期望数据中的下一项是一个整数或一个值(比如说3.8),它可以转换成整数,但去掉小数部分。如果不是(例如,如果是一个字母或特殊字符),程序将给出一个错误信息,如“无效的数字格式”并停止。我们说程序会崩溃。如果数据有效,该数字将存储在变量a中。声明
scanf("%d", &b);
以类似的方式工作。
该声明包括:
scanf这个词- 左右括号
- 括号内的两项(称为参数),用逗号分隔
和printf一样,第一项是一个叫做格式字符串的字符串。在这个例子中,字符串只包含格式规范%d。它指定了要读取的数据类型。这里,%d用于表示要读取的整数值。
第二个参数指定存储读取值的位置。尽管我们希望值存储在a,scanf要求我们通过写&a来指定。简单的解释是,我们必须告诉scanf存储值的内存位置的地址;&a代表“?? 的地址”你需要相信,为了使用scanf将一个值读入一个变量,变量前面必须有&,如&a和&b。请注意,这仅适用于scanf语句。除此之外,变量以其正常形式使用(无&),如下所示:
printf("%d + %d = %d\n", a, b, a + b);
我们可以使用scanf一次读取多个值。例如,假设我们想读取变量a、b和c的三个整数值。为此,我们需要在格式规范中写三次%d,因此:
scanf("%d %d %d", &a, &b, &c);
当这个语句被执行时,它寻找三个整数。第一个存储在a中,第二个存储在b中,第三个存储在c中。由用户来确保数据中接下来的三项是整数。如果不是这样,将会显示“无效数字格式”信息,程序将会崩溃。
输入数据时,数字必须用一个或多个空格分隔,如下所示:
42 -7 18
使用scanf时,可以灵活的方式提供数据。唯一的要求是以正确的顺序提供数据。在本例中,三个数字可以如上或如下提供:
42
-7
18
或者这个:
42 -7
18
或者甚至用一个空行,就像这样:
42
-7 18
空格、制表符和空行(所谓的空白)无关紧要;scanf将简单地继续读取数据,忽略空格、制表符和空白行,直到它找到这三个整数。但是,我们强调,如果在读取数据时遇到任何无效字符,程序将会崩溃。例如,如果用户键入
42 -7 v8
或者
42 = 18 24
程序会崩溃。第一种情况,v8不是有效整数;并且,在第二种情况下,=不是整数的有效字符。
3.3.1 将数据读入float变量
如果我们希望将一个浮点数读入一个浮点变量 x,我们可以使用
scanf("%f", &x);
规范%f用于将一个值读入float(但不是double,见下一节)变量。执行时,scanf期望在数据中找到一个有效的浮点常量。例如,以下任何一项都是可以接受的:
4.265
-707.96
2.345E+1
在最后一种情况下,例如在5和E之间或者在E和+之间或者在+和1之间不能有空格。以下都将对读取数字23.45无效:
2.345 E+1
2.345E +1
2.345E+ 1
3.3.2 将数据读入double变量
如果我们希望将一个浮点数读入一个double变量y,我们可以使用
scanf("%lf", &y);
规格%lf (percent ell f)用于将一个值读入double变量。除了规格之外,对于float和double变量,数据以相同的方式输入。小心——你不能使用%f将数据读入double变量。如果你这样做,你的变量将包含无意义的内容,因为读取的值将存储在 32 位而不是 64 位,即double的大小(见 2.6 节)。然而,如您所见,您可以使用%f来打印double变量的值。
为float/double变量输入数据时,整数是可以接受的。如果你输入42,比方说,它会被解释为42.0。但是,如上所述,如果您为一个int变量输入一个浮点常量(例如,2.35,它将被截断(在本例中,截断到2)。
如果需要,可以使用一条scanf语句将值读入多个变量。如果x和y是double变量,可以用
scanf("%lf %lf", &x, &y);
将数值读入x和y。执行时,scanf期望在数据中找到两个有效的浮点(或整数)常量。第一个存储在x中,第二个存储在y中。数字之前、之间或之后可以有任意数量的空格或空白行。
您也可以在同一个scanf语句中读取int、double或float变量的值。您只需要确保对每个变量使用正确的规范。假设item和quantity是int,而price是double。声明
scanf("%d %lf %d", &item, &price, &quantity);
期望在数据中找到三个数字。
- 第一个必须是存储在
item中的int常量。 - 第二个必须是存储在
price中的double(或int)常量。 - 第三个必须是存储在
quantity中的int常量。
以下是该scanf语句的所有有效数据:
4000 7.99 8.7 // 8.7 is truncated to 8
3575 10 44 // price will be interpreted as 10.00
5600 25.0 1
通常,任何数量的空格都可以用来分隔数字。
以下是该scanf语句的所有无效数据:
4000 7.99 x.8 // x.8 is not an integer constant
25cm 10 44 // 25cm is not an integer constant
560 25 amt = 7 // a is not a valid numeric character
当scanf获取一个数字时,它保持在该数字之后;随后的scanf将继续从该点读取数据。举例来说,假设某些数据的类型为
4000 7.99 8
考虑一下这些陈述
scanf("%d", &item);
scanf("%lf", &price);
scanf("%d", &quantity);
第一个scanf将把4000存储在item中。完成后,它保持在4000后的空间。下一个scanf将从该点继续读取,并将7.99存储在price中。此scanf将在7.99后的空格处停止。第三个scanf将从该点继续读取,并将8存储在quantity中。此scanf将在8后的字符处停止;这可能是一个空格或行尾字符。任何后续的scanf将从该点继续读取。
当读取数据项时,想象一个“数据指针”在数据中移动是很有用的。在任何时候,它都标记数据中的位置,下一个scanf将从该位置开始寻找下一项数据。
3.4 读取字符串
在 2.6 节中,我们看到了如何声明一个变量来保存一个字符串值。例如,《宣言》
char item[50];
让我们在item中存储一个字符串值(最大长度为 49)。我们还看到了如何使用标准的字符串函数strcpy给item赋值。
现在我们向您展示如何从输入中读取一个值到item。在 c 语言中有几种方法可以做到这一点。我们将使用gets(通常读作 get s not gets)语句(更准确地说,是一个函数),如下所示:
gets(item);
这将从数据指针的当前位置开始读取字符并将它们存储在item中,直到到达行尾。不存储行尾字符。数据指针位于下一行的开头。
例如,如果数据线是
Right front headlamp
然后将字符串Right front headlamp存储在item中。效果和我们写的一样
strcpy(item, "Right front headlamp");
机警的读者会注意到,我们没有在item前加一个&,就像我们一直用scanf读数字一样。现在,只需注意item是一个“字符数组”, C 语言中的规则是,在向数组中读取数据时,不能在数组名前放入&。在我们讨论了第八章中的数组之后,你可能会更好地理解这一点。简单的解释是,数组名表示“数组第一个元素的地址”,所以不需要&获取地址。现在,就把它当成你需要遵守的规则。
考虑以下语句(假设声明char name[50]):
printf("Hi, what’s your name? ");
gets(name);
printf("Delighted to meet you, %s\n", name);
当执行时,
printf语句将询问您的姓名。- 会等你输入你的名字。输入后,名称将存储在变量
name中。 printf然后会用您的名字打印问候语。
您的计算机屏幕将如下所示(假设键入Birdie作为名称):
Hi, what’s your name? Birdie
Delighted to meet you, Birdie
3.5 示例
我们现在编写程序来解决一些问题。在寻找解决方案之前,你应该先试着解决问题。在示例运行中,带下划线的项目由用户键入;其他的都是电脑打印的。
问题 1 -平均
写一个程序请求三个整数并打印它们的平均值到一个小数位。该程序应该如下工作:
Enter 3 integers: 23 7 10
Their average is 13.3
解决方案如程序 P3.4 所示。
Program P3.4
//request 3 integers; print their average
#include <stdio.h>
int main() {
int a, b, c;
double average;
printf("Enter 3 integers: ");
scanf("%d %d %d", &a, &b, &c);
average = (a + b + c) / 3.0;
printf("\nTheir average is %3.1f\n", average);
}
关于程序 P3.4 的注意事项:
- 变量 average 被声明为
double而不是int,因为平均值可能不是整数。 - 如果数据中没有输入整数,程序将会崩溃,或者至多给出不正确的结果。
- 我们使用
3.0而不是3来计算平均值。这将强制执行浮点除法。如果我们使用了3,就会执行整数除法,给出13.0作为样本数据的答案,如上。 - 在最后一个
printf中,第一个\n用于打印输出中的空行。 - 我们可以在一个语句中声明
average并赋值给它,就像这样:
double average = (a + b + c) / 3.0;
- 变量
average在这个程序中并不是真正必要的。我们可以计算并打印出printf语句中的平均值
printf("\nTheir average is %3.1f\n", (a + b + c) / 3.0);
3.5.2 问题 2 -正方形
写一个程序来请求一个整数并打印这个数和它的平方。该程序应该如下工作:
Enter a whole number: 6
Square of 6 is 36
解决方案如程序 P3.5 所示。
Program P3.5
//request a whole number; print its square
#include <stdio.h>
int main() {
int num, numSq;
printf("Enter a whole number: ");
scanf("%d", &num);
numSq = num * num;
printf("\nSquare of %d is %d\n", num, numSq);
}
关于程序 P3.5 的注意事项:
-
为了使输出可读,请注意
f后面的空格和is周围的空格。如果省略这些空格,示例输出将是Square of6is36 -
变量
numSq并不是真正必要的。它可以完全省略,相同的输出打印为printf("\nSquare of %d is %d\n", num, num * num); -
程序假定将输入一个整数;如果输入的不是整数,程序会崩溃或给出不正确的结果。为了迎合带点的数字,声明
num(和numSq,如果使用的话)为double。
问题 3 -银行业务
给定银行中客户的以下数据:姓名、账号、平均余额和当月交易次数。需要计算所得利息和服务费。
利息计算如下:
interest = 6% of average balance
服务费是这样计算的:
service charge = 50 cents per transaction
编写一个程序,为客户读取数据,计算利息和服务费,并打印客户的姓名、平均余额、利息和服务费。
以下是该程序的运行示例:
Name? Alice Wonder
Account number? 4901119250056048
Average balance? 2500
Number of transactions? 13
Name: Alice Wonder
Average balance: $2500.00
Interest: $150.00
Service charge: $6.50
解决方案如程序 P3.6 所示。
Program P3.6
//calculate interest and service charge for bank customer
#include <stdio.h>
int main() {
char customer[30], acctNum[30];
double avgBalance, interest, service;
int numTrans;
printf("Name? ");
gets(customer);
printf("Account number? ");
gets(acctNum);
printf("Average balance? ");
scanf("%lf", &avgBalance);
printf("Number of transactions? ");
scanf("%d", &numTrans);
interest = avgBalance * 0.06;
service = numTrans * 0.50;
printf("\nName: %s\n", customer);
printf("Average balance: $%3.2f\n", avgBalance);
printf("Interest: $%3.2f\n", interest);
printf("Service charge: $%3.2f\n", service);
}
这个问题比我们到目前为止看到的那些问题更复杂。它涉及更多的数据和更多的处理。但是,如果我们采取小步骤解决问题,我们可以简化它的解决方案。
首先,让我们概述一下解决这个问题的算法。这可以是:
prompt for and read each item of data
calculate interest earned
calculate service charge
print required output
这里的逻辑相当简单,稍加思考就能让我们相信这些是解决问题所需的步骤。
接下来,我们必须为需要存储的数据项选择变量。
- 对于客户的名字,我们需要一个字符串变量—我们称之为
customer。 - 我们可能会尝试使用整数变量作为账号,但这不是一个好主意,原因有二:账号可能包含字母(如
CD55887700);或者它可能是一个很长的整数,太大而不适合一个int变量。出于这些原因,我们使用一个叫做acctNum的字符串变量。 - 平均余额可能包含一个小数点,必须存储在一个
double变量中;我们称之为avgBalance。 - 交易的数量是一个整数,所以我们使用一个变量
int,numTrans。
接下来,我们需要变量来存储利息和服务费。由于这些可能包含小数点,我们必须使用double变量——我们称它们为interest和service。
鉴于我们到目前为止所介绍的内容,提示和读取数据相当简单。我们只需要强调,当输入数字数据时,它必须是一个数字常量。例如,我们不能将平均余额输入为$2500或2,500。我们必须以2500或2500.0或2500.00的名字报名。
利息和服务费的计算是最大的挑战。我们必须以计算机能够理解和执行的形式来指定计算。
例如,我们不能书写
interest = 6% of avgBalance;
以至
interest = 6% * avgBalance;
或者
service = 50 cents per transaction;
我们必须使用适当的常量、变量和运算符,将每个右边表示为适当的算术表达式。因此,
“平均余额的 6%”必须表示为
avgBalance*0.06
或者
0.06*avgBalance
“每笔交易 50 美分”必须表示为
0.50*numTrans
或者
numTrans*0.5
或者类似的东西
numTrans/2.0
打印输出相当简单。例如,即使我们在输入平均余额的数据时不能使用$,我们也可以在打印它的值时在它前面打印一个美元符号。我们需要做的就是将$作为字符串的一部分打印出来。如何做到这一点显示在程序中。类似地,我们打印标有美元符号的利息和服务费。
我们使用规格%3.2f来打印avgBalance。我们有意使用一个小的字段宽度3,这样avgBalance只使用打印其值所需的精确的打印列数进行打印。这确保了它的值就印在美元符号的旁边。类似的话也适用于interest和service。
3.5.4 问题 4-门票
在足球比赛中,门票分为三类出售:保留票、看台票和场地票。对于这些类别中的每一个,您都会得到票价和售出的门票数量。编写一个程序来提示这些值,并打印从每一类门票中收取的金额。同时打印售出的门票总数和收取的总金额。
我们将编写运行时如下操作的程序:
Reserved price and tickets sold? 100 500
Stands price and tickets sold? 75 4000
Grounds price and tickets sold? 40 8000
Reserved sales: $50000.00
Stands sales: $300000.00
Grounds sales: $320000.00
12500 tickets were sold
Total money collected: $670000.00
如图所示,我们提示并一次读取两个值,价格和售出的门票数量。
对于每个类别,销售额的计算方法是将票价乘以售出的门票数量。
售出的门票总数是通过将每个类别售出的门票数相加计算出来的。
通过将每个类别的销售额相加来计算收集的总金额。
用于解决该问题的算法的概要如下:
prompt for and read reserved price and tickets sold
calculate reserved sales
prompt for and read stands price and tickets sold
calculate stands sales
prompt for and read grounds price and tickets sold
calculate grounds sales
calculate total tickets
calculate total sales
print required output
一个解决方案如程序 P3.7 所示,价格可以输入整数或double常量;票的数量必须以整数常量的形式输入。
Program P3.7
//calculate ticket sales for football match
#include <stdio.h>
int main() {
double rPrice, sPrice, gPrice;
double rSales, sSales, gSales, tSales;
int rTickets, sTickets, gTickets, tTickets;
printf("Reserved price and tickets sold? ");
scanf("%lf %d", &rPrice, &rTickets);
rSales = rPrice * rTickets;
printf("Stands price and tickets sold? ");
scanf("%lf %d", &sPrice, &sTickets);
sSales = sPrice * sTickets;
printf("Grounds price and tickets sold? ");
scanf("%lf %d", &gPrice, &gTickets);
gSales = gPrice * gTickets;
tTickets = rTickets + sTickets + gTickets;
tSales = rSales + sSales + gSales;
printf("\nReserved sales: $%3.2f\n", rSales);
printf("Stands sales: $%3.2f\n", sSales);
printf("Grounds sales: $%3.2f\n", gSales);
printf("\n%d tickets were sold\n", tTickets);
printf("Total money collected: $%3.2f\n", tSales);
}
Exercises 3For each of the following, give examples of data that will be read correctly and examples of data that will cause the program to crash. Assume the declaration int i, j; double x, y;); (a) scanf("%d %d", &i, &j); (b) scanf("%lf %lf", &x, &y); (c) scanf("%d %lf %d", &i, &x, &j); For 1(c), state what will be stored in i, x, and j for each of the following sets of data: (a) 14 11 52 (b) -7 2.3 52 (c) 0 6.1 7.0 (d) 1.0 8 -1 Write a program that requests a user to enter a weight in kilograms, and converts it to pounds. (1 kilogram = 2.2 pounds.) Write a program that requests a length in centimeters and converts it to inches. (1 inch = 2.54 cm.) Assuming that 12 and 5 are entered as data, identify the logic error in the following statements (a, b, c, d, and e are int): scanf("%d %d", &a, &b); c = (a - b) * 2; d = e + a; e = a / (b + 1); printf("%d %d %d\n", c, d, e); When the error is corrected, what is printed? What is printed by the following (a, b, and c are int)? a = 13; b = a + 12; printf("%d %d\n", a, b); c = a + b; a = a + 11; printf("%d %d %d\n", a, b, c); Write a program that requests a price and a discount percent. The program prints the original price, the discount amount, and the amount the customer must pay. Same as 7, but assume that 15% tax must be added to the amount the customer must pay. Write a program to calculate electricity charges for a customer. The program requests a name, previous meter reading, and current meter reading. The difference in the two readings gives the number of units of electricity used. The customer pays a fixed charge of $25 plus 20 cents for each unit used. Print all the data, the number of units used, and the amount the customer must pay, appropriately labeled. Modify 9 so that the program requests the fixed charge and the rate per unit. Write a program to request a student’s name and marks in four subjects. The program must print the name, total marks, and average mark, appropriately labeled. Write a program that requests a person’s gross salary, deductions allowed and rate of tax (e.g., 25, meaning 25%), and calculates his net pay as follows: Tax is calculated by applying the rate of tax to the gross salary minus the deductions. Net pay is calculated by gross salary minus tax. Print the gross salary, tax deducted, and net pay, appropriately labeled. Also print the percentage of the gross salary that was paid in tax. Make up appropriate sets of data for testing the program. Write a program that, when run, works as follows (underlined items are typed by the user): Hi, what’s your name? Alice Welcome to our show, Alice How old are you? 27 Hmm, you don’t look a day over 22 Tell me, Alice, where do you live? Princes Town Oh, I’ve heard Princes Town is a lovely place A ball is thrown vertically upwards with an initial speed of U meters per second. Its height H after time T seconds is given by H = UT - 4.9T 2 Write a program that requests U and T and prints the height of the ball after T seconds. Write a program to calculate the cost of carpeting a rectangular room in a house. The program must do the following:
- 请求房间的长度和宽度(假设以米为单位)。
- 询问每平方米地毯的价格。
- 计算房间的面积。
- 计算房间地毯的价格。
- 打印面积和成本,并适当标记。
Write a program which, given a length in inches, converts it to yards, feet, and inches. (1 yard = 3 feet, 1 foot = 12 inches). For example, if the length is 100 inches, the program should print 2 yd 2 ft 4 in.