C 这些方法是否可以消除'\n';关于性能,fgets的左图有所不同?
我目前正在学习C,如果这个问题看起来很简单或者是新手,那么你知道为什么。 因此,我知道有很多方法可以删除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 他们完成了自己的工作,但就表现而言,他们是
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'
使用两个额外的测试,以减少执行