C 如何从动态字符串中删除换行符
我有这段代码,它接受各种字符串的输入。他们不必在最后有换行符,我需要一个接一个的换行符,因为我正在CSV文件中编写它们。我把这个穿了一会儿C 如何从动态字符串中删除换行符,c,string,C,String,我有这段代码,它接受各种字符串的输入。他们不必在最后有换行符,我需要一个接一个的换行符,因为我正在CSV文件中编写它们。我把这个穿了一会儿 do { printf("Put the ingredient:\n"); fgets(recipe.ingredients[j], 30, stdin); len = strlen(recipe.ingredients[j]) + 1; recipe.ingredients[len]
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
与长度相加或相减,但在这两种情况下,我只有第一个没有换行符的字符串,而其他字符串都有换行符。我以为用空终止符替换换行符可以解决我的问题,但也许我遗漏了什么。有什么线索可以解决这个问题吗?我有点困惑…发布的代码片段中有很多问题:
- 不测试
的返回值,如果fgets()
失败,则在访问缓冲区内容时会导致未定义的行为fgets()
- 呼叫
最多只能将3个字节读入fgets(sentinel,4,stdin)
,以便为后面的空终止符保留空间。因此,用户在sentinel
之后输入的换行符将保留在输入流中,并导致对yes
的下一次调用立即返回,缓冲区内容为fgets()
“\n”
len=strlen(配方成分[j])+1代码>太大:如果存在,并且如果
不是空字符串,则换行符的偏移量将为recipe.components[j]
strlen(recipe.components[j])-1
recipe.components[len]=“0”代码>完全是假的:它只是一个比较,而不是一个赋值,它比较苹果和橙子:一个
和一个char
const 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
的增量是否超过数组配方的大小。配料发布的代码片段中存在许多问题:
- 不测试
fgets()
的返回值,如果fgets()
失败,则在访问缓冲区内容时会导致未定义的行为
- 呼叫
fgets(sentinel,4,stdin)
最多只能将3个字节读入sentinel
,以便为后面的空终止符保留空间。因此,用户在yes
之后输入的换行符将保留在输入流中,并导致对fgets()
的下一次调用立即返回,缓冲区内容为“\n”
len=strlen(配方成分[j])+1代码>太大:如果存在,并且如果recipe.components[j]
不是空字符串,则换行符的偏移量将为strlen(recipe.components[j])-1
recipe.components[len]=“0”代码>完全是假的:它只是一个比较,而不是一个赋值,它比较苹果和橙子:一个char
和一个const 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
的增量是否超过了数组配方的大小。配料
这只是一个扩展注释,可能对某些人有用
对于所述的问题,答案是正确的
我只是想在这个问题上做一点扩展,并展示一种可以轻松扩展到修剪前导和尾随空格的替代方法
核心逻辑很简单。我们在字符串中使用两个索引,i
和o
i
是要检查的下一个字符的索引,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;
}
这一次,在复制(或检查)所有字符后,我们将“备份”要删除的尾随字符。因为out
指向下一个位置,所以前一个字符是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 ");
或
将不起作用,因为您不应该尝试修改字符串文字。请记住,在上述形式中,oldstring
和mystring
是指向字符串文本的指针
相反,创建一个初始化为字符串文字的字符数组。您可以自由修改字符数组a
char mybuffer[] = " This will work just Fine.\n";
clean(mybuffer);
char line[256] = "This will work, too.\n";
printf("Read '%s'.\n", trim(line));
char line[256] = "This will work, too.\n";
trim(line);
printf("Read '%s'.\n", line);