Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/65.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 这些方法是否可以消除'\n';关于性能,fgets的左图有所不同?_C_Performance_Escaping_Fgets_C Strings - Fatal编程技术网

C 这些方法是否可以消除'\n';关于性能,fgets的左图有所不同?

C 这些方法是否可以消除'\n';关于性能,fgets的左图有所不同?,c,performance,escaping,fgets,c-strings,C,Performance,Escaping,Fgets,C Strings,我目前正在学习C,如果这个问题看起来很简单或者是新手,那么你知道为什么。 因此,我知道有很多方法可以删除fgets()留下的'\n',前面已经讨论过了 我将集中讨论这三种方法: char*strchr(常量char*s,intc) char*strtok(char*str,const char*delim) 大小\u t strcspn(常量字符*s,常量字符*拒绝) 假设变量p和token声明为char*p=NULL,*token=NULL 他们完成了自己的工作,但就表现而言,他们是

我目前正在学习C,如果这个问题看起来很简单或者是新手,那么你知道为什么。

因此,我知道有很多方法可以删除
fgets()
留下的
'\n'
,前面已经讨论过了

我将集中讨论这三种方法:

  • char*strchr(常量char*s,intc)

  • char*strtok(char*str,const char*delim)

  • 大小\u t strcspn(常量字符*s,常量字符*拒绝)

假设变量
p
token
声明为
char*p=NULL,*token=NULL

他们完成了自己的工作,但就表现而言,他们是否有所不同

有一次,在网上冲浪(很抱歉,我没有证据证明这一点,因为我忘记了链接),我发现如果一个人对性能感兴趣,
strspn
不是一个真正的好方法,因此我提出了问题

在发布这篇文章之前,我已经在这里搜索了,但没有找到我想知道的。我自己也尝试过分析它,都是使用
time./executable
,然后发现是这样的。但是我没有运气,因为结果不一致

有人能帮我找出我描述的是错误的还是他们真的是平等的吗


编辑:是我发现
strcspn
无效的链接。

这个方法是这样写的

if (fgets(sentence, 11, stdin) != NULL) {
    p = strchr(sentence,'\n');
    p = '\0';
    //^^ must be *p 
}
不正确,因为字符串中可能缺少新行字符。在这种情况下,指针
p
将等于
NULL
,代码段将具有未定义的行为

你需要像这样改变它

    if ( p ) *p = '\0';

因为只搜索一个字符,所以它足够有效

但是,它的缺点是需要一个额外的变量来指向新行字符

这种方法

if (fgets(sentence, 11, stdin) != NULL)
    token = strtok(sentence, "\n");
语义上不太合适。当您需要将字符串拆分为令牌时,通常在其他上下文中使用函数
strtok
。 如果字符串只包含新行字符,则函数返回空指针

所以最合适的方法是

if (fgets(sentence, 11, stdin) != NULL)
    sentence[strcspn(sentence, "\n")] = 0;
因为它是安全的,不需要额外的变量

至于我,那么在C++中,我会使用< /P>

if ( char *p = strchr( sentence, '\n' ) ) *p = '\0';
在C中,我会使用:)


在讨论性能之前,验证正确性是很重要的

让我们看看您的方法和其他一些流行的方法:

strchr

if (fgets(sentence, 11, stdin) != NULL) {
    p = strchr(sentence, '\n');
    *p = '\0';
}
您忘记测试
p
是否为
NULL
。这是一个大问题,因为
语句
可能不包含
\n
,这可能是因为读取的行很长,只有一部分在
语句
中,或者如果文件中的最后一行没有以
\n
结尾,或者文件包含空字节。您应按以下方式编写此版本:

if (fgets(sentence, 11, stdin) != NULL) {
    char *p = strchr(sentence, '\n');
    if (p != NULL)
        *p = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    sentence[strlen(sentence) - 1] = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    size_t len = strlen(sentence);
    if (len > 0 && sentence[len - 1] == '\n')
        sentence[--len] = '\0';
    // useful side effect: len has been updated.
    ...
}
strchrnul

一些C库有一个非标准的函数
strchrnul
,使用这个原型:

char *strchrnul(const char *s, int c);
它返回一个指针,指向字符串
s
中第一个出现的
c
,如果找不到出现的
\0
,则返回一个指针,指向最后一个
\0
。此函数允许以非常简单有效的方式剥离
\n

if (fgets(sentence, 11, stdin) != NULL) {
    *strchrnul(sentence, '\n') = '\0';
    ...
}
唯一的缺点是该功能不是C标准的一部分,在某些平台上可能不可用

strtok

if (fgets(sentence, 11, stdin) != NULL) {
    token = strtok(sentence, "\n");
    ...
}
此版本不正确:
strtok
对其内部数据有副作用。此版本将干扰使用strtok的周围代码。如果将此方法隐藏在函数中,则会隐藏此副作用,并可能导致使用此函数的程序员很难找到bug。您可以使用可重入版本的
strtok
strtok\u r
,但它并不总是可用

此外,正如user3121023所评论的那样,
strtok
不会删除
\n
,如果它位于字符串的开头。这肯定使这种方法不合格。(
strtok
有太多的怪癖,无论如何都应该完全避免。)

strlen

您没有提到strlen
备选方案。我经常看到它是这样写的:

if (fgets(sentence, 11, stdin) != NULL) {
    char *p = strchr(sentence, '\n');
    if (p != NULL)
        *p = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    sentence[strlen(sentence) - 1] = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    size_t len = strlen(sentence);
    if (len > 0 && sentence[len - 1] == '\n')
        sentence[--len] = '\0';
    // useful side effect: len has been updated.
    ...
}
这是不正确的,原因有多种:

  • 语句
    可能没有一个
    \n
    作为其最后一个字符,正如前面对
    strhr
    版本所解释的那样。尝试以这种方式删除
    \n
    将删除有效字符

  • 语句
    可能是空字符串,在这种情况下,代码将具有未定义的行为。对于
    语句
    为空,需要C标准中未指定的特殊条件:如果输入流在行首包含NUL字节,
    fgets()
    可能返回空缓冲区

为确保正确性,应通过以下方式实施此方法:

if (fgets(sentence, 11, stdin) != NULL) {
    char *p = strchr(sentence, '\n');
    if (p != NULL)
        *p = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    sentence[strlen(sentence) - 1] = '\0';
    ...
}
if (fgets(sentence, 11, stdin) != NULL) {
    size_t len = strlen(sentence);
    if (len > 0 && sentence[len - 1] == '\n')
        sentence[--len] = '\0';
    // useful side effect: len has been updated.
    ...
}
strcspn

if (fgets(sentence, 11, stdin) != NULL) {
    sentence[strcspn(sentence, "\n")] = '\0';
    ...
}
这是最简单的版本。无论
语句
是否包含
\n
,甚至是空字符串,它都可以工作。它不太可能被程序员误用

性能

strcspn的效率是高于还是低于其他方法很大程度上取决于C库实现和编译器性能。性能应该比strtok更好,因为它只进行一次扫描。它的效率可能低于
strhr
,甚至低于
strlen
,但为了确保正确性,
strlen
替代方案还应该对
len>0
句子[len-1]=='\n'
使用两个额外的测试,以减少执行