目录
字符串函数
strlen
strcpy
strcat
strcmp
strstr
内存函数
memcpy
memmove
人生百态,苦事之多。烦恼穿心,何来解脱?打开博客,吸取干货。
以码消愁,以串解忧。泱泱年轮,唯有生活。一起撸串,快乐几何。
在C语言中,我们通过字符数组的方式来存储字符串,也可以直接使用常量字符串,如何去判断字符串的长度便是一个问题。比如char ch[100] = "abcdef"; 难道长度就是100吗?显然不对,我只有abcdef这六个字母,长度应该为6才对,所谓上天有好生之德,留下了一个库函数strlen可以求得字符串的长度。
#include
#include
int main()
{
char ch[100] = "abcdef";
int len = strlen(ch);
printf("%d\n", len);//结果为6
len = strlen("abcdef")
printf("%d\n", len);//结果依然为6
return 0;
}
strlen使用起来竟如此方便,只需把数组名或常量字符串往里一掷,它便会自己返回字符串长度。
如何模拟实现计算字符串长度?我们需要知道,C语言是如何打印字符串的。
char ch[] = "abcdef";
printf("%s\n", "abcdef");
printf("%s\n", ch);
放一个字符串以%s打印显然没问题,但是放一个数组名用%s打印也没问题,而数组名是什么?数组名是数组首元素地址(除开sizeof和&的特殊情况)那是否常量字符串也是首元素地址?
printf("%p\n", "abcdef");
由此可以得知,其实C语言打印字符串是根据它的首元素地址一直往下找,找到一个打印一个,那么何时停止?根据我们扎实的C语言基础知识,我们都知道字符串以\0结尾,所以找到\0的时候便不用再打印了。
知道了原理,接下来就是模拟实现了。既然字符串是根据地址找的,我们就传入首地址,既然是以\0结尾的,我们就把\0之前的字符全部统计一遍,这样就能得到字符串长度了。
int my_strlen(const char* str)//因为只需要统计个数 不需要改变字符 所以用const
{
assert(str); //防止传NULL指针 引用头文件assert.h进行断言
int len = 0;
while(*str != '\0')
{
str++;
len++;
}
return len;
}
strcpy函数用于把一个字符串拷贝到另一个字符串中。但要在使用的时候注意一些事项:
1. 目标空间必须足够大 2. 源空间必须存在\0 3.目标空间必须可以更改
char ch[100];//目标空间可更改 且足够大
char cpy[] = "abcdef";//源空间存在\0
strcpy(ch, cpy);//将cpy的内容拷贝到ch中
printf("%s\n", ch);
既然我们知道字符串以\0结尾,那我们只需要知道他们两个字符串的首元素地址,然后到被拷贝字符串的\0为止全部拷贝进去即可。
模拟实现:
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//断言
char* ret = dest;//用于返回给用户拷贝完后的地址 方便用户操作
while(*dest++ = *src++);//当src不为\0时拷贝到dest中 并后移一位
return ret;
}
strcat(str1, str2);会把str2的内容追加到str1后面。但也有一些注意事项:
1.目标空间必须足够大 2.目标空间必须存在\0 3.源空间必须存在\0 4.目标空间必须可更改
char ch1[100] = "I love";//目标空间足够大且存在\0且可更改
char ch2[] = " You";//源空间存在\0
strcat(ch1, ch2);
printf("%s\n", ch1);
模拟实现:(找到目标空间\0之后和strcpy实现方式类似)
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while(*dest)
{
dest++;
}
while(*dest++ = *src++);
return ret;
}
当我们需要比较两个字符串是否一致时,许多小白都会直接用 == 进行比较,但我们之前提到过,字符串本质上是首元素地址, ==进行比较时比较的也是首元素的地址,所以答案会与我们的期待的不符,实际上C语言有一个库函数strcmp可以比较两个字符串,相等返回0,不相等时返回值根据第一个不等的字符词典序进行返回(大于返回>0 小于返回<0) 如: a 和 b 显然 词典序中a
char ch1[] = "abc";
char ch2[] = "abc";
char che3[] = "abd";
int flag1 = strcmp(ch1, ch2);//0
int flag2 = strcmp(ch1, ch3);//<0
模拟实现:从头开始比较,相等继续比较,不等根据词典序返回,比到\0返回0
int my_strcmp(const char* str1, const char* str2)
{
while(*str1 == *str2 && *str1 && *str2)
{
if(*str1 == '\0')
return 0;
str1++,str2++;
}
return *str1 - *str2;
}
strstr的作用是在一个字符串中查找子串并返回地址。
char ch[] = "I love you";
char* low = strstr(ch, "love");//在ch中查找 love 找到返回love的首地址 找不到返回NULL
printf("%s\n", low);//love you
模拟实现:既然是找子串,那么主串没找完且长度大于子串的情况下都得继续找,所以为了方便我们就多创建一个指针指向当前主串要找的起始位置,然后循环比较。
char* my_strstr(const char* str1, const char* str2)
{
char* s1 = str1;
char* s2 = str2;
char* cp = str1;
while(*cp)
{
s1 = cp;
s2 = str2;
while(*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
if(*s2 == '\0')
return cp;
cp++;
}
return NULL;
}
既然干货都发出来了,何不再来了解了解string.h中的内存函数?
内存函数的优点就在于可以自己指定字节数,且兼容所有的指针类型。
int a = 4;
inb b;
memcpy(&b, &a, 4);//把a地址开始到后面4个字节复制给b(a为整型 4个字节 即把a复制给b)
实现:我们需要知道要兼容所有的地址类型,我们需要void*指针 由于又是按字节复制 所以我们将void*使用时强制类型转换为char*即可。
void* my_memcpy(void* dest, const void* src, int size)
{
assert(dest && src);
void* ret = dest;
while(size--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//不用++的原因是 void*指针直接++只有在某些编译器上才可以
src = (char*)src + 1;
}
return ret;
}
memmove相比memcpy啥的可厉害太多了,memcpy如果自己复制给自己就有可能因为重叠覆盖而出错,而memmove就修复了这一点。
memmove是如何弥补这一点的?比如 12345 你要复制45 到34的位置 如果我们先复制5 那么就会把4覆盖 复制4的时候就会出现问题, 但我们先复制4便可以解决。所以memmove可以根据你复制的目标位置和源位置作比较得到的结果,来判断先复制左端还是右端,避免重叠空间被覆盖。
用法与memcpy一致 所以不再演示。
模拟实现:
void* memmove(void* dest, const void* src, int size)
{
assert(dest && src);
void* ret = dest;
if(dest <= src || (char*)dest >= (char*)src + size)//即没有重叠空间或目标空间在左方时
{
while(size--)
{
*(char*)dest = *(char*)src;
dest = *(char*)dest + 1;
src = *(char*) + 1;
}
}
else//当重叠时 dest在右方时
{
dest = *(char*)dest + size;
src = *(char*)src + size;
while(size--)
{
*(char*)dest = *(char*)src;
dest = *(char*)dest - 1;
src = *(char*) - 1;
}
}
}