我尝试编写自己的strchr()方法实现。
现在看起来像这样:
char *mystrchr(const char *s, int c) {
while (*s != (char) c) {
if (!*s++) {
return NULL;
}
}
return (char *)s;
}
最后一行是
return s;
但这不起作用,因为s是const。我发现需要这个演员(char *),但老实说我不知道我在那里做什么:(有人可以解释一下吗?
答案 0 :(得分:19)
我认为这实际上是C标准对strchr()
函数定义的一个缺陷。 (我会很高兴被证明是错的。)(回答这些评论,它是否真的是一个缺陷是有争议的;恕我直言,它仍然是糟糕的设计。可以安全使用,但它太容易了不安全地使用它。)
以下是C标准所说的内容:
char *strchr(const char *s, int c);
strchr 函数定位第一次出现的 c (转换为字符)在 s 指向的字符串中。该 终止空字符被认为是字符串的一部分。
这意味着这个程序:
#include <stdio.h>
#include <string.h>
int main(void) {
const char *s = "hello";
char *p = strchr(s, 'l');
*p = 'L';
return 0;
}
即使它小心地将指向字符串文字的指针定义为 const
char
的指针,也有未定义的行为,因为它修改了字符串文字。 gcc,至少,没有对此发出警告,程序因分段错误而死亡。
问题是strchr()
采用const char*
参数,这意味着它承诺不会修改s
指向的数据 - 但它会返回普通char*
,允许调用者修改相同的数据。
这是另一个例子; 它没有未定义的行为,但它悄悄地修改了一个const
限定的对象而没有任何强制转换(进一步认为,我相信它有未定义的行为):
#include <stdio.h>
#include <string.h>
int main(void) {
const char s[] = "hello";
char *p = strchr(s, 'l');
*p = 'L';
printf("s = \"%s\"\n", s);
return 0;
}
我认为,(回答您的问题),strchr()
的C实现必须将其结果转换为const char*
转换为char*
,或者执行相同的操作
这就是为什么C ++在对C标准库进行的少数更改之一中用两个同名的重载函数替换strchr()
:
const char * strchr ( const char * str, int character );
char * strchr ( char * str, int character );
当然C不能这样做。
替代方法是将strchr
替换为两个函数,一个使用const char*
并返回const char*
,另一个使用char*
并返回{{} 1}}。与C ++不同,这两个函数必须具有不同的名称,可能是char*
和strchr
。
(从历史上看,strcchr
已经定义const
后添加到了strchr()
。这可能是在不破坏现有代码的情况下保留strchr()
的唯一方法。)
strchr()
不是唯一存在此问题的C标准库函数。受影响的功能列表(我认为此列表已完成,但我不保证):
void *memchr(const void *s, int c, size_t n);
char *strchr(const char *s, int c);
char *strpbrk(const char *s1, const char *s2);
char *strrchr(const char *s, int c);
char *strstr(const char *s1, const char *s2);
(全部在<string.h>
中声明)和:
void *bsearch(const void *key, const void *base,
size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
(在<stdlib.h>
中声明)。所有这些函数都使用指向const
数据的指针,该数据指向数组的初始元素,并返回指向该数组元素的非const
指针。
答案 1 :(得分:14)
从非修改函数返回非const指针到const数据的做法实际上是在C语言中广泛使用的 idiom 。它并不总是漂亮,但它已经相当成熟。
这里的reationale很简单:strchr
本身就是一个非修改操作。然而,对于常量字符串和非常量字符串,我们需要strchr
功能,这也会将输入的常量传播到输出的常量。 C和C ++都没有为这个概念提供任何优雅的支持,这意味着在两种语言中你都必须编写两个几乎相同的函数,以避免带有const正确性的任何风险。
在C ++中,你可以通过声明两个具有相同名称的函数来使用函数重载
const char *strchr(const char *s, int c);
char *strchr(char *s, int c);
在C中你没有函数重载,所以为了在这种情况下完全强制const正确,你必须提供两个带有不同名称的函数,比如
const char *strchr_c(const char *s, int c);
char *strchr(char *s, int c);
虽然在某些情况下这可能是正确的做法,但它通常(并且正确地)被认为过于繁琐且涉及C标准。您可以通过仅实现一个函数
以更紧凑(尽管风险更大)的方式解决这种情况char *strchr(const char *s, int c);
将非const指针返回到输入字符串中(通过在出口处使用强制转换,就像您一样)。请注意,此方法不违反任何语言规则,但它为调用者提供了违反它们的方法。通过抛弃数据的常量,这种方法简单地将责任从函数本身委托给调用者。只要调用者知道发生了什么并记得“玩得很好”,即使用const限定指针指向const数据,由此类函数创建的const-correctness中的任何临时破坏都会立即得到修复。
我认为这个技巧是减少不必要的代码重复的完全可接受的方法(特别是在没有函数重载的情况下)。标准库使用它。假设你明白自己在做什么,你也没有理由避免它。
现在,关于strchr
的实现,从风格的角度来看,我看起来很奇怪。我会使用循环标头迭代我们正在操作的整个范围(完整字符串),并使用内部if
来捕获提前终止条件
for (; *s != '\0'; ++s)
if (*s == c)
return (char *) s;
return NULL;
但这样的事情总是个人喜好的问题。有人可能更喜欢
for (; *s != '\0' && *s != c; ++s)
;
return *s == c ? (char *) s : NULL;
有些人可能会说在函数内修改函数参数(s
)是一种不好的做法。
答案 2 :(得分:1)
const
关键字表示无法修改参数。
您无法直接返回s
,因为s
被声明为const char *s
,并且该函数的返回类型为char *
。如果编译器允许您这样做,则可以覆盖const
限制。
向char*
添加一个显式强制转换告诉编译器你知道自己在做什么(尽管Eric解释说,如果你不这样做会更好。)
更新:为了上下文,我引用了Eric的答案,因为他似乎删除了它:
你不应该修改s,因为它是一个const char *。
相反,定义一个表示char *类型结果的局部变量,并用它代替方法体中的s。
答案 3 :(得分:0)
函数返回值应该是字符的常量指针:
strchr
接受const char*
并且还应返回const char*
。你返回一个非常量,这是有潜在危险的,因为返回值指向输入字符数组(调用者可能期望常量参数保持不变,但如果它的任何部分返回为{{1}它是可修改的指针)。
如果找不到匹配的字符,则函数返回值应为NULL:
如果找不到所寻找的角色,char *
也应返回strchr
。如果在找不到字符时返回非NULL,或者在这种情况下返回s,则调用者(如果他认为行为与strchr相同)
可能会假设结果中的第一个字符实际匹配(没有NULL返回值
没有办法判断是否有匹配。)
(我不确定这是不是你打算做的。)
以下是执行此操作的功能示例:
我写了这个函数并运行了几个测试;我添加了一些非常明显的健全性检查以避免潜在的崩溃:
NULL