如何从动态字符串中删除换行符

时间:2018-07-06 21:14:43

标签: c string

我有这段代码,可以输入各种字符串。它们不必最后有换行符,因为我将它们写在CSV文件上,所以我需要一个接一个。我把它放了一段时间

    do {
        printf("Put the ingredient:\n");
        fgets(recipe.ingredients[j], 30, stdin);
        len = strlen(recipe.ingredients[j]) + 1;
        recipe.ingredients[len] == "\0";
        fprintf(fbr, "%s,", recipe.ingredients[j]);
        j++;
        counter++;
        printf("Do you want to continue? (yes/no)");
        fgets(sentinel, 4, stdin);
    } while(strcmp("yes", sentinel) == 0);

问题是,自从输入该条件以来,我输入的第一个字符串没有换行符。我尝试过在长度上加上和减去1,但是在两种情况下,我只有第一个字符串没有换行符,而其他字符串总有换行符。我以为用空终止符替换换行符可以解决我的问题,但也许我遗漏了一些东西。有解决的提示吗?我有点困惑...

2 个答案:

答案 0 :(得分:4)

发布的代码片段中有很多问题:

  • 您不测试fgets()的返回值,如果fgets()失败,则在访问缓冲区内容时会导致未定义的行为。
  • 呼叫fgets(sentinel,4,stdin);只会将最多3个字节读取到sentinel中,以保留尾随空终止符的空间。因此,用户在yes之后输入的换行符将保留在输入流中,并使对fgets()的下一次调用立即返回,缓冲区内容为"\n"
  • len = strlen(recipe.ingredients[j]) + 1;太大:如果存在换行符且strlen(recipe.ingredients[j]) - 1不是空字符串,则换行符的偏移量将为recipe.ingredients[j]
  • recipe.ingredients[len] == "\0";完全是伪造的:只是比较,而不是赋值,它比较苹果和橘子:charconst char *

剥离换行符(如果存在)的更简单方法是:

char *p = recipe.ingredients[j];
p[strcspn(p, "\n")] = '\0';   // overwrite the newline, if present

这是修改后的版本:

for (;;) {
    char sentinel[100];
    char *p;

    printf("Put the ingredient: ");
    if (!fgets(recipe.ingredients[j], 30, stdin))
        break;
    p = recipe.ingredients[j];
    p[strcspn(p, "\n")] = '\0';   // overwrite the newline, if present
    fprintf(fbr, "%s,", recipe.ingredients[j]);
    j++;
    counter++;
    printf("Do you want to continue? (yes/no): ");
    if (!fgets(sentinel, sizeof sentinel, stdin))
        break;
    if (strcmp(sentinel, "yes") != 0)
        break;
}

请注意,您还应该检查j的增量是否超过数组recipe.ingredient的大小。

答案 1 :(得分:0)

这只是扩展注释,可能对某些人有用。

answer by chqrlie对上述问题是正确的。

我只是想在此问题上扩大一点,并展示一种可以轻松扩展为修剪前导和尾随空白的替代方法。

核心逻辑很简单。我们在字符串中使用两个索引ioi是要检查的下一个字符的索引,o是我们要保存的下一个字符的索引。

现在,我更喜欢使用指针而不是字符数组的索引。没关系,但是对我来说,这种形式(与数组索引形式相比)更容易读写。我也喜欢它,因为通常足以使那些从互联网上抢走代码的人绊倒,以作为自己的家庭作业,而完全不了解代码的作用。我也很喜欢绊倒那些骗子。


从字符串中删除所有前导空格的函数通常称为ltrim()。这是可能的实现方式。这也删除了所有控制字符:

#include <stdlib.h>
#include <ctype.h>

char *ltrim(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* If there was no leading whitespace,
           we can simply return the original string. */
        if (in == out)
            return s;

        /* Move the rest of the string to start at s. */
        while (*in)
            *(in++) = *(out++);

        /* Terminate the string. */
        *out = '\0';
    }

    /* The function always returns the argument. */
    return s;
}

