文章目录
一.库函数功能以及模拟实现
1.1求字符串长度
1.1.1strlen
strlen计算的是从参数给的地址向后找,找到“\0”之前的长度,它非常在意“\0”,没有“\0”就一直疯狂向后找,即使不在它的空间内他也要找,直到找到“\0”才停止
比如一个字符串是“abcdef”,这时候没有“\0”,程序就会一直向后找,找到f的
时候,发现没有“\0”,程序就继续向后找,虽然越界了,但是只要没有“\0”就不停止,这个时候strlen计算出来的结果肯定就不对了
如果这个字符串是“abcdef\0”,计算“\0”之前的个数,就是6
当然,这里我就是举个例子,正常的,每个字符串后面都默认有“\0”,只不过是我们看不到,当你调试起来的时候就可以看到了,比如
“abc”,这时候你计算它,就是3,因为c后面默认有一个“\0”,上面我说的那个“abcdef”没有“\0”只是举例子,其实它也有,只不过我为了让大家更直观的理解
有几个注意事项:1.参数指向的字符串必须以“\0”结尾
2.参数的返回值是size_t,是无符号的(可以把size_t理解为正数),因为长度都是正数
3.它的头文件是#include<string.h>,也就是说你要用这个函数,你在程序的最开始必须加上这个
下面上代码
//这个是库函数的功能
#include<stdio.h>//这个基本你写c语言你就加上
#include<assert.h>//这个是断言的意思,就是保证你程序不会出错,如果出错,程序会给你警告,断言里面的内容必须是正确的
#include<string.h>
int main()
{
char arr[] = "abcde";//这里我们把字符串弄成“abcde”,虽然看不到“\0”,但是它是存在的,默认在e的后面
int sz = strlen(arr);
printf("%d", sz);
return 0;
}
下面我们来看库函数的模拟实现
#include<stdio.h>//这个基本你写c语言你就加上
#include<assert.h>//这个是断言的头文件
#include<string.h>
//非递归
int my_strlen(char* a)//把int换成size_t最好,但是小编习惯了用int
{
assert(a);//这个是断言的意思,就是保证你程序不会出错,如果出错,程序会给你警告,断言里面的内容必须是正确的
//这个断言就是保证你的指针a不会是空指针
int count = 0;
while (*a != '\0')
{
a++;
count++;
}
return count;
}
int main()
{
char arr[] = "abcde";//这里我们把字符串弄成“abcde”,虽然看不到“\0”,但是它是存在的,默认在e的后面
int sz = my_strlen(arr);
printf("%d", sz);
return 0;
}
//递归
//把int换成size_t最好,但是小编习惯了用int
int my_strlen(char* str)
{
assert(str);
if (*str == '\0')
{
return 0;
}
return 1 + my_strlen(str+1);
}
int main()
{
char arr[] = "abcde";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
//指针-指针(两个指针之间的元素的个数)
int my_strlen(char* str)//把int换成size_t最好,但是小编习惯了用int
{
assert(str);
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
char arr[] = "abcde";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
1.2长度不受限制的字符串函数
1.2.1strcpy
这个的头文件是#include<string.h>
它的作用就是把一个字符串的内容拷贝到另一个字符串里
strcpy(目的地, 源头)
注意:
1.源字符串必须以“\0”结束
2.会将源字符串中的“\0”拷贝到目标空间
3.目标空间必须足够大
4.目标空间必须可变
比如下面这个就不对
因为char* p后面的是常量字符串,常量字符串不可被修改
int main()
{
char* p = "abcdef";
char arr2[] = "xiaobai";
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
下面开始上代码
//strcpy的功能
int main()
{
char arr1[20] = "xxxxxxxxx";
char arr2[] = "xiaobai";
strcpy(arr1, arr2);//第一个是目的地,第二个是源头,
//这个意思就是把arr2的内容拷贝到arr1里去
printf("%s", arr1);
return 0;
}
strcpy的模拟实现
void my_strcpy(char* str1, char* str2)
{
assert(str1&&str2);
while (*str2 != '\0')
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = *str2;//最后一步是把‘\0’也拷贝上
}
int main()
{
char arr1[]= "xxxxxxxxxxxxx";
char arr2[] = "xiaobai";
my_strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
1.2.2strcat
追加函数,就是把arr2的内容追加在arr1的后面
头文件是#include<string.h>
strcat(目的地,源头)
注意:
1.源字符串必须以“\0”结束
2.目标空间必须足够大,能够容纳下源字符串的内容
3.目标空间必须可修改
//功能实现
int main()
{
char arr1[50]= "xiaobai";
char arr2[] = "buhuixiedaima";
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
//模拟实现
void my_strcat(char* str1, char* str2)
{
while (*str1 != '\0')//找目标空间的‘\0’
{
str1++;
}
while (*str2 != '\0')//把str2的内容从str1的‘\0’的位置开始追加
{
*str1 = *str2;
str1++;
str2++;
}
*str1 = *str2;//把str2的‘\0’也放到str1里面
}
int main()
{
char arr1[50] = "xiaobai";
char arr2[] = "buhuixiedaima";
my_strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
1.2.3strcmp
这个函数是比较两个字符串大小的函数,头文件是头文件是#include<string.h>
虽然是比较大小的,但是不是比较长度的,是比较ASCII码值
比如arr1=“abcdef”;
arr2=“abz”;
strcmp(arr1,arr2)
这时候程序开始比较了,两个数组第一个都是a,一样,开始比较下一个,都是b一样,开始比较下一个,arr1的下一个是从,arr2的下一个是z,不一样了,这时候就比较c和z的
ASCII码值,z的大于c,所以arr2比arr1大
注意:strcmp(arr1,arr2)
如果arr1>arr2,函数返回大于0的数字,
arr1=arr2,函数返回0
arr1<arr2,函数返回小于0的数字
函数功能
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abz";
int ret=strcmp(arr1, arr2);
printf("%d", ret);
return 0;
}
模拟实现
int my_strcmp(char* str1, char* str2)
{
while (*str1 == *str2)
{
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else if (*str1 == *str2)
{
return 0;
}
else
{
return -1;
}
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abz";
int ret=my_strcmp(arr1, arr2);
printf("%d", ret);
return 0;
}
strcpy,strcat,strcmp都是长度不受限制的字符串函数,什么是长度不受限制的字符串呢,就是一直到‘\0’为止,没有具体的数字概念,比如我想拷贝一个字符串的前5个数字,就是受限制的字符
不受限制的字符串函数是不安全的,因为它们不关心你放不放的下,一直执行到‘\0’为止
1.3长度受限制的字符串函数
1.3.1strncpy
跟strcpy基本一样,只不过加了一个限制长度的数字,strncpy(arr1,arr2,3)的意思就是把arr2的前三个字符拷贝到ar1里面去,注意,不拷贝‘\0’
函数功能
int main()
{
char arr1[] = "xxxxxxxx";
char arr2[] = "abc";
strncpy(arr1, arr2, 3);
printf("%s", arr1);
}
模拟实现
void my_strncpy(char* str1, char* str2,int n)
{
int count = 0;
while (count < n)
{
*(str1 + count) = *(str2 + count);
count++;
}
}
int main()
{
char arr1[] = "xxxxxxxx";
char arr2[] = "abc";
my_strncpy(arr1, arr2, 3);
printf("%s", arr1);
}
1.3.2strncat
也是大同小异,在目的地字符串后面追加指定的字符串的前几个字符
功能
int main()
{
char arr1[80] = "xxxxxxxx";
char arr2[] = "abc";
strncat(arr1, arr2, 2);
printf("%s", arr1);
}
模拟实现
void my_strncat(char* str1, char* str2,int n)
{
while (*str1!='\0')
{
str1++;
}
int count = 0;
while (count < n)
{
*(str1 + count) = *(str2 + count);
count++;
}
}
int main()
{
char arr1[80] = "xxxxxxxx";
char arr2[] = "abc";
my_strncat(arr1, arr2, 2);
printf("%s", arr1);
}
1.3.3strncmp
比较两个字符串的前几个字母
功能
int main()
{
char arr1[] = "abz";
char arr2[] = "abcdef";
int ret=strncmp(arr1, arr2, 2);
printf("%d", ret);
}
模拟实现
int my_strncmp(char* str1, char* str2,int n)
{
int count = 0;
while (count < n)
{
if (*str1 == *str2)
{
count++;
str1++;
str2++;
}
else
{
break;
}
}
if (count == n)
{
return 0;
}
else
{
if (*str1 > *str2)
{
return 1;
}
if (*str1 < *str2)
{
return -1;
}
}
}
int main()
{
char arr1[] = "abz";
char arr2[] = "abcdef";
int ret=my_strncmp(arr1, arr2, 3);
printf("%d", ret);
}
1.4字符串查找
1.4.1strstr
strstr(arr1,arr2)的意思就是在arr1里面找arr2,比如
arr1是abcdeffedcba
arr2是bcde
arr1里面有arr2,就返回arr2里面第一个元素b的地址,也就是输出的时候是
bcdeffedcba,如果arr1里面有好几个arr2,就需要返回第一个arr2中第一个元素的地址
函数功能
int main()
{
char arr1[] = "abbbbbbbcdef";
char arr2[] = "bcdef";
char* ret=strstr(arr1, arr2);
printf("%s", ret);
}
模拟实现
char* my_strstr(char* str1, char* str2)
{
if (*str2 == '\0')
{
return str1;
}
char* s1 = NULL;
char* cp = str1;
char* s2 = NULL;
while (*cp != '\0')
{
s1 = cp;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return cp;//找到了
}
cp++;
}
return NULL;//没找到
}
int main()
{
char arr1[] = "abbbcdbbcef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
1.4.2strtok
char arr[] = “192#168.120.85”;
char* p = “#.”;
strtok(arr,p);
意思就是arr里面的元素都是通过p里面的元素分割的
strtok函数会改变被操作的字符串
strtok函数找到arr中的第一个标记#,并将其用‘\0’结尾,返回一个指向这个标记的指针
当找到了第一个#的时候,把它改成‘\0’之后,strtok里面一定记录了#之后的地址
如果第一次找,就是strtok(非空指针,p)
第二次往后就是strtok(空指针,p)
int main()
{
//char arr[] = "bbbb@yaaa.net";//"@."
char arr[] = "192#168.120.85";
char* p = "#.";
char buf[20] = { 0 };
strcpy(buf, arr);
char* ret = NULL;
for (ret = strtok(buf, p); ret != NULL; ret=strtok(NULL, p))
{
printf("%s\n", ret);
}
1.5错误信息报告
1.5.1strerror
头文件#include<string.h>
C语言的库函数在运行的时候,如果发生错误,就会将错误码放在一个变量中,这个变量是:errno
错误码是一些数字:0 1 2 3 4 5
我们需要将错误码翻译成错误信息
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));//告诉你5代表什么错误信息
return 0;
}
#include <errno.h>//errno的头文件
int main()
{
打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
//perror的头文件是#include<stdio.h>
perror("fopen");//可以理解成printf+strerror
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
二.内存函数功能以及模拟实现
2.1memcpy
strcpy只能针对字符串,现在我们的要求变高了,字符串不能满足我们的要求了,我们想要拷贝别的,比如数字,我们就可以用memcpy
头文件#include<string.h>
int main()
{
int arr1[] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9 };
memcpy(arr1, arr2 + 2, 20);//目的地,源头,字节数
//这句话的意思是从arr2下标为2的位置开始
//往后五个元素,都拷贝到arr1里面去,5个整形的字节数就是20
return 0;
}
模拟实现
void* my_memcpy(void* dest, void* src, int num)//参数类型是void*,这个意思就是函数不知道要传过来的数据是什么类型,它都可以拷贝
{
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;//记住最好这么写,不能写成后置++,有的编译器报错,这么写基本上都不会报错
}
return ret;//void*也可以返回,因为程序要把你想要的类型转换之后用到你自定义的函数里
}
int main()
{
int arr1[] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9 };
my_memcpy(arr1, arr2 + 2, 20);
return 0;
}
注意,memcpy不能自己复制自己
比如memcpy(arr1,arr1+2,5)
就是错的
2.2memmove
为了解决memcpy不能复制自己的问题,就创造了memmove这个函数,它可以复制自己,其他的跟memcpy差不多
void* my_memmove(void* dest, void* src, int num)//参数类型是void*,这个意思就是函数不知道要传过来的数据是什么类型,它都可以拷贝
{
void* ret = dest;
while (num--)
{
//从前向后
if (dest < src)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
//从后向前
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
}
return ret;//void*也可以返回,因为程序要把你想要的类型转换之后用到你自定义的函数里
}
int main()
{
int arr1[] = { 0 };
int arr2[] = { 1,2,3,4,5,6,7,8,9 };
my_memmove(arr2, arr2 + 2, 20);
return 0;
}
2.3memcmp
比较两个数组的大小,不一定是字符串,可以是数字数组,结构体数组等
int main()
{
int arr1[] = { 1,2,6 };//01 00 00 00 02 00 00 00 06 00 00 00
int arr2[] = { 1,2,5 };//01 00 00 00 02 00 00 00 05 00 00 00
int ret = memcmp(arr1, arr2, 8);//比较这两个数组的前八个字节,arr1>arr2,就是1,等于是0,小于是-1
printf("%d\n", ret);
return 0;
}
2.4memset(内存设置函数)
//memset - 内存设置函数
//以字节为单位来设置内存中的数据的
int main()
{
char arr[] = "hello world";
memset(arr, 'x', 5);//把arr数组的前五个字节变成x
printf("%s\n", arr);
memset(arr+6, 'y', 5);//从arr数组下标为6的元素开始,往后5个字节都变成y
printf("%s\n", arr);
//下面这两行本来是想把arr数组的元素全部变成1,但是最后结果不是我们想要的
//这是因为memset是以字节赋值的
int arr[10] = { 0 };
memset(arr, 1, 40);
return 0;
}