关键词盘点完了,接下来进入标准库环节。
11. bsearch, memchr, strchr, strpbrk, strrchr, strstr, wcschr, wcspbrk, wcsrchr, wcsstr, wmemchr
把这些函数放在一起是有原因的,以strchr为例,它在C17当中的原型如下:

看起来好像人畜无害的样子,但是仔细一想就会发现这里面有大问题。
假设现在从外部传来一个指针:extern const char *cstr;,这时某不愿意透露姓名的张三突发奇想,试图修改cstr指向的缓冲区当中的内容,他要怎么做呢?
直接char *str=(char*)cstr;?不行,这样太没品味了,强制类型转换非君子之道,恰在此时,strchr映入了张三的眼帘,张三灵机一动,写出了如下代码:
char *str=strchr(cstr,cstr[0]);
“哈哈哈哈,我可真是个小天才!”,张三沾沾自喜,开始放肆地修改cstr中的内容,然后……张三就被抓了。
张三直呼冤枉,“你们可看清楚了,是标准库函数先动的手,是strchr擅自把const脱掉的,这可跟我没关系啊!”,张三激动地辩解道。
“好啦好啦,就知道你会这么说。strchr返回char*又怎么样?它返回char*你就真把它当char*来用?”
这就是strchr面临的问题,它接收一个const char*类型的指针,却返回一个char*类型的指针,弄得好像strchr有什么魔法一样,可以把const char*的const给去了。那么问题来了,为什么strchr要设计成这个样子呢?
在回答这个问题之前,不妨先思考另一个问题:为什么strchr的第一个参数类型为const char*?
这个问题很好回答,因为实参的类型可以是char*或者const char*,而char*是可以隐式转换到const char*的,形参使用const char*可以兼顾两种情况,反之不行。
既然实参有两种情况,我们不妨为这两种情况单独设计strchr函数:
char *strchr(char *s, int c);
const char *strchr(const char *s, int c);
这样看起来是不是就合理多了?
悲催的是,C不支持函数重载,所以我们只好把上述两种情况合二为一,参数s的类型是const char*这是肯定的了,那么返回类型应该是什么呢?
答案是char*。理由还是一样的,因为char*可以隐式转换到const char*。假如传入实参的类型是char*,那我当然希望用char*类型的变量来接收返回值,如果strchr的返回类型设计成const char*,这种情况下我就需要强制类型转换了。如果传入实参的类型是const char*,那么我用const char*类型的变量来接收返回值自然不会有任何问题。
说了这么多我们不难发现,strchr的设计突出的就是俩字:方便!它相信程序员能够正确对待它的返回类型,否则就会闹出前面张三的笑话。
可是,程序员真的总是那么可靠吗?
C23引入了一个新概念:Generic function,没错,开头列举的那一排函数都是Generic function,和<tgmath.h>里面那些泛型宏不同,泛型宏是Type-generic macro。strchr的面貌也变得焕然一新:

QChar是个什么玩意儿?Q是qualified,一般来说QChar就是char或者const char其中之一。
完美!现在传入实参是char*返回类型就是char*,传入实参是const char*返回类型就是const char*,编译器再也不用担心我会写错代码啦!
等等,好像哪里有点不对,这个Generic function到底是个什么东西?它又是怎么实现的?
strchr还好说,毕竟所谓的QChar也就那样:

这时一位蒙面高手突然出现,挡住了我们的去路:

怎么办?QVoid*意味着实参可以是任何类型的指针,就算我们可以把所有内建类型都罗列在_Generic里面,当遇到用户自定义类型的时候又当如何呢?救救lz。