C语言入门,字符串和字符串函数知识讲解

前言

在 C 语言的学习过程中,字符与字符串的处理是一块绕不开的基础内容。本文围绕两个最常用的头文件展开:ctype.h 中的字符分类与字符转换函数,以及 string.h 中的常用字符串函数。

对于 ctype.h 部分,我们会介绍 isdigit、islower、ispunct 等字符分类函数,以及 tolower、toupper 两个字符转换函数,并结合练习演示它们的实际用法。

对于 string.h 部分,我们会依次讲解 strlen、strcpy、strcat、strcmp、strncpy、strncat、strncmp、strstr、strtok、strerror 等函数。每个函数都会先说明函数原型、功能、参数与返回值,再通过代码演示其使用方式;其中 strlen、strcpy、strcat、strcmp、strstr 还会进一步给出模拟实现的思路与代码,帮助大家理解这些函数在底层是如何工作的。

本文适合已经掌握 C 语言基本语法(变量、指针、数组、函数)的读者阅读,尤其适合正在学习 C 语言库函数、准备面试或想深入理解字符串函数实现原理的同学。建议按章节顺序阅读:先看第 1、2 节的字符函数打好基础,再进入第 3 节之后的字符串函数;对于带「模拟实现」的小节,可以先自己动手写一遍,再对照文中的思路与代码进行验证,效果会更好。

目录

1.字符分类函数

在C语言中有一类函数是专门对字符进行分类的,就是一个字符属于什么类型的字符。

在使用这些函数之前,必须要包含对应的头文件:#include <ctype.h>

那么这些函数具体有哪些呢?如下图所示:

若参数符合函数对应的条件,则会返回一个非0数,不符合返回0。

对上面的一些概念做一下解释:

这些函数对字符的类型做出了分类,下面我们就简单用几个函数做一个演示:

isdigit函数原型:

c 复制代码
   int isdigit(int c);

根据函数原型,我们可以将字符填入到函数的参数部分:

islower函数原型:

c 复制代码
   int islower(int c);

演示:

ispunct函数原型:

c 复制代码
   int ispunct(int c);

演示:

在演示完几个函数之后,我们对字符分类函数有了一定的了解了,我们就可以来小试牛刀一下。

练习:

写一个代码,将字符串中的小写字母转大写,其他字符不变。

我们先来捋一下思路:

根据上面的思路,我们就可以尝试去写出以下代码了:

2.字符转换函数

C语言提供了2个字符转换函数:

c 复制代码
   int tolower(int c);//将参数传进去的大写字母转成小写,只对大写字母有作用
   int toupper(int c);//将参数传进去的小写字母转成大写,只对小写字母有作用

我先来演示一下:

大小写转换就刚好可以去对上面练习的代码进一步的优化:

以下字符串函数(函数模拟实现除外)均要包含头文件:#include <string.h>

3.strlen的使用和模拟实现

strlen函数在之前的讲解中已经使用过了很多次,相信大家已经再熟悉不过了,但是在今天我们还是要带着大家在重新系统性的学一遍strlen函数相关的知识,让我们开始吧!

strlen函数原型:

c 复制代码
   size_t strlen(const char* str);

strlen函数的功能:统计在字符串中'\0'前面出现的字符个数(不包括'\0'),将统计到的个数作为返回值。

注意事项:

1.字符串以**'\0'作为结束标志,参数指向的字符串必须要以 '\0'结束。
2.使用strlen函数时,需要包含头文件:
#include <string.h>**

3.函数的返回值为size_t类型的,是无符号整型,因为字符串的长度是非负数。

学习过以上知识之后,我们还是来一道练习题来巩固一下,代码见下图:

提问:这段代码的运行结果是什么?

我们知道str2的字符串长度是3,str1的字符串长度是6,3-6得到-3,那么是不是输出str1>str2呢?

我们来看一下运行结果:

令我们没有想到的是,它输出的竟然是str2>str1,这到底是什么原因呢?下面我来为大家解释一下:

首先strlen函数的返回值是无符号整型,我们在计算到结果为-3时,由于-3在内存中是以补码的形式存储,但是打印在屏幕上的是原码,我们将-3转化成2进制的原码,通过换算得到补码,因为-3是无符号整型,不存在最高位是符号位,所有位都是数值位,所以它的原反补码相同,无符号整型减去无符号整型还是无符号整型,计算出来的结果始终是大于0的数。

