【C 语言】常用的字符串函数和内存函数
[TOC]
今天我们来学习一些新的字符串函数和内存函数,了解它们背后运行的原理,并完成部分函数的自我实现😘
1. 字符串函数
1.1 strlen
这个函数我们已经很熟悉了,它的作用是计算字符串的大小,以 \0
作为结尾
模拟实现如下:
1 | //1.strlen模拟实现 |
assert
:断言,库函数,用于判断指针是否为空,若为空会报错
1.2 strcpy
该函数用于拷贝字符串,将 arr2 里的内容拷贝到 arr1 里
1 | char* strcpy(char * destination, const char * source ); |
它有以下几个特点
Copies the C string pointed by source into the array pointed by destination, including the terminating null character (and stopping at that point).
- 源字符串必须以 ‘\0’ 结束
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改
如果源字符串里没有 \0,该函数就无法正确进行拷贝
strcpy 拷贝的时候是复制
而不是剪贴,源空间里的内容不会消失
以下模拟实现
需要注意的就是 strcpy 会将源字符串的’\0’一并拷贝,所以在编写判断条件的时候就要考虑到这个情况
1 | //2.strcpy模拟实现,拷贝 |
1.3 strcat
strcat 函数用于追加字符串,简单来说就是把两个字符串接在一起
其函数返回值为 dest
1 | char * strcat ( char * destination, const char * source ); |
- 源字符串必须以 ‘\0’ 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
注意,使用 strcat 函数的时候不能自己追加自己,程序会死循环
以下是模拟实现
- 先让 dest 找到目的地字符串里的
\0
- 然后进行追加,注意源字符串里的
\0
同样会被追加过去
1 | //3,strcat函数 |
1.4 strcmp
strcmp 我们也是经常使用的,用于比较字符串
1 | int strcmp ( const char * str1, const char * str2 ); |
该函数在比较字符串的时候,实际上是一个字符一个字符地比较的
- 第一个字符串大于第二个字符串,则返回大于 0 的数字
- 第一个字符串等于第二个字符串,则返回 0
- 第一个字符串小于第二个字符串,则返回小于 0 的数字
模拟实现
在 VS 编译器下,如果字符串 s1 大于 s2,返回的是 1。若小于返回的是 - 1。但 C 语言只要求大于的时候返回大于 0 的数字,小于的时候返回小于 0 的数字,所以我们可以直接用字符相减得出返回值
1 | //4.strcmp函数 |
1.5 strncpy/cat/cmp
以上 4 个库函数,都对操作数没有要求。
而 strncpy
、strncat
、strncmp
这三个库函数,对操作数有要求
- strncmp,最后一个参数 4,代表只对比前 4 个字符的大小
1 |
|
- strncat,最后一个参数 3,代表只追加前 3 个字符到目的地
- strncpy,最后一个参数 5,代表只拷贝前 5 个字符到目的到底
这三个函数的用法非常简单,这里不多赘述!
1.6 strstr
1 | const char * strstr ( const char * str1, const char * str2 ); |
这个函数就是第一次见了,它的作用是在字符串 s1 里面查找是否有字符串 s2
- 如果有,返回字符串 s2 在字符串 s1 里的起始地址
- 如果没有,返回 NULL
模拟实现
str 函数的模拟实现相对来说比较复杂
最重要的就是遇到多个字符相同而最后不同的情况
- 需要用另外一个指针 C 来遍历字符串,找寻 C 和 ptr2 所指元素相等的第一个字符
- 然后用 ptr1 来和 ptr2 比较,C 保持不变
- 如果匹配成功,返回 C 指针
- 如果匹配失败,C++ 后赋值给 ptr1,继续进行查找
1 | //5.strstr 判断str1里面有没有str2 |
1.7 strtok
该函数用于查找一个字符串中的分隔符
1 | char * strtok ( char * str, const char * sep ); |
sep 参数是个字符串,定义了用作分隔符的字符集合 (可以包含多个分隔符)
第一个参数指定一个字符串,它包含了 0 个或者多个由 sep 字符串中一个或者多个分隔符分割的标记
strtok 函数找到 str 中的下一个标记,并将其用
\0
结尾,返回一个指向这个标记的指针
注:strtok 函数会改变被操作的字符串(把分隔符改为 \0),所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容,并且可修改
- strtok 函数的第一个参数不为 NULL,函数将找到 str 中第一个标记,strtok 函数将保存它在字符串中的位置
- strtok 函数的第一个参数为 NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
- 如果字符串中不存在更多的标记,则返回 NULL 指针
如下如所示,在后续调用的时候,我们可以往 strtok 函数里传入 buf,也可以直接传入 NULL,因为传入 NULL 的时候该函数会在上一次操作的字符串里继续查找分隔符
这样写很多行太麻烦,我们可以尝试用 for 循环的方式来简化代码
- for 循环的第一个表达式只会执行一次,让 str=strtok 第一次查找的返回值
- 如果该返回值为空(没找到更多的分隔符),停止循环
- 如果该返回值不为空,就让
str=strtok(NULL, p)
,继续查找并打印下一部分
1 | int main() |
1.8 strerror
1 | char * strerror ( int errnum ); |
C 语言中规定了一部分错误码,这些错误码有他们对应的错误信息
这个函数的作用比较特殊:将错误代码翻译成提示信息
errno 是 C 语言提供的一个全局变量,可以直接使用,放在 errno.h 文件中
当库函数使用发生错误时,会把 errno 这个全局的错误变量设置为本次执行库函数产生的错误码
这时候可以用 strerror 函数将 errno 错误码翻译成错误信息
1 |
|
1.9 strcasecmp
这个函数的作用是比较的时候忽略大小写,注意头文件是 strings.h
1 |
|
这里还有一个 strncasecmp,作用相同,但是比较的是前 n 个字符
1 | The strncasecmp() function is similar, except it compares the only first n bytes of s1. |
可以通过代码测试看出 strcmp 和 strcasecmp 两个函数的区别
1 | void test_cmp() |
运行
1 | strcmp: 32 |
因为小写英文字母的 ascii 码大于大写英文字母,所以 strcmp 的结果大于 0,而 strcasecmp 忽略了大小写,返回 0 代表二者相同
2. 内存函数
2.1 memcpy
这个函数的作用也是拷贝内容,和 strcpy 不同,memcpy 可以拷贝任意类型
1 | void * memcpy ( void * destination, const void * source, size_t num ); |
- 函数 memcpy 从 source 的位置开始向后复制 num 个字节的数据到 destination 的内存位置
- 该函数在遇到 ‘\0’ 的时候并不会停下来
- 如果 source 和 destination 有任何的重叠,复制的结果都是未定义的
使用方法如下
模拟实现
在之前的 qsort 快速排序函数的模拟实现里面,我们接触到了 void*
指针,以及用 char*
指针来进行单个字节访问的模拟方法
在这里我们使用 void*
指针进行数据的拷贝
- 要注意的是我们不能直接对 void * 指针进行 ++,而要将其强制类型转换成 char * 指针后 + 1
1 | ////1.memcpy模拟实现 |
2.2 memmove
1 | void * memmove ( void * destination, const void * source, size_t num ); |
这个函数和 memcpy 的功能基本一致,只有一点不同
- memmove 在拷贝的时候,源地址和目的地可以重叠
如图所示,我们可以将 arr1 数组的一部分拷贝回该数组里面
但如果你测试一下,就会发现 vs 编译器下 memcpy 也是能够拷贝内存重叠的数据的
- C 语言并没有对 memcpy 函数做出如下要求,部分编译器的 memcpy 可能就不支持这样操作
- 为了避免出错,我们在拷贝内存重叠数据的时候最好使用 memmove 函数
模拟实现
在编写该函数的时候,我们需要注意拷贝的顺序
如果重叠部分还是从前向后拷贝的时候,就会出现后面的内容被前面拷贝来的数据篡改,结果不符合要求的情况
实现思路是,如果起始地址大则从后往前复制,如果起始地址小则从前往后复制:
- 如果我们的目的地在源地址的后面,就应该从后向前拷贝,避免数据被改写;
- 如果我们的目的地在源地址的前面,就应该从前向后拷贝;
这里的前 / 后都是指有重叠的情况,如果没有重叠,从前往后 / 从后往前都不影响
最终的函数模拟如下
1 | void* my_memmove(void* dest, const void* sour, int num) |
2.3 memcmp
1 | int memcmp ( const void * ptr1, |
这个函数的作用是:以字节为单位进行比较
模拟实现
1 | int my_memcmp(const void* ptr1, const void* ptr2, int num) |
运行结果
2.4 memset
1 | void * memset ( void * ptr, int value, size_t num ); |
这个函数的作用是把内存中 ptr
所在位置的 num 个字节的内容改为 value
示例代码如下图所示
2.5 bzero
这个函数的作用是将一个地址的前 n 个字节设置为 0,可以理解为 memset 的简化版本。其需要使用的头文件是 strings.h
,范围广于 string.h
1 |
|
使用方法也很简单
1 |
|
可以看到,代码运行后,前三个字符没有被打印出来。这是因为 0 在 ASCII 码中是 \0
,对于字符来说什么都不会打印
3. 字符串打印
下面的函数都是对 printf 的变种,用法和 printf 是一样的,只不过打印的目标位置不同
3.1 sprintf
1 | int sprintf(char *str, const char *format, ...) |
和 printf 的使用一样,只不过多了第一个参数,将 printf 的内容打印到一个字符数组 str 中
3.2 snprintf
1 | int snprintf ( char * str, size_t size, const char * format, ... ); |
将 printf 的目标设置为 str 字符数组,并限定 size,超过 size 的部分会被截断(也就是最多只能打印 size 字节到 str 里面)
3.3 fprintf
1 | int fprintf(FILE *stream, const char *format, ...) |
这个函数也是多了一个 FILE 文件指针,将 printf 的内容输出到文件指针中
结语🍑
今天的内容有点小多,这些函数以后我们就会经常接触啦~
熟能生巧!
- 最新
- 最热
- 最早
- 作者
点击重新获取 | 打开控制台