从 fgets() 输入中删除尾随换行符

Removing trailing newline character from fgets() input

提问人:sfactor 提问时间:4/23/2010 最后编辑:Jonathan Lefflersfactor 更新时间:11/22/2023 访问量:549624

问:

我正在尝试从用户那里获取一些数据并将其发送到 gcc 中的另一个函数。代码是这样的。

printf("Enter your Name: ");
if (!(fgets(Name, sizeof Name, stdin) != NULL)) {
    fprintf(stderr, "Error reading Name.\n");
    exit(1);
}

但是,我发现它最后有一个换行符。因此,如果我输入它最终会发送.如何删除它并发送正确的字符串。\nJohnJohn\n\n

C 字符串 gcc 换行符 fgets

评论

31赞 4/23/2010
if (!fgets(Name, sizeof Name, stdin))(至少不要使用两个否定词,! 和 !=)
8赞 chux - Reinstate Monica 8/24/2016
@Roger佩特“不要使用两个否定”——>嗯,如果我们深入挖掘,“不要”和“否定”都是否定。;-).也许“使用 .if (fgets(Name, sizeof Name, stdin)) {
6赞 R Sahu 4/6/2018
@chux,我相信你的意思是if (fgets(Name, sizeof Name, stdin) == NULL ) {
0赞 chux - Reinstate Monica 4/6/2018
@RSahu True: 讨厌 :!

答:

231赞 Jerry Coffin 4/23/2010 #1

优雅的方式:

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

略显丑陋的方式:

char *pos;
if ((pos=strchr(Name, '\n')) != NULL)
    *pos = '\0';
else
    /* input too long for buffer, flag error */

略显奇怪的方式:

strtok(Name, "\n");

请注意,如果用户输入空字符串(即仅按 Enter),则该函数不会按预期工作。它使角色完好无损。strtok\n

当然,还有其他人。

评论

7赞 Michael Burr 4/23/2010
任何线程感知的 C 运行时库(也就是说,大多数面向多线程平台的库)都将是线程安全的(它将使用线程本地存储来实现“调用间”状态)。也就是说,通常最好使用非标准(但足够常见)的变体。strtok()strtok_r()
2赞 Tim Čas 2/12/2015
请参阅我的答案,了解完全线程安全和可重入的变体,类似于您的方法(它适用于空输入)。其实,一个好的实现方式就是使用 和 。strtokstrtokstrcspnstrspn
3赞 Malcolm McLean 1/9/2017
如果您处于存在排长队风险的环境中,请务必处理其他情况。以静默方式截断输入可能会导致非常有害的错误。
4赞 twobit 8/17/2017
如果您喜欢单行代码并且正在使用 glibc,请尝试 .*strchrnul(Name, '\n') = '\0';
1赞 chux - Reinstate Monica 12/7/2017
当 时,除了“输入太长,无法缓冲,标记错误”之外,还存在其他可能性:读取了最后一个文本未以 null 结尾或罕见的嵌入 null 字符。strchr(Name, '\n') == NULLstdin'\n'
87赞 James Morris 4/23/2010 #2
size_t ln = strlen(name) - 1;
if (*name && name[ln] == '\n') 
    name[ln] = '\0';

评论

9赞 Edward Olamisan 5/31/2013
如果字符串为空,可能会抛出异常,不是吗?喜欢超出范围的索引。
3赞 James Morris 7/15/2013
@EdwardOlamisan,字符串永远不会为空。
6赞 chux - Reinstate Monica 7/2/2014
@James 莫里斯 在不寻常的情况下--> .1) 读作第一个 a .2) 3) 返回,则内容可以是任何东西。(OP的代码确实测试了NULL)建议:fgets(buf, size, ....)strlen(buf) == 0fgets()char'\0'size == 1fgets()NULLbufsize_t ln = strlen(name); if (ln > 0 && name[ln-1] == '\n') name[--ln] = '\0';
2赞 abligh 3/17/2015
如果字符串为空怎么办? 将是 -1,除了事实是无符号的,因此写入随机内存。我想你想使用和检查是>0。lnsize_tssize_tln
3赞 AnT stands with Russia 3/30/2016
@legends2k:对编译时值(尤其是 中的零值)的搜索可以比普通的逐个字符搜索更有效。出于这个原因,我认为这个解决方案比基于或基于的解决方案更好。strlenstrchrstrcspn
12赞 Amitabha 6/30/2013 #3