那么代码怎么改进呢?其实是由2种方法的,首先第一种方法是直接让strlen函数进行大小比较:

第二种方法就是将strlen函数的返回值进行强制类型转化,让它变成有符号整型,进行正常运算。

这道题讲完之后,紧接着我们来讲一下关于strlen函数的模拟实现:在之前我们已经讲过了2种方法,如下:

方式1:

方式2:

但是如果题目改成不能在函数内部创建临时变量,怎么求字符串的长度?

接下来,我讲一种新方法,这里涉及函数递归,我们先来分析一下思路:

通过以上思路,我们可以尝试写出代码来:

在这里解释一下,为什么不能用str++呢?

不能使用str++的原因:str++是先使用,再++,所以my_strlen函数会先把str存的地址传过去,然后在原来的位置上在再进行++,传过去str是原来的地址,指向的对象不变,没有经过++的运算,递归可能会陷入死循环。

在这里,++str是可以使用的,先进行str++,改变指针的指向,再将str传过去,是可以实现传过去的指针发生变化的,但是在某些场景下可能会存在风险,因为str原来的地址也会被改变。我们所写的代码中,str+1可以保证str存的地址不变的基础上,可以将str+1的新地址传给下一个my_strlen。

4.strcpy的使用和模拟实现

strcpy函数原型:

c 复制代码
   char* strcpy(char* destination, const char* source);

strcpy函数的功能:字符串拷贝,将源字符串逐字符复制到目标字符串中,包括结束符'\0'。

参数 :

destination:指针,指向目标空间

source:指针,指向源头数据

返回值 :

strcpy函数返回的目标空间的起始地址

学习了这些知识之后,你也许还不懂是什么意思,没关系的,接下来我们会通过举例来说明,结合知识去进一步的理解:

假设有2个字符串分别叫arr1、arr2,arr1中的字符串想要拷贝到arr2中去,我们会怎么做?

是直接把arr2赋给arr1吗?肯定不是

所以才有了strcpy函数,帮助我们去实现字符串的拷贝,演示一下,如下图:

使用strcpy函数完成拷贝,利用目标空间arr2的地址传给printf函数去打印:

另一种写法:将strcpy函数的返回值传给printf函数去打印,其实返回值也是目标空间arr2的地址

运行结果:

注意事项 :

1.源字符串必须以'\0'结束,才能知道拷贝到那里结束

2.会将源字符串中的'\0'拷贝到目标空间

3.目标空间必须足够大,以确保能放得下源字符串

4.目标空间必须可修改

对于注意事项的第4点,如果目标空间存放的是常量字符串,能实现字符串的拷贝吗?

肯定是不能实现的,因为常量字符串是不能被修改的,将源字符串拷贝到目标空间中,就要求目标空间是可以修改的,因为是常量字符串,所以不能被修改。

对于注意事项的第2点,拷贝时会将源字符串的'\0'拷贝到目标空间去一个验证:

我们在调试过程中去观察,发现确实会将源字符串的'\0'拷贝到目标空间中。

那么我还有一个问题,就是当源字符串中含有'\0'时,它还会将处于'\0'之后的字符拷贝到目标空间中吗?我们来验证一下:

结果是不会拷贝'\0'之后的字符了,strcpy函数在拷贝时只管把'\0'前(包括'\0')的所有字符拷贝到目标空间中。

接下来,我们就要去模拟实现一下strcpy函数,首先我们先来捋一下strcpy函数的思路:

通过上面的思路,我们可以尝试去写一下代码:

对于这段代码来说,其实还存在优化的空间,那么哪些部分可以优化呢?

我们先来对while循环内的循环体进行优化:

这样我们可以将循环体中解引用部分和++部分做了一个很好的优化,但是'\0'打印的部分没有进行优化,接着继续优化,就产生了我们下一个版本:

另外一种写法:

在循环过程中,要对指针解引用,是不是要保证2个指针不是空指针:

我们希望源字符串不能被修改,目标空间可以被修改:

至此,我们完成了这段代码的优化。

5.strcat的使用和模拟实现

strcat函数原型:

c 复制代码
   char* strcat(char* destination,const char* source);

strcat函数的功能:字符串追加,把source指向的源字符串中的所有字符都追加到destination指向的空间中。

参数 :

destination:指针,指向目标空间

source:指针,指向源头数据