请注意in如何指向字符串中要检查的下一个字符,而out则如何指向下一个位置以存储保留的字符。我们总是有in >= out,即我们不会尝试读取已经被覆盖的头寸,因为每当我们增加out时,我们也会增加in

有几种方法可以实现rtrim(),该功能可以删除所有结尾的空格,包括换行符。这是一种方法。这将删除结尾的空格和控制字符。

char *rtrim(char *s)
{
    if (s) {
        char *oug = s;

        /* This just implements out = s + strlen(s). */
        while (*out != '\0')
            out++;

        /* Back over trailing whitespace and controls. */
        while (out > s && (isspace(out[-1]) || iscntrl(out[-1])))
            out--;

        /* Terminate the string here. */
        *out = '\0';
    }

    /* This function also always returns the argument. */
    return s;
}

这一次,在我们复制(或检查)所有字符后,我们“备份”了要删除的尾随字符。因为out指向 next 位置,所以前一个字符是out[-1](相当于C中的*(out-1))。但是,我们必须小心,不要回到字符串的开头。

与其将trim()实施为对上述两个函数的调用,不如将它们组合为一个函数更有效:

char *trim(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* Move the rest of the string to start at s. */
        while (*in)
            *(in++) = *(out++);

        /* Back up over trailing whitespace and control characters. */
        while (out > s && (isspace(out[-1]) || iscntrl(out[-1])))
            out--;

        /* Terminate the string. */
        *out = '\0';
    }

    /* Always return the argument. */
    return s;
}

我通常只实现trim()

在某些情况下,您可能希望具有一个既可以删除开头和结尾的空格和控制字符,又可以将所有连续的空格和控制字符转换为单个空格的函数,例如以清理某些输入。只是稍微复杂一点:

char *clean(char *s)
{
    if (s != NULL) {
        char *in = s;
        char *out = s;

        /* Skip leading whitespace and control characters. */
        while (isspace(*in) || iscntrl(*in))
            in++;

        /* Move the rest of the string to start at s,
           combining consecutive whitespaces and control
           characters to a single space. */
        while (*in)
            if (isspace(*in) || iscntrl(*in)) {
                /* Skip all consecutive whitespaces and
                   control characters first. */
                while (isspace(*in) || iscntrl(*in));
                    in++;
                /* "Replace" them with a single space. */
                *(out++) = ' ';
            } else
                *(in++) = *(out++);

        /* Back up over the one trailing space we might have copied. */
        if (out > s && out[-1] == ' ')
            out--;

        /* Terminate the string. */
        *out = '\0';
    }

    /* Always return the argument. */
    return s;
}

本质上,与trim()的唯一区别在于,在复制循环中,如果遇到空白或控制字符,则将其全部跳过(即连续的),然后仅存储一个空格以用那个空格“替换”它们。


由于上述函数修改了给定的字符串,因此不能在字符串文字上使用它们。也就是说,类似

char *mystring = clean(" This Will Not Work ");

char *oldstring = " Thiss Will Not Work Either ";
char *mystring = clean(oldstring);

将不起作用,因为您不应尝试修改字符串文字。请记住,在以上形式中,oldstringmystring是指向字符串文字的指针。

相反,创建一个初始化为字符串文字的字符数组。之后,您可以自由修改字符数组:

char  mybuffer[] = " This will work just Fine.\n";
clean(mybuffer);

char  line[256] = "This will work, too.\n";
printf("Read '%s'.\n", trim(line));

请注意,在前一个示例中不使用返回值(指向字符串的指针),而是在后一个示例中将其作为要打印的字符串提供。如果使用后一个示例中使用的格式,请记住它修改了line[];在printf调用之后,该行仍被“修剪”。简而言之,后者等同于

char  line[256] = "This will work, too.\n";
trim(line);
printf("Read '%s'.\n", line);

最后一种形式最容易阅读和理解,因此也易于维护,但是程序员通常更喜欢前一种形式,因为它较短。希望,此扩展注释本身足以说明为什么较短并不总是更好。 :)