如果每行都有 '\n',则直接从 fgets 输出中删除 '\n'

line[strlen(line) - 1] = '\0';

否则:

void remove_newline_ch(char *line)
{
    int new_line = strlen(line) -1;
    if (line[new_line] == '\n')
        line[new_line] = '\0';
}

评论

1赞 Mike Mertsock 6/30/2013
请注意,使用代替 .strnlenstrlen
3赞 Étienne 11/20/2013
在链接的问题中,对第一个答案的评论指出:“请注意,strlen()、strcmp() 和 strdup() 是安全的。'n' 选项为您提供了额外的功能。
4赞 M.M 10/29/2015
@esker不,它不会。插入 an 不会神奇地提高安全性,在这种情况下,它实际上会使代码更加危险。与 类似,这是一个非常不安全的函数。您链接到的帖子是不好的建议。nstrncpy
5赞 alk 6/17/2017
对于空字符串 (),这会惨遭失败。也返回 not 。""strlen()size_tint
6赞 Jean-François Fabre 6/15/2018
这对于空字符串来说是不安全的,它将写入索引 -1。不要使用它。
28赞 chux - Reinstate Monica 1/1/2015 #4

下面是从 保存的字符串中删除电位的快速方法。
它使用 ,有 2 个测试。
'\n'fgets()strlen()

char buffer[100];
if (fgets(buffer, sizeof buffer, stdin) != NULL) {

  size_t len = strlen(buffer);
  if (len > 0 && buffer[len-1] == '\n') {
    buffer[--len] = '\0';
  }
  // `len` now represents the length of the string, shortened or not.

现在根据需要使用。bufferlen

此方法的附带优点是后续代码的值。它可以很容易地比 .裁判YMMV,但这两种方法都有效。lenstrchr(Name, '\n')


buffer,从原文中在某些情况下不会包含:
A)该行太长,因此仅在前面保存在。未读字符保留在流中。
B) 文件中的最后一行没有以 .
fgets()"\n"bufferchar'\n'buffer'\n'

如果输入在某处嵌入了 null 字符,则报告的长度将不包括位置。'\0'strlen()'\n'


其他一些答案的问题:

  1. strtok(buffer, "\n");无法删除 when 是 。从这个答案 - 在此答案之后进行修改,以警告此限制。'\n'buffer"\n"

  2. 在极少数情况下,当第一次读取者是 时,以下操作会失败。当输入以嵌入式 .然后成为访问内存肯定超出了 的合法范围。黑客在愚蠢地读取 UTF16 文本文件时可能会尝试或发现的东西。这是写这个答案时的答案的状态。后来,一个非 OP 对其进行了编辑,以包含类似此答案的代码。charfgets()'\0''\0'buffer[len -1]buffer[SIZE_MAX]buffer""

     size_t len = strlen(buffer);
     if (buffer[len - 1] == '\n') {  // FAILS when len == 0
       buffer[len -1] = '\0';
     }
    
  3. sprintf(buffer,"%s",buffer);是未定义的行为:Ref。此外,它不会保存任何前导、分隔或尾随空格。现已删除

  4. [由于后来的好答案而编辑]与方法相比,除了性能之外,1 衬垫没有问题。修整性能通常不是问题,因为代码正在执行 I/O - CPU 时间的黑洞。如果以下代码需要字符串的长度或具有很高的性能意识,请使用此方法。否则,这是一个很好的选择。buffer[strcspn(buffer, "\n")] = 0;strlen()strlen()strcspn()

评论

0赞 rrz0 11/10/2018
感谢您的有用回答。当缓冲区大小是动态分配时,我们可以使用吗?strlen(buffer)malloc
0赞 chux - Reinstate Monica 11/11/2018
@Rrz0 是错误的 - 指向的内存中的数据未知。 没问题buffer = malloc(allocation_size); length = strlen(buffer);bufferbuffer = malloc(allocation_size_4_or_more); strcpy(buffer, "abc"); length = strlen(buffer);
627赞 Tim Čas 2/12/2015 #5