返回值 :

strcat函数返回的目标空间的起始地址

接下来,我们先来演示一下:

运行结果:

注意事项 :

1.源字符串必须以'\0'结束,否则不知道追加到源字符串的哪里结束追加。

2.目标字符串也得有'\0',否则没办法知道从目标空间的哪里开始追加。

3.目标空间必须要足够大,能容纳下源字符串的内容。

4.目标空间必须可修改。

我们想知道这个函数也会将源字符串的'\0'追加到目标空间吗?验证如下:

我们可以观察到arr2的变化,'\0'从源字符串追加到了arr2的空间里头,说明了我们的猜测是对的。

接下来,我们来模拟实现一下这个函数:

思路分析:

依据上面的思路,我们来尝试写一下代码:

6.strcmp函数的使用

接下来,我们来学习一个字符串的比较函数,首先是函数的原型:

c 复制代码
   int strcmp(const char* str1,const char* str2);

**功能:**用来比较str1和str2指针分别指向的字符串,从两个字符串的第一个字符开始比较,如果两个字符的ASCLL码值相等,就比较下一个字符,直到遇到不相等的2个字符,或者字符串结束。

参数 :

str1:指针,指向要比较的第一个字符串的首字符

str2:指针,指向要比较的第二个字符串的首字符

返回值:

标准规定:第一个字符串大于第二个字符串,返回大于0的数,相等返回0,小于返回小于0的数。

接下来,我用以上函数的基本信息来简单写一段代码:

或者直接将字符串作为参数,直接写进括号里面:

对函数的使用有了一定的了解后,我来模拟实现一下strcmp函数:

思路分析:

依据上面的思路,我可以尝试写出一下代码:

我查看一下运行的结果:

以上我学习的这组字符串函数,是对字符串长度没有限制的函数,那么肯定是由对字符串长度有限制的函数了,我就继续学习了。

7.strncpy函数的使用

先来看一下函数的原型:

c 复制代码
   char* strncpy(char* destination,char* source,size_t num);

**功能:**字符串拷贝,将source指向的字符串拷贝到destination指向的空间中,最多拷贝num个字符

参数:

destination:指针,指向目标空间

source:指针,指向源头数据

num:从source指向的字符串中最多拷贝的字符个数

返回值:

strncpy函数返回的目标空间的起始地址

接下来,我用上面的知识写一段简单的代码:

运行结果:

假如我拷贝字符串超过源字符串的字符个数,目标空间字符串会发生什么变化呢?

调试截图:

首先它会拷贝过去源字符串的所有字符串,想要拷贝的字符个数与实际源字符串的个数差了几个,在拷贝过去的源字符串后补几个'\0'

**总结:**拷贝num个字符从源字符串到目标空间,如果源字符串的长度小于num,则拷贝源字符串之后,在目标的后边追加几个'\0',直到补充到num个

比较strcpy和strncpy函数 :

· strcpy函数拷贝到'\0'为止,如果目标空间不够的话,容易出现越界行为。

· strncpy函数指定了拷贝的长度,源字符串不一定要有'\0',同时在设计参数的时候,就会多一层思考:目标空间的大小是否够用,strncpy相对strcpy函数更加安全。

8.strncat函数的使用

strncat的函数原型:

c 复制代码
    char* strncat(char* destination,const char* source,sizt_t num);

**功能:**字符串追加;将source指向的字符串的内容,追加到destination指向的空间,最多追加num个字符。

参数:

destination:指针,指向了目标空间

source:指针,指向了源头数据

num:最多追加的字符的个数

**返回值:**返回的是目标空间的起始地址

学习了函数的相关知识之后,我来尝试简单的写一段代码:

运行结果:

但是在追加的时候是否会把'\0'拷贝到目标空间内,我来验证一下:

调试截图:

但是这能说明'\0'是从源字符串拷贝过来的吗?如果num小于源字符串的个数,那还会有'\0'拷贝过来吗?将num的值从3改为2,我来测试一下:

结果是这个'\0'不是源字符串拷贝过来的 ,而是在追加到num个源字符串的字符时,在末尾自动会补一个'\0'作为追加结束的。

如果num大于源字符串的字符个数,那么会发生什么情况呢?我们在上段代码的基础上,将num的值从2改为5,我来测试一下:

