我有这段代码,可以输入各种字符串。它们不必最后有换行符,因为我将它们写在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
,但是在两种情况下,我只有第一个字符串没有换行符,而其他字符串总有换行符。我以为用空终止符替换换行符可以解决我的问题,但也许我遗漏了一些东西。有解决的提示吗?我有点困惑...
答案 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";
完全是伪造的:只是比较,而不是赋值,它比较苹果和橘子: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
的增量是否超过数组recipe.ingredient
的大小。
答案 1 :(得分:0)
这只是扩展注释,可能对某些人有用。
answer by chqrlie对上述问题是正确的。
我只是想在此问题上扩大一点,并展示一种可以轻松扩展为修剪前导和尾随空白的替代方法。
核心逻辑很简单。我们在字符串中使用两个索引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;
}
请注意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);
将不起作用,因为您不应尝试修改字符串文字。请记住,在以上形式中,oldstring
和mystring
是指向字符串文字的指针。
相反,创建一个初始化为字符串文字的字符数组。之后,您可以自由修改字符数组:
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);
最后一种形式最容易阅读和理解,因此也易于维护,但是程序员通常更喜欢前一种形式,因为它较短。希望,此扩展注释本身足以说明为什么较短并不总是更好。 :)