也许最简单的解决方案使用我最喜欢的鲜为人知的函数之一 strcspn():

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

如果你希望它也处理(比如说,如果流是二进制的):'\r'

buffer[strcspn(buffer, "\r\n")] = 0; // works for LF, CR, CRLF, LFCR, ...

该函数计算字符数,直到它命中 a 或 a(换句话说,它找到第一个 或 )。如果它没有命中任何东西,它就会停在(返回字符串的长度)处。'\r''\n''\r''\n''\0'

请注意,即使没有换行符,这也有效,因为停在 .在这种情况下,整行只是用 替换成 。strcspn'\0''\0''\0'

评论

48赞 chux - Reinstate Monica 2/12/2015
这甚至处理了罕见的比开始的,这给这种方法带来了悲伤。buffer'\0'buffer[strlen(buffer) - 1] = '\0';
7赞 Tim Čas 2/12/2015
@chux:是的,我希望更多的人知道。IMO是库中比较有用的功能之一。我决定编写并发布一堆常见的 C 技巧,就像今天一样;使用 和 的实现是最早的实现之一:codepad.org/2lBkZk0w警告:我不能保证它没有错误;它是匆忙编写的,可能有一些)。不过,我还不知道我会在哪里发布它们,但我打算本着著名的“bit twiddling hacks”的精神来制作它。strcspn()strtok_rstrcspnstrspn
4赞 chux - Reinstate Monica 2/12/2015
研究了稳健地修剪 fgets() 的方法。这似乎是唯一正确的单行。strlen 更快 - 虽然没有那么简单。strcspn()
7赞 Tim Čas 4/9/2017
@sidbushes:这个问题,无论是在标题还是内容中,都询问了 fgets() 输入的尾随换行符。这始终也是第一个换行符。
10赞 Tim Čas 5/8/2017
@sidbushes:我理解你的来历,但我不对特定字词的谷歌搜索结果负责。与谷歌交谈,而不是与我交谈。
2赞 BEPP 3/16/2015 #6

对于单个 '\n' 修剪,

void remove_new_line(char* string)
{
    size_t length = strlen(string);
    if((length > 0) && (string[length-1] == '\n'))
    {
        string[length-1] ='\0';
    }
}

对于多个 '\n' 修剪,

void remove_multi_new_line(char* string)
{
  size_t length = strlen(string);
  while((length>0) && (string[length-1] == '\n'))
  {
      --length;
      string[length] ='\0';
  }
}

评论

1赞 melpomene 9/17/2018
当您可以使用简单地编写一个条件时,为什么要嵌套?那个循环有一个奇怪的结构;它可能只是.if&&whilewhile (length > 0 && string[length-1] == '\n') { --length; string[length] = '\0'; }
0赞 BEPP 9/18/2018
@melpomene感谢您的建议。更新代码。
1赞 melpomene 9/18/2018
我建议第一个函数更自然地定义为:。这也更好地反映了第二个定义(只是使用而不是 )。size_t length = strlen(string); if (length > 0 && string[length-1] == '\n') { string[length-1] = '\0'; }ifwhile
0赞 BEPP 9/18/2018
@elpomene谢谢。这是有道理的。我更新了代码。
-1赞 Philippe A. 11/19/2015 #7

Tim Čas 一行对于通过调用 fgets 获得的字符串来说是惊人的,因为你知道它们在末尾包含一个换行符。

如果您处于不同的上下文中,并且想要处理可能包含多个换行符的字符串,则可能需要查找 strrspn。它不是 POSIX,这意味着您不会在所有 Unices 上找到它。我为自己的需要写了一个。

/* Returns the length of the segment leading to the last 
   characters of s in accept. */
size_t strrspn (const char *s, const char *accept)
{
  const char *ch;
  size_t len = strlen(s);

more: 
  if (len > 0) {
    for (ch = accept ; *ch != 0 ; ch++) {
      if (s[len - 1] == *ch) {
        len--;
        goto more;
      }
    }
  }
  return len;
}

