目录标题
前言
大家学习c语言发现一个现象没有就是我们的c语言有各种各样的类型,有整型,有浮点型,有字符类型等等,那么大家发现一个问题没有就是我们c语言是没有字符串类型的,我们通常将字符串放到常量字符串中或者字符数组中,那么我们c语言为了更好的操作我们的字符串,就提供了一些列的函数来帮助我们的操作字符串。
求字符串长度的函数:strlen
我们c语言提供了一个函数strlen,该函数能够帮助我们求得字符串的长度。我们来看看该字符串的介绍:
通过这个介绍我们可以看到我们这个函数需要一个字符指针作为参数,然后这个函数返回类型是size_t类型的值,那么我们这个函数的作用就是计算我们字符串的长度,那么我们这个长度如何来理解呢?就是从你给的那个字符指针的位置开始依次往后计算,遇到一个字符那么我们的长度就加1,这样一直往后直到遇到了’\0’我们就停止了加1,所以我们这里’\0’其实是没有加上去的,那么我们这个函数最终返回的值就是我们这里加到最后的值,那么我们来看一个例子来看看这个代码是如何来使用的:
#include<stdio.h>
int main()
{
int arr[] = "abcdefg";//该字符串后面会自动添加\0
int a = strlen(arr);
printf("该字符串的长度为:%d", a);
int b = sizeof(arr) / sizeof(arr[0]);
printf("该数组的元素个数为:%d", b);
return 0;
}
首先我们这里知道的一点就是我们以这样的方式来创建的字符数组是会自动在末尾加上’ \0 '的,所以我们这里的strlen计算的长度就是abcdefg的长度也就是这里字符的个数为7,然后我们下面又执行了这么一个代码sizeof(arr) / sizeof(arr[0])
这个代码计算的是数组中元素的个数,那么我们这里的字符数组虽然是赋值的是abcdefg但是由于这里会自动添加一个\0上去,所以我们这里数组元素的个数就是字符的个数+1为8,,那么我们可以看看这个代码的运行的结果:
那么我们这里可以将这个代码进行一定的修改就可以体现我们这里的strlen函数的特性,这个特性是知道计算到\0为止,我们可以将上面的初始化的方式进行修改:将以字符串的形式进行初始化改成单个字符的形式进行初始化,那么我们这里的代码修改如下:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[6] = {'a','b','c','d','e','f'};
int a = (int)strlen(arr);
printf("该字符串的长度为:%d\n", a);
int b = sizeof(arr) / sizeof(arr[0]);
printf("该数组的元素个数为:%d\n", b);
return 0;
}
那么我们这里的初始化的时候特意将数组的形式初始化为6,而且也特意的将我们数组的元素全部都初始化为非\0的字符,因为我们不对其进行完全初始化的话我们编译器这里会默认将剩下的元素全部初始化为0,而我们\0的ascall码值也是0所以我们这里就会自动添加\0上去,所以我们这么做的原因就是其实数组中的元素不会出现\0这样的话我们就可以验证我们的strlen函数会不会出现越界访问的情况,那么我们这里的运行的结果为:
我们可以看到外面的strlen在计算的时候确实会一直访问知道遇到\0为止,那么我们了解了strlen函数的性质之后我们来看看如何来模拟实现这个strlen函数,首先我们知道这个函数需要的参数是一个字符指针,那么我们是不是可以对这个字符指针解引用来得到这个指针对应的字符,然后我们再对其进行判断,判断它是否为\0,那么这里我们就再创建一个变量来计算它的个数,如果不是\0的话我们这里就将这个变量的值加上1,然后再让我们的我们的指针++,因为我们这里的是字符指针,所以每次加一都会跳过一个字节,这样的话我们就可以访问下一个字节了,那么这里我们就将上面的判断过程和放到一个循环里面,这个循环结束的标志就是当判断为\0的时候就跳出循环,跳出循环之后就将我们的这里创建变量的值作为这个函数的返回值,那么根据上面的思路我们这里的代码实现如下:
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char * str)
{
size_t count = 0;
assert(str);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdefg";
int a = (int![请添加图片描述](https://img-blog.csdnimg.cn/255ff2b801c04da19dea2bfd8fb92c57.png)
)my_strlen(arr);
return 0;
}
那么这里我要对这个代码说明几点就是我们这里的参数这边使用的是:const char *
来接收我们的传过来的参数,那么这里是因为我们该函数的目的是求得这个字符串的长度,并不需要改变我们这个传过来的参数里面的内容,所以我们这里就用const来修饰这样就可以防止我们改变它的内容,那么我们这里再来看开这里的assert(str)
是什么意思呢?我们称这个函数是断言函数,那么我们这里传过来的是一个字符指针,那么这个指针它能是空指针吗?当然不能,那么我们这里为了防止使用这个函数的人传一个空指针过来我们就使用一个assert来进行断言一下,如果assert里面是一个空指针的话,我们这里就会直接显示错误,并且告诉我们错误在哪一行,那么这里报错的样子就是这样:
并且还会显示是哪一行出现了问题:
我们这里显示的就是28行出现了空指针断言的问题,那么这里我们还可以看到这里创建变量的类型是size_t类型,那么这个类型实际上是unsigned int类型也就是一个无符号整型,那么我们这里之所以采用这样类型的变量来计数的原因就是我们这里的长度它是不可能为负数的,那么我们这里就采用size_t的形式来表明它的形式是为正数的,但是这样做的话我们就有那么一个问题,我们来通过下面的一个代码来表明这个问题是什么:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "abcdef";
char arr1[] = "abcdefg";
if (strlen(arr) - strlen(arr1) > 0)
{
printf("hello\n");
}
else
{
printf("world\n");
}
return 0;
}
这段代码很好理解,很明显我们这里的arr的长度是小于我们arr1的长度的,所以我们这里相减的结果为应该为负数,但是我们这里打印的结果应该是world,但是我们将代码运行起来之后我们就会发现答案视乎并不一样:
这是因为我们strlen放回的类型是size_t类型,也就是无符号整型,既然两个无符号整型相减,那么我们得到的结果就还是无符号整型,那么我们这里无符号整型是不可能小于0的,所以这里不管两个字符串的长度为多少我们这里都会打印hello,那么这里就是我们平时使用时要注意的一点,那么看到这里想必大家对strlen这个函数已经非常的了解,那么接下来我们再来看看另外一个函数。
长度不受限制的字符串函数
首先我们来了解一下什么叫长度不受限制,也就说你给多少个字符我就接收多少个字符,那么我们这里就要介绍3个函数分别是:strcpy,strcat,strcmp我们来分别看看这些函数的作用以及注意事项和要点。
strcpy
我们首先来看看这个函数的基本信息:
那我们这里来简单的翻译一下就是将源字符串的内容复制到我们目标目标指针所指向的内容里面去,复制的过程是遇到\0就停止,而且这个复制的过程还包括\0的拷贝,那么接下来我们就可以通过一个例子来看看这个函数是如何使用的,首先来看一下这个代码:
#include<string.h>
#include<stdio.h>
int main()
{
char arr1[10] = "abcdefg";
char* p = "hijhklmn";
strcpy(arr1, p);
printf("%s", arr1);
return 0;
}
那么这里我们首先创建了一个数组,我们将这个数组的内容以字符串的形式进行初始化,然后我们再创建一个指针,这个指针的内容是一个字符串常量的首元素的地址,那么接下来我们就开始调用我们的strcpy函数我们将arr1放到目标地址所对应的位置,将p放到源地址所对应的位置,这样的话我们就可以将p地址里面的内容拷贝到我们的arr1所对应的内容里面去,最后我们再将arr1地址里面对应的内容打印出来,那么这里我们可以看看运行结果:
我们可以看到这里确实是发生了改变,但是我们这里还是有几个点需要我们注意的。
第一点:
我们如何来验证我们这里会将源字符串中的\0也拷贝到目标字符串里面去呢?那么这里我们就可以将这里的代码稍微做一点改动,我们再往arr1数组里面添加新的内容,使其长度超过我们这里的常量字符串,这样的话我们就可以用监视来看这里面发生改变的过程比如说这样:
#include<string.h>
#include<stdio.h>
int main()
{
char arr1[20] = "abcedfg123456";
char* p = "hijhklmn";
strcpy(arr1, p);
printf("%s", arr1);
return 0;
}
那么我们这里先来看看执行strcpy之前我们数组arr1里面的内容为:
而我们执行完strcpy之后我们会发现这个数组里面的元素变成了这样:
我们可以恒明显的发现我们这里在拷贝的过程中拷贝了\0进去,那么这里希望大家可以理解。
第二点:
我们的源字符串必须得以\0结尾,因为我们这里复制的过程是依靠\0来作为标志来表示结束的,所以如果你源字符串里面没有\0的话,那么这里这里就会将很多的未知数据也拷贝进来,那么这里我们可以将代码进行修改改成这样再来通过监视来看看这个复制的结果是怎么样的
#include<string.h>
#include<stdio.h>
int main()
{
char arr1[20] = "abcedfg123456";
char arr2[3] = { 'y','c','f' };
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
那么我们我们这里复制后的结果就为:
我们发现除了我们熟悉的ycf以外还有很多的未知值,那么出现这个原因就是因为我们这里的源字符串里面没有\0导致的,那么它会将这个数组后面的内容也拷贝到我们这个字符串里面,所以我们的源字符串里面一定得以\0结尾。
第三点:
目标空间得足够大,以确保能够存放源字符串。那么这个就很好理解为什么了,因为我们这里是将源字符串里面的内容复制到我们的目标空间里面去,并且这个复制的过程是遇到源字符串里面的\0才停止的,那么如果你目标字符串的空间不够大的话,那么这里就会出现越界访问的问题,我们来看看这段段代码的打印结果:
#include<string.h>
#include<stdio.h>
int main()
{
char arr1[8] = "abcedfg";
char arr2[4] = "hij";
strcpy(arr2, arr1);
printf("%s", arr2);
return 0;
}
这段代码就有个很明显的错误就是这里的arr2的大小明显比arr1的大小要小,但是我们依然要把arr1的内容复制到我们的arr2里面去,那么这样的话我们来看看这里会爆出什么样的错误:
我们发现这里确实将字符串打印出来了,但是我们这里爆出了错误说我们越界访问。
第四点:
我们目标空间必须得是可以修改的。这句话是什么意思呢?我们这个函数的作用是将源字符串的内容复制到目标字符串里面去,那么这里就出现了一个问题就是,我们知道字符串存储的位置有两个一个是用字符数组来进行存储,另外一个就是放到自读数据区来进行存储,但是我们的自读数据区他是不能进行修改的啊,那么如果你把这个区域里面的元素放到目标字符串的位置的话,那么是不是就会报错啊,对吧比如说下面的代码:
#include<string.h>
#include<stdio.h>
int main()
{
char* arr1 = "abcedfg";
char arr2[4] = "hij";
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
我们将这个代码运行一下就会发现这里啥反应都没有:
什么都没有打印出来,所以大家这里要注意一下哈,我们目标空间是必须得可以修改的。那么看到这里想必大家应该都能够了解我们这里的strcpy的用法,那么接下来我们就来看看如何来模拟实现这个函数。
strcpy的模拟实现
首先我们知道这个函数需要的两个参数是两个char类型的指针,那么我们要将源地址里面的内容复制到目标地址所对应的内容里面去,那么这里我们就可以用解引用的方式来进行实现,我们这里复制的过程是到\0就停止,那么这个停止的功能我们可以放到循环的判断条件里面来进行实现,又因为我们这里的不仅仅是拷贝一个字符串,而是很多个,所以我们就将这个拷贝的过程放到一个循环里面这个循环结束的条件就是遇到了\0,那么每次循环我们都让目标指针和源指针++来跳转到下一个字符,那么我们将上面的思路总结起来代码就是这样的:
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest);
assert(src);
char* ret = dest;//记录目标函数的起始位置
while (*src)//如果目标位置跳到\0的话就停止循环
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//拷贝\0到目标位置里面去
return ret;//返回目标空间的起始位置
}
int main()
{
char arr1[10] = "abcdefg";
char* p = "hijhklmn";
my_strcpy(arr1, p);
printf("%s", arr1);
return 0;
}
那么根据代码上面的注解想必大家应该能够很好的理解我们这里的实现过程,那么接下来我们就来看看下面的函数介绍吧。
strcat
首先我们来看看这个函数的介绍:
那么我们上面讲的这个函数他是从目标空间的开头开始复制,那么我们这个函数他的功能就和上面的有那么些不一样了,我们这里是从目标空间里的字符串的结尾也就是\0的位置来开始复制,一直复制到我们的源字符串的结尾,包括\0也会复制进去,那么我们可以通过下面的代码来看看这个函数具体是如何来使用的:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[10] = "abcd";
char arr2[] = "efgh";
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
那么这个代码那就非常的好理解了,我们这里创建了两个数组,然后我们就使用strcat函数将arr2里面的内容尾插到我们的arr1里面去,再将arr1数组里面的内容打印出来,我们来看看代码的运行结果如何:
我们可以看到确实我们将arr2里面的内容尾插到我们的arr1里面去了,并且我们还发现这里没有打印一些奇奇怪怪的数据,那么这就说明我们在复制的时候确实将我们的源字符串中的\0也复制进去了,但是这里有细心的小伙伴们应该发现了一个问题,你凭什么说这里他把\0复制进去了啊,你说他没有越界访问打印奇奇怪怪的数据就是将\0也复制进去了,但是我好像知道的一点就是不完全初始化的化,剩下的部分就会默认初始化为0,而\0对饮的ascall码值也是0,那么我咋知道你这个尾插的过程是将\0复制进去了还是原本初始化的末尾初始化了有\0呢?那么为了解决这个疑问我们可以这样做:我们对这个这个目标数组进行特殊的修改一下,我们将他的每一个元素全部都初始化到,然后在这个元素的中间特意的初始化一个\0,那么我们在尾插的时候就会跑到中间的这个\0进行尾插,因为我们这里是将这个每个元素都进行初始化了,所以除了中间的那个\0以外是没有其他的\0的,那么我们再将源字符串中的元素尾插进去他就会覆盖掉目标空间中的唯一个\0,那么我们再将其进行打印的化,如果出现了越界访问那么就说明我们这个尾插是不会复制源字符串中的\0的,如果不会出现越界访问说明我们这个尾插会把\0也尾插进去,那么这里我们的代码就如下:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[11] = {'h','e','l','l','o','\0','w','o','r','l','d'};
char arr2[] = "ycf";
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
将这个代码运行一下就会发现并没有出现越界访问:
而且我们还可以通过监视的方式来看看这个这个内部变化的过程:
这是复制之前的样子,复制之后就成为了这样:
我们可以看到下标为8的位置发生了修改变成了\0那么这里就说明了我们这里的尾插的过程其实是会把源字符串中的\0也尾插进去的,那么所以看到这里想必大家应该注意到了一点就是我们这里的源字符串必须得是以\0结束的不然的话很可能会出现越界访问,比如说我们下面的代码:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[11] = { 'h','e','l','l','o','\0','w','o','r','l','d' };
char arr2[3] = {'y','c','f'};
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
我们再运行的时候就会发现出现了问题越界访问了:
所以我们在使用尾插的时候一定要注意了我们的源字符串必须得是\0结尾,那么根据上面的学习我们这里肯定还有两个要注意的点就是我们这里的目标空间必须得足够大,以至于能够容纳下源字符串的内容,并且我们这里的目标空间必须得是可以修改的,那么这里的原因与我们上面讲的原因那是一模一样的我们这里就不进行过多的描述,那么这里肯定有一些脑回路比较强的小伙伴会想出一个情况就是我们这里要是自己尾插自己的话会是一个什么样的情况呢?我们可以讲这个代码写出来看看:
#include<stdio.h>
#include<string.h>
.
int main()
{
char arr2[10] = "ycf";
char* p = arr2;
strcat(p, arr2);
printf("%s", arr2);
return 0;
}
在我们的vs2022平台下这个代码它是是可以正常跑过去的,但是在有一些编译器下这个代码他是跑步过去的,那么这里跑不过去的原因是什么呢?那么这里我们就得先来讲讲strcat的模拟实现再来解释这个问题。
strcat的模拟实现
其实我们这个函数的模拟实现还是非常简单的,因为跟上面的strcpy非常的相似,唯一的区别就是一个是从头开始复制,另外一个就是从尾开始复制,那么我们这里就可以先通过循环来找到我们这里的目标地址的尾,再来跟strcpy一样实现剩下的尾插功能,那么这里我们的代码实现如下:
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;//记录起始位置
while (*dest)//寻找目标空间中的\0
{
dest++;
}
while (*src)//开始复制一直到到\0结束
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//复制\0
return ret;//返回起始位置
}
int main()
{
char arr1[10] = "abcd";
char arr2[] = "efgh";
my_strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
那么看到这个代码想必大家应该能够明白为什么我们有的编译器在自己尾插自己的时候会报错,那么我们这里可以发现我们在对自己实现尾插的时候会不断的将我后面的\0覆盖掉变成其他的值,而我们尾插的结束的标志就是遇到\0,而你尾插自己的时候你会不断的把后面的\0覆盖掉这样的话就会使得一直遇到不了\0就会一直复制下去,直到越界访问的时候报错,所以如果我们自己尾插自己的时候很有可能会进入死循环,但是有些编译器却不会出错,这是因为实现的原理不一样,但是有些编译器还是会报错的。
strcmp
首先我们来看看这个函数的介绍:
那么我们称这个函数叫比较函数,这是用来比较这两个字符串是否相等的,那么在之前的学习当中肯定有小伙伴们这样比较两个字符串是否相等:
#include<stdio.h>
int main()
{
if ("hello world" == "hello ycf")
{
printf("haha");
}
else
{
printf("hehe");
}
return 0;
}
这样比较之后发现确实不相等,确实打印出来了hehe,于是就会觉得这里的比较就是正确的,但实际上这样的比较其实是错误的,因为这样比较的实质是比较两个字符串的首元素的地址,那么为了解决字符串比较的问题,我们这里给出了strcmp这个函数,那么我们这个函数是如何来实现这个比较的过程的呢?首先我们可以看到这个函数的介绍中写道这个函数需要两个char类型的指针,那么这个指针指向的就是两个不同的字符串,然后简介中写到了我们这里的函数在比较的时候时一个字符一个字符的比较,就是指针指向的那个字符进行比较,如果指向的两个字符相等的话两个指针就会同时加一跳转到紧挨着的下一个字符,这样一直比较下去直到出现了不同或者说两个指针同时指向了函数的末尾\0,就会停止比较并且返回一个值,那么这里的不同指的是ascall码值的不同,如果在比较的过程中出现了两个指针所指向的字符的ascall值的不同的话,那么我们这里就会立刻停止比较,并且返回一个值来结束这个函数,那么这里返回的值是这么来决定的,就是:
- 第一个字符串大于第二个字符串,就返回大于0的数字。
- 第一个字符串等于第二个字符串,就返回0。
- 第一个字符串大于第二个字符串,就返回小于0的数字。
那么看到这里我们应该能够理解这个函数的基本用法,那么我们来看看用代码是如何使用这个函数的:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdefg";
int a = strcmp(arr1, arr2);
if (a)
{
printf("arr1中的字符串较大\n");
}
else
{
printf("arr2中的字符串较大\n");
}
printf("%d", a);
return 0;
}
那么我们看看这个代码运行的结果:
我们发现这样的比较就合理的多了,并且这里说的小于0的值就是-1,那么你要是下去测的话,你会发现那个大于0的值其实就是1 。看到了这里想必大家能够很好的理解我们这里函数的用法,这里还是得跟大家再来强调的一点就是我们这里比较的不是长度哈,比较的一对一对的字符串的大小,那么接下来我们就来看看这个函数具体是如何来实现的。
strcmp的模拟实现
首先我们这里可以清楚的一件事就是我们这里首先得需要两个指针,这连个指针分别指向了两个字符串的首元素的地址,那么根据介绍我们这里是一对字符一对字符的进行比较,那么我们这里就可以对这两个指针进行解引用,这样我们就可以得到这两个字符,然后我们就可以对齐内容进行比较,那么这里如果内容是不相同我们就可以直接根据需求返回一个一个整型值,那么如果我们这里的两个字符是相等的,那么我们这里就可以让两个指针进行++,因为我们这里比较的不是一个字符而是一长段的字符串,所以我们这里就可以将其放到一个循环里面这个循环的结束的条件就是两个指针指向的内容全是\0这样的话我们就可以直接放回0,那么这里就是我们这个函数实现的一个思路,那么我们将这个思路总结成代码就是这样:
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abc";
char arr2[] = "abcd";
int ret = my_strcmp(arr1, arr2);
if (ret < 0)
{
printf("<\n");
}
else if (ret == 0)
{
printf("==");
}
else
{
printf(">\n");
}
return 0;
}
长度受限制的字符串函数
那么有了上面的经验我们看这一类的函数,那么就很好理解了,我们上面是长度不受限制的字符串函数,就是一直进行操作一直遇到\0为止,但是很多时候我们是不用将整个字符串都进行操作的,我们有时候只想执行前几个字符,比如说我只想执行追加3个字符,我只想比较4个字符,我只想复制5个字符,那么这种要求的话我们上面的函数是无法做到的,并且上面的函数还有那么一个缺点就是容易写出bug,因为我们上面的函数是整个字符串都进行炒作,而我们很多的炒作有时候可能会因为空间的不足而导致程序出错,而我们这里的长度受限制的字符串函数在进行操作的时候就会让程序员自己输入一个长度,这样的话就相当于给使用者一个提醒让他看一下自己输入的这个长度是否合理,所以就有了我们这里的长度受限制的字符串函数。
strncpy
我们之前的讲的strcpy这个函数的作用是复制整个字符串到另一个字符串里面去,比如说这样:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "higklmn";
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
我们来看看这段代码的运行结果:
那么我们发现我们这里的复制就是一整个字符串的复制,直到遇到\0就结束了,那么如果我们这里不想拷贝整个字符串,我们只想拷贝这个字符串中的前3个字符到目标字符串里面去,那我们又该怎么做呢?那么这里我们就可以使用strncpy这个函数,我们首先来看看这个函数的介绍:
我们这个函数的参数就与之前的那个函数有所不同,它多了一个参数这个参数需要一个size_t类型的一个变量,那么我们根据这个简介就知道这个size_t类型的变量就是我们想要复制的字符的个数,那么这样的话我们就可以实现我们上述的功能:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "higklmn";
strncpy(arr1, arr2,3);
printf("%s", arr1);
return 0;
}
我们来看看打印的结果:
那么我们发现我们这里确实只复制了前三个字符,但是我们这里就有了这么一个问题就是我们这里给的数字大小要是大于我们源字符串中字符的长度那会发生什么呢?比如说我们这里只有4个字符,但是我想让他复制6个位置,那么多出来的位置该如何处理呢?比如说下面的这段代码:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "ycf";
strncpy(arr1, arr2,6 );
printf("%s", arr1);
return 0;
}
我们来看看这段代码的运行结果是怎么样的:
嗯?这是怎么回事呢?我们为啥只打印出来3个字符呢?那么为了搞清楚这个问题,我们可以通过监视来看看这个复制的过程是怎么样的,这是复制之前的内容:
这是复制之后的内容:
我们可以很清楚的发现如果我们这里在剩余的位置处全部都变成了\0,所以我们这里就发现了这个函数的一个性质就是如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边自动追加\0直到num个为止。
strncpy的模拟实现
那么这个实现和我们strcpy的实现其实基本上都是相同,就是多了一个限制的条件嘛,这个限制的条件就是我们这里的num,我们创建一个变量k,我们没进行一次复制我们就让k的值+1,这样等我们的k的值大于num的时候我们就直接跳出这个循环,那么这里就还剩下一个问题就是我们这里要复制的字符的个数要是大于我们源字符串的长度那么这里又该如何来实现呢?那么我们这里就可以添加两个if语句来判断一下,当我们的指针指向了源字符串的末尾的时候也就是\0的时候,我们就不再对指向源字符串的指针进行++,如果没有指向末尾我们就还可以对齐指针进行加加,那么我们这里的代码实现就如下:
#include<stdio.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
size_t k = 0;
char * ret = dest;
while (k < num)
{
if (*src != '\0')
{
*dest = *src;
dest++;
src++;
k++;
}
else
{
*dest = *src;
dest++;
k++;
}
}
return ret;
}
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "hig";
my_strncpy(arr1, arr2,6);
printf("%s", arr1);
return 0;
}
strncat
那么我们这里的尾插和上面的也就是同样的道理,我们可以先看一下这个函数的介绍:
我们发现这个函数中的参数与我们上面的参数是一样的,那么我们这里就很好理解了,这最后一个参数就是你想追加的字符个数,第一个参数是我们要追加的目的字符串,我们第二个参数就是我们追加的内容就是我们所说的源字符串,那么我们这里就来看看我们如何使用这个函数:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "ycf";
char arr2[] = "lovelxy";
strncat(arr1, arr2, 7);
printf("%s", arr1);
return 0;
}
我们这里看看运行的结果是什么?
我们发现这里确实追加了7个字符上去了,那么这里大家就要注意一点的就是我们之前的那个长度不受限制的字符串函数中的追加函数,他是一直追加到\0为止,并且它还会把\0加上去,那么我们这里的这个函数就会出现一个问题,就是我们这里会不会添加\0进去呢?答案是会的,我们这里在追加完之后会自动的追加一个\0上去,比如说你要追加4个字符上去,它就会自动的追加4个字符上去,但是在追加完这4个字符之后还会再追加一个\0上去,那么既然我们这里涉及到一个字符个数,那么这里就同样会出现一个问题就是要是给的数字大小大于我们这里的字符串长度的话,那会出现什么样的情况呢?那么我们这里就不会跟上面的复制函数一样不停的复制\0,而是只会添加一个\0上去就不管了,不管你给的数字有多大只要你超过了字符串长度的话都只会添加一个\0上去,那么我们可以用这段代码来进行验证:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "######\0########";
char arr2[] = "ycf";
strncat(arr1, arr2, 10);
printf("%s", arr1);
return 0;
}
因为我们这里的追加是先找到\0的位置进行追加,虽然我们这里有两个\0但是我们这里追加的地方是我们的第一个\0的地方,那么我们这里的追加前的内存信息是:
那么我们这里来看看追加只后的结果为:
我们可以发现虽然我们这里是追加10个字符,但是我们内存里面的情况依然是只会追加一个\0上去,那么看到这里想必大家应该能够理解我们这里的这个函数的性质了,那么我们接下俩就来看看这个函数是如何来实现的。
模拟实现strncat函数
那么我们这里的函数实现其实就和我们的上面strcat函数的实现差不多,就多了一个特征就是给的数字超过了字符串的长度就只会加一个\0,如果没超过也会自动加一个\0上去,那么我们这里首先得想到的一点就是我们这里肯定得用到循环,并且这个每次循环我们都使得追加一个字符上去,每次循环我们都使两个指针++,那么这里我们就就得想想我们这里的结束的条件是什么,首先肯定可以想到我们这里的追加的字符数等于我们给的那个数字的时候就可以直接跳出我们的循环,到外面我们就可以直接再复制一个\0上去,那么这里我们就可以讲两个条件放到一起去只要有一个条件成立我们就可以直接跳出循环,那么我们实现的代码如下:
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest);
assert(src);
size_t k = 0;
char* ret = dest;
while (*dest)
{
dest++;
}
while ((k != num) && (*src))
{
*dest = *src;
dest++;
src++;
k++;
}
*dest = '\0';
return ret;
}
int main()
{
char arr1[] = "######\0########";
char arr2[] = "ycf";
my_strncat(arr1, arr2, 10);
printf("%s", arr1);
return 0;
}
strncmp
我们首先来看看这个函数的基本的介绍:
那么根据我们上面的两个例子想必大家应该能够推理出我们这个函数的作用就是比较字符串中一部分的,那么这个一部分就是我们给定的,比如说两个个字符串中的前3个字符,两个字符串中的的前5个字符进行比较,那么比如下面的这个代码:
#include<stdio.h>
#include<string.h>
int main()
{
char* arr1 = "abcdefg";
char* arr2 = "abcdefghijk";
int ret = strncmp(arr1, arr2, 7);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
我们首先看到我们这里的两个字符串的长度完全不一样,所以我们要是用之前的那个strcmp来进行比较的话我们这里肯定是会执行下面的else语句的,但是我们这里不一样我们这里只让它比较前面四个字符的长度的数据,那么我们这里比较的结果就会完全相反,因为我们这里就比较前面7个数据,而我们这里的两个字符串前面的7个字符都是一模一样的所以我们这里就会打印出字符串相等这个语句
那么我们这里要是将这个比较的字符个数改成8,那么结果就会不相同,因为我们这里的第一个字符串的第8个字符是\0,而我们这里的第二个字符串中第8个字符是h,所以我们这里运行的结果就为:两个字符串不相等。
#include<stdio.h>
#include<string.h>
int main()
{
char* arr1 = "abcdefg";
char* arr2 = "abcdefghijk";
int ret = strncmp(arr1, arr2, 8);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
那么这里同样也会涉及到这么一个问题,要是我们这里给的数字大于我们的字符串的长度那会发生什么呢?比如说我们两个字符串是相等的,但是如果我们这里要比较的长度大于我们的字符串的长度会发生什么?它会比较这个字符数组后面内存的内容吗?于是又小伙伴们就写出了这样的代码来进行测试:
#include<stdio.h>
#include<string.h>
int main()
{
char* arr1 = "abcdefg";
char* arr2 = "abcdefg";
int ret = strncmp(arr1, arr2, 30);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
结果测试的结果就是:
那么这里大家有没有想过一个问题,我们这里测试采用的数组是常量字符串,而我们这里用的arr1和arr2是一个字符指针,所以我们这里的arr1和arr2里面装的都是都是这个常量字符串的首元素的地址,那么这两个变量的地址都是一模一样的,那么你觉得比较的结果会是不同的吗?那么这样看的话你怎么知道它会不会越界访问其他的字符,就算越界进行比较的话,那么它比较的结果也是一样的,所以我们采用这样的方式进行比较固然不大合理,所以我们就可以采用这样的方式来对其进行测试,我们采用数组的形式来存储这个字符串,这样的话我们就会在内存当中开辟两个内存来存储这个字符串,那么我们这里再来进行比较的话我们就有了一个判断的标准,如果我们这里会进行越界比较的话,我们这里越界之后的内容肯定是不一样的,因为我们这里的两个地址都是不一样的,如果我们这里不会进行越界比较,比较到有一个字符串出现了\0就结束的话,那么我们这里就会打印出两个字符串相等,因为我们这里的字符串是一样的所以他们会同时到\0同时结束,然后打印出字符串相等,那么这里我们的代码就是这样的:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "abcdefg";
int ret = strncmp(arr1, arr2, 30);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
我们来看看打印的结果是什么样的:
我们发现这里打印的结果是字符串相等,那么这就说明我们这里如果给的长度大于我们的字符串的长度,那么它在比较的时候遇到\0就会结束比较,而不会再往后继续越界比较两个指针指向的内容。那么这里我们还有一个更好的比较用的代码就是这样的:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abc\0defg";
char arr2[] = "abc\0hefg";
int ret = strncmp(arr1, arr2, 30);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
那么这样的话就可以更好的突出我们这里的特性就是双方都遇到\0那么比较就结束了,那么看到这里想必大家应该能够理解我们这里的这个函数的使用,那么我们接下来就来看看如何模拟实现这个函数。
strncat的模拟实现
那么我们这里的模拟实现又有那么点不同,我们这里多了一个结束的条件就是我们这里给出了比较的是数目多少,那么我们这里在比较的时候就得加一个判断的条件,创建一个变量来记录我们的我们这里比较的次数,如果次数达到了我们这里的要求我们就直接跳出循环,那么我们这里就可以对其进入修改:
#include<stdio.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
size_t k = 0;
while ((k < num)&&(*str1)&&(* str2))//当出现了\0就跳出循环
{
if (*str1 == *str2)//相等就跳到下一个字符
{
str1++;
str2++;
k++;
}
else
{
return *str1 - *str2;//在\0之前出现不同就返回这两个字符的差
}
}
if (*str1 == *str2)//跳出了循环要么是遇到了\0要么就是k达到了给的值
{
return 0;//这两种情况都是相等导致如果相等我们就返回0
}
else
{
return *str1 - *str2;//当然还有种情况就是有一边有\0有一=边却没有
}
}
int main()
{
char arr1[] = "abcdefg";
char arr2[] = "abchefg";
int ret = my_strncmp(arr1, arr2, 30);
if (ret == 0)
{
printf("字符串相等\n");
}
else
{
printf("字符串不相等\n");
}
return 0;
}
字符串查找函数
strstr
那么首先我们可以看看这个函数的介绍:
那么这里就是我们的字符串查找函数,它的作用就是 查找一个字符串在另一个字符串中的位置,如果找到了这个字符串的位置就返回这个字符串出现的第一个位置的地址,如果没有找到则返回一个空指针,然后我们这个函数就需要两个参数但是都是char类型的指针,那么我们这里第一个指针就是我们要找的地方,我们第二个指针指向的就是我们要找的内容,那么我们这里就可以来看看这么一个例子来看看这个函数是如何来使用的:
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "this is a simple string";
char* pch;
pch = strstr(str, "simple");
printf("%s", pch);
return 0;
}
那么我们的pch里面首先装的就是我们这里的这个字符数组str中的simple的s的地址,然后我们再对其进行打印这时候就会将这个s后面的全部内容都打印出来知道遇到\0为止,那么我们这里的打印的结果就是这样的:
那么如果我们这里找不到的话我们这里返回的地址就是一个空指针,比如说下面的这个代码:
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "this is a simple string";
char* pch;
pch = strstr(str, "sample");
if (pch == NULL)
{
printf("NULL");
}
else
{
printf("%s", pch);
}
return 0;
}
我们再来看看打印的结果如何:
我们发现这里的指针确实是一个空函数,那么看到这里想必大家知道这个函数的使用方法,那么我们接着往下看如何来模拟实现这个函数。
strstr函数的模拟实现
那么我们这个模拟实现就相对于上面的几个函数就比较复杂,首先我们可以看到我们这个函数的的两个参数,只有char类型的指针,然后我们这个函数的功能就是查找一个字符串在另一个字符串中的位置,那么我们这里的比较的情况就是这样的:
首先我们有两个指针,指针s1指向的是我们的目标字符串的首元素的地址,指针s2指向的就是我们源字符串的首元素的地址,那么我们这里首先要进行的操作就是将这两个指针的内容进行比较,你哈嘛我们这里的指针指向的内容是不一样的,那么我们这里就得让我们的s1指针往后走一个位置,然后再进行比较就成了这样:
那么我们这里就会再进行一次比较,那么这里比较的结果就是相同的,那么我们这里就会让两个指针指向的位置都往后进一格
那么这里我们就会再进行一次比较,那么我们这里比较的结果就会有所不同了,因为一格时b一个是c所以我们这里的比较的结果就是不一样,那么不一样的话我们这里就是不是该字符串出现的位置,那么我们这里就应该从新来进行比较,那么我们这里重新比较的位置在哪呢?首先我们很明确的知道s2应该回到我们的初始位置,也就是最开始的b,那我们的s1呢?它会去往哪里,它是继续沿着当前的位置往后移动一个位置呢?还是移动到刚开始相等的下一个位置呢?那么这里肯定是后面一种的情况啊,那么我们这里的图片就变成了这样:
那么我们这里就会继续进行比较,那么我们这里比较的是相等的,所以两个指针都会往后退一格,但是我们再进行比较的话这里的的b和c就不一样了,所以我们这里的s2就会又回到字符串的开头,我们这里的s1就会回到刚开始比较的字符的位置的下一个,那么这里我们的比较的情况就成了这样:
那么我们这里再进行比较我们是相等的,两个指针都指向下一个位置。那么再比较还是相等的,再移动到下一位比较也是相等的,那么我们这里让这两个指针再往下移动一位是不是就成了这样:
那么按道理来说我们这里是不是就应该找到了这个字符串在另一个字符串中的位置,那么我们这里是不是就应该停止寻找了,所以我们这里就有了一个停止的条件就是我们这里的s2指到\0的时候,那么我们这里的寻找就应该结束了,那么我们这里是一种情况,那么我们这里还有比一种情况就是我们这里的s1也指向了\0是不是也应该停止寻找了,那么我们这里就有了两个停止寻找的条件,那么我们这里继续寻找的条件是什么呢?是不是就是两个字符串的内容相等啊,只要有一个字符串指向的位置是\0我们就可以直接退出寻找,那么我们这里的是不是就得将这些条件用&&连接起来,那么我们这里退出了寻找是不是就得判断找没找到啊,那么我们这里就可以根据指针s2指向的值来判断找没找到,如果s2指向的值是\0那么就说明是找到了,如果不是的那么这时候退出了循环就说明没有找到,那么这里我们就可以根据我们这里的s2指向的内容判断返回什么?如果s2指向的是\0那么我们就返回s1开始往后比较的位置,如果s2指向的不是\0那么我们就返回一个空指针,那么看到这里想必大家应该能够发现一个事情就是我们这里好像得要一个变量p来储存我们这里一s1开始比较的位置,那么这里我们就得要两个循环来嵌套,最外面的一层循环是来移动我们这里的变量p的,而我们里面的循环是用来进行比较的,那么这里我们就可以将上述的思路进行汇总就是我们这里的代码形式:
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1);
assert(str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p)
{
s1 = p;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)p;
}
p++;
}
return NULL;
}
int main()
{
char str[] = "abbbcdef";
char* pch;
pch = my_strstr(str, "bbc");
if (pch == NULL)
{
printf("NULL");
}
else
{
printf("%s", pch);
}
return 0;
}
strtok
那么在我们的生活中有很多时候是需要切割一个字符串的,比如说一个网址,我想将他进行切割成几个部分,比如说我们www.baidu.com
这个字符串我们想要以这个( . )为分隔符来将这个字符串切割成三个部分,那么我们这里就可以用到这个函数来对它进行切割,将他切割成:www baidu com这三个部分,那么这个函数具体是怎么使用的我们可以来看看下面的介绍:
那么关于这个函数我们这里有如下几点要跟大家说一下的就是:
- strDelimit参数是一个字符串,定义了用作分隔符的字符合集。
- 第一个参数指定了一个字符串,它包含了0个或者多个由strDelimit字符串中一个或者多个分隔符分格的标记,那么综上两点说的就是我们这里的第一个参数是你要分割的字符串,而第二个参数就是你想要以什么样的字符来分割这个字符串。
- strtok函数找到strToken中的下一个标记,并将其用\0进行结尾,返回一个指向这个标记的指针,(strtok函数会改变杯操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可以修改)那么这句话的意思就是比如说我们这里要切割的是
www.baidu.com
这个字符串,切割的标记是.那么我们这里就会将第一个出现.的地方将这个.改成\0,然后再返回这里的一开始的w的地址,那么就是这样www\0baidu.com
,然后我们再调用一次这个函数他就会从刚刚改成\0的下一个位置继续开始寻找切割点,这里就找到第二个.那么这里就会将这第二个点也改成\0,并且返回这里刚刚\0后面的b的地址,那么这里我们的字符串就变成了这样:www\0baidu\0com
,最后再进行一次切割我们这里的就没有了分隔符所以我们这里就会返回我们的第二个\0后面的地址,然后就不会做任何的修改。 - strtok函数的第一个参数不为NULL,函数将找到strToken中的第一个标记,strtok函数会保存它在字符串中的位置。
- strtok函数中的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
- 如果字符串中不存在更多的笔记,则返回空指针
那么我们这里说的这么多也就说明了一个道理就是我们这里的第一次开始切割字符串的时候我们得在strtok函数的第一个参数里面写上这个字符串的地址,因为我们这里每调用一次它只会切割一个部分出来,要是一个字符串要是有对各切割符,我们就要多次调用这个函数,那么这时再调用的话我们这里就只用在第一个参数中填入空指针就可以了,因为在上一次调用中我们这个函数保存这个字符串被切割的位置,所以我们就只用传空指针。那么看到这里我们就可以来实际操作一下:
首先我们创建了一个字符指针,这个指针指向的内容就是我们的分隔符的集合,然后我们这里还创建一个数组arr里面装的就是我们要分割的目标数组,因为我们这里的这个函数他会改变这个字符串的内容,所以我们这里就再创建一个数组,然后将这个数组里面的内容拷贝一份到这个数组里面来,那么我们这里的代码如下:
#include<stdio.h>
#include<string.h>
int main()
{
const char * p= "*()-+";
char arr[] = "18*10+(543+124)-100";
char arr1[30];
strcpy(arr1, arr);
return 0;
}
然后我们这里就开始调用这个函数来对我们的字符串进行切割,那么我们这里是第一次进行调用所以我们这里第一次调用函数传参时我们就将arr1放进去,因为这个函数会返回一个char类型的指针,所以我们这里就创建一个char类型的指针变量来进行接收,因为我们这里不止一个分隔符,所以我们这里就得多次调用这个函数,因为我们这里第一次传递了这个字符的地址,所以下面几次的调用就只用传空指针,那么我们的代码就如下
#include<stdio.h>
#include<string.h>
int main()
{
const char * p= "*()-+";
char arr[] = "18*10+(543+124)-100";
char arr1[30];
strcpy(arr1, arr);
char *ret=strtok(arr1, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
return 0;
}
我们来看看这段代码的运行结果:
那么这里我们发现如果我们再调用的时候没有分隔符了那么这里就会返回一个空指针,那么这里大家不知道发现一个问题没有就是我们这里的调用方式很死板,因为我们实际过程中我们又如何来指导该调用这个函数多少次呢?我不可能一个一个的数吧,所以我们这里就可以将这个放到一个循环里面,那么这个循环结束的条件就是当这个函数返回值为空指针的时候,那么我们这里修改之后的代码就是这样:
#include<stdio.h>
#include<string.h>
int main()
{
const char* p = "*()-+";
char arr[] = "18*10+(543+124)-100";
char arr1[30];
strcpy(arr1, arr);
for (char* ret = strtok(arr1, p); ret!= NULL;ret=strtok(NULL,p))
{
printf("%s\n", ret);
}
return 0;
}
、那么看到这里想必大家能够了解这个函数的使用了,那么我们接着往下看下面的内容。
错误信息报错
strerror
我们的c语言的库函数在执行执行失败的时候都会设置有错误码,那么这些不同的错误码就对应着着不同的错误原因,这些错误码就有0 1 2 3 4 5 6等等等,那么这些是对应错误信息的错误码,但是我们使用者如果不去了解这些错误码对应的错误信息的话,那么我们是不知道这些错误码的意思是什么,所以我们这里就有了这么一个函数:
strerror他的作用就是将这些错误码进行翻译成我们看的懂的错误信息,比如说我们下面的代码:
#include<stdio.h>
#include<string.h>
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
printf("%s\n", strerror(4));
printf("%s\n", strerror(5));
return 0;
}
那么我们这里运行的结果为:
那么我们这里可以看到我们这个函数将这个错误码对应的信息打印了出来,那么我们这里就又有了这么一个问题,我们怎么知道这个函数的执行错误是因为什么呢?那么我们c语言这里就给给了这么一个东西:errno这时一个全局变量这个变量是专门用来存放我们的错误码的,那么如果你要使用这个变量的话就得映入我们的头文件errno,h,那么我们使用的情况就如下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE*pf = fopen("test,txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
}
return 0;
}
那么我们这里在该路径下是没有这个文件的,那么我们这里用errno记录了错误码,所以我们将这段代码运行起来就会可以看到我们这里的打印了错误信息
那么我们这里的函数介绍就只有这么多这个函数很简单所以就没必要多说啥了,那么我们继续往下看。
字符操作
那么有关字符的函数除了上面几种,我们这里还有几个功能类似的函数,我们可以看一下:
那么这些函数的作用主要就是判断的功能,比如判断一个字符他是不是大写的字母啊,判断一个字符他是不是数字啊等等,那么如果确实符合这个他判断的条件的话,那么这个函数的返回值就是真,如果不是那么判断的就是假,那么我们可以看一段代码来看看如何使用这些函数:
#include<stdio.h>
#include<ctype.h>
int main()
{
char* p = "a1b2c3d4e5";
char* p1 = p;
while (*p1)
{
if (isdigit(*p1))
{
printf("%c ", *p1);
}
p1++;
}
return 0;
}
我们可以来看看这个函数运行的结果如何:
那么我们这里就通过循环加这个判断的过程打印一个字符串中的所以十进制的数字,那么其他的函数的用法也是类似的那么我们这里就不多描述了。
内存操作函数
那么我们上面的函数的操作对象都是我们的字符串中的字符,那么我要是想要操作其他的内容那么我们又该如何去做呢?比如说我们想拷贝一个整型的数组到另外一个数组里面去,我想拷贝一个浮点数的数组到另外一个浮点数的数组里面,那么要是遇到这样的情况的话,我们上面的函数就有点力不从心了,所以我们这里就有了内存操作函数这个函数操作的对象就不是我们上面的字符,而是我们内存中的一个一个字节,那么如果是这样的话,那那么你不管是什么类型,我都可以进行一定的操作,那么我们就来看看有那些内存操作函数。
memcpy
首先我们来看看这个函数,这个函数跟我们上面的函数有那么一点点的相似,我们上面是strcpy前面的str表示的意思是字符串的意思,那么我们这里的mem表示的意思就是memory这里有记忆的意思,当然还有内存的意思,那么根据字面的意思我们就知道这个函数的作用就是内存复制的意思,那么我们这里来看看这个函数的基本所需的参数和这个函数返回的类型:
我们可以看到这个函数需要三个参数,其中两个参数都是void的指针第一个指针表示的是你要拷贝的位置,第二个指针就是你要拷贝的内容,另外一个参数就是你想要拷贝的字符的大小,那么我们这里如何来理解这个指针的类型呢?为什么这里要给一个void的指针而不是其他类型的呢?那么这里我们前面也说了,我们这个函数的操作对象可以很多种,可以是整型也可以是浮点型,也可以是字符类型等等等,那么在实现这个函数的时候那个作者会知道你要操作的是哪个类型的数据吗?不知道所以这里为了兼容性就设计了一个void类型的指针,因为我们知道void类型的指针它可以接收任意类型的数据,那么这里我们就可以通过下面的代码来看看这个函数是如何使用的:
#include<stdio.h>
#include<memory.h>
int main()
{
int arr1[] = {0,1,2,3,4,5,6,7,8,9};
int arr2[10];
memcpy(arr2, arr1, sizeof(arr1));
return 0;
}
我们这里首先创建了一个整型的数组将他初始化为0,1,2,3…9,然后我们又创建了一个数组,但是我们没有进行初始化,然后我们就想着通过我们这里的memcpy函数将我们的arr1里面的内容复制到我们的arr2里面去,那么我们这里可以通过调试的方法看看我们这里复制前arr2里面的内容是什么:
我们可以看到在复制之前我们这里的数组里面全部都是随机值,那么我们这个函数操作之后再来看看这个数组里面的内容:
我们发现这个数组里面的内容和我们的arr1里面的内容一模一样了,那么我们这里可以将这个整型的数组改成其他类型的数组,那么操作的过程也是一样的,因为我们这里针对的是内存并不是类型对吧,那么看到这里想必大家应该能够明白我们这里的函数是如何来使用的,那么我们这里就来看看我们是如何来实现这个函数的。
memcpy函数的模拟实现
首先我们这里肯定是得判断这两个指针是否为空指针,那么我们这里就可以使用assert来进行判断,然后我们这里的函数的返回值也是一个void*的指针这个指针指向的是我们这里的目标位置的地址,那么这里我们就得创建一个指针来记录我们这里一开始的地址,以免以后数据发生更改而无法返回,那么我们这里想要更改的字节肯定不是一个所以我们这里就得把这个字节内容的复制放到一个循环里面,那么这里循环的次数就得由我们的这里的给的次数来进行确定,那么我们这里每复制一次我们就让那个值-- 一直到减到这个值等于0位置,那么我们这里每复制一次我们就得让我们这里的两个指针进行++让他跳转到下一个字节里面去,那么根据上面的思路我们的代码实现如下:
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest);
assert(src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest=(char *)dest+1;
src=(char *)src+1;
}
return ret;
}
int main()
{
int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
int arr2[10];
my_memcpy(arr2, arr1, sizeof(arr1));
return 0;
}
那么看到这个代码有些小伙伴们就脑回路打开说,我们这里模拟实现的函数能不能这样进行拷贝啊,我们拷贝自己啊,比如说我们上面的例子arr1里面的1,2,3拷贝到我们的2,3,4所对应的位置里面去,那么这样的话我们拷贝的结果就为0,1,1,2,3,5,6,7,8,9,那么我们这里就可以用这个代码测试的结果就如下:
我们就发现了一些问题,我们什么我们这里会有这么多的1呢?那么这里的原因就很简单了,我们这里是从前往后进行拷贝,我们第一次拷贝的时候将我们这里的数组中的第三个元素由2改成了1,而我们第二次拷贝的时候是将我们第三个元素的值复制到我们的第四个元素里面,那么这里就出现了问题,我们这里的第三个元素在外面第一次复制的时候就已经发生了改变变成了了1,所以我们第二次拷贝的时候就又将这个1拷贝到了第四个位置,而我们第三次拷贝又是将第四个位置的值复制到我们第五个位置里面去,所以这里就出现了全部都是1的情况,那么出现这样的原因就是因为我们前面的复制导致了我们后面复制的值出现了问题,那么我们如何来解决这个问题呢?那么我们c语言就专门给了一个函数来解决这种内存重叠的拷贝,那么这里我们就继续往下看(当然我们这里的代码是无法来解决内容重叠的问题,但是小伙伴们要是下去尝试的话就会发现其实我们的memcpy也可以解决这个问题的,但是不同的编译器实现的情况是不同的,所以具体能不能实现大家是可以自行尝试一下)
memmove
那么我们这里的memmove和memcpy的区别就是我们这里可以处理的源内存块和目标内存块是可以重叠的,那么我们可以看看这个函数的使用:
那么我们这里可以看到他的参数和我们的memcpy的参数是一模一样的,那么我们用代码来完成一下上面的情况:
#include<stdio.h>
#include<memory.h>
int main()
{
int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
memmove(arr1+2, arr1+1, 12);
return 0;
}
那么我们这里的代码运行之后的内存的情况是这样的:
那么我们发现这个函数确实是可以实现我们上述的要求,那么我们这个函数是如何来实现的呢?
memmove的模拟实现
我们这么想既然前面的复制会导致我们后面的值发生了改变,那么我们能不能将我们的复制的顺序修改一下呢?我们将从前往后复制改成从后往前进行复制,那么这样的话我们是不是就可以避免这样的情况发生呢?我们先将这里的3复制到第五个元素的位置,再将我们的2复制到我们的第4个元素的位置,再将1复制到我们的第三个元素的位置,那么这样的话我们就可以避免前面出现的问题,因为在这些值发生改变之前我们以及将这个值复制到其他的位置去了,所以就可以避免问题的出现,那么这里还有个问题,如果我们这里的复制是这样的情况呢?我们将这里的2,3,4的元素复制到我们的1,2,3位置里面去呢?那么我们要是采用后面先复制的情况的话,那么我们这里是不是又会出现同样的问题啊,那么我们发现要是这样的话我们前面先复制就会解决这个问题,啊?那么这时候肯定有很多的小伙伴们感到十分的疑惑咋有时候需要前面先复制,有时候又需要后面先复制呢?那么这里我们其实是可以总结出来一个规律的:首先我们知道的一件事就是我们的内存的使用是从高地址往低地址开始使用的,那么我们这里的数组中是随着我们的下标的增长地址从低处往高处开始使用,所以我们这里的规律就是:当我们的dest指针指向的地址低于我们的src指针的话那么我们这里就从前往后进行复制,那么如果我们这里的dest指针指向地址大于我们的src指针的话,那么我们这里就采用从后往前进行拷贝,那么我们这里的就可以根据这个规律将我们的代码实现:
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest=(char *)dest+1;
src=(char *)src+1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
my_memmove(arr1+2, arr1+1, 12);
return 0;
}
memcmp
那么我们之前有字符串的内容比较函数,那么我们这里就有一个内存的比较函数,那么这个函数的作用就是对内存中的数据进行比较,那么我们可以看看这个函数的基本参数:
那么我们这里可以看到这个函数同样也是需要两个指针和一个整型,那么这个整型表示的意思就是需要比较多少个字节的内容,如果我们这两个指针内容都是相等的话我们这个函数的返回值就是0,如果第一指针指向的内容大于我们的第二个指针指向的内容的话我们这里的就返回一个大于0的数字,如果小于的话我们这里就会返回一个小于0的数字,那么我们通过下面的代码来看看这个函数的使用:
#include<stdio.h>
#include<memory.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 1,3,2 };
int ret = memcmp(arr1, arr2, 12);
printf("%d", ret);
}
那么我们这里的运行结果就为:
那么这个原理和实现也和我们上面说的差不多,那么我们这里就不多说了。
点击此处获取代码i