详解字符串函数及内存函数
字符串函数
作用对象是字符串。
头文件是
#include<string.h>
🎃求字符串长度
💡strlen
🎉strlen函数介绍
顾名思义:求的是字符串的长度 。
size_t 是unsigned int 通过重命名后的类型名
注意事项
1.与sizeof区分
strlen接收字符串的地址,从该地址往后计算字符的个数直到’\0‘结束,‘\0’不算在个数内。
注意与sizeof区分,用sizeof求字符串的大小的时候,’\0’是要被计算进去的。因为sizeof求的是类型所占空间的大小单位是字节。(sizeof关注点在空间,strlen关注点在’\0’)
所以对 char str[] = { ‘a’,‘b’,‘c’};求str字符串长度是无意义的。求出来的结果是随机值。
2.注意返回类型
返回类型是无符号的
来看下面这段代码,请问输出结果是什么?
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "asdasf";
char str2[] = "abda";
if (strlen(str1) - strlen(str2) > 0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
运行结果
原因很简单:无符号数减无符号数得到的数仍然是无符号的。
✨模拟实现strlen的三种方式
知道了strlen的原理我们来模拟实现下吧
const 修饰指针的作用
在写之前先说说,const在这的作用,const char* str,const修饰在* 前,表示 *str不可以被改变,str可以改变。如果改成char * const str ,const修饰在*的后面,表示str不可改变,*str可以被改变
主函数
#include<stdio.h>
int main()
{
char arr[] = "abcdef";
size_t ret = my_strlen(arr);
printf("%u", ret);
return 0;
}
方法一:遍历法(计数器的方法)
size_t my_strlen(const char* str)
{
int count = 0;
while (*str != '\0')
{
str++;
count++;
}
return count;
}
方法二:指针-指针
不了解指针-指针的请点击这里
size_t my_strlen(const char* str)
{
char* start = str;
while (*str!='\0')
{
str++;
}
return str - start;
}
不理解的可以点击链接看看那篇博客在结合这个图片看看
方法三:函数递归
size_t my_strlen(const char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
看图片理解递归实现
🎃长度不受限制的字符串函数介绍
💡strcpy
🎉介绍strcpy
将source指向的字符串拷贝到destination(目标空间)指向的字符串中
注意事项
以下面的例子为例:将str2拷贝到str1 【 strcpy(str1,str2) (错误的拷贝的例子)】
1.
destination(str1)指向的空间必须足够大,不然会造成数组越界访问。
char str1[7]="abcdef";
char str2[10]="abcdefg";
str1数组大小7个字节,明显的str2的字符长度的大小是大于7,那么拷贝的时候,那么就会造成非法访问。
2.
拷贝的时候,也会把source中的‘\0’一起拷贝到destination。所以source必须是以‘\0’结尾,不然也会造成非法访问。
✨模拟实现strcpy
主函数
#include<stdio.h>
#include<assert.h>//assert的头文件
int main()
{
char arr1[] = "xxxxxxxxxxx";
char arr2[] = "abcdef";
printf("%s", my_strcpy(arr1, arr2));
return 0;
}
char* my_strcpy(char* dest, const char* source)
{
char* ret = dest; //需要返回目的地址,要保存一下
//assert() ()里为真,才执行,否则报错
//防止接收的是NULL
assert(dest && source);
//这里用到了,'\0'的ASCLL码值为0,为0是假
//先执行内部表达式(先赋值),在判断真假。
while (*dest++=*source++)
{
;
}
return ret;
}
💡strcat
🎉介绍strcat
char str1[7]="abcdef";
char str2[10]="abcdefg";
strcat(str1,str2) ;将str2(destnation)追加到str1(source)中。
注意事项
从哪开始追加?
从str1中的’\0’处开始时追加,追加结束,要以’\0’结尾
空间问题
str1的空间大小必须足够大,像这里,str1[7]小于str2[10],拷贝的时候会有问题。
对str2有什么要求?
需要’\0’结尾,不然拷贝的时候会越界。
能否自己追加自己?答案是不能。
✨模拟实现strcat
主函数
#include<stdio.h>
#include<string.h>
#include<assert.h>
int main()
{
char str1[20] = "I love ";
char str2[6] = "you.";
//printf("%s\n", strcat(str1, str2));
printf("%s\n", MyStrcat(str1, str2));
return 0;
}
char* MyStrcat(const char* dest,const char* sour)
{
char* s1 = dest;
char* s2 = sour;
while (*s1)//找 \0
{
s1++;
}
while (*s1++ = *s2++)
{
;
}
return dest;
}
💡strcmp
🎉介绍strcmp
功能:字符串的比较,是一个字符一个字符的比较。比较的是字符所对应的ASCLL值
返回值:
str1>str2 返回大于0的数
str1<str2 返回小于0的数
str1=str2 返回0
✨模拟实现strcmp
Vs中返回的是1,-1,0
int MyStrcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = str1;
char* s2 = str2;
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)
{
return 1;
}
else
{
return -1;
}
}
也可以不返回1,-1,0
int MyStrcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
char* s1 = str1;
char* s2 = str2;
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
//对应的ASCLL相减
return *s1 - *s2;
}
🎃长度受限制的字符串函数介绍
💡strncpy
🎉介绍strncpy
功能:和strcpy差不多,就添加了需要拷贝多少(num)个字符
注意事项
和上面的strcpy一样,还要补充的是,当num大于source所指向字符串的长度时,多出来的个数在拷贝的时候补‘\0’
✨模拟实现strncpy
主函数
#include<stdio.h>
#include<string.h>
#include<assert.h>
int main()
{
char str1[12] = "I love ";
char str2[7] = "you.";
//strncpy(str1, str2, 8);
my_strncpy(str1, str2, 2);
printf("my:%s\n", str1);
strncpy(str1, str2,2);
printf("%s\n", str1);
return 0;
}
char* my_strncpy(char* dest, const char* sour, size_t num)
{
assert(dest && sour);
char* ret = dest;//保存起始地址
int len = strlen(sour);
int k = num - len;
//"I love "
//"you."
if (num > len)
{
while (len--)
{
*dest++ = *sour++;
}
//超出长度置为'\0'
while (k--)
{
*dest++ = '\0';
}
}
else
{
while (num--)
{
*dest++ = *sour++;
}
}
return ret;
}
💡strncat
🎉介绍strncat
功能:向目标字符串追加num个字符
注意事项
1.和strcat大体一样,从目标字符串的‘\0’开始追加,追加结束会添加’\0’。同样也需要,目标字符串的空间足够大。
2.与strcat不同的是,strncat,可以自己追加自己。
✨模拟实现strncat
主函数
#include<stdio.h>
#include<assert.h>
#include<string.h>
int main()
{
char str1[20] = "I love ";
char str2[10] = "you.";
strncat(str1, str2, 2);
printf("%s\n", str1);
my_strncat(str1, str2, 2);
printf("my:%s\n", str1);
return 0;
}
char* my_strncat(char* dest, char* sour, size_t num)
{
assert(dest && sour);
char* ret = dest;
//找到目标空间的'\0'
while (*dest)
{
dest++;
}
//进行追加,sour追加到自身的'\0'结束循环
while (num-- && *sour !='\0')
{
*dest++ = *sour++;
}
return ret;
}
💡strncmp
🎉介绍strncmp
和strcmp差不多,比较字符的大小,返回值也是一样,只比较num个字符。
✨模拟实现strncmp
主函数
int main()
{
printf("%d\n", MyStrncmp("abcd", "abfd", 5));
printf("%d\n", MyStrncmp("abcd", "abbd", 5));
printf("%d\n", MyStrncmp("abcd", "abcd", 5));
return 0;
}
int MyStrncmp(const char* str1, const char* str2,size_t num)
{
assert(str1 && str2);
char* s1 = str1;
char* s2 = str2;
while (*s1 == *s2 && num--)
{
if (*s1 == '\0')
{
return 0;
}
if (num != 0)
{
s1++;
s2++;
}
}
if (*s1 > *s2)
{
return 1;
}
else if (*s1 < *s2)
{
return -1;
}
else
return 0;
//也可以这样
//return *s1 - *s2;
}
🎃字符串查找
💡strstr
🎉介绍strstr
在字符串str1中找子串str2,找到了并返回起始地址,找不到则返回NULL
该起始地址为,主串从第几个字符开始与str2匹配。
例如:
过程如何来的我就不在细说啦,都在链接中的那篇博客。
✨模拟实现strstr(KMP难)
主函数
int main()
{
char str1[10] = "abcdcaf";//4
char str2[5] = "dc";//2
//printf("%s\n",strstr(str1, str2));
printf("%s\n",MyBFstrstr(str1, str2));
//char* p = MyKMPStrstr(str1, str2);
//printf("%s\n",p);
return 0;
}
BF算法(暴力求解)两种实现
char* MyBFstrstr(char* str1, char* str2)
{
//数组形式
//int i = 0;
//int j = 0;
//int p = 0;//记录主串的起始位置
//int len1 = strlen(str1);
//int len2 = strlen(str2);
i,j自增是有条件的,必须相同的时候,所以不要用for循环
//while (i < len1 && j < len2)
//{
// //"aabbcde", "cde"
// i = p;
// j = 0;
// //相同的情况下
// while (str1[i] == str2[j] && str2[j] != '\0')
// {
// i++;
// j++;
// }
// //不相同的情况下2中情况
// if (str2[j] == '\0')
// {
// return str1 + p;
// }
// p++;
//}
//return NULL;
//指针形式
char* s1 = str1;//遍历搜寻
char* s2 = str2;//遍历搜寻
char* p = str1;//记录主串起始位置
while (*p)
{
//"aabbcde", "cde"
s1 = p;//回退到当前起始位置
s2 = str2;//回退到起始位置
//*s2 !='\0' 必须得有
//如果*s1 == *s2 == '\0'那么会出错
while (*s1 == *s2 && *s2 != '\0')
{
s1++;
s2++;
}
//找到
if (*s2 == '\0')
{
return p;
}
//未找到
p++;
}
//遍历完未找到
return NULL;
}
KMP算法
void GetNext(char* str2, int* next, int len2)
{
next[0] = -1;
next[1] = 0;
int i = 2;
int k = 0;//记录当前下标的前一个返回地址
//"a b c d c a f"
//"d c"
//-1 0 0 0 0
while (i < len2)
{
if (str2[i - 1] == str2[k] || k == -1)
{
next[i] = k + 1;
k++;
i++;
}
else
{
k = next[k];
}
}
}
char* MyKMPStrstr(char* str1, char* str2)
{
if (*str2 == '\0')
{
return NULL;
}
int len1 = strlen(str1);
int len2 = strlen(str2);
int i = 0;
int j = 0;
int* next = (int*)malloc(sizeof(int) * len2);
GetNext(str2, next, len2);
while(i < len1 && j < len2)
{
if (str1[i] == str2[j] || j== -1)
{
j++;
i++;
}
else
{
j = next[j];
}
}
if (i == len1)
{
return NULL;
}
return str1 + (i - j);
}
💡strtok
🎉介绍strtok
将字符串(str)分割,delimiters指向的是分割元素的集合(数组)
根据什么分割?根据你分割符来分割,你的分隔符可能有多个,因此是个数组。
例如:
char str1[] = "aaa.aa@bbb.cc@ccc.aa.cccdd.d.@adf";
char str2[] = "@.";
str1就是要分割的字符串,str2是分隔符的集合。
怎么实现的呢?
第一次调用:在str1中找分割符,找到了,将这个位置标记,改为’\0’,返回地址起始地址。
第二次调用:从这个标记的位置处开始向后查找,找到了,继续标记,再改为‘\0’,返回地址(前一次标记的地址+1,为什么加一呢,因为前一次标记的地址是’\0’)。第三次…第四次…
直到字符串中找不到分隔符就返回NULL
进行分割后
char str1[] = "aaa\0aa\0bbb\0cc\0ccc\0aa\0cccdd\0d\0\0adf";
巧妙的使用方法
int main()
{
char str1[] = "aaa.aa@bbb.cc@ccc.aa.cccdd.d.@adf";
char str2[] = "@.";
char* arr = NULL;
for (arr = strtok(str1, str2); arr != NULL; arr = strtok(NULL, str2))
{
printf("%s\n", arr);
}
return 0;
}
一般分割后原来的字符串会被破环掉,一般拷贝后,在进行分割。
✨模拟实现strtok(难)
主函数
int main()
{
char str1[] = "@@..aaa.aa@bbb.cc@ccc.aa.cccdd.d.@adf";
char str2[] = "@.";
char* arr = NULL;
//for (arr = strtok(str1, str2); arr != NULL; arr = strtok(NULL, str2))
//{
// printf("%s\n", arr);
//}
for (arr = my_strtok(str1, str2); arr != NULL; arr = my_strtok(NULL, str2))
{
printf("%s\n", arr);
}
return 0;
}
char* my_strtok(char* str, char* sep)
{
//标记地址
static char* p = NULL;
//标记不存在分隔符的情况
static int t = 0;
if (t)
return NULL;
//二次以上调用,更改地址
if (str == NULL)
{
str = p;
}
int i = 0;
int j = 0;
int len2 = strlen(sep);
int len1 = strlen(str);
//第一次就不存在分割符的情况
if (len2 == 0)
{
t = 1;
return str;
}
//@@..aaa.aa@..bbb.cc@ccc.aa.cccdd.d.@adf
//找连续分隔符
for (j = 0; j < len2; j++)
{
while (*str == sep[j])
{
str++;
//防止下一个位置还是同一个分隔符
j--;
break;
}
}
//用来标记,找到分隔符
int flag = 0;
//因为经过找连续分割符后,str可能已经改变,那么len1的长度也会变化。
len1 = strlen(str);
//遍历主串放在外层循环会更简单
for (i = 0; i < len1 && str[i] != '\0'; i++)
{
//@@..aaa.aa@bbb.cc@ccc.aa.cccdd.d.@adf
//@.
if (flag)
break;
for (j = 0; j < len2; j++)
{
if (!flag)
{
if (str[i] == sep[j])
{
flag = 1;
//分割符,改为'\0'
str[i] = '\0';
//标记位置
p = &str[i + 1];
break;
}
}
}
}
if (i == len1)
t = 1;
return str;
}
🎃错误信息报告
💡strerror
🎉介绍strerror
返回错误码对应的起始位置。
可以在动态内存,与文件相关操作,等其他方面使用。这里举一个动态内存的例子
errno,是错误码变量,在<errno.h>的头文件中,如上图的0,1,2,3,4都是错误码,如果程序出错,errno就等于对应的错误码值。
这时候程序errno的值是12,12这个错误码所对应的错误信息的起始地址是Not enough space的起始地址。
🎃内存操作函数
内存函数可以操作任意类型的。
头文件
#include<string.h >
💡memcpy
🎉介绍memcpy
从source中拷贝num个字节到destnation中。
为什么可以拷贝任意类型的呢?原因很简单,void* 像回收站可以接收任意类型的地址,不过它不可直接使用,只能通过强制类型转化后才可使用。
这里要注意的是num个字节拷贝
✨模拟实现memcpy
主函数
int main()
{
int arr1[] = { 0x11223344,0x11223344,0x11223344 };
int arr2[4] = { 0 };
//memcpy(arr1, arr2, 3);
my_memcpy(arr1, arr2, 3);//3个字节,不是3个元素
for (int i = 0; i < sizeof(arr1) / sizeof(arr1[0]); i++)
{
printf("%x ", arr1[i]);
}
return 0;
}
void* my_memcpy(void* dest, void* src, size_t num)
{
//0x11223344
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
// 这样写有的编译器可能过不去
//((char*)dest)++;
//((char*)src)++;
//一般这样写
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
不过我们这样实现的memcpy,存在一定的问题,因为在自己拷贝自己的时候,出现重叠的部分,拷贝达不到效果
(str1 + 3, str1, 5)拷贝后正确的结果是 abcabcdeijk,但我们没能实现。
这就要引入我们的memmove了
💡memmove
🎉介绍memmove
一般是用在自己拷贝自己的情况,拷贝不同的变量(同一类型)也是可以的
vs中的memcpy,也可以实现自我拷贝。
按照c语言对memcpy,memmove的定义来看。memcpy处理的是拷贝不同的变量(同一类型),memmove处理的是拷贝自己的情况。
不管怎么分,我们先来实现自己拷贝自己的情况。
✨模拟实现memmove
主函数
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "abcdefghijk";
char str2[] = "XXXXXX";
my_memmove(str1, str1 + 3, 5);
printf("%s\n", str1);
char str3[] = "abcdefghijk";
my_memmove(str3+ 2, str3 + 5, 5);
printf("%s\n", str3);
return 0;
}
void* my_memmove(void* dest, void* src, size_t num)
{
void* ret = dest;
if (dest == src)
return ret;
if (src < dest)
{
//abcde
//defgh
//从后向前拷贝
dest = (char*)dest + num - 1;
src = (char*)src + num - 1;
while (num--)
{
//abcdefghijk
*(char*)dest = *(char*)(src);
dest = (char*)dest - 1;
src = (char*)src - 1;
}
}
else
{
//fghij
//defgh
//从前向后拷贝
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
return ret;
}
💡memset
🎉介绍memset
功能:ptr指向的内存块前num个字节设置为"value“(value可以是字符也可以是数字)
一般是可用来对数组初始化
例如
💡memcmp
🎉介绍memcmp
和strncmp类似,都是按一个字节一个字节的比较,比较的是ASCLL值。不过memcmp可以比较不同类型的,strncmp只能比较字符串
返回值:
当ptr1>ptr2,返回大于0的数
当ptr1<ptr2,返回小于0的数
当ptr1=ptr2,返回0
✨模拟实现memcmp
主函数
#include<stdio.h>
int main()
{
char arr[] = "11223344";
char arr1[] = "10223344";
int ret = my_memcmp(arr, arr1, 3);
printf("%d\n", ret);
return 0;
}
int my_memcmp(const void* str1, const void* str2, size_t num)
{
while (num--)
{
if ((char*)str1 > (char*)str2)
{
return 1;
}
else if ((char*)str1 < (char*)str2)
{
return -1;
}
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
return 0;
}
🛴结束语
测试用例可能不太全面,模拟实现大家参考即可,要是有错误请告诉我下下❤❤
讲了一推的函数,一堆的实现大家可以自己模拟实现,加深自己对这些函数的理解