对于那些在 C 中寻找 Perl chomp 等效物的人来说,我认为就是这样(chomp 只删除尾随换行符)。

line[strrspn(string, "\r\n")] = 0;

strrcspn 函数:

/* Returns the length of the segment leading to the last 
   character of reject in s. */
size_t strrcspn (const char *s, const char *reject)
{
  const char *ch;
  size_t len = strlen(s);
  size_t origlen = len;

  while (len > 0) {
    for (ch = reject ; *ch != 0 ; ch++) {
      if (s[len - 1] == *ch) {
        return len;
      }
    }
    len--;
  }
  return origlen;
}

评论

2赞 chux - Reinstate Monica 11/19/2015
“因为你知道它们在末尾包含一个换行符。” --> 它甚至在没有(或者如果字符串是)时起作用。'\n'""
0赞 Philippe A. 11/19/2015
作为对你的第一条评论的回应,我的回答保留了这一点。当没有.strrcspn\n
0赞 chqrlie 8/17/2016
为什么用代替?goto end;return len;
0赞 Philippe A. 11/30/2016
@chqrlie我需要摆脱我陷入的这个不优雅的 2 级循环。伤害已经造成。为什么不是goto?
0赞 chqrlie 11/30/2016
你的代码中有两种:一种是无用的,可以用语句代替,另一种是被认为是邪恶的。使用有助于以更简单的方式实现: 和gotogotoreturngotostrchrstrrspnstrrcspnsize_t strrspn(const char *s, const char *accept) { size_t len = strlen(s); while (len > 0 && strchr(accept, s[len - 1])) { len--; } return len; }size_t strrcspn(const char *s, const char *reject) { size_t len = strlen(s); while (len > 0 && !strchr(reject, s[len - 1])) { len--; } return len; }
-2赞 fnisi 2/18/2016 #8

下面的函数是我在 Github 上维护的字符串处理库的一部分。它从字符串中删除不需要的字符,这正是您想要的

int zstring_search_chr(const char *token,char s){
    if (!token || s=='\0')
        return 0;

    for (;*token; token++)
        if (*token == s)
            return 1;

    return 0;
}

char *zstring_remove_chr(char *str,const char *bad) {
    char *src = str , *dst = str;
    while(*src)
        if(zstring_search_chr(bad,*src))
            src++;
        else
            *dst++ = *src++;  /* assign first, then incement */

    *dst='\0';
        return str;
}

一个示例用法可以是

Example Usage
      char s[]="this is a trial string to test the function.";
      char const *d=" .";
      printf("%s\n",zstring_remove_chr(s,d));

  Example Output
      thisisatrialstringtotestthefunction

您可能想检查其他可用功能,甚至为项目做出贡献:)https://github.com/fnoyanisi/zString

评论

0赞 chqrlie 8/17/2016
您应该删除 in 和 make 和 。另外为什么不用代替呢? 不能在您的函数中。**src++;badtokendconst char *strchrzChrSearch*src'\0'zStrrmv
0赞 fnisi 10/21/2016
谢谢@chqrlie!更新了代码以反映您的建议.....zstring 最初是一个有趣的项目,目的是在不使用任何标准库函数的情况下创建一个字符串操作库,因此我没有使用strchr
3赞 melpomene 9/17/2018
编写“不使用任何标准库函数的字符串操作库”是一个很好的练习,但为什么要告诉其他人使用它呢?如果有的话,它将比任何标准库更慢,测试更少。
0赞 Jonathan Leffler 10/14/2018
这与问题所问的工作不同。它可能可以用来摆脱唯一的换行符,但感觉有点矫枉过正。
-2赞 Matheus Martins Jerônimo 9/17/2017 #9
 for(int i = 0; i < strlen(Name); i++ )
{
    if(Name[i] == '\n') Name[i] = '\0';
}

你应该试一试。这段代码基本上遍历字符串,直到找到“\n”。找到后,“\n”将被空字符终止符“\0”替换

请注意,您在此行中比较的是字符而不是字符串,因此无需使用 strcmp():

if(Name[i] == '\n') Name[i] = '\0';

