strchr实现如何工作
我试着编写自己的strchr()方法实现。
它现在看起来像这样:
char *mystrchr(const char *s, int c) { while (*s != (char) c) { if (!*s++) { return NULL; } } return (char *)s; }
最后一行原来是
return s;
但这不起作用,因为s是const。 我发现需要这个演员(char *),但老实说我不知道我在那里做什么:(有人可以解释一下吗?
我相信这实际上是C Standard对strchr()
函数定义的一个缺陷。 (我会很高兴被certificate是错误的。)(回答这些评论,它是否真的是一个缺陷是有争议的;恕我直言,它仍然是糟糕的设计。它可以安全使用,但它太容易使用它不安全。)
这是C标准所说的:
char *strchr(const char *s, int c);
strchr函数定位s指向的字符串中第一次出现的c (转换为char )。 终止空字符被认为是字符串的一部分。
这意味着这个程序:
#include #include 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 #include 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*
并返回一个char*
。 与C ++不同,这两个函数必须具有不同的名称,可能是strchr
和strcchr
。
(从历史上看,在strchr()
已经定义之后, const
被添加到C中。这可能是保持strchr()
而不破坏现有代码的唯一方法。)
strchr()
不是唯一具有此问题的C标准库函数。 受影响的function列表(我认为此列表已完成,但我不保证)是:
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);
(全部在
声明)和:
void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
(在
声明)。 所有这些函数都使用指向const
数据的指针,该指针指向数组的初始元素,并返回非const
指针指向该数组的元素。
将非const指针从非修改函数返回到const数据的实践实际上是在C语言中相当广泛使用的习惯用法 。 它并不总是漂亮,但它已经相当成熟。
这里的reationale很简单: strchr
本身是一个非修改操作。 然而,我们需要为常量字符串和非常量字符串提供strchr
function,这也会将输入的常量传播到输出的常量。 C和C ++都没有为这个概念提供任何优雅的支持,这意味着在两种语言中你都必须编写两个几乎相同的函数,以避免采用const-correctness带来任何风险。
在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;
有人可能会说在函数内部修改函数参数是一种不好的做法。
const
关键字表示不能修改参数。
你不能直接返回s
,因为s
被声明为const char *s
,函数的返回类型是char *
。 如果编译器允许您这样做,则可以覆盖const
限制。
向char*
添加显式强制转换告诉编译器你知道自己在做什么(尽管Eric解释说,如果你不这样做会更好)。
更新:为了上下文,我引用了Eric的答案,因为他似乎删除了它:
你不应该修改s,因为它是一个const char *。
相反,定义一个表示char *类型结果的局部变量,并使用它代替方法体中的s。
函数返回值应该是字符的常量指针:
strchr
接受一个const char*
并且还应该返回const char*
。 你返回一个非常量,这是有潜在危险的,因为返回值指向输入字符数组(调用者可能期望常量参数保持不变,但如果它的任何部分作为char *
指针返回它是可修改的)。
如果找不到匹配的字符,则函数返回值应为NULL:
如果找不到所寻找的字符, strchr
也应该返回NULL
。 如果在找不到字符时返回非NULL,或者在这种情况下返回s,则调用者(如果他认为行为与strchr相同)可能会假设结果中的第一个字符实际匹配(没有NULL返回值)没有办法判断是否有匹配)。
(我不确定这是不是你打算做的。)
以下是执行此操作的函数的示例:
我写了这个函数并运行了几个测试; 我添加了一些非常明显的健全性检查以避免潜在的崩溃:
const char *mystrchr1(const char *s, int c) { if (s == NULL) { return NULL; } if ((c > 255) || (c < 0)) { return NULL; } int s_len; int i; s_len = strlen(s); for (i = 0; i < s_len; i++) { if ((char) c == s[i]) { return (const char*) &s[i]; } } return NULL; }