结果追加到源字符串末尾后,直接加一个'\0',作为追加结束的标志,不会用'\0'去真正追加到5个字符,就只是追加完源字符串的所有字符就行(不包括'\0')。

strcat和strncat对比:

· 参数不同,strncat多了一个参数

· strcat函数在追加的时候要将源字符串的所有内容,包括'\0'都追加过去,但是strncat函数指定了追加的长度。

· strncat函数中源字符串不一定要有'\0'了

· strncat更加灵活,也更加安全

9.strncmp函数的使用

strncat函数的原型:

c 复制代码
   int strncmp(const char* str1,const char* str2,size_t num);

**功能:**字符串比较;比较str1和str2指向的两个字符串的内容,最多比较num个字符。

参数:

str1:指针,指向一个比较的字符串

str2:指针,指向另一个比较的字符串

num:最多比较的字符个数

**返回值:**若第一个字符串大于第二个字符串,则返回大于0的数;相等返回0;小于返回小于零的数。

学习了函数的相关知识之后,我来尝试写一段简单的代码:

运行结果:

只比较前5个字符,两个字符串确实是相等的

strcmp和strncmp比较:

· 参数不同

· strncmp可以比较任意长度

· strncmp函数更加灵活,更加安全

10.strstr函数

strstr函数的原型:

c 复制代码
   char* strstr(const char* str1,const char* str2);

功能: strstr函数,查找str2指向的字符串在str1指向的字符串中出现的位置。简单来说,就是在一个字符串中找另一个字符串第一次出现的位置。

参数:

str1:指针,指向了被查找的字符串

str2:指针,指向了要查找的字符串

返回值:

· 如果str1指向的字符串中存在str2指向的字符串,那么返回第一次出现位置的指针

· 如果str1指向的字符串不存在str2指向的字符串,那么返回NULL

学习了函数的相关知识,我来尝试写一段简单的代码:

接下来,我来尝试模拟实现一下strstr函数:

思路分析:

由题目要求画出以下图:

只要被查找的字符串的指针指向'\0'时,就说明没有找到,这个可以作为循环的条件

第一次比较:

第二次比较:

第三次比较:

改进:

第四次比较:

第五次比较:

第六次比较:

依据上面的思路分析,我可以尝试去写出代码:

在特殊场景下的处理,当要查找的字符串是空字符串时:

11.strtok函数的使用

strtok函数的原型:

c 复制代码
   char* strtok(char* str,const char* delim);

功能:

· 分割字符串: 根据delim参数中指定的分隔符,将输入的字符串str拆分成多个子字符串。

· 修改原始字符串: strtok会直接在原始字符串中插入'\0'终止符,替换分隔符的位置,因此原始字符串会被修改。

参数:

1.str:首次调用时传入待分割的字符串;后续调用传入NULL,表示继续分割同一个字符串。

2.delim:包含所有可能分隔符的字符串(每个字符均视为独立的分隔符)。

返回值:

· 成功时返回指向当前子字符串的指针。

· 没有更多子字符串时返回NULL。

使用步骤

1.首次调用: 传入待分割字符串和分隔符。

2.后续调用: 传入NULL和相同的分隔符,继续分割。

3.结束条件: 当返回NULL时,表示分割完成。

看完使用步骤,是不是看起来有点困难,不用担心,接下来我来写一段代码,相信能够帮助你去更好的理解:

运行结果:

但实际上在分割完字符串的时候,返回的空指针并不能去打印,这里其实像是报了一个错误警告了。

事实上,strtok函数并不是这样用的,它可以借助for循环,无论要分割几段字符串,它都能一次性完成分割,参考代码如下:

注意事项

· 破坏性操作: strtok会直接修改原始字符串,将其中的分隔符替换为'\0'。如果需要保留原字符串,应先拷贝一份。

· 连续分隔符: 多个连续的分隔符会被视为单个分隔符,不会返回空字符串。

