C++ 编程学习手册(三)
六、字符
在本章中,我们将解释以下内容:
- 字符集的一些重要特性
- 如何使用字符常量和值
- 如何在 C 中声明字符变量
- 如何在算术表达式中使用字符
- 如何阅读、操作和打印字符
- 如何使用
\n测试行尾 - 如何使用
EOF测试文件结束 - 如何比较人物
- 如何从文件中读取字符
- 如何将数字从字符转换成整数
6.1 字符集
我们大多数人都熟悉计算机或打字机键盘(称为标准英语键盘)。在上面,我们可以键入字母表中的字母(大写和小写)、数字和其他“特殊”字符,如+、=、、&和%—这些就是所谓的可打印字符。
在计算机上,每个字符被赋予一个唯一的整数值,称为它的代码。根据所使用的字符集,不同计算机的代码可能会有所不同。例如,A的代码在一台计算机上可能是33,但在另一台计算机上可能是65。
在计算机内部,这个整数代码被存储为一个比特序列;例如,33的 6 位代码是100001,而65的 7 位代码是1000001。
现在,大多数计算机使用 ASCII(美国信息交换标准码)字符集来表示字符。这是一个 7 位字符标准,包括标准键盘上的字母、数字和特殊字符。它还包括控制字符,如退格、制表符、换行符、换页符和回车符。
ASCII 码从0到127(可使用 7 位存储的数字范围)。ASCII 字符集如附录 b 所示。值得注意的有趣特性如下:
- 数字
0到9占用代码48到57。 - 大写字母
A到Z占用代码65到90。 - 小写字母
a到z占用代码97到122。
然而,请注意,尽管 ASCII 集合是使用 7 位代码定义的,但它在大多数计算机上是以 8 位字节存储的——在 7 位代码的前面添加了一个0。比如A的 7 位 ASCII 码是1000001;在计算机上,存储为01000001,占用一个字节。
在本书中,我们将尽可能在编写程序时不对底层字符集做任何假设。在不可避免的情况下,我们将假设使用 ASCII 字符集。例如,我们可能需要假设大写字母被分配了连续的代码,对于小写字母也是如此。对于另一个字符集来说,这不一定是真的。即便如此,我们也不会依赖于代码的具体值,只知道它们是连续的。
6.2 字符常量和值
字符常量是用单引号括起来的单个字符,如' A ','+'和' 5 '。有些字符不能这样表示,因为我们不能键入它们。其他的在 C 中起着特殊的作用(比如',)。对于这些,我们用单引号括起转义序列。下表显示了一些示例:
| 茶 | 描述 | 密码 | | --- | --- | --- | | `'\n'` | `new line` | `10` | | `'\f'` | `form feed` | `12` | | `'\t'` | `tab` | `9` | | `'\''` | `single quote` | `39` | | `'\\'` | `backslash` | `92` |字符常量'\0'在 C 中比较特殊;就是代码为0的字符,通常称为空字符。它的一个特殊用途是指示内存中一个字符串的结束(见第八章)。
字符常量的字符值是所表示的字符,没有单引号。因此,'T'的角色值是T,'\\'的角色值是\。
字符常量有一个与之关联的整数值,即所代表字符的数字代码。因此,'T'的整数值是84,因为T的 ASCII 码是84。因为\的 ASCII 码是92,所以'\\'的整数值是92。并且'\n'的整数值是10,因为换行符的 ASCII 码是10。
我们可以使用printf中的规范%c打印字符值,使用%d打印整数值。例如,语句
printf("Character: %c, Integer: %d\n", 'T', 'T');
将打印
Character: T, Integer: 84
6.3 类型char
在 C 中,我们使用关键字char来声明一个变量,我们希望在其中存储一个字符。例如,语句
char ch;
将ch声明为字符变量。例如,我们可以给ch分配一个字符常量,如下所示:
ch = 'R'; //assign the letter R to ch
ch = '\n'; //assign the newline character, code 10, to ch
我们可以使用printf中的%c打印字符变量的字符值。我们可以使用%d打印一个字符变量的整数值。例如,
ch = 'T';
printf("Mr. %c\n", ch);
printf("Mr. %d\n", ch);
将打印
Mr. T
Mr. 84
6.4 算术表达式中的字符
c 允许我们在算术表达式中直接使用char类型的变量和常量。当我们这样做时,它使用字符的整数值。例如,语句
int n = 'A' + 3;
将68分配给n,因为'A'的代码是65。
类似地,我们可以将一个整数值赋给一个char变量。例如,
char ch = 68;
在这种情况下,“代码为 68 的字符”被分配给ch;这个人物就是'D'。
对于更有用的示例,请考虑以下内容:
int d = '5' - '0';
由于'5'的代码是53而'0'的代码是48,所以整数5被分配给d。
请注意,字符形式的数字的代码与数字的值不同;例如,字符'5'的代码是53,但是数字5的值是5。有时我们知道一个字符变量包含一个数字,我们想得到这个数字的(整数)值。
上面的语句显示了我们如何获得该数字的值——我们只需从该数字的代码中减去'0'的代码。数字的实际代码是什么并不重要;重要的是0到9的代码是连续的。(练习:假设数字有一组不同的代码值,自己检查一下。)
一般来说,如果ch包含一个数字字符('0'到'9',我们可以用语句获得该数字的整数值
d = ch - '0';
6.4.1 大写字母到小写字母的转换
假设ch包含一个大写字母,我们想把它转换成等价的小写字母。例如,假设ch包含'H',我们想把它改成'h'。首先我们观察到'A'到'Z'的 ASCII 码范围从65到90,而'a'到'z'的码范围从97到122。我们进一步观察到,字母的两种大小写的代码之间的差异总是32;例如,
'r' - 'R' = 114 – 82 = 32
因此,我们可以通过在大写代码中添加32来将字母从大写转换成小写。这可以通过
ch = ch + 32;
如果ch包含'H'(代码72),上面的语句在72上加32给出104;将“代码为 104 的字符”赋给ch,即'h'。我们已经将ch的值从'H'更改为'h'。相反,要将一个字母从小写转换成大写,我们从小写代码中减去32。
顺便说一下,我们真的不需要知道字母的代码。我们所需要的是大写和小写代码之间的区别。我们可以让 C 用'a' - 'A'告诉我们有什么区别,像这样:
ch = ch + 'a' - 'A';
不管字母的实际代码是什么,这都是有效的。当然,它假设ch包含一个大写字母,并且所有字母的大写和小写代码之间的差异是相同的。
6.5 阅读和打印字符
许多程序都围绕着一次读写一个字符的思想,开发编写这种程序的技能是编程的一个非常重要的方面。我们可以使用scanf将标准输入(键盘)中的单个字符读入一个char变量(比如说ch)中:
scanf("%c", &ch);
数据中的下一个字符存储在ch中。非常重要的是,要注意读一个数字和读一个字符之间的巨大差异。当读取一个数字时,scanf将跳过任意数量的空格,直到找到该数字。当读取一个字符时,下一个字符(不管它是什么,即使它是一个空格)存储在变量中。
虽然我们可以使用scanf,但是读取字符非常重要,所以 C 提供了一个特殊的函数getchar来从标准输入中读取字符。(严格地说,getchar就是所谓的宏,但这种区别对我们的目的来说并不重要。)大部分情况下,我们可以认为getchar返回数据中的下一个字符。然而,它实际上返回下一个字符的数字代码。因此,它通常被赋给一个int变量,如:
int c = getchar(); // the brackets are required
但它也可以赋给一个char变量,如:
char ch = getchar(); // the brackets are required
准确地说,getchar返回数据中的下一个字节——实际上,这是下一个字符。如果我们在没有更多的数据时调用getchar,它将返回-1。
更准确地说,它返回由在stdio.h中定义的符号常量EOF(全大写)指定的值。这个值通常是-1,尽管并不总是如此。实际值取决于系统,但EOF将始终表示运行程序的系统返回的值。当然,我们总是可以通过打印EOF来找出返回值,因此:
printf("Value of EOF is %d \n", EOF);
例如,考虑以下语句:
char ch = getchar();
假设用户键入的数据如下:
Hello
当执行ch = getchar()时,第一个字符H被读取并存储在ch中。然后我们可以以任何我们喜欢的方式使用ch。假设我们只想打印读取的第一个字符。我们可以使用:
printf("%c \n", ch);
这会打印出来
H
单独在一条线上。当然,我们可以将输出标记为下面的语句:
printf("The first character is %c \n", ch);
这会打印出来
The first character is H
最后,我们甚至不需要ch。如果我们只想打印数据中的第一个字符,我们可以使用:
printf("The first character is %c \n", getchar());
如果我们想打印第一个字符的数字代码,我们可以通过使用规范%d而不是%c来实现。这些想法都包含在程序 P6.1 中。
Program P6.1
//read the first character in the data, print it,
//its code and the value of EOF
#include <stdio.h>
int main() {
printf("Type some data and press 'Enter' \n");
char ch = getchar();
printf("\nThe first character is %c \n", ch);
printf("Its code is %d \n", ch);
printf("Value of EOF is %d \n", EOF);
}
以下是运行示例:
Type some data and press 'Enter'
Hello
The first character is H
Its code is 72
Value of EOF is -1
提醒一句:我们可能会想写以下内容:
printf("The first character is %c \n", getchar());
printf("Its code is %d \n", getchar()); // wrong
但是如果我们这样做了,并且假设将Hello作为输入,那么这些语句将会打印出来:
The first character is H
Its code is 101
为什么呢?在第一个printf,getchar返回H,它被打印出来。第二个printf,getchar返回下一个字符,是e;打印的是e的代码(101)。
在程序 P6.1 中,我们可以使用一个int变量(比如说n)来代替ch,程序将以相同的方式工作。如果使用%c打印一个int变量,变量的最后(最右边)8 位被解释为一个字符并打印该字符。例如,H的代码是72,也就是二进制的01001000,使用 8 位。假设n是 16 位int,当H被读取时,分配给n的值将为
00000000 01001000
如果n现在印有%c,最后 8 位将被解释为一个字符,当然是H。
类似地,如果将一个int值n赋给一个char变量(ch),那么n的最后 8 位将被赋给ch。
如上所述,getchar返回所读取字符的整数值。当用户按下键盘上的“Enter”或“return”时,它返回什么?它返回换行符\n,其代码是10。使用程序 P6.1 可以看到这一点。当程序等待您键入数据时,如果您只按“Enter”或“Return”键,输出的第一行将如下所示(注意空行):
The first character is
Its code is 10
为什么是空行?由于ch包含\n,该语句
printf("\nThe first character is %c \n", ch);
实际上与下面的相同(用ch的值代替%c)
printf("\nThe first character is \n \n");
is后的\n结束第一行,最后的\n结束第二行,有效地打印一个空行。然而,请注意\n的代码打印正确。
在程序 P6.1 中,我们只读取第一个字符。如果我们想阅读和打印前三个字符,我们可以用程序 P6.2 来做。
Program P6.2
//read and print the first 3 characters in the data
#include <stdio.h>
int main() {
printf("Type some data and press 'Enter' \n");
for (int h = 1; h <= 3; h++) {
char ch = getchar();
printf("Character %d is %c \n", h, ch);
}
}
以下是该程序的运行示例:
Type some data and press 'Enter'
Hi, how are you?
Character 1 is H
Character 2 is i
Character 3 is,
如果我们想读取并打印前 20 个字符,我们所要做的就是将for语句中的3改为20。
假设数据行的第一部分包含任意数量的空白,包括没有空白。我们如何找到并打印第一个非空白字符?因为我们不知道要读多少个空格,所以我们不能说“读 7 个空格,然后读下一个字符”
更有可能的是,我们需要说类似“只要读到的字符是空白,就继续读下去。”只要某些“条件”为真,我们就有做某事(阅读角色)的概念;这里的条件是字符是否为空。这可以更简明地表达如下:
read a character
while the character read is a blank
read the next character
程序 P6.3 显示了如何读取数据并打印第一个非空白字符。(这段代码将在本节的后面写得更简洁。)
Program P6.3
//read and print the first non-blank character in the data
#include <stdio.h>
int main() {
printf("Type some data and press 'Enter' \n");
char ch = getchar(); // get the first character
while (ch == ' ') // as long as ch is a blank
ch = getchar(); // get another character
printf("The first non-blank is %c \n", ch);
}
以下是该程序的运行示例(表示空白):
Type some data and press 'Enter'
你好
The first non-blank is H
程序将定位第一个非空白字符,不管它前面有多少个空格。
作为对while语句如何工作的提醒,考虑程序 P6.3 中带有不同注释的以下代码部分:
char ch = getchar(); //executed once; gives ch a value
//to be tested in the while condition
while (ch == ' ')
ch = getchar(); //executed as long as ch is ' '
假设输入的数据是(◊表示空格):
◇♂Hello
代码将按如下方式执行:
The first character is read and stored in ch; it is a blank. The while condition is tested; it is true. The while body ch = getchar(); is executed and the second character is read and stored in ch; it is a blank. The while condition is tested; it is true. The while body ch = getchar(); is executed and the third character is read and stored in ch; it is a blank. The while condition is tested; it is true. The while body ch = getchar(); is executed and the fourth character is read and stored in ch; it is H. The while condition is tested; it is false. Control goes to the printf, which prints. The first non-blank is H
如果H是数据中的第一个字符呢?代码将按如下方式执行:
The first character is read and stored in ch; it is H. The while condition is tested; it is false. Control goes to the printf, which prints. The first non-blank is H
还能用!如果第一次测试时while条件为false,则主体根本不执行。
作为另一个例子,假设我们想打印所有字符,但不包括第一个空格。为此,我们可以使用程序 P6.4。
Program P6.4
//print all characters before the first blank in the data
#include <stdio.h>
int main() {
printf("Type some data and press 'Enter' \n");
char ch = getchar(); // get the first character
while (ch != ' ') { // as long as ch is NOT a blank
printf("%c \n", ch);// print it
ch = getchar(); // and get another character
}
}
以下是 P6.4 的运行示例:
Type some data and press 'Enter'
Way to go
W
a
y
while的主体由两条语句组成。这些由{和}括起来,以满足 C 的规则,即while主体必须是单个语句或块。在这里,只要读取的字符不是空白,就执行主体——我们使用!=(不等于)来编写条件。
如果字符不是空白,则打印该字符并读取下一个字符。如果不是空白,则打印出来并读取下一个字符。如果不是空白,则打印出来并读取下一个字符。依此类推,直到一个空白字符被读取,使while条件false,导致从循环中退出。
如果我们不告诉你一些 c 语言的表达能力,那我们就大错特错了。例如,在 P6.3 程序中,我们可以读取字符并在while条件下测试它。我们可以重写以下三行:
ch = getchar(); // get the first character
while (ch == ' ') // as long as ch is a blank
ch = getchar(); // get another character
作为一条线
while ((ch = getchar()) == ' '); // get a character and test it
ch = getchar()为赋值表达式,其值为赋给ch的字符,即读取的字符。然后测试这个值,看它是否为空。因为==比=具有更高的优先级,所以ch = getchar()周围的括号是必需的。如果没有它们,条件将被解释为ch = (getchar() == ' ')。这将把一个条件的值(在 C 语言中,是代表false的0或代表true的1)赋给变量ch;这不是我们想要的。
既然我们已经把身体中的语句移入了条件,身体就是空的;这在 c 语言中是允许的。条件现在会被重复执行,直到它变成false。
再举一个例子,在程序 6.4 中,考虑下面的代码:
char ch = getchar(); // get the first character
while (ch != ' ') { // as long as ch is NOT a blank
printf("%c \n", ch) // print it
ch = getchar(); // and get another character
}
这可以重新编码如下(假设ch在循环之前声明):
while ((ch = getchar()) != ' ') // get a character
printf("%c \n", ch); // print it if non-blank; repeat
既然正文只包含一条语句,就不再需要大括号了。五行被减成了两行!
6.6 计算字符数
程序 P6.3 打印第一个非空白字符。假设我们想计算在第一个非空白之前有多少个空白。我们可以使用一个整数变量numBlanks来保存计数。程序 P6.5 是计算前导空白的修改程序。
Program P6.5
//find and print the first non-blank character in the data;
// count the number of blanks before the first non-blank
#include <stdio.h>
int main() {
char ch;
int numBlanks = 0;
printf("Type some data and press 'Enter' \n");
while ((ch = getchar()) == ' ') // repeat as long as ch is blank
numBlanks++; // add 1 to numBlanks
printf("The number of leading blanks is %d \n", numBlanks);
printf("The first non-blank is %c \n", ch);
}
以下是该程序的运行示例(表示空格):
Type some data and press 'Enter'
你好
The number of leading blanks is 4
The first non-blank is H
对程序 P6.5 的意见:
numBlanks在while循环之前被初始化为0。numBlanks在循环内由1递增,因此每次执行循环体时numBlanks都会递增。由于循环体是在ch包含空白时执行的,所以numBlanks的值总是到目前为止读取的空白数。- 当我们退出
while循环时,numBlanks中的值将是读取的空白的数量。然后打印该值。 - 注意,如果数据中的第一个字符不为空,
while条件将立即变为false,控制将直接转到第一个printf语句,其中numBlanks的值为0。该程序将正确打印:
The number of leading blanks is 0
6.6.1 计算一行中的字符数
假设我们想计算一行输入中的字符数。现在,我们必须阅读字符,直到行尾。我们的程序如何测试行尾?回想一下,当用户按下“Enter”或“Return”键时,换行符\n由getchar返回。下面的while条件读取一个字符并测试\n。
while ((ch = getchar()) != '\n')
程序 P6.6 读取一行输入并计算其中的字符数,不计算“行尾”字符。
Program P6.6
//count the number of characters in the input line
#include <stdio.h>
int main() {
char ch;
int numChars = 0;
printf("Type some data and press 'Enter' \n");
while ((ch = getchar()) != '\n') // repeat as long as ch is not \n
numChars++; // add 1 to numChars
printf("The number of characters is %d \n", numChars);
}
这个程序和程序 P6.5 的主要区别在于,这个程序读取字符直到行尾,而不是直到第一个非空白。运行示例如下:
Type some data and press 'Enter'
One moment in time
The number of characters is 18
6.7 计算一行数据中的空白
假设我们要计算一行数据中的所有空格。我们仍然必须读取字符,直到遇到行尾。但现在,每读一个字,都要检查是否是空格。如果是,则计数递增。我们需要两个计数器——一个计数一行中的字符数,另一个计数空格数。该逻辑可以表示为:
set number of characters and number of blanks to 0
while we are not at the end-of-line
read a character
add 1 to number of characters
if character is a blank then add 1 to number of blanks
endwhile
该逻辑的实现如程序 P6.7 所示。
Program P6.7
//count the number of characters and blanks in the input line
#include <stdio.h>
int main() {
char ch;
int numChars = 0;
int numBlanks = 0;
printf("Type some data and press 'Enter' \n");
while ((ch = getchar()) != '\n') { // repeat as long as ch is not \n
numChars++; // add 1 to numChars
if (ch == ' ') numBlanks++; // add 1 if ch is blank
}
printf("\nThe number of characters is %d \n", numChars);
printf("The number of blanks is %d \n", numBlanks);
}
下面是一个运行示例:
Type some data and press 'Enter'
One moment in time
The number of characters is 18
The number of blanks is 3
if语句测试条件ch == ' ';如果是true(即ch包含空白),numBlanks递增1。如果是false,则numBlanks不递增;控制通常会转到循环中的下一条语句,但是没有(if是最后一条语句)。因此,控制返回到while循环的顶部,在那里读取另一个字符并对\n进行测试。
6.8 比较字符
可以使用关系运算符==,!=,和> =。我们已经使用ch == ' '和ch != ' '将char变量ch与空白变量进行了比较。
现在让我们写一个程序来读取一行数据并打印出“最大”的字符,即代码最高的字符。例如,如果一行是由英语单词组成的,那么字母表中最后出现的字母将被打印出来。(回想一下,小写字母比大写字母具有更高的代码,例如,'g'大于'T'。)
“寻找最大字符”包括以下步骤:
- 选择一个变量来保存最大值;我们选择
bigChar。 - 将
bigChar初始化为一个非常小的值。选择的值应该是这样的,无论读取什么字符,它的值都大于这个初始值。对于字符,我们通常使用'\0'——空字符,即代码为0的“字符”。 - 当每个字符(
ch,比方说)被读取时,它与bigChar进行比较;如果ch大于bigChar,那么我们有一个‘更大’的字符,并且bigChar被设置为这个新字符。 - 当所有字符都被读取和检查后,
bigChar将包含最大的一个。
这些想法在程序 P6.8 中表达。
Program P6.8
//read a line of data and find the 'largest' character
#include <stdio.h>
int main() {
char ch, bigChar = '\0';
printf("Type some data and press 'Enter' \n");
while ((ch = getchar()) != '\n')
if (ch > bigChar) bigChar = ch; //is this character bigger?
printf("\nThe largest character is %c \n", bigChar);
}
下面是一个运行示例;因为它的代码是所有输入字符中最高的,所以被打印出来。
Type some data and press 'Enter'
Where The Mind Is Without Fear
The largest character is u
6.9 从文件中读取字符
在我们到目前为止的例子中,我们已经阅读了在键盘上输入的字符。如果我们想从一个文件中读取字符(input.txt),我们必须声明一个文件指针(in),并使用
FILE * in = fopen("input.txt", "r");
一旦完成,我们可以用下面的语句将文件中的下一个字符读入一个字符变量(ch:
fscanf(in, "%c", &ch);
但是,C 提供了更方便的函数getc(获取字符)来从文件中读取字符。它的用法如下:
ch = getc(in);
getc接受一个参数,文件指针(不是文件名)。它读取并返回文件中的下一个字符。如果没有更多的字符需要读取,getc 返回EOF。因此,getc的工作方式与getchar完全一样,除了getchar从键盘读取,而getc从文件读取。
举例来说,让我们编写一个程序,从文件input.txt中读取一行数据,并将其打印在屏幕上。这显示为程序 P6.9。
Program P6.9
#include <stdio.h>
int main() {
char ch;
FILE *in = fopen("input.txt", "r");
while ((ch = getc(in)) != '\n')
putchar(ch);
putchar('\n');
fclose(in);
}
这个程序使用标准函数putchar将单个字符写入标准输出。(像getchar,putchar是一个宏,但是这种区别对于我们的目的并不重要。)它将一个字符值作为唯一的参数,并将该字符写入输出中的下一个位置。然而,如果该字符是控制字符,则产生该字符的效果。例如,
putchar('\n');
将结束当前输出行——与按下“Enter”或“Return”的效果相同。
程序从文件中一次读取一个字符,并使用putchar将其打印在屏幕上。它一直这样做,直到\n被读取,表示整行都已被读取。在退出while循环时,它使用putchar('\n')终止屏幕上的行。
不过,要小心。该程序假设数据行由一个行尾字符\n(当您按下“Enter”或“Return”时生成)终止。但是,如果行没有被\n终止,程序就会‘挂起’——它会陷入一个无法摆脱的循环(我们说它会陷入无限循环)。为什么呢?
因为由于没有要读取的\n,所以while条件((ch = getc(in)) != '\n')永远不会变成false(当ch为'\n'时会发生这种情况)。但是,如前所述,当我们到达文件末尾时,由getchar返回的值,现在也由getc返回,是在stdio.h中定义的符号常量EOF d。了解了这一点,我们可以通过在while条件下测试\n和EOF来轻松解决我们的问题,因此:
while ((ch = getc(in)) != '\n' && ch != EOF)
即使\n不存在,当到达文件末尾时getc(in)将返回EOF,并且条件ch != EOF将为false,导致从循环中退出。
6.10 将字符写入文件
假设我们想把字符写到一个文件中(output.txt)。像往常一样,我们必须声明一个文件指针(out),并使用
FILE * out = fopen("output.txt", "w");
如果ch是一个 char 变量,我们可以用
fprintf(out, "%c", ch);
c 也提供了函数putc(放置一个字符)来做同样的工作。要将ch的值写入与out相关的文件,我们必须写:
putc(ch, out);
注意文件指针是putc的第二个参数。
6.10.1 回声输入,数字线
让我们扩展上一页的示例,从文件中读取数据,并将相同的数据写回(回显数据)到屏幕上,从 1 开始对行进行编号。
程序将从文件中读取数据,并将其写入屏幕,因此:
1\. First line of data
2\. Second line of data
etc.
这个问题比我们到目前为止遇到的那些问题更难一点。当面对这样的问题时,最好是一次解决一点,解决问题的简单版本,然后逐步解决整个问题。
对于这个问题,我们可以先写一个程序,简单的回显输入,不需要对行进行编号。当我们把这个做好了,我们就可以开始给这些线编号了。
这个第一版本的算法概要如下:
read a character, ch
while ch is not the end-of-file character
print ch
read a character, ch
endwhile
这将保持数据文件的行结构,例如,当从文件中读取\n时,它会立即打印到屏幕上,强制当前行结束。
程序 P6.10 实现了上述算法,用于从文件中读取数据,并在屏幕上打印精确的副本。
Program P6.10
#include <stdio.h>
int main() {
char ch;
FILE *in = fopen("input.txt", "r");
while ((ch = getc(in)) != EOF)
putchar(ch);
fclose(in);
}
既然我们可以回显输入,我们只需要弄清楚如何打印行号。一种简单的方法基于以下大纲:
set lineNo to 1
print lineNo
read a character, ch
while ch is not the end-of-file character
print ch
if ch is \n
add 1 to lineNo
print lineNo
endif
read a character, ch
endwhile
我们只是在上面的算法中添加了处理行号的语句。我们可以很容易地将处理行号的代码添加到程序 P6.10 中,以得到程序 P6.11。注意,当我们打印行号时,我们不使用\n终止行,因为数据必须与行号写在同一行。
Program P6.11
//This program prints the data from a file numbering the lines
#include <stdio.h>
int main() {
char ch;
FILE *in = fopen("input.txt", "r");
int lineNo = 1;
printf("%2d. ", lineNo);
while ((ch = getc(in)) != EOF) {
putchar(ch);
if (ch == '\n') {
lineNo++;
printf("%2d. ", lineNo);
}
}
fclose(in);
}
假设输入文件包含以下内容:
There was a little girl
Who had a little curl
Right in the middle of her forehead
程序 P6.11 将打印以下内容:
1\. There was a little girl
2. Who had a little curl
3\. Right in the middle of her forehead
4.
差不多,但不完全正确!小问题是我们在末尾打印了一个额外的行号。要了解原因,请查看if语句。当第三条数据线的\n被读取时,1将被加到lineNo上,成为4,由下一条语句打印。如果输入文件是空的,这种额外行号的打印也成立,因为在这种情况下行号1将被打印,但是没有这样的行。
为了解决这个问题,我们必须延迟打印行号,直到我们确定该行至少有一个字符。我们将使用一个int变量writeLineNo,初始设置为1。如果我们要打印一个字符,而writeLineNo是1,则打印行号,并将writeLineNo设置为0。当writeLineNo为0时,所发生的只是将刚刚读出的字符打印出来。
当\n打印结束一行输出时,writeLineNo设置为1。如果结果是下一行有一个字符要打印,由于writeLineNo是1,行号将首先被打印。如果没有更多的字符要打印,就不再打印;特别是,不打印行号。
程序 P6.12 包含所有细节。运行时,它将对行进行编号,而不打印额外的行号。
Program P6.12
//This program prints the data from a file numbering the lines
#include <stdio.h>
int main() {
char ch;
FILE *in = fopen("input.txt", "r");
int lineNo = 0, writeLineNo = 1;
while ((ch = getc(in)) != EOF) {
if (writeLineNo) {
printf("%2d. ", ++lineNo);
writeLineNo = 0;
}
putchar(ch);
if (ch == '\n') writeLineNo = 1;
}
fclose(in);
}
我们将if条件写成如下:
if (writeLineNo)...
如果writeLineNo为1,则条件评估为1,因此为true;如果是0,则条件为false。我们也可以把条件写成
if (writeLineNo == 1)...
在声明中
printf("%d. ", ++lineNo);
表达式++lineNo表示lineNo在打印前先递增。相比之下,如果我们使用了lineNo++,那么lineNo将首先被打印,然后递增。
练习:修改程序 P6.12,将输出发送到文件linecopy.txt。
练习:编写一个程序,将文件input.txt的内容复制到文件copy.txt中。提示:你只需要对程序 P6.10 做一些小的改动。
6.11 将数字字符转换为整数
让我们考虑如何将一系列数字转换成整数。当我们输入数字 385 时,我们实际上是在输入三个独立的字符——3,8,5。在计算机内部,整数 385 和三个字符“3”“8”“5”完全不同。因此,当我们键入 385 并试图将其读入一个int变量时,计算机必须将这三个字符的序列转换为整数 385。
举例来说,字符“3”、“8”和“5”的 8 位 ASCII 码分别是 00110011、00111000 和 00110101。当输入到屏幕或文件中时,数字 385 表示为:
00110011 00111000 00110101
假设使用 16 位存储整数,则整数 385 由其等价的二进制表示
0000000110000001
请注意,字符表示与整数表示有很大不同。当我们要求scanf(或fscanf)读取我们输入的整数时,它必须将字符表示转换成整数表示。我们现在展示这是如何做到的。
基本步骤要求我们将数字字符转换成等价的整数值。例如,我们必须将字符“5”(用 00110101 表示)转换为整数 5(用 000000000000101 表示)。
假设数字0到9的代码是连续的(因为它们在 ASCII 和其他字符集中),这可以如下进行:
数字的整数值=数字字符的代码-字符'0'的代码
例如,在 ASCII 中,“5”的代码是 53,而“0”的代码是 48。从 53 中减去 48 得到字符“5”的整数值(5)。一旦我们可以转换单个数字,我们就可以使用以下算法从左到右读取数字,从而构建数字的值:
set num to 0
get a character, ch
while ch is a digit character
convert ch to the digit value, d = ch - '0'
set num to num*10 + d
get a character, ch
endwhile
num now contains the integer value
字符序列 385 被转换如下:
num = 0
get '3'; convert to 3
num = num*10 + 3 = 0*10 + 3; num is now 3
get '8'; convert to 8
num = num*10 + 8 = 3*10 + 8; num is now 38
get '5'; convert to 5
num = num*10 + 5 = 38*10 + 5; num is now 385
没有更多的数字,num的最终值是 385。
让我们用这个想法写一个程序,一个字符一个字符地读取数据,直到找到一个整数。它构造并打印整数。
程序将不得不读取字符,直到它找到一个数字,整数的第一个。找到第一个数字后,只要它一直得到一个数字,就必须通过读取字符来构造整数。例如,假设数据如下:
Number of items: 385, all in good condition
该程序将读取字符,直到它找到第一个数字,3。它将使用 3 构造整数,然后读取 8 和 5。当它读取逗号时,它知道整数已经结束。
这个大纲可以用伪代码表示如下:
read a character, ch
while ch is not a digit do
read a character, ch
endwhile
//at this point, ch contains a digit
while ch is a digit do
use ch to build the integer
read a character, ch
endwhile
print the integer
我们如何测试ch中的字符是否是数字?我们必须测试
ch >= '0' && ch <= '9'
如果这是真的,我们就知道这个角色在'0'和'9'之间,包含这两个值。反过来,为了测试ch是否不是一个数字,我们可以测试
ch < '0' || ch > '9'
把所有这些想法放在一起,我们就有了 P6.13 程序。
Program P6.13
#include <stdio.h>
int main() {
printf("Type data including a number and press \"Enter\"\n");
char ch = getchar();
// as long as the character is not a digit, keep reading
while (ch < '0' || ch > '9') ch = getchar() ;
// at this point, ch contains the first digit of the number
int num = 0;
while (ch >= '0' && ch <= '9') { // as long as we get a digit
num = num * 10 + ch - '0'; // update num
ch = getchar();
}
printf("Number is %d\n", num);
}
下面显示了一个运行示例:
Type data including a number and press "Enter"
hide the number``&``(%%)4719``&``*(``&
Number is 4719
这个程序会找到这个数字,不管它隐藏在这行的什么地方。
EXERCISES 6Give the range of ASCII codes for (a) the digits (b) the uppercase letters (c) the lowercase letters. How is the single quote represented as a character constant? What is the character value of a character constant? What is the numeric value of a character constant? How is the expression 5 + 'T' evaluated? What is its value? What value is assigned to n by n = 7 + 't'? What character is stored in ch by ch = 4 + 'n'? If ch = '8', what value is assigned to d by d = ch - '0'? If ch contains any uppercase letter, explain how to change ch to the equivalent lowercase letter. If ch contains any lowercase letter, explain how to change ch to the equivalent uppercase letter. Write a program to request a line of data and print the first digit on the line. Write a program to request a line of data and print the first letter on the line. Write a program to request a line of data and print the number of digits and letters on the line. Write a program to read a passage from a file and print how many times each vowel appears. Modify Program P6.13 so that it will find negative integers as well. Write a program that reads a file containing a C program and outputs the program to another file with all the // comments removed. Write a program to read the data, character by character, and store the next number (with or without a decimal point) in a double variable (dv, say). For example, given the following data your program should store 43.75 in dv. Mary works for $43.75 per hour In the programming language Pascal, comments can be enclosed by { and } or by (* and *). Write a program which reads a data file input.pas containing Pascal code and writes the code to a file output.pas, replacing each { with (* and each } with *). For example, the statements read(ch); {get the first character} while ch = ' ' do {as long as ch is a blank} read(ch); {get another character} writeln('The first non-blank is ', ch); should be converted to read(ch); (*get the first character*) while ch = ' ' do (*as long as ch is a blank*) read(ch); (*get another character*) writeln('The first non-blank is ', ch); You are given the same data as in 17, but now remove the comments altogether. Someone has typed a letter in a file letter.txt, but does not always start the word after a period with a capital letter. Write a program to copy the file to another file format.txt so that all words after a period now begin with a capital letter. Also ensure there is exactly one space after each period. For example, the text Things are fine. we can see you now. let us know when is a good time. bye for now. must be rewritten as Things are fine. We can see you now. Let us know when is a good time. Bye for now.
七、函数
在本章中,我们将解释以下内容:
- 为什么函数在编程中很重要
- 如何编写函数
- 当一个函数被调用时会发生什么
- 在程序中放置函数的地方
- 用几个例子说明与函数有关的一些重要概念
7.1 关于函数
到目前为止,我们所有的程序都由一个名为main的函数组成。然而,我们使用了预定义的 C 函数,如printf、scanf、strcpy和fopen。当我们运行一个程序时,它从main中的第一条语句开始执行,并在到达最后一条语句时结束。
正如我们所见,只用main就可以编写出相当有用的程序。然而,这种方法有许多限制。要解决的问题可能太复杂,无法用一个函数解决。我们可能需要把它分解成子问题,并尝试逐个解决。在一个函数中解决所有子问题是不切实际的。编写一个单独的函数来解决每个子问题可能更好。
此外,我们可能希望重用常见问题的解决方案。如果一个解决方案是一个更大问题的解决方案的一部分,那么重用它是很困难的。例如,如果我们需要几个地方的两个数字的最大公因数(HCF ),最好编写一个例程来计算两个给定数字的 HCF;每当我们需要找到两个数的 HCF 时,我们就调用这个例程。
一个编写良好的函数执行一些定义良好的任务;例如,在输出中跳过指定数量的行,或者按升序排列一些数字。然而,通常情况下,函数也会返回值;例如,计算一个人的工资并返回答案,或者玩一局游戏并返回该局的分数。返回值通常在调用函数时使用。
之前,我们使用了字符串函数strcmp,它返回一个值,告诉我们比较两个字符串的结果。我们使用了getchar和getc来返回输入中的下一个字符。
我们现在准备学习如何编写我们自己的函数(称为用户定义函数),我们将在本书的其余部分看到几个例子。
7.2 skipLines
我们已经看到,我们可以在 printf 语句中使用\n来打印一个空行。例如,语句
printf("%d\n\n%d\n", a, b);
将在一行打印a,跳过一行,在下一行打印b。我们通常可以通过在printf语句中写入适当数量的\n来跳过任意数量的行。
有时我们可能想跳过 3 行,有时 2 行,有时 5 行,等等。如果有一个我们可以用来跳过任意行数的语句就好了。例如,跳过 3 行,我们应该能够写
skipLines(3);
为了跳过 5 行,我们写
skipLines(5);
我们想要的是一个名为skipLines的函数,它接受一个整数参数(比如说n)并跳过n行。在 C 语言中,我们将该函数编写如下:
void skipLines(int n) {
for (int h = 1; h <= n; h++)
printf("\n");
}
注意函数的结构与main的结构相似。它由一个标题(第一行,除了{)和一个用大括号括起来的正文组成。单词void表示该函数不返回值,并且(int n)将n定义为一个整数参数。当函数被调用时,我们必须给它提供一个整数值来匹配参数n。
这是函数skipLines的定义。我们通过在main中编写如下语句时调用该函数来使用该函数:
skipLines(3);
(一个函数通常可以从任何其他函数调用,但是,为了集中我们的讨论,我们将假设它是从main调用的。)
我们说我们用参数调用函数。(在本书中,我们在提到函数的定义时使用术语“参数”,在调用函数时使用术语“自变量”。其他人互换使用这两个术语。)按如下方式执行“调用”:
- 自变量的值被确定。在这种情况下,它只是常量 3,但一般来说,它可以是一个表达式。
- 该值被复制到临时内存位置。该位置被传递给函数,并标有参数名
n。实际上,参数变量n被设置为自变量的值。我们可以这样描述: - 执行函数的主体。在这种情况下,由于
n是 3,for 循环变成for (int h = 1; h <= 3; h++)并打印\n三次。 - 当函数完成时,包含自变量的位置被丢弃,控制返回到
main到skipLines(3)之后的语句。
注意,我们可以通过在调用时提供不同的参数来让skipLines打印不同数量的空行。
当一个参数的值被传递给一个函数时,我们说这个参数是“按值”传递的在 C # 中,参数是“按值”传递的
7.3 具有函数的程序
我们编写程序 P7.1 来展示skipLines如何融入一个完整的程序。
Program P7.1
#include <stdio.h>
int main() {
void skipLines(int);
printf("Sing a song of sixpence\n");
skipLines(2);
printf("A pocket full of rye\n");
} //end main
void skipLines(int n) {
for (int h = 1; h <= n; h++)
printf("\n");
} //end skipLines
当我们希望在main中使用一个变量时,我们必须在main中声明这个变量。同样,如果我们想在main中使用skipLines,我们必须使用所谓的函数原型告诉 C。函数原型是一种声明,很像函数头。在程序中,我们使用原型:
void skipLines(int);
原型通过声明函数的返回类型(void,在本例中)、函数的名称(skipLines)和任何参数的类型(在本例中为int)来描述函数。如果您愿意,可以在类型后面写一个变量,如下所示:
void skipLines(int a);
只有当编译器需要生成错误信息时,才会使用这个变量。在本书中,我们将只使用类型来编写原型。
注意,函数原型后面是分号,而函数头后面是左括号。
作为另一个例子,原型
int max(int, int);
说明max是一个接受两个整数参数并返回一个整数值的函数。
初学者常犯的一个错误是忘记写函数原型。然而,这不是一个大问题。如果你忘记了,编译器会提醒你。这就像忘记声明一个变量——编译器会告诉你的。你只要修好它然后继续前进。
在布局上,组成一个 C 程序的函数,包括main,可以任意顺序出现。然而,习惯上把main放在第一位,这样可以很容易地看到程序的整体逻辑。
我们强调该程序仅用于说明目的,因为使用该程序可以更容易地生成输出:
printf("Sing a song of sixpence\n\n\n");
printf("A pocket full of rye\n");
7.3.1 函数标题
在我们的例子中,我们使用了函数头
void skipLines(int n)
通常,函数头包括:
- 一个类型(如
void、int、double、char,指定函数返回值的类型。如果没有返回值,我们使用单词void。函数skipLines不返回值,所以我们使用void。 - 我们为函数起的名字,在例子中是
skipLines。 - 零个或多个参数,称为参数列表,用括号括起来;示例中使用了一个类型为
int的参数n。如果没有参数,括号必须仍然存在,就像在printHeading()中一样。
函数头后面是函数体的左括号。
参数的指定方式与变量的声明方式相同。事实上,它们确实是声明。以下是 void 函数头的所有有效示例:
void sample1(int m, int n) // 2 parameters
void sample2(double a, int n, char c) // 3 parameters
void sample3(double a, double b, int j, int k) // 4 parameters
每个参数必须单独声明,两个连续的声明用逗号分隔。例如,写是无效的
void sample1(int m, n) //not valid; must write (int m, int n)
很快,我们将看到返回值的函数的例子。
7.3.2 函数如何获取数据
函数就像一个迷你程序。在我们编写的程序中,我们已经说明了必须向程序提供什么数据,必须进行什么处理,以及输出(结果)应该是什么。我们在编写函数时也必须这样做。
当我们编写函数头时,我们使用参数列表来指定在调用函数时必须向函数提供什么数据。该列表指定了数据项的数量、每个数据项的类型以及它们必须被提供的顺序。
比如我们用一个整型参数n写skipLines;这意味着在调用skipLines时必须提供一个整数值。当调用skipLines时,所提供的参数成为n的特定值,并且假设n具有该值,则执行该函数。在调用skipLines(3)中,参数3是skipLines执行其工作所需的数据。
值得强调的是,main通过使用scanf以及其他函数来读取和存储变量中的数据。另一方面,函数在被调用时获取数据。参数列表中的变量被设置为调用中使用的相应参数的值。例如,当我们写标题时
void sample(int n, char c, double b)
我们说,当我们调用 sample 时,我们必须使用三个参数:第一个必须是一个int值,第二个是一个char值,第三个是一个double值。
假设num是int,ch是char,x是double,以下都是有效的采样调用:
sample(25, 'T', 7.5);
sample(num, 'A', x);
sample(num, ch, 7); //an int argument can match a double parameter
sample(num + 1, ch, x / 2.0);
如果调用函数时,实参的类型与对应的形参不同,C 会尝试将实参转换为所需的类型。例如,在呼叫中
sample(num, 72, 'E');
值72被转换为char,参数c被设置为'H'(因为H的代码是72);'E'(即69)的数值被转换为双精度值69.0,参数b被设置为69.0。
如果无法将参数转换为所需的类型,您将得到一个“类型不匹配”错误,如调用
sample(num, ch, "hi"); // error - cannot convert string to double
如果没有提供所需数量的参数,也会出现错误,如
sample(num, x); // error - must have 3 arguments
最大 7.4
有时我们需要找到两个值中较大的一个。如果a和b是两个数字,我们可以将变量max设置为两者中较大的一个,如下所示:
if (a > b) max = a;
else max = b;
如果两个数相等,max将被设置为b(将执行else部分)。当然,每当我们想得到两个值中较大的一个时,我们都可以写这个语句。但这会变得笨拙和尴尬。如果我们可以简单地编写类似这样的代码,将会更加方便,可读性更好
big = max(a, b);
以至
printf("The bigger is %d\n", max(a, b));
我们可以,如果我们把函数max写成如下:
int max(int a, int b) {
if (a > b) return a;
return b;
}
第一行(除了{)是函数头。它包括
- 单词
int,表示函数返回整数值。 - 我们为函数起的名字,在例子中是
max。 - 一个或多个参数,称为参数列表,用括号括起来;示例中使用了类型为
int的两个参数a和b。
函数的主体是从{到}的部分。这里,我们使用if s语句来确定a和b中较大的一个。如果a变大,函数“返回”a;如果没有,则返回b。
在 C 语言中,函数通过使用return语句“返回值”。它由单词return后跟要返回的值组成。该值返回到调用该函数的位置。
为了展示max如何融入整个程序以及如何使用它,我们编写了程序 P7.2,它读取整数对,并为每一对打印两个整数中较大的一个。当用户输入0 0时,程序结束。
Program P7.2
#include <stdio.h>
int main() {
int n1, n2;
int max(int, int);
printf("Enter two whole numbers: ");
scanf("%d %d", &n1, &n2);
while (n1 != 0 || n2 != 0) {
printf("The bigger is %d\n", max(n1, n2));
printf("Enter two whole numbers: ");
scanf("%d %d", &n1, &n2);
}
} //end main
int max(int a, int b) {
if (a > b) return a;
return b;
} //end max
以下是 P7.2 的运行示例:
Enter two whole numbers: 24 33
The bigger is 33
Enter two whole numbers: 10 -13
The bigger is 10
Enter two whole numbers: -5 -8
The bigger is -5
Enter two whole numbers: 0 7
The bigger is 7
Enter two whole numbers: 0 0
为了从main调用max,我们必须使用函数原型在main中“声明”max
int max(int, int);
这表示max接受两个整数参数并返回一个整数值。
在main中声明的变量n1和n2被认为属于main。
程序运行时,假设n1为24,n2为33。当从printf内用max(n1, n2)调用该函数时,会发生以下情况:
- 确定自变量
n1和n2的值。这些分别是24和33。 - 每个值都被复制到一个临时内存位置。这些位置被传递给函数
max,其中24用第一个参数a标记;并且33标有第二参数b。我们可以这样描述: - 执行
if语句;由于a(24)不大于b (33,控制转到语句return b;,并且33作为函数值返回。这个值被返回到调用max的地方(printf语句)。 - 就在函数返回之前,包含参数的位置被丢弃。由
max(在我们的例子中是33)返回的值替换了对max的调用。因此,max(n1, n2)被33和printf印刷所取代
The bigger is 33
当函数返回值时,在需要值的情况下使用该值是有意义的。上面,我们打印了值。我们也可以将值赋给一个变量,如
big = max(n1, n2);
或者将它用作表达式的一部分,如
ans = 2 * max(n1, n2);
没有意义的是在语句中单独使用它,因此:
max(n1, n2); //a useless statement
在这里,该值没有以任何方式被使用,因此该语句毫无意义。这就好像我们在一行上单独写了一个数字,就像这样
33; //a useless statement
当你调用一个返回值的函数时,请仔细考虑。在你的头脑中要非常清楚你打算用这个值做什么。
如上所述,max返回两个整数中较大的一个。如果我们想找到两个数字中较大的一个呢?我们可以用max吗?不幸的是,没有。如果我们用double值作为参数调用max,当一个double数字被赋给一个int参数时,我们可能会得到奇怪的结果。
另一方面,如果我们用double参数和double返回类型编写max,它将同时适用于double和int参数,因为我们可以将int值赋给double参数而不会丢失任何信息。
但是,请注意,如果我们用两个字符参数调用max,它将返回两个代码中较大的一个。例如,max('A', 'C')将返回C的代码67。
Exercise
编写函数来返回两个整数和两个浮点数中较小的一个。
7.5 打印日期
让我们编写一个程序,请求一个从 1 到 7 的数字,并打印出星期几的名称。例如,如果用户输入5,程序打印Thursday。程序 P7.3 使用一系列if...else语句来完成这项工作。
Program P7.3
#include <stdio.h>
int main() {
int d;
printf("Enter a day from 1 to 7: ");
scanf("%d", &d);
if (d == 1) printf("Sunday\n");
else if (d == 2) printf("Monday\n");
else if (d == 3) printf("Tuesday\n");
else if (d == 4) printf("Wednesday\n");
else if (d == 5) printf("Thursday\n");
else if (d == 6) printf("Friday\n");
else if (d == 7) printf("Saturday\n");
else printf("Invalid day\n");
}
现在假设打印一周中某一天的名称是一个更大的程序的一小部分。我们不想让这段代码变得杂乱无章,也不想在每次需要打印一天的名字时都重写这段代码。如果我们能写下printDay(n)并打印出合适的名字,那就更好了。如果我们写一个函数printDay来完成这项工作,我们就能做到这一点。
首先要问的是printDay完成工作需要哪些信息。答案是它需要当天的数字。这立刻暗示了printDay必须用当天的数字作为参数来写。除此之外,函数体将包含与程序 P7.3 基本相同的代码。此外,printDay不返回值,因此其“返回类型”为void。
void printDay(int d) {
if (d == 1) printf("Sunday\n");
else if (d == 2) printf("Monday\n");
else if (d == 3) printf("Tuesday\n");
else if (d == 4) printf("Wednesday\n");
else if (d == 5) printf("Thursday\n");
else if (d == 6) printf("Friday\n");
else if (d == 7) printf("Saturday\n");
else printf(“Invalid day\n”);
}
Tip
当我们写函数时,我们可以为参数使用任何我们想要的变量名。我们永远不必担心函数将如何被调用。很多初学者误以为用printDay(n)调用函数,那么头中的参数一定是n。但这不可能是真的,因为它可以用printDay(4)或printDay(n)或printDay(j)甚至printDay(n + 1)来称呼。这取决于调用函数。
我们需要知道的是,无论参数的值是什么,该值都将被赋给d(或者我们碰巧用作参数的任何变量),并且函数将在假设参数(在我们的例子中是d)具有该值的情况下执行。
我们现在将程序 P7.3 重写为 P7.4,以说明该函数如何适合整个程序以及如何使用它。
Program P7.4
#include <stdio.h>
int main() {
int n;
void printDay(int);
printf("Enter a day from 1 to 7: ");
scanf("%d", &n);
printDay(n);
} //end main
void printDay(int d) {
if (d == 1) printf("Sunday\n");
else if (d == 2) printf("Monday\n");
else if (d == 3) printf("Tuesday\n");
else if (d == 4) printf("Wednesday\n");
else if (d == 5) printf("Thursday\n");
else if (d == 6) printf("Friday\n");
else if (d == 7) printf("Saturday\n");
else printf("Invalid day\n");
} //end printDay
既然我们已经将打印委托给了一个函数,请注意main是如何变得不那么杂乱的。然而,我们确实需要为main中的printDay编写函数原型,这样就可以从main中调用printDay。这是原型:
void printDay(int);
与所有 C 程序一样,执行从main中的第一条语句开始。这将提示用户输入一个数字,然后程序通过调用函数printDay继续打印当天的名称。
运行示例如下:
Enter a day from 1 to 7: 4
Wednesday
在main中,假设n的值为4。调用printDay(n)执行如下:
- 确定自变量
n的值。就是4。 - 值
4被复制到临时存储位置。这个位置被传递给函数printDay,在这里用参数名d标记。实际上,d被设置为参数的值。 - 执行函数的主体。在这种情况下,由于
d是4,语句printf("Wednesday\n")将被执行。 - 打印
Wednesday后,函数完成。包含自变量的位置被丢弃,并且控制返回到调用printDay(n)之后的语句的main。在这种情况下,没有更多的语句,所以程序结束。
7.6 最高公因数
在第五章中,我们编写了程序 P5.2,读取两个数,找到它们的最高公因数(HCF)。你应该看一看这个节目来恢复记忆。
如果每当我们想找到两个数字的 HCF(比如说,m和n)时,我们可以调用函数hcf(m, n)来得到答案,那就太好了。例如,调用hcf(42, 24)将返回答案6。为了能够做到这一点,我们编写如下函数:
//returns the hcf of m and n
int hcf(int m, int n) {
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
} //end hcf
查找 HCF 的逻辑与程序 P5.2 中使用的逻辑相同。不同之处在于m和n的值将在函数被调用时传递给函数。在 P5.2 中,我们提示用户输入m和n的值,并使用scanf获取它们。
假设用hcf(42, 24)调用函数。会发生以下情况:
- 每个参数都被复制到一个临时内存位置。这些位置被传递给函数 hcf,其中 42 用第一个参数 m 标记,24 用第二个参数 n 标记。我们可以将此描述为:
- 执行 while 循环,计算出 HCF。在退出循环时,HCF 存储在 m 中,此时将包含 6。这是函数返回到调用它的地方的值。
- 就在函数返回之前,包含参数的位置被丢弃;然后,控制返回到发出调用的地方。
程序 P7.5 通过读取数字对并打印每对的 HCF 来测试函数。对hcf的调用在printf语句中进行。如果任一数字小于或等于0,程序停止。
Program P7.5
#include <stdio.h>
int main() {
int a, b;
int hcf(int, int);
printf("Enter two positive numbers: ");
scanf("%d %d", &a, &b);
while (a > 0 && b > 0) {
printf("The HCF is %d\n", hcf(a, b));
printf("Enter two positive numbers: ");
scanf("%d %d", &a, &b);
}
} //end main
//returns the hcf of m and n
int hcf(int m, int n) {
while (n != 0) {
int r = m % n;
m = n;
n = r;
}
return m;
} //end hcf
以下是 P7.5 的运行示例:
Enter two positive numbers: 42 24
The HCF is 6
Enter two positive numbers: 32 512
The HCF is 32
Enter two positive numbers: 100 31
The HCF is 1
Enter two positive numbers: 84 36
The HCF is 12
Enter two positive numbers: 0 0
我们再次强调,即使函数是用参数m和n编写的,它也可以用任意两个整数值来调用——常量、变量或表达式。特别是,它不必用名为m和n的变量来调用。在我们的程序中,我们用a和b来称呼它。
我们提醒您,为了在main中使用hcf,我们必须使用函数原型“声明”它
int hcf(int, int);
如果您愿意,您可以将main中的两个int声明写成一个:
int a, b, hcf(int, int);
7.6.1 使用 HCF 查找 LCM
算术中的一个常见任务是找到两个数的最小公倍数(LCM)。例如,8 和 6 的 LCM 是 24,因为 24 是能整除 8 和 6 的最小数。
如果我们知道这两个数字的 HCF,我们可以通过将这两个数字相乘并除以它们的 HCF 来找到 LCM。给定 8 和 6 的 HCF 为 2,我们可以通过算出
找到它们的 LCM
也就是 24。总的来说,
LCM(m, n) = (m x n) / HCF(m, n)
知道了这一点,我们可以很容易地编写一个函数lcm,给定两个参数m和n,返回m和n的 LCM。
//returns the lcm of m and n
int lcm(int m, int n) {
int hcf(int, int);
return (m * n) / hcf(m, n);
} //end lcm
既然lcm使用了hcf,我们必须通过写它的原型来声明hcf。我们把它作为一个练习,让你写一个程序来测试lcm。记得在你的程序中包含hcf函数。您可以将hcf放在lcm之前或之后。
7.7 阶乘
到目前为止,我们已经编写了几个函数,说明了在编写和使用函数时需要了解的各种概念。我们现在写另一个并详细讨论它,加强我们到目前为止遇到的一些概念并引入新的概念。
在我们写函数之前,让我们先写一个程序,它读取一个整数 n 并打印 n!(n 阶乘)其中
0! = 1
n! = n(n - 1)(n - 2)...1 for n > 0
比如 5!= 5.4.3.2.1 = 120.
该程序将基于以下算法:
set nfac to 1
read a number, n
for h = 2 to n do
nfac = nfac * h
endfor
print nfac
用n的值3模拟运行该算法,并说服自己它将打印出3!的值6。当n为0或1时,检查它是否产生正确的答案。(提示:当n为0或1时,不执行for循环。)
该算法不会验证n的值。例如,n不应该是负数,因为没有为负数定义阶乘。有趣的是,如果n为负,算法会输出什么?(提示:不执行for循环。)为了简单起见,我们的程序 P7.6 不验证n。
Program P7.6
#include <stdio.h>
int main() {
int nfac = 1, n;
printf("Enter a positive whole number: ");
scanf("%d", &n);
for (int h = 2; h <= n; h++)
nfac = nfac * h;
printf("%d! = %d\n", n, nfac);
}
该程序的运行示例如下所示:
Enter a positive whole number: 4
4! = 24
我们现在考虑编写一个函数(我们称之为factorial)的问题,给定一个整数n,计算并返回n!的值。由于n!是一个整数,所以函数的“返回类型”是int。
我们首先编写函数头。这是
int factorial(int n)
有趣的是,函数头是我们正确使用函数所需的所有信息。暂时忽略阶乘的其余部分,我们可以这样使用它:
printf("5! = %d\n", factorial(5));
或者像这样:
scanf("%d", &num);
printf("%d! = %d\n", num,factorial(num));
在后一种情况下,如果 num 为 4,printf 将打印:
4! = 24
调用factorial(num)将值24直接返回给printf语句。
按照程序 P7.6 的逻辑,我们编写函数factorial如下:
int factorial(int n) {
int nfac = 1;
for (int h = 2; h <= n; h++)
nfac = nfac * h;
return nfac;
} //end factorial
比较程序 P7.6 和函数是值得的:
- 程序提示并读取
n的值;当函数被调用时,函数得到一个值n,就像在factorial(4)中一样。试图在此函数中读取n的值是错误的。 - 除了
n,程序和函数都需要变量nfac和h来表达它们的逻辑。 - 程序和函数计算阶乘的逻辑是相同的。
- 程序打印答案(在
nfac);该函数将答案(在nfac中)返回给调用函数。答案回到了factorial被调用的地方。
对factorial的其他评论
- 在函数中声明的变量被称为函数的局部变量。因此,
nfac是一个局部变量,用于保存阶乘。有趣的是,h对于for语句是局部的。当factorial被调用时,存储器被分配给nfac和h。这些变量用来计算阶乘。就在函数返回之前,nfac和h被丢弃。 - 如果
n是0或1 (,也就是说,它返回1,你应该验证这个函数是否正常工作。
我们现在详细看看调用factorial时会发生什么(比如说从main)。考虑以下陈述(m和fac是int):
m = 3;
fac = factorial(m);
第二条语句执行如下:
- 确定自变量
m的值;就是3。 - 这个值被复制到一个临时内存位置,这个位置被传递给函数。该函数用参数名
n对其进行标记。净效果就好像函数的执行是从语句n = 3;开始的 - 在编程术语中,我们说参数
m是“通过值”传递的参数的值被复制到一个临时位置,这个临时位置被传递给函数。该函数无法访问原始参数。在本例中,factorial无法访问m,因此不能以任何方式影响它。 - 在
n被赋予值3后,阶乘的执行如上所述进行。就在函数返回之前,n占用的存储位置被丢弃。实际上,参数n被视为局部变量,只是它被初始化为所提供参数的值。 - 该函数返回的值是存储在
nfac中的最后一个值。在本例中,分配给nfac的最后一个值是6。因此,值6被返回到发出调用factorial(3)的地方。 - 将
factorial返回的值6赋给fac。 - 下一条语句(如果有)将继续执行。
使用阶乘
我们通过编写一个完整的程序 P7.7 来说明如何使用阶乘。对于 n = 0,1,2,3,4,5,6 和 7。
Program P7.7
#include <stdio.h>
int main() {
int factorial(int);
printf(" n n!\n\n");
for (int n = 0; n <= 7; n++)
printf("%2d %5d\n", n, factorial(n));
} //end main
int factorial(int n) {
int nfac = 1;
for (int h = 2; h <= n; h++)
nfac = nfac * h;
return nfac;
} //end factorial
运行时,该程序打印以下内容:
n n!
0 1
1 1
2 2
3 6
4 24
5 120
6 720
7 5040
如你所见,阶乘的值增加得非常快。甚至 8!= 40320,太大了,不适合 16 位整数(可以存储的最大值是 32767)。作为练习,编写从0到8的循环,看看会发生什么。
让我们仔细看看main。第一条语句是factorial的函数原型。这是必要的,因为factorial将从main被调用。
当执行main时,
printf打印标题- 使用
n执行for循环,假设值为0、1、2、3、4、5、6、7。对于n的每个值,factorial以n为自变量被调用。阶乘被计算并返回到printf中调用它的地方。
我们特意在main中使用了一个名为n的变量来说明这个n并不(也不能)与factorial的参数n冲突。假设main中的n存储在存储单元865中,其值为3。调用factorial(n)将n即3的值存储在一个临时位置(比如说472)中,并且这个临时位置被传递给factorial,在那里它被称为n。这一点说明如下:
我们现在有两个位置叫做n。在factorial时,n是指位置472;在main时,n是指位置865;factorial无法访问位置865。
这里没有发生,但是如果factorial要改变n的值,那么位置472中的值将被改变;位置865中的值不会受到影响。当factorial结束时,位置472被丢弃——即n不再存在。
从另一个角度来看,factorial不知道用来调用它的实际参数,因为它只看到参数的值,而不是它是如何被导出的。
我们用main中的n作为循环变量来说明上面的观点。然而,我们可以使用任何变量。特别是,我们可以使用h,这样就不会与函数factorial的局部变量h发生冲突。当在factorial时,h指局部变量;在main中,h是指main中声明的h。
组合
假设一个委员会有 7 个人。可以组成多少个 3 人小组委员会?答案用 7 C 3 表示,计算如下:
这使我们的值为 35。我们说 7 个物体有 35 种组合,每次取 3 个。
一般来说, n C r 表示一次取 r 个对象的组合数,由公式
计算
使用factorial,我们可以编写一个函数combinations,给定n和r,返回一次获取的n对象的组合数量r。这是:
int combinations(int n, int r) {
int factorial(int);
return factorial(n) / (factorial(n-r) * factorial(r));
} //end combinations
主体由factorial的函数原型和一个return语句组成,其中包含对factorial的 3 次调用。
我们顺便注意到,这可能是最简单的,但不是最有效的评估方法。例如,如果我们手工计算 7 C 3 ,我们会使用:
而不是
该函数使用的。作为练习,写一个计算组合的有效函数。
为了在一个完整的程序中显示函数factorial和combinations以及如何使用它们,我们编写了一个程序来读取n和r的值,并打印一次从n对象获取的组合数r。
程序 P7.8 展示了它是如何完成的。
Program P7.8
#include <stdio.h>
int main() {
int n, r, nCr, factorial(int), combinations(int, int);
printf("Enter values for n and r: ");
scanf("%d %d", &n, &r);
while (n != 0) {
nCr = combinations(n, r);
if (nCr == 1)
printf("There is 1 combination of %d objects taken "
"%d at a time\n\n", n, r);
else
printf("There are %d combinations of %d objects taken "
"%d at a time\n\n", nCr, n, r);
printf("Enter values for n and r: ");
scanf("%d %d", &n, &r);
}
} //end main
int factorial(int n) {
int nfac = 1;
for (int h = 2; h <= n; h++)
nfac = nfac * h;
return nfac;
} //end factorial
int combinations(int n, int r) {
int factorial(int);
return factorial(n) / (factorial(n-r) * factorial(r));
} //end combinations
程序读取n和r的值,并打印组合数。这样做,直到为n输入了0的值。以下是运行示例:
Enter values for n and r: 7 3
There are 35 combinations of 7 objects taken 3 at a time
Enter values for n and r: 5 2
There are 10 combinations of 5 objects taken 2 at a time
Enter values for n and r: 6 6
There is 1 combination of 6 objects taken 6 at a time
Enter values for n and r: 3 5
There are 0 combinations of 3 objects taken 5 at a time
Enter values for n and r: 0 0
观察使用if...else让程序“说出”正确的英语。在语句中,还要注意如何将一个长字符串分成两部分,并将每一部分放在一行中。回想一下,在 C 中,字符串常量的左引号和右引号必须在同一行。当程序被编译时,这些片段将被连接在一起,并作为一个字符串存储在内存中。
7.8 工作费用
在程序 4.6 中,我们读取了工作小时数和零件成本,并计算了一项工作的成本。让我们写一个函数,给定工作时间和零件成本,返回工作的成本。这是:
#define ChargePerHour 100
#define MinJobCost 150
double calcJobCost(double hours, double parts) {
double jobCharge;
jobCharge = hours * ChargePerHour + parts;
if (jobCharge < MinJobCost) return MinJobCost;
return jobCharge;
} //end calcJobCost
当我们说一个函数被给定了一些数据,这立刻意味着这些数据应该被定义为函数的参数。函数的逻辑与程序的逻辑相同。在这里,参数列表指示了当函数被调用时,什么数据将被提供给函数。此外,我们必须指定函数的返回类型;因为作业成本是一个double值,所以是double。
当函数被调用时,如
jobCost = calcJobCost(1.5, 87.50);
参数hours设置为1.5,parts设置为87.50;然后使用这些hours和parts的值执行函数体。
作为练习,编写一个完整的程序来读取工作时间和零件成本的几个值,并为每一对打印工作成本。
7.9 计算工资
在程序 P4.7 中,我们读取hours和rate的值,并计算净工资。所有的代码都是用main写的。我们现在编写一个函数,给定hours和rate的值,返回如 4.3.1 节所述计算的净工资值。该函数如下所示。
#define MaxRegularHours 40
#define OvertimeFactor 1.5
double calcNetPay(double hours, double rate) {
if (hours <= MaxRegularHours) return hours * rate;
return MaxRegularHours * rate +
(hours - MaxRegularHours) * rate * OvertimeFactor;
} //end CalcNetPay
如果hours小于等于MaxRegularHours,则执行第一个return;如果为假,则执行第二个return。注意,这里不需要else。如果第一个return被执行,我们退出该函数,第二个return不能被执行。
如果我们想知道某人以每小时 12 美元的价格工作了 50 个小时的净工资,我们所要做的就是调用calcNetPay(50, 12.00)。
作为练习,编写一个完整的程序来读取姓名、工作时间和工资率的几个值;并且,打印每个人收到的净工资。提示:学习计划 P5.8。
7.10 整除因子之和
让我们写一个函数来返回给定整数的整除因子之和。我们假设除数包括 1,但不包括给定的数。例如,50 的精确除数是 1、2、5、10 和 25。他们的总和是 43。该函数如下所示。
//returns the sum of the exact divisors of n
int sumDivisors(int n) {
int sumDiv = 1;
for (int h = 2; h <= n / 2; h++)
if (n % h == 0) sumDiv += h;
return sumDiv;
} //end sumDivisors
sumDiv用于保存整除因子的和;它被设置为1,因为1总是一个精确的除数。- 其他可能的约数有
2、3、4,等等,直到n/2。for循环依次检查每一项。 - 如果
h是n的整除因子,那么n除以h的余数就是0,也就是说n % h就是0。如果是这样,h加到sumDiv上。 - 最后一条语句将
sumDiv的值返回到调用sumDivisors的地方。
在下一个例子中,我们将看到如何使用sumDivisors。
对数字进行分类
正整数可以根据它们的整除因子的和来分类。如果 n 是整数,s 是它的整除因子之和(包括 1 但不包括 n ),则:
- 如果 s < n,则 n 亏;例如 15(约数 1,3,5;总和 9)
- 如果 s = n,n 是完美的;例如 28(约数 1,2,4,7,14;总和 28)
- 如果 s > n,则 n 是丰富的;例如 12(约数 1,2,3,4,6;总和 16)
让我们编写程序 P7.9 来读取几个数字,并打印出每个数字是不足的、完美的还是丰富的。
Program P7.9
#include <stdio.h>
int main() {
int num, sumDivisors(int);
printf("Enter a number: ");
scanf("%d", &num);
while (num != 0) {
int sum = sumDivisors(num);
if (sum < num) printf("Deficient\n\n");
else if (sum == num) printf("Perfect\n\n");
else printf("Abundant\n\n");
printf("Enter a number: ");
scanf("%d", &num);
}
} //end main
//returns the sum of the exact divisors of n
int sumDivisors(int n) {
int sumDiv = 1;
for (int h = 2; h <= n / 2; h++)
if (n % h == 0) sumDiv += h;
return sumDiv;
} //end sumDivisors
注意,我们调用sumDivisors一次(针对每个数字)并将结果存储在sum中。当我们需要“约数之和”而不是每次都重新计算时,我们使用sum。
以下是程序 P7.9 的运行示例:
Enter a number: 15
Deficient
Enter a number: 12
Abundant
Enter a number: 28
Perfect
Enter a number: 0
作为练习,写一个程序找出所有小于 10,000 的完全数。
7.11 一些字符函数
在这一节中,我们编写了几个与字符相关的函数。
也许最简单的是一个以字符为自变量的函数;如果字符是数字,它返回1,否则返回0。(回想一下,在 C 语言中,零值被解释为false,非零值被解释为true。)这个描述表明我们必须编写一个函数,它接受一个char参数并返回一个int值。我们就叫它isDigit。这是:
int isDigit(char ch) {
return ch >= '0' && ch <= '9';
} //end isDigit
如果ch位于'0'和'9'之间,则布尔表达式(ch >= '0' && ch <= '9')为true;也就是说,如果ch包含一个数字。因此,如果ch包含一个数字,函数返回1(代表true);如果ch不包含数字,则返回0(代表false)。
我们可以将函数体写成
if (ch >= '0' && ch <= '9') return 1;
return 0;
但是上面使用的单个return语句是首选方式。
类似地,我们可以编写函数isUpperCase,,如果它的参数是大写字母,则返回1,如果不是,则返回0,因此:
int isUpperCase(char ch) {
return ch >= 'A' && ch <= 'Z';
} //end isUpperCase
接下来我们有函数isLowerCase,,如果它的参数是小写字母,则返回1,如果不是,则返回0。
int isLowerCase(char ch) {
return ch >= 'a' && ch <= 'z';
} //end isLowerCase
如果我们想知道字符是否是一个字母(大写或小写),我们可以用isUpperCase和isLowerCase写isLetter,。
int isLetter(char ch) {
int isUpperCase(char), isLowerCase(char);
return isUpperCase(ch) || isLowerCase(ch);
} //end isLetter
注意,我们需要包含isUpperCase和isLowerCase的函数原型。
7.11.1 字母表中字母的位置
让我们写一个函数,给定一个字符,如果它不是英文字母表中的一个字母,返回0;否则,它返回字母在字母表中的位置(一个整数值)。无论字符是大写字母还是小写字母,该函数都应该工作。例如,给定'T'或't',函数应该返回20。
该函数接受一个char参数并返回一个int值。使用函数isUpperCase和isLowerCase,我们编写函数(我们称之为position)如下:
int position(char ch) {
int isUpperCase(char), isLowerCase(char);
if (isUpperCase(ch)) return ch - 'A' + 1;
if (isLowerCase(ch)) return ch - 'a' + 1;
return 0;
} //end position
我们用isUpperCase和isLowerCase来确立我们拥有什么样的性格。如果都不是,控制转到最后一条语句,我们返回0。
如果我们有一个大写字母,我们通过从字母的代码中减去A的代码来找到字母和A之间的距离。例如,A和A之间的距离是0,A和F之间的距离是5。添加1给出字母在字母表中的位置。在这里,添加1得到A的1和F的6。
如果我们有一个小写字母,我们通过从字母的代码中减去a的代码来得到字母和a之间的距离。例如,a和b之间的距离是1,a和z之间的距离是25。添加1给出字母在字母表中的位置。在这里,添加1得到b的2和z的26。
为了说明如何使用该函数,我们编写了程序 P7.10,它读取一行输入;对于行中的每个字符,如果不是字母,它将打印出0,如果是字母,则打印出它在字母表中的位置。
Program P7.10
#include <stdio.h>
int main() {
char c;
int position(char);
printf("Type some letters and non-letters and press 'Enter'\n");
while ((c = getchar()) != '\n')
printf("%c%2d\n", c, position(c));
} //end main
int isUpperCase(char ch) {
return ch >= 'A' && ch <= 'Z';
} //end isUpperCase
int isLowerCase(char ch) {
return ch >= 'a' && ch <= 'z';
} //end isLowerCase
int position(char ch) {
int isUpperCase(char), isLowerCase(char);
if (isUpperCase(ch)) return ch - 'A' + 1;
if (isLowerCase(ch)) return ch - 'a' + 1;
return 0;
} //end isPosition
以下是 P7.10 的运行示例:
Type some letters and non-letters and press "Enter"
FaT($hY``&
F 6
a 1
T 20
( 0
$ 0
h 8
Y 25
&  0
n 14
我们已经编写了函数isDigit、isUpperCase、isLowerCase,和isLetter来说明关于角色函数的基本概念。然而,C 提供了许多预定义的函数(实际上是宏,但区别对我们来说并不重要)来处理字符。其中有isdigit(测试数字)、isupper(测试大写字母)、islower(测试小写字母)和isalpha(测试字母)。要使用这些函数,您需要放置指令
#include <ctype.h>
在你项目的最前面。作为练习,使用isupper和islower重写 P7.10。没有isUpperCase、isLowerCase和它们的原型,你的程序会短很多。
7.12 获取下一个整数
之前我们编写了程序 P6.13,一个字符一个字符的读取数据,构造并存储在变量中找到的下一个整数,最后打印出这个整数。
现在让我们编写一个函数getInt,它逐字符读取数据并返回找到的下一个整数。该函数不接受任何参数,但括号仍必须写在名称之后。代码与 P6.13 中的基本相同,除了我们使用了预定义的函数isdigit。这里是getInt:
int getInt() {
char ch = getchar();
// as long as the character is not a digit, keep reading
while (!isdigit(ch)) ch = getchar() ;
// at this point, ch contains the first digit of the number
int num = 0;
while (isdigit(ch)) { // as long as we get a digit
num = num * 10 + ch - '0'; // update num
ch = getchar();
}
return num;
} //end getInt
注意到
while (ch < '0' || ch > '9')
程序 P6.13 的替换为
while (!isdigit(ch))
和
while (ch >= '0' && ch <= '9')
被替换为
while (isdigit(ch))
我们相信这使得程序更具可读性。
该函数需要变量ch和num来完成它的工作;ch保存数据中的下一个字符,num保存到目前为止构建的数字。我们在函数中声明它们,使它们成为局部变量。这样,它们就不会与程序中其他地方声明的同名变量发生冲突。这使得函数是独立的——它不依赖于其他地方声明的变量。
该函数可以在中使用
id = getInt();
这将从输入中获取下一个正整数,不管它前面有多少个字符和什么类型的字符,并将它存储在id中。回想一下,scanf("%d", &id)只有在下一个整数前面有零个或多个空白字符时才起作用。我们的getInt比较笼统。
我们通过重写程序 P4.2 来测试它,该程序请求以米和厘米为单位给出的两个长度,并求出总和。我们注意到数据必须只用数字输入。例如,如果我们输入了3m 75cm,我们会得到一个错误,因为3m不是一个有效的整数常量。使用getInt,我们将能够在3m 75cm表格中输入数据。新程序显示为程序 P7.11
Program P7.11
//find the sum of two lengths given in meters and centimeters
#include <stdio.h>
#include <ctype.h>
int main() {
int m1, cm1, m2, cm2, mSum, cmSum, getInt();
printf("Enter first length: ");
m1 = getInt();
cm1 = getInt();
printf("Enter second length: ");
m2 = getInt();
cm2 = getInt();
mSum = m1 + m2; //add the meters
cmSum = cm1 + cm2; //add the centimeters
if (cmSum >= 100) {
cmSum = cmSum - 100;
mSum = mSum + 1;
}
printf("\nSum is %dm %dcm\n", mSum, cmSum);
} //end main
int getInt() {
char ch = getchar();
// as long as the character is not a digit, keep reading
while (!isdigit(ch)) ch = getchar() ;
// at this point, ch contains the first digit of the number
int num = 0;
while (isdigit(ch)) { // as long as we get a digit
num = num * 10 + ch - '0'; // update num
ch = getchar();
}
return num;
} //end getInt
示例运行如下:
Enter first length: 3m 75cm
Enter second length: 5m 50cm
Sum is 9m 25cm
我们鼓励您执行以下操作:
- 修改
getInt,使其适用于负整数。 - 编写一个函数
getDouble,,返回输入中的下一个浮点数。即使下一个数字不包含小数点,它也应该工作。
EXERCISES 7Explain why functions are important in writing a program. Given the function header void test(int n) explain carefully what happens when the call test(5) is made. Given the function header double fun(int n) explain carefully what happens when the following statement is executed: printf("The answer is %f\n", fun(9)); Given the function header void test(int m, int n, double x) say whether each of the following calls is valid or invalid. If invalid, state why. test(1, 2, 3); test(-1, 0.0, 3.5); test(7, 2); test(14, '7', 3.14); Write a function sqr, which given an integer n, returns n2. Write a function isEven, which given an integer n, returns 1 if n is even and 0 if n is odd. Write a function isOdd, which given an integer n, returns 1 if n is odd and 0 if n is even. Write a function isPerfectSquare, which given an integer n, returns 1 if n is a perfect square (e.g., 25, 81) and 0 if it is not. Use only elementary arithmetic operations. Hint: Try numbers starting at 1. Compare the number times itself with n. Write a function isVowel, which given a character c, returns 1 if c is a vowel and 0 if it is not. Write a function, which given an integer n, returns the sum 1 + 2 +...+ n Write a function, which given an integer n, returns the sum 1 2 + 2 2 +...+ n 2 Write a function, which given three integer values representing the sides of a triangle, returns:
0如果值不能是任何三角形的边。如果任何一个值为负或零,或者任何一条边的长度大于或等于其他两条边的长度之和,就会出现这种情况。1如果三角形是不规则的(所有边都不同)。2如果三角形是等腰的(两边相等)。3如果三角形是等边的(三条边相等)。
Write a function, which given three integer values representing the sides of a triangle, returns 1 if the triangle is right angled and 0 if it is not. Write a function power, which given a double value x and an integer n, returns xn. Using the algorithm of problem 10, Exercises 4, write a function, which given a year between 1900 and 2099, returns an integer value indicating the day on which Easter Sunday falls in that year. If d is the day of the month, return d if the month is March and -d if the month is April. For example, if the year is 1999, return -4 since Easter Sunday fell on April 4 in 1999. Assume that the given year is valid. Write a program, which reads two years, y1 and y2, and, using the function above, prints the day on which Easter Sunday falls for each year between y1 and y2. Given values for month and year, write a function to return the number of days in the month. Write a function numLength, which given an integer n, returns the number of digits in the integer. For example, given 309, the function returns 3. Write a function max3, which given 3 integers, returns the biggest. Write a function isPrime, which given an integer n, returns 1 if n is a prime number and 0 if it is not. A prime number is an integer > 1, which is divisible only by 1 and itself. Using isPrime, write a program to prompt for an even number n greater than 4 and print all pairs of prime numbers that add up to n. Print an appropriate message if n is not valid. For example, if n is 22, your program should print 3 19 5 17 11 11 You are required to generate a sequence of integers from a given positive integer n, as follows. If n is even, divide it by 2. If n is odd, multiply it by 3 and add 1. Repeat this process with the new value of n, stopping when n = 1. For example, if n is 13, the following sequence will be generated: 13 40 20 10 5 16 8 4 2 1 Write a function, which given n, returns the length of the sequence generated, including n and 1. For n = 13, your function should return 10. Using the function, write a program to read two integers m and n (m < n), and print the maximum sequence length for the numbers between m and n, inclusive. Also print the number that gives the maximum length. For example, if m = 1 and n = 10, your program should print 9 generates the longest sequence of length 20 We can code the 52 playing cards using the numbers 1 to 52. We can assign 1 to the Ace of Spades, 2 to the Two of Spades, and so on, up to 13 to the King of Spades. We can then assign 14 to the Ace of Hearts, 15 to the Two of Hearts, and so on, up to 26 to the King of Hearts. Similarly, we can assign the numbers 27–39 to Diamonds and 40–52 to Clubs. Write a function, which given integers rank and suit, returns the code for that card. Assume rank is a number from 1 to 13 with 1 meaning Ace and 13 meaning King; suit is 1, 2, 3, or 4 representing Spades, Hearts, Diamonds, and Clubs, respectively.