
目录1.引言2.字符分类函数与字符转换函数3.strlen 函数4.strcpy 函数与 strncpy 函数5.strcat 函数与 strncat 函数6.strcmp 函数与 strncmp 函数7.strstr 函数8.strtok 函数9.strerror 函数10.结语1.引言我们前面已经学过了字符串的基础知识也知道了 strlen 函数可用来统计字符串的字符个数那除了我们熟知的 strlen 函数在C语言的 string.h 头文件下还提供了许多其他与字符串相关的函数所以本篇博客就来详细讲解一下与字符串相关的各种函数的作用及使用语法。2.字符分类函数与字符转换函数在C语言中有这么一类函数是专门用来做字符分类的也就是判断一个字符属于什么类型。那此类函数的使用方法非常相似也都是由 ctype.h 头文件提供。我们这里就着重展示两个剩下的就靠小萌新们自己去探索了。那在C语言中还有一类函数是专门用来做字符转换的也就是把字符从一个类型转为另一个类型。那此类函数的使用方法与前面的字符分类函数一致也同样由 ctype.h 头文件提供。字符转换函数只有下面这两个tolower 函数主要用于大写字符的小写转换而 toupper 函数主要用于小写字符的大写转换。关于这里的返回值类型与参数类型为什么都是 int 类型是因为所有的字符在实际储存时存的都是对应的 ASCII 值。在没有学习这些函数之前我们想要实现字符的大小写转换可能会像图一那样书写代码。那如果我们掌握了可以判断字符大小写类型的字符分类函数与可以转换字符大小写类型的字符转换函数就能将代码改成图二那样。3.strlen 函数关于 strlen 函数的用法及作用我们前面已经讲过了这里主要再补充两个点。第一个点就是 strlen 函数的返回值类型为 size_t但由于 size_t 表示的是无符号整型我们在对数据类型为size_t 的变量或表达式做结果判断就要注意负数的判断情况在 size_t 类型下的负数实际上表示的是一个极大的正数。第二个点就是 strlen 函数的模拟实现我们之前是使用指针-指针的方式实现的那如果要不额外创建变量完成对字符串的字符个数统计我们就可以考虑使用函数递归的方式具体代码如下。关于这个函数的具体运行逻辑就不再解释了看不懂的小萌新建议再回去看一遍函数递归的知识讲解。4.strcpy 函数与 strncpy 函数然后我们来学习一下 strcpy 函数的作用及其使用语法。strcpy 函数可用来拷贝字符串也就是将一串字符串的内容拷贝到另一串字符串里完成对应内容的替换。此时有小萌新可能会问我们前面学过指针知识知道可以通过指针获取地址然后解引用操作完成值的修改那为什么还要使用 strcpy 函数这是因为字符串及数组与普通变量不同我们通过指针得到的是字符串的首地址或数组的首地址解引用操作后得到的也是这个地址中的值那进行内容修改时也只会修改这个地址中的值我们可以看下图的代码演示。所以想要完整替换字符串的内容就要使用 strcpy 函数。那 strcpy 函数的使用语法如下图所示。这个函数的返回值类型为 char* 指针类型且返回的是目标字符串的首地址所以我们也可以创建一个指针变量接收 strcpy 函数的返回值然后借助这个指针变量访问目标字符串的内容。strcpy 函数的参数有两个分别是目标字符串的首地址与源头字符串的首地址且一定是目标字符串的首地址在前源头字符串的首地址在后因为是从目标字符串的首地址开始将源头字符串的内容拷贝进去。那除此之外还要注意在拷贝时是将源头字符串的 \0 也一起拷贝过去的所以源头字符串一定要以 \0 结尾不然会出问题。且目标字符串必须是可修改的所开辟的内存空间也要足够大这样才能确保正常完成拷贝操作不然就会发生越界访问同样会出问题。那在清楚了 strcpy 函数的作用与使用方法后我们来模拟一下 strcpy 函数。根据刚刚我们对 strcpy 函数的理解就能写出下图代码。首先通过 tmp 临时指针变量储存目标字符串的首地址这样就能在函数结束时正常传回目标字符串的首地址。然后通过 while 循环来实现字符串的拷贝操作因为源头字符串的拷贝是到 \0 结束的那就可以通过判断当下字符是否为 \0 来控制循环的进行。而每次在完成 *p2*p1 的拷贝操作后都要进行相应的指针偏移操作。值得注意的是在循环结束时我们还要再进行一次拷贝操作因为源头字符串的 \0 也要拷贝到目标字符串里。虽然我们已经模拟实现了 strcpy 函数但我们还要再深入思考一下代码是否可以进一步改进。首先从安全性上来看我们只是完成代码的拷贝操作那源头字符串就不能被修改为了保护源头字符串我们可以在前面使用 const 修饰而且在使用指针前应先判断指针是否为空那我们就可以使用 assert 断言记得在前面引入头文件 assert.h。然后再观察刚刚的代码我们发现最后还要再进行一次拷贝操作那能不能直接完成其实是可以的我们只要将 while 循环体里的代码改写为 *p2 *p1 就能实现字符拷贝与指针偏移操作。然后将这个表达式作为 while 循环的判断条件就能在源头字符串拷贝到 \0 时终止循环且直接完成 \0 的字符拷贝。我们简单解释一下就是表达式原本是完成字符拷贝操作作为判断语句时 while 循环就会判断刚刚拷贝的字符是否为 \0因为 \0 的 ASCII值为 0如果是则循环终止而且 \0 的字符拷贝会在循环终止前完成。值得注意的是while 循环体的代码不能为空所以最后的 ; 不可忘记这表示执行的是空语句。那理解了 strcpy 函数之后再来理解 strncpy 函数就相当容易了。前面的 strcpy 函数是长度不受限的字符串函数也就是说我们并不能控制字符串的拷贝长度利用 strcpy 函数只能将源头字符串完全拷贝到目标字符串里。而后面的 strncpy 函数则是长度受限制的字符串函数也就是说我们可以通过参数来控制字符串的拷贝长度利用 strncpy 函数就可以只拷贝源头字符串的一部分到目标字符串里。那从前面的对比中我们就知道了 strncpy 函数与 strcpy 函数的区别也知道了strncpy 函数应该有三个参数除了目标字符串的首地址与源头字符串的首地址外还有至多拷贝的字符个数。然后我们来看下图代码。通过调试我们可以看到当我们规定的拷贝长度小于源头字符串长度时只会拷贝源头字符串的一部分且不会将源头字符串的 \0 字符拷贝过来。所以在使用 strncpy 函数时不再要求源头字符串必须以 \0 结尾。那当我们修改 strncpy 函数的参数大小后再次运行代码结果有所不同。通过调试我们可以看到当我们规定的拷贝长度大于源头字符串长度时除了将源头字符串全部拷贝过来外还在末尾补了多个 \0 字符。因为我们规定了拷贝长度那当源头字符串长度不够时就会通过在末尾补 \0 字符的方式来凑齐所需的长度。然后我们也模拟一下 strncpy 函数具体代码如下。关于 strncpy 函数的模拟就不细致讲解了感兴趣的小萌新可以自己研究一下。5.strcat 函数与 strncat 函数然后我们来学习一下 strcat 函数的作用及其使用语法。strcat 函数可用来拼接字符串也就是将一串字符串的内容拼接到另一串字符串的后面完成对应内容的追加。strcat 函数的使用方法与strcpy 函数很像返回值类型也为 char* 指针类型返回的是目标字符串的首地址函数的参数也是两个一个是目标字符串的首地址另一个是源头字符串的首地址。我们直接来看下图的代码演示。那除此之外还要注意除了源头字符串一定要以 \0 结尾外目标字符串也一定要以 \0 结尾不然就无法获取字符串拼接的起始地址。同样的目标字符串必须是可修改的所开辟的内存空间也要足够大。那在清楚了 strcat 函数的作用与使用方法后我们来模拟一下 strcat 函数。根据上图的拼接结果提示与刚刚我们对 strcat 函数的理解就能写出下图代码。其实 strcat 函数也是在做字符串的内容修改操作只是与 strcpy 函数修改的起始地址不同strcpy 函数是从目标字符串的首地址开始修改而 strcat 函数要从目标字符串结尾的 \0 字符的地址开始修改。所以我们先要找到目标字符串的 \0 字符所在地址至于后续操作则与 strcpy 函数的模拟逻辑相同这里就不再重复了。strcat 函数与 strncat 函数的区别也在于拼接字符个数是否受限。利用 strcat 函数只能将源头字符串全部拼接到目标字符串的后面而利用 strncat 函数就可以只拼接源头字符串的一部分到目标字符串的后面。那 strncat 函数同样有三个参数除了目标字符串的首地址与源头字符串的首地址外还有至多拼接的字符个数。然后我们来看下图代码。通过调试我们可以看到当我们规定的拼接长度小于源头字符串长度时只会拼接源头字符串的一部分但此时会在末尾自动补上一个 \0 字符以保证拼接完后的字符串依旧是一个合法的字符串这点与 strncpy 函数不同。那当我们修改 strncat 函数的参数大小后再次运行代码结果有所不同。通过调试我们可以看到当我们规定的拼接长度大于源头字符串长度时就可以将整个源头字符串拼接到目标字符串的后面且不会像 strncpy 函数那样通过在末尾补 \0 字符的方式来凑齐所需的长度所以此时的 strncat 函数就等同于 strcat 函数。然后我们也模拟一下 strncat 函数具体代码如下。关于 strncat 函数的模拟就不细致讲解了感兴趣的小萌新可以自己研究一下。6.strcmp 函数与 strncmp 函数然后我们来学习一下 strcmp 函数的作用及其使用语法。strcmp 函数可用来比较字符串他所比较的是两个字符串的内容是否相同。strcmp 函数的使用方法与前面两个类似这里就不再重复了。不过要说明两点首先就是 strcmp 函数的返回值类型为 int 类型因为最后要根据返回值与 0的大小关系来判断两个字符串的大小关系。如果返回值 0说明第一个字符串大于第二个字符串如果返回值 0说明第一个字符串等于第二个字符串如果返回值 0说明第一个字符串小于第二个字符串。其次字符串的大小比较是按照字典序来的也就是比较对应位置上字符的 ASCII 值的大小。然后我们来看下图代码。第一行代码的结果证明字符串是按字典序比较大小的因为第一个字符串的首字符 a 的ASCII 值为 97而第二个字符串的首字符 b 的 ASCII 值为 98通过字符 a 与字符 b 的 ASCII 值比较那返回值就是 0 的数说明第一个字符串小于第二个字符串。第二行代码的结果证明当两个字符串长度不同且一个字符串为另一个字符串的一部分时也能正常判断两个字符串的大小因为字符串结尾有隐藏的 \0 字符通过字符 e 与字符 \0 的ASCII 值比较那返回值就是 0 的数说明第一个字符串大于第二个字符串。第三行代码的结果证明在将带有 \0 字符的字符串存入字符数组时虽然能正常储存所有字符但 \0 是字符串结束的标志所以在有关字符串的运算中第二个字符串会被认为是字符串 bcd此时与第一个字符串 bcd 相比内容就是完全一样的那返回值就 0说明第一个字符串等于第二个字符串。那在清楚了 strcmp 函数的作用与使用方法后我们来模拟一下 strcmp 函数。通过刚刚的代码讲解我们知道字符串的比较方式是从两个字符串的首地址开始对应位置一位一位的向下比较两个字符的 ASCII 值当两个字符串第一次出现不相同的字符时直接结束比较并返回对应的比较结果此时返回值 0 则说明第一个字符串更大返回值 0 则说明第二个字符串更大若两个字符串完成比较也就是都读取到 \0 字符且此前的字符完全相同此时返回值 0 说明两个字符串相同。那基于刚刚的原理分析我们就能写出下图代码。我们可以利用 while 循环来判断两个字符串是否存在不同的字符如果存在此时直接返回差值这同样符合返回值的规定。反之两个字符串就是相同的此时只要判断其中一个字符串获取的字符是否为 \0 就能知道比较是否完成一旦完成就返回 0。strcmp 函数与 strncmp 函数的区别也在于比较字符个数是否受限。利用 strcmp 函数就是完整比较两个字符串的内容是否相同而利用 strncmp 函数就可以只比较两个字符串的一部分内容是否相同。所以利用 strcmp 函数比较结果为不同的两个字符串利用 strncmp 函数比较时就可能是相同的。那 strncmp 函数同样有三个参数除了第一个字符串的首地址与第二个字符串的首地址外还有至多比较的字符个数。然后我们来看下图代码。我们来分析一下结果。对于第一行代码而言我们通过参数控制比较字符个数为 4个那 arr1 中所存的字符串就取出 aabc 这一部分而 arr2 中所存的字符串就取出 aabc 这一部分此时两个字符串的部分内容就是相同的返回值就为 0。对于第二行代码而言我们通过参数控制比较字符个数为 7个那 arr1 中所存的字符串就取出 aabcdeg 这一部分而 arr2 中所存的字符串就取出 aabcdcg 这一部分此时第一个字符串中字符 e 的 ASCII 值大于第二个字符串中字符 c 的 ASCII 值返回一个 0 的值。然后我们也模拟一下 strncmp 函数具体代码如下。关于 strncmp 函数的模拟就不细致讲解了感兴趣的小萌新可以自己研究一下。7.strstr 函数然后我们来学习一下 strstr 函数的作用及其使用语法。strstr 函数可用来查找子串也就是在一个字符串中查找另一个字符串第一次出现的位置。这个函数的返回值类型为 char* 指针类型返回结果有两种如果在母串中找到子串则返回子串第一次出现位置的指针如果没有在母串中找到子串则返回 NULL 指针。我们可以创建一个指针变量来接收 strstr 函数的返回结果并通过结果判断得知是否在母串中找到子串。strstr 函数的参数有两个分别是被查找的母串的首地址与要查找的子串的首地址且母串的首地址在前子串的首地址在后。然后我们来看一下 strstr 函数的具体使用。首先 arr1 数组中存放的就是被查找的母串arr2 与 arr3 数组中存放的都是要查找的子串。当我们在母串中查找子串 abc 时可以看到是从母串的第五个字符开始的所以最后结果就是将母串的首地址向后偏移四个单位后所得的地址。而当我们在母串中查找子串 cfg 时可以看到母串中并没有这个子串所以最后结果就是 NULL 指针NULL 本来表示 0 值那作为地址时在 x86 环境下就是 00000000。那在清楚了 strstr 函数的作用与使用方法后我们来模拟一下 strstr 函数。那在书写代码前我们先要了解一下 strstr 函数查找子串的逻辑。如果母串中能找到子串那子串就一定得是母串的一部分也就是说母串的部分内容应与子串完全相同。所以当子串的首字符与母串的某个字符相同时我们就开始比对接下来的字符内容如果完全一样则说明找到子串此时就返回母串中最开始匹配上的字符地址。但如果整个母串走完了也没能与子串匹配上则说明母串中找不到子串此时就返回 NULL 指针。所以我们应该设计三个指针变量第一个用来遍历母串的每一个字符同时用来保存与子串首字符匹配的字符地址第二个用来遍历匹配成功后母串的剩余字符第三个用来遍历匹配成功后子串的剩余字符。这样在我们找到子串时就可以直接返回第一个指针变量的值因为第一个指针变量指向的就是与子串首字符匹配上的字符地址而如果始终没能找到子串就在最后返回 NULL 指针。至此我们就能写出下图代码来模拟 strstr 函数的运行逻辑。上述结果是没有问题的但整体代码还可以做优化改进。首先我们要知道空字符串是任意字符串的子串因为只要是合法的字符串就一定会有 \0 字符而空字符串就是只有 \0 字符的字符串。那我们在进入 while 循环前就可以先判断子串是否是空串如果是此时直接返回母串的首地址。除此之外对于上图样例中 arr4 数组所存的子串我们可以看到母串的一部分是可以匹配子串的前半部分的但在匹配子串的后半部分时母串已经没有多余字符可以继续匹配了此时就可以直接返回 NULL 指针因为无论后续再怎么匹配也无法在母串中找到子串。最终我们的代码就变成了下图这样。8.strtok 函数然后我们来学习一下 strtok 函数的作用及其使用语法。strtok 函数可用来分割字符串也就是根据分隔符集合中可能的分隔符在字符串中通过插入 \0 字符替代分隔符的方式将一个大字符串分割为几个小字符串。这个函数的返回值类型为 char* 指针类型返回结果有两种每次成功完成分割操作后就会返回当前分割出的子串的首地址当分割操作无法继续也就是不能再分割出新的子串时就会返回 NULL 指针。我们可以创建一个指针变量来接收 strtok 函数的返回结果并通过结果判断得知是否可以继续进行分割操作通常配合循环语句使用。参数有两个分别是被分割的字符串的首地址与分隔符集合的首地址。不过 strtok 函数有一个特性只有在首次调用时需要传入被分割的字符串的首地址后续调用时直接传入 NULL 指针就行表示继续分割同一个字符串。然后我们来看一下 strtok 函数的具体使用。首先 arr1 数组中存放的就是要分割的字符串arr2 数组中存放的就是分隔符的集合这里的分隔符只有 与 /所以在进行分割时字符串中的 . 字符不会被视作分隔符最后的结果也证明了这一点。关于为什么要使用 strcpy 函数将 arr1 数组中的字符串拷贝到 arr3 数组中后再对 arr3 使用strtok 函数是因为 strtok 函数具有破坏性每次做分割操作时都会将原有的分隔符改为 \0 字符。所以当我们想要保留原字符串或之后还要使用原字符串时就要先进行拷贝操作然后对拷贝字符串做分割操作。strtok 函数在处理多个连续分隔符时会将这些分隔符视作一个分隔符也就只会进行一次修改操作将最前面的分隔符改为 \0 字符后完成本次分割操作。下一次进行分割操作时会直接略过这些分隔符从下一个非分隔符的字符开始往后找新的分隔符继续做分割操作所以并不会返回空字符串。同时关于 strtok 函数的 NULL 指针使用情况必须保证前面有非空指针的调用不然属于未定义行为是会引发报错问题的。那 strtok 函数除了最开始的写法外我们还能使用 for 循环的方式如下图。因为 for 循环语句的三个表达式分别为初始条件终止条件与步进条件所以我们可以把首次调用作为初始条件而后续调用作为步进条件当返回值为 NULL 时说明无法再做分割操作所以最后的终止条件就是P NULL。9.strerror 函数在讲解 strerror 函数前我们先要了解一下错误码的相关知识。在不同系统和C语言标准库的实现中其实都规定了一些错误码一般是放在 errno.h 这个头文件中。那在C语言程序启动时是会使用一个全局变量 errno 来记录当前程序的错误码只不过在程序最开始启动时 errno 为 0也就是没有任何错误发生而当我们在使用标准库中的函数发生某种错误时就会将对应的错误码存放到 errno 里。错误码本质上就是对各种错误进行数字编号编译器能够通过数字知道发生了什么错误我们却难以直接理解但每个错误码都有对应的错误信息我们通过 strerror 函数就可以将错误码对应的错误信息字符串的首地址返回从而知道发生了什么错误。通过 Everything 搜索 errno.h 这个头文件后打开就能看到各种错误信息以及对应的错误码。在清楚了错误码是什么之后我们就来学习一下 strerror 函数的作用及其使用语法。关于 strerror 函数的作用其实刚刚我们也说过了就是根据参数部分传入的错误码找到对应的错误信息并返回这个错误信息字符串的首地址。要注意的是strerror 函数仅仅是针对于标准库中的函数在使用时发生错误后根据所设置的错误码进行信息转换。且使用这个函数同样需要包含头文件 string.h。而参数部分一般就是传递 errno 这个变量的值那要使用这个全局变量就需要包含头文件 errno.h。然后我们来看一下 strerror 函数的具体使用。下图列举了一个大家平时可能会遇到的例子当我们以只读的形式去打开一个不在当前路径下的文件时就会出现文件打开失败的情况。那这里所使用的文件操作我们还没讲过关于文件的相关内容同样会在后续博客里详细介绍感兴趣的小萌新可以先去了解一下这里主要是借这个例子来演示 strerror 函数的使用场景。当文件打开失败传回 NULL 指针后我们将 errno 变量传入到 strerror 函数里再创建一个指针变量接收 strerror 函数的返回值并打印输出。那从最后的输出结果我们知道这个错误的错误码为 2错误信息是找不到该文件或文件夹。然后我们顺带讲一下 perror 函数的使用语法。perror 函数可以认为是 printf 函数与 strerror 函数的组合因为他既能像 printf 函数那样打印输出字符串又能像 strerror 函数那样将错误码转换为对应的错误信息字符串。但 perror 函数打印是有限制的我们只能通过参数传递一个字符串当字符串为空串时直接打印输出错误信息字符串但当字符串不为空串时就会先打印输出传入的字符串然后在字符串结尾加上一个冒号与一个空格后再打印输出错误信息字符串。10.结语本篇博客到这里就结束了希望能够帮到各位刚接触C语言的小萌新们下篇博客将继续讲解C语言的相关知识。