` 空指针处理: 如果输入的str为NULL且没有前序调用,行为未定义。

12.strerror函数的使用

strerror函数的原型:

c 复制代码
   char* strerror(int errnum);

功能:

1.strerror函数可以通过参数部分的errnum表示错误码,得到对应的错误信息,并且返回这个错误信息字符串首字符的地址。

2.strerror函数只针对标准库中的函数发生错误后设置的错误码转换。

参数:

errnum:表示错误码

这个错误码一般传递的是errno这个变量的值,在C语言有一个全局变量叫:errno,当库函数的调用发生错误的时候,就会将本次错误的错误码存放到errno这个变量中,使用这个全局变量需要包含一个头文件errno.h 。

返回值:

函数返回通过错误码得到的错误信息字符串的首字符的地址。

学习完函数的相关知识后,我来尝试写一段简单的代码,帮助大家更好的理解这个函数的使用:

运行结果:

后续我们还会见到库函数调用的一些错误,就不再过多在本文讲解了。

总结

本文围绕 ctype.h 与 string.h 两个头文件,梳理了 C 语言中最常用的一批字符与字符串函数,下面用要点列表做一次归纳。

一、ctype.h 字符分类与转换函数

  • 字符分类函数 (isdigit、islower、isupper、isalpha、isalnum、isspace、ispunct 等):用于判断一个字符属于哪一类,参数符合条件时返回非 0 值,不符合返回 0。
  • 字符转换函数 (tolower、toupper):只对相应大小写的字母生效,其他字符原样返回,常与分类函数配合完成大小写转换。
  • 注意事项 :参数类型为 int,传入的应当是 unsigned char 范围内的值或 EOF;函数只做判断与转换,不会修改原字符,需要接收返回值。

二、string.h 字符串函数

  • strlen :统计 '\0' 之前的字符个数,返回 size_t(无符号);字符串必须以 '\0' 结束。
  • strcpy :把源字符串连同 '\0' 一起拷贝到目标空间;目标空间必须足够大且可修改。
  • strcat :把源字符串追加到目标字符串末尾,并追加 '\0';目标空间必须足够大且可修改。
  • strcmp:按字符 ASCII 码值逐字符比较,返回大于 0、等于 0、小于 0 三种结果。
  • strncpy :最多拷贝 num 个字符;源串长度不足 num 时用 '\0' 补齐,相对 strcpy 更安全。
  • strncat :最多追加 num 个字符,追加结束后自动补一个 '\0';源串不要求以 '\0' 结尾。
  • strncmp :最多比较 num 个字符,比 strcmp 更灵活、更安全。
  • strstr :在 str1 中查找 str2 第一次出现的位置,找到返回该位置指针,找不到返回 NULL。
  • strtok :按分隔符拆分字符串,会直接修改原串(把分隔符替换为 '\0'),首次传字符串、后续传 NULL。
  • strerror :把错误码(通常取 errno)转换成对应的错误信息字符串,需包含 errno.h。

三、学习建议

  1. 先手写模拟实现,再对照标准库 :对 strlen、strcpy、strcat、strcmp、strstr 这类函数,先自己按思路写一遍,再对照标准库的行为与边界处理,理解会更牢固。
  2. 注意 size_t 的无符号陷阱 :strlen 等函数返回无符号整型,两个无符号数相减不会得到负数,比较时容易出错,必要时先强制转换为有符号类型或直接比较。
  3. 优先使用带 n 的安全版本 :在长度不确定的场景下,优先选择 strncpy、strncat、strncmp,并始终确认目标空间大小,避免越界。

参考资料

  • C 标准库文档(ctype.h、string.h 各函数的原型与行为说明)
  • cppreference 上关于 C 标准库头文件 ctype.h、string.h 的条目,可查阅各函数的详细定义与边界情况
  • 各编译器(如 GCC、MSVC)自带的库函数手册与调试实践
相关推荐
xxxiugou12339 分钟前
C++命名空间与引用全解析
开发语言·c++
yangmu32032 小时前
编程语言发展简史:从机器指令到智能协作
开发语言
Pniubi5 小时前
Java为什么要分堆和栈
java·开发语言
小小晓.5 小时前
Qt 对话框、QLineEdit 与布局管理实战详解
开发语言·qt
炸薯条!6 小时前
博客系统测试报告
开发语言·lua
Evand J6 小时前
【MATLAB例程12】永磁同步电机基于增广无迹卡尔曼滤波的未知负载转矩在线观测
开发语言·matlab
时间的拾荒人7 小时前
CMake 实战指南:从基础语法到静态库/动态库制作与链接
开发语言·c++·面试
智者知已应修善业7 小时前
【51单片机实现从下到上,从上到下,从中间到两边,两边到中间】2024-10-23
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
xyouyuweizhu7 小时前
Java InetAddress 详解:从 IP 地址表示到主机名解析
java·开发语言·python