因为您将使用单引号而不是双引号。如果您想了解更多信息,这里有一个关于单引号与双引号的链接

评论

3赞 chux - Reinstate Monica 12/7/2017
效率低下:会调用很多次(循环变化),所以有一个长度,这是一个解决方案。只需要调用 1 次 ,如果有的话,就可以提供 O(N)' 解。不清楚为什么使用而不是 .考虑for(int i = 0; i < strlen(Name); i++ )strlen(Name)Name[]NO(N*N)strlen(Name)int isize_t ifor(size_t i = 0; i < Name[i]; i++ )
0赞 melpomene 9/17/2018
@chux 更多类似for (size_t i = 0; Name[i]; i++) { if (Name[i] == '\n') { Name[i] = '\0'; break; } }
0赞 chux - Reinstate Monica 9/17/2018
@melpomene 是的,这将是直接和好的。然而,如果 不存在,则会发生,并且以下值为 0,停止循环。你的好主意的优点是循环之后的字符串长度。breaki++Name[i]i
0赞 chux - Reinstate Monica 9/17/2018
@melpomene我现在明白了。是的,应该是for(size_t i = 0; i < Name[i]; i++ )for(size_t i = 0; Name[i]; i++ )
1赞 sjsam 11/25/2017 #10

如果使用 POSIX getline() 是一个选项 - 不要忽视它的安全问题,并且如果你想用指针括起来 - 你可以避免使用字符串函数,因为返回字符数。如下所示:getline

#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *fname, *lname;
    size_t size = 32, nchar; // Max size of strings and number of characters read
    fname = malloc(size * sizeof *fname);
    lname = malloc(size * sizeof *lname);
    if (NULL == fname || NULL == lname)
    {
        printf("Error in memory allocation.");
        exit(1);
    }
    printf("Enter first name ");
    nchar = getline(&fname, &size, stdin);
    if (nchar == -1) // getline return -1 on failure to read a line.
    {
        printf("Line couldn't be read..");
        // This if block could be repeated for next getline too
        exit(1);
    }
    printf("Number of characters read :%zu\n", nchar);
    fname[nchar - 1] = '\0';
    printf("Enter last name ");
    nchar = getline(&lname, &size, stdin);
    printf("Number of characters read :%zu\n", nchar);
    lname[nchar - 1] = '\0';
    printf("Name entered %s %s\n", fname, lname);
    return 0;
}

注意getline[安全问题]不容忽视。

1赞 Duck Ling 2/6/2019 #11

我的新手方式;-)如果正确,请告诉我。它似乎适用于我的所有情况:

#define IPT_SIZE 5

int findNULL(char* arr)
{
    for (int i = 0; i < strlen(arr); i++)
    {
        if (*(arr+i) == '\n')
        {
            return i;
        }
    }
    return 0;
}

int main()
{
    char *input = malloc(IPT_SIZE + 1 * sizeof(char)), buff;
    int counter = 0;

    //prompt user for the input:
    printf("input string no longer than %i characters: ", IPT_SIZE);
    do
    {
        fgets(input, 1000, stdin);
        *(input + findNULL(input)) = '\0';
        if (strlen(input) > IPT_SIZE)
        {
            printf("error! the given string is too large. try again...\n");
            counter++;
        }
        //if the counter exceeds 3, exit the program (custom function):
        errorMsgExit(counter, 3); 
    }
    while (strlen(input) > IPT_SIZE);

//rest of the program follows

free(input)
return 0;
}
1赞 RobertS supports Monica Cellio 3/6/2020 #12

以最明显的方式删除换行符的步骤:

  1. 使用 , header 确定里面字符串的长度。请注意,这不计算终止 .NAMEstrlen()string.hstrlen()\0
size_t sl = strlen(NAME);

  1. 查看字符串是否以一个字符开头或仅包含一个字符(空字符串)。在这种情况下,因为正如我上面所说,它不计算并在第一次出现时停止:\0sl0strlen()\0
if(sl == 0)
{
   // Skip the newline replacement process.
}

  1. 检查正确字符串的最后一个字符是否为换行符。如果是这种情况,请替换为 .请注意,索引计数从 开始,因此我们需要执行以下操作:'\n'\n\00NAME[sl - 1]
