【问题标题】:Stripping newline is being very tricky剥离换行符非常棘手
【发布时间】:2016-08-08 05:04:53
【问题描述】:

看起来 scanf() 正在读取前一个 '\n'。我正在尝试将我的连接字符串放在一行上。如何摆脱第二个字符串开头的 '\n' 。

这是一个 c 程序。

我猜我必须在读入下一行之前以某种方式刷新缓冲区。但我不知道如何在没有错误的情况下执行此操作。

顺便说一句,我不能在这里使用 cin.get() 或其他标准 cin/cout 操作。我也不能使用标准的字符串操作,因为我只使用 c-strings 和 strings.h,而不是 string。

来自标准输入的输入是:

12 4.0 Spot run!

int main() {
    int i = 4;
    double d = 4.0;
    char s[] = "See ";

    int isum;
    int MAX_SIZE = 256;
    double dsum;
    char s2[MAX_SIZE];

    scanf("%i",&isum);
    scanf("%lf",&dsum); // Hope to clear out the newline from reading 
                        // in the double value. Or any other newline 
                        // before the next scarf(), but haven't figured
                        // how.
    scanf("%79c", s2); 

    isum += i;
    printf("%d \n",isum);

    dsum += d;
    printf("%.1f \n",dsum);

    char newchar[MAX_SIZE];
    strcpy(newchar,s);
    newchar[strcspn(newchar,"\n")]='\0';
    s2[strcspn(s2,"\n")] = 't'; //To test where newline is in second string.
    strcat(newchar,s2);
    printf("%s",newchar);

    return 0;

我的输出是:

16 8.0 See tSpot run! //

【问题讨论】:

  • 你确定你在学习C++吗?这看起来像一个 C 程序。
  • 你说得对,我会改正的。我从一群仍然使用 c 风格习惯的老 C 程序员那里学习了 C++,所以我没有立即学习。我会更正标签。
  • "*看起来 scanf() 正在读取前一个 '\n'。*" 你觉得这很奇怪吗?如果是这样,你认为会读到什么?
  • 我确实觉得这很令人惊讶,我认为它会开始读取下一行,而不是上一行中的 '\n'。我怎样才能让它不读?
  • 关于scanf() 及其亲属的行为有很多问题,它们是标准C 库中一些最微妙和最复杂的函数。当下一个字符不是数字的一部分时,转换规范(例如%lf)会停止读取——让该字符由下一个转换规范处理。请注意%c(连同%[…] 扫描集和%n)不会跳过前导空格;所有其他转换规范都会跳过前导空格。如果您不希望数字输入留下换行符,请使用 " %79c" 跳过任何空格。

标签: c printf scanf stdin c-strings


【解决方案1】:

根据要求。

关于scanf() 及其亲属的行为有很多问题,它们是标准C 库中一些最微妙和最复杂的函数。当下一个字符不是数字的一部分时,转换规范(例如%lf)会停止读取——让该字符由下一个转换规范处理。请注意%c(连同%[…] 扫描集和%n)不会跳过前导空格;所有其他转换规范都会跳过前导空格。如果您不希望数字输入留下换行符,请使用 " %79c" 跳过任何空格。格式字符串中的空格会跳过零个或多个空格字符,包括空格、制表符和换行符。

就目前而言,"%79c"4.0 之后读取换行符,'Spot run!' 中的字符包括其后的换行符,并在假设输入然后到达 EOF 的情况下停止读取。

See 之后不应出现换行符。 s2[strcspn(s2,"\n")] = 't';写的t覆盖了s2的第一个字节,也就是4.0之后的换行符。

你是否意识到这一行:

newchar[strcspn(newchar, "\n")] = '\0';

用空字节覆盖See 之后的空字节,这是一个空操作。

我经常使用的一种技术是将输入日期打印在一组合适的括号中,有时是<<…>>,有时是[…],这取决于突发奇想或可能的内容。例如:

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

int main(void)
{
    int i = 4;
    double d = 4.0;
    char s[] = "See ";

    int isum;
    int MAX_SIZE = 256;
    double dsum;
    char s2[MAX_SIZE] = "";  // See comments.

    scanf("%i", &isum);
    scanf("%lf", &dsum);
    scanf("%79c", s2);

    printf("s2 = [%s]\n", s2);

    isum += i;
    printf("%d\n", isum);

    dsum += d;
    printf("%.1f\n", dsum);

    char newchar[MAX_SIZE];
    strcpy(newchar, s);
    newchar[strcspn(newchar, "\n")]='\0';
    s2[strcspn(s2, "\n")] = 't';
    strcat(newchar, s2);
    printf("%s", newchar);

    return 0;
}

正如Cool Guy 在他的comment 中所指出的,重要的是要记住%c 不会添加空字节(它只需要存储单个字符);同样,%79c 不会在末尾添加空字节。因此,将s2 初始化为“所有字节为零”是必要的,以防止在连接到newchar 时出现未定义的行为。或者您必须对%n(可能使用两次)使用复杂的技术来找出有多少字节被读入s2,这样您就可以在事件发生后将其终止。

这个变体的输出是:

s2 = [
Spot run!
]
16
8.0
See tSpot run!

所以在输入之后s2 的开头和结尾都有一个换行符。

【讨论】:

  • 不是printf("s2 = [%s]\n", s2); UB,因为s2 不是NUL 终止的?
  • @CoolGuy: s2 是空终止的,除非%79c 不是空终止。我很少使用%c 计数,所以我会去看看手册。 […时间过去…] POSIX 部分表示:没有添加空字节。 所以你是对的。我有一些关于不被空终止的评论,但它是基于错误的理由,所以我删除了它——没有意识到/不记得%79c 也不会空终止。修复很简单:char s2[MAX_SIZE] = ""; 将数组初始化为全零字节。我已经修改了代码——谢谢。
猜你喜欢
  • 2011-03-23
  • 2013-12-06
  • 2012-11-22
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 1970-01-01
相关资源
最近更新 更多