在这个答案中,我假设您正在阅读并且
解释文本行。
也许您正在提示正在输入内容的用户并且
击中返回。或者也许你正在阅读结构化的行
来自某种数据文件的文本。
由于您正在阅读文本行,因此组织起来很有意义
您的代码围绕一个库函数读取,嗯,一行
文本。
标准函数是fgets(),尽管还有其他函数(包括getline)。然后下一步是解释
那一行文字不知何故。
这是调用fgets 来读取一行的基本方法
文字:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
这只是读入一行文本并将其打印出来。
正如所写的那样,它有一些限制,我们将在
一分钟。它还有一个非常棒的特点:我们的那个数字 512
作为第二个参数传递给fgets 是数组的大小
line 我们要求 fgets 阅读。这个事实——我们可以
告诉fgets 允许读取多少——意味着我们可以
确保fgets 不会因读取过多而溢出数组
进入它。
所以现在我们知道如何阅读一行文本,但如果我们真的
想要读取整数、浮点数或
一个字符,还是一个单词? (也就是说,如果
scanf 我们试图改进的电话一直在使用一种格式
说明符,例如 %d、%f、%c 或 %s?)
很容易将一行文本——一个字符串——重新解释为这些东西。
要将字符串转换为整数,最简单的(虽然
不完美)的方法是调用atoi()。
要转换为浮点数,有atof()。
(还有更好的方法,我们马上就会看到。)
这是一个非常简单的例子:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
如果您希望用户键入单个字符(可能是 y 或
n 作为是/否的回应),你可以直接抓住第一个
行的字符,如下所示:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(当然,这忽略了用户键入
多字符响应;它悄悄地忽略任何额外的字符
输入的。)
最后,如果你想让用户输入一个字符串,肯定不包含
空格,如果你想处理输入行
hello world!
作为字符串 "hello" 后跟其他内容(这就是
scanf 格式 %s 会做),好吧,在那种情况下,我
有点小谎言,重新解释这条线并不那么容易
毕竟,那样的话,问题的那部分的答案就会有
稍等片刻。
但首先我想回到我跳过的三件事。
(1) 我们一直在打电话
fgets(line, 512, stdin);
读入数组line,其中512是数组的大小
数组line 所以fgets 知道不会溢出它。但要使
确保 512 是正确的数字(尤其是检查是否可能
有人调整了程序来改变大小),你必须阅读
回到声明 line 的地方。很麻烦所以
有两种更好的方法可以使尺寸保持同步。
您可以,(a)使用预处理器为大小命名:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
或者,(b) 使用 C 的 sizeof 运算符:
fgets(line, sizeof(line), stdin);
(2) 第二个问题是我们没有检查
错误。当您阅读输入时,您应该始终检查
错误的可能性。如果出于某种原因fgets 不能
阅读您要求的文本行,它通过以下方式表明这一点
返回一个空指针。所以我们应该一直在做类似的事情
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
最后,还有一个问题是为了阅读一行文本,
fgets 读取字符并将它们填充到您的数组中,直到它
找到终止行的\n 字符,并填充
\n 字符也放入您的数组中。你可以看到这个,如果
你稍微修改我们之前的例子:
printf("you typed: \"%s\"\n", line);
如果我运行它并在提示时输入“Steve”,它会打印出来
you typed: "Steve
"
第二行的" 是因为它读取的字符串和
打印出来的其实是"Steve\n"。
有时额外的换行符并不重要(比如我们调用
atoi 或 atof,因为它们都忽略了任何额外的非数字
在数字后输入),但有时它很重要。所以
通常我们会想去掉那个换行符。有几种
方法来做到这一点,我会在一分钟内得到。 (我知道我已经
说了很多。但我保证会回到所有这些事情上。)
此时,你可能会想:“我以为你说的是scanf
不好,这种其他方式会好得多。
但是fgets 开始看起来很讨厌。
致电scanf 非常如此简单!不能继续用吗?”
当然,如果你愿意,你可以继续使用scanf。 (对于真的
简单的事情,在某些方面它更简单。)但是,请不要
当它因为它的 17 个怪癖之一而让你失望时来向我哭泣
和弱点,或者因为输入你的
没想到,或者当你想不通怎么用它来做
更复杂的东西。让我们看看fgets的
实际麻烦:
-
您始终必须指定数组大小。嗯,当然,
这根本不是一件麻烦事——这是一个功能,因为缓冲区
溢出是一件非常糟糕的事情。
-
您必须检查返回值。其实就是洗头
因为要正确使用scanf,你必须检查它的返回
也很有价值。
-
你必须把\n 去掉。这是,我承认,一个真实的
滋扰。我希望有一个我可以指出的标准函数
你到那个没有这个小问题。 (请没人
提出gets。)但与scanf's相比有17个不同
烦人,我会在任何一天接受fgets这个烦人的事情。
那么如何你去掉那个换行符?方法有很多:
(a) 明显方式:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) 复杂而紧凑的方式:
strtok(line, "\n");
不幸的是,doesn't work quite right 在空行中。
(c) 另一种紧凑且略显晦涩的方式:
line[strcspn(line, "\n")] = '\0';
还有其他方法。我,我总是只使用 (a),因为它简单明了,如果不够简洁的话。
请参阅this question 或this question,了解更多(更多)关于从fgets 中剥离\n 给您的信息。
现在已经不碍事了,我们可以回到另一个
我跳过的东西:atoi() 和atof() 的缺陷。
问题是它们没有给你任何有用的东西
成功或失败的标志:他们悄悄地忽略
尾随非数字输入,如果有,它们会悄悄返回 0
根本没有数字输入。首选的替代品——
还具有某些其他优势——strtol 和strtod。
strtol 还允许您使用 10 以外的基数,这意味着您可以
获得%o 或%x 和scanf 的效果(除其他外)。
但是展示如何正确使用这些功能本身就是一个故事,
并且会过多地分散已经转向的东西
变成一个相当支离破碎的叙述,所以我不会说
现在有更多关于他们的信息。
其余的主要叙述涉及您可能正在尝试的输入
解析比单个数字更复杂的或
特点。如果您想读取包含两个
数字,或多个空格分隔的单词,或特定的
标点符号?这就是事情变得有趣的地方,并且
如果您尝试,事情可能会变得复杂
使用scanf 做事,还有更多
选项现在您已经使用fgets 清晰地阅读了一行文本,
尽管所有这些选项的完整故事可能会填满
一本书,所以我们只能在这里触及表面。
-
我最喜欢的技巧是将队列分成
空格分隔的“单词”,然后对每个单词做进一步的处理
“单词”。这样做的一个主要标准功能是
strtok(这也有问题,也对整体进行了评分
单独讨论)。我自己的偏好是专用功能
用于构造指向每个断开的指针的数组
“word”,我描述的一个功能
these course notes。
无论如何,一旦你有了“词”,你就可以进一步处理
每一个,也许是相同的atoi/atof/strtol/strtod
我们已经看过的函数。
-
自相矛盾的是,尽管我们已经花费了相当多的
在这里花费时间和精力来弄清楚如何远离scanf,
另一种处理我们刚刚阅读的文本行的好方法
fgets 是将其传递给sscanf。这样,你最终得到
scanf 的大部分优点,但没有
缺点。
-
如果您的输入语法特别复杂,可能适合使用“正则表达式”库来解析它。
-
最后,您可以使用任何适合的 ad hoc 解析解决方案
你。您可以使用
char * 指针检查您期望的字符。或者你可以
使用 strchr 或 strrchr 等函数搜索特定字符,
或strspn 或strcspn 或strpbrk。或者您可以解析/转换
并使用strtol 跳过数字字符组或
strtod 我们之前跳过的函数。
显然还有很多话要说,但希望
本介绍将帮助您入门。