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 ++不同,这两个函数必须具有不同的名称,可能是strchrstrcchr

(从历史上看,在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本身是一个非修改操作。 然而,我们需要为常量字符串和非常量字符串提供strchrfunction,这也会将输入的常量传播到输出的常量。 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; }