if(NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

请注意,如果仅在字符串请求时按 Enter(字符串内容仅由换行符组成),则此后 in 中的字符串将是一个空字符串。fgets()NAME


  1. 我们可以结合第 2 步。和 3.使用逻辑运算符在一个 -语句中:if&&
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

  1. 完成的代码:
size_t sl = strlen(NAME);
if(sl > 0 && NAME[sl - 1] == '\n')
{
   NAME[sl - 1] = '\0';
}

如果您更喜欢通过处理输出字符串来使用此技术的函数,而无需每次都重新键入,这里是:fgetsfgets_newline_kill

void fgets_newline_kill(char a[])
{
    size_t sl = strlen(a);

    if(sl > 0 && a[sl - 1] == '\n')
    {
       a[sl - 1] = '\0';
    }
}

在您提供的示例中,它将是:

printf("Enter your Name: ");

if (fgets(Name, sizeof Name, stdin) == NULL) {
    fprintf(stderr, "Error reading Name.\n");
    exit(1);
}
else {
    fgets_newline_kill(NAME);
}

请注意,如果输入字符串中嵌入了 s,则此方法不起作用。如果是这样的话,将只返回字符的数量,直到第一个 .但这并不是一种很常见的方法,因为大多数字符串读取函数通常停在第一个函数上,并将字符串直到该 null 字符为止。\0strlen()\0\0

撇开问题本身不谈。尽量避免使代码更不清晰的双重否定:.你可以简单地做.if (!(fgets(Name, sizeof Name, stdin) != NULL) {}if (fgets(Name, sizeof Name, stdin) == NULL) {}

评论

0赞 ad absurdum 3/6/2020
不知道为什么要这样做。删除换行符的重点不是以 null 结尾的字符串;这是为了删除换行符。将字符串末尾的 a 替换为 a 是“删除”换行符的一种方式。但是,替换字符串中的字符会从根本上改变字符串。字符串中故意包含多个换行符的情况并不少见,这将有效地切断这些字符串的末端。要删除此类换行符,数组内容需要左移以覆盖 .\n\0\n\n
0赞 RobertS supports Monica Cellio 3/6/2020
@exnihilo 如何使用输入包含多个换行符的字符串?fgets()
0赞 ad absurdum 3/6/2020
好吧,您可以将通过多次调用获得的字符串连接起来。但我不明白你的反对意见:你是提出处理多个换行符的代码的人。fgets()
0赞 RobertS supports Monica Cellio 3/6/2020
@exnihilo 你是对的,我会过度考虑策略。我只是想添加一种非常苛刻但可行的方法来获得所需的结果。
0赞 RobertS supports Monica Cellio 3/6/2020
@exnihilo 完全编辑了我的答案,并使用等遵循主要方法。不重复的理由: 1.按步骤解释代码。2. 作为基于函数和上下文的解决方案提供。3. 提示避免双重否定表达式。strlen
1赞 William Pursell 11/26/2020 #13

一般来说,与其修剪你不想要的数据,不如首先避免写入它。如果不希望缓冲区中使用换行符,请不要使用 fgets。请改用 or 或 .也许是这样的:getcfgetcscanf

#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
        char Name[256];
        char fmt[32];
        if( snprintf(fmt, sizeof fmt, "%%%zd[^\n]", sizeof Name - 1) >= (int)sizeof fmt ){
                fprintf(stderr, "Unable to write format\n");
                return EXIT_FAILURE;
        }
        if( scanf(fmt, Name) == 1 ) {
                printf("Name = %s\n", Name);
        }
        return 0;
}

请注意,这种特定方法将使换行符保持未读状态,因此您可能希望使用格式字符串,例如丢弃它(例如,),或者在扫描后加上一个 ."%255[^\n]%*c"sprintf(fmt, "%%%zd[^\n]%%*c", sizeof Name - 1);getchar()

评论

0赞 Sapphire_Brick 8/20/2021
您是否意识到上述代码片段容易受到缓冲区溢出的影响? 不检查缓冲区的大小!sprintf
1赞 William Pursell 8/20/2021
@Sapphire_Brick 事实并非如此。格式字符串的长度将为 7 + 以 10 为基数表示名称长度的位数。如果该长度大于 24,则会遇到其他问题。如果你想安全使用,你当然可以,但这适用于明显大于 PB 的缓冲区。snprintf
0赞 William Pursell 8/20/2021
为了溢出缓冲区,您需要创建一个大约 8 个字节的自动数组,因为在缓冲区大小超过 2^83 字节之前,您不会溢出缓冲区。实际上,这不是问题。但是,是的,应该始终优先于 .代码已编辑。Namesnprintfsprintf
0赞 supercat 11/25/2022
以这种方式使用而不是简单地使用循环有什么好处吗?scanfgetchar()
0赞 William Pursell 11/26/2022
@supercat 绝对不是。 IMO,永远不应该用于任何事情,但它似乎是一个流行的选择,并且经常被滥用。scanf
-2赞 YuTakamoto 12/11/2021 #14

这是我的解决方案。很简单。

// Delete new line
// char preDelete[256]  include "\n" as newline after fgets

char deletedWords[256];
int iLeng = strlen(preDelete);
int iFinal = 0;
for (int i = 0; i < iLeng; i++) {
    if (preDelete[i] == '\n') {

    }
    else {
        deletedWords[iFinal]  = preDelete[i];
        iFinal++;
    }
    if (i == iLeng -1 ) {
        deletedWords[iFinal] = '\0';
    }
}
0赞 Lundin 3/13/2023 #15

扩展 @Jerry Coffin 和 @Tim Čas 的答案:

从设计上讲,该版本比 (并且版本可能是最快的)快得多。strchrstrcspnstrlen

的内部必须遍历字符串,如果实现合理,它只执行一次,并将字符串长度存储在某处。然后,在搜索时,它还必须使用嵌套的 for 循环来遍历字符串。strcspn"\n""\n"

忽略这些函数的库质量实现会考虑的字大小等因素,朴素的实现可能如下所示:

char* my_strchr (const char *s, int c)
{
  while(*s != '\0')
  {
    if(*s == c)
      return (char*)s;
    s++;
  }
  return NULL;
}

size_t my_strcspn (const char *s1, const char *s2)
{
  size_t s2_length = strlen(s2);
  size_t i;
  for(i=0; s1[i] != '\0'; i++)
  {
    for(size_t j=0; j<s2_length; j++)
    {
      if(s1[i] == s2[j])
      {
        return i;
      }
    }
  }
  return i;
}
  • 如果是 ,则每个字符有两个分支。一个搜索 null 终止符,另一个将当前字符与搜索的字符进行比较。strchr

  • 在 的情况下,它要么必须像我的示例中那样预先计算大小,要么在查找 null 和搜索键时遍历它。后者本质上就是它的作用,所以内部循环可以用 .无论我们如何实现它,都会有很多额外的分支。strcspns2strchrstrchr

    细心的语言律师可能还会发现标准图书馆定义中缺少。这意味着编译器不允许假定 和 是不同的字符串。这也阻止了一些优化。restrictstrcspns1s2

该版本将比两者都快,因为只需要检查空终止,而不需要检查其他任何内容。虽然正如 @chux - 恢复莫妮卡的答案中提到的,但在某些情况下它不起作用,因此它比其他版本稍微脆弱一些。strlenstrlen

问题的根源是函数的 API 错误——如果它在过去实现得更好,它会返回与实际读取的字符数相对应的大小,这本来很棒。或者,指向最后一个字符的指针读作 .相反,标准库通过返回指向传递的字符串中第一个字符的指针来浪费返回值,这有点有用。fgetsstrchr

评论

0赞 Lundin 3/13/2023
想象一下,如果我们有一个像这样的函数。嗖嗖嗖,尾随固定和实际大小读取,就这样。要是老 Unix 人多花 5 分钟考虑函数的 API 就好了......char* result = fgetsane(s,n,stdin); if(result != NULL) { size_t size = result - s; if(*result == '\n') *result = '\0'; }\n