前言,因为C语言中没有字符串的数据类型,所以这里说的对字符串操作是指对一个存放多个字符类型数据的数组,即字符数组。如char arr[10]="abcdef"。除此之外,字符串也可被放入常量字符串中,但由于一些库函数涉及到字符串值的改变,所以我们这里不用常量字符串(常量字符串值不可改变)。推荐学习C语言库函数网站网址:cplusplus.com - The C++ Resources Network
目录
1.对字符串操作的库函数模拟实现
1.1 strlen
1.2 strcpy
1.3 strcmp
1.4 strcat
1.5 strstr
2.可以对任意类型数据操作的库函数模拟实现
2.1 memcpy
2.2 memmove
3.知识点拓展
3.1 str(n)cpy中n加与不加的区别
3.2 strtok,strerror,memcmp的使用
1.对字符串操作的库函数模拟实现
1.1 strlen
strlen库函数用于计算一个字符数组的元素个数,即一个字符串的长度。我们可以从前言推荐的网站中搜索到其形参类型及返回值
初学C语言的码友可能不懂该函数返回值为size_t,在VS编译器环境下,我们可以直接对size_t类型进行转定义:
可以看出size_t本质就是unsigned int(无符号整型),也就是返回的字符串长度不可能为负数。只用计算字符串长度,不用改变字符串的值,所以使用const修饰,避免字符串发生改变。
实现代码及注释:
//size_t 无符号整型(返回字符串长度>=0)
size_t my_strlen(const char* str) {
const char* temp = str; //中间指针变量控制指针后移
while (*temp++) {//直到temp指向空间的值为'\0'才停止后移
;
}
return temp - str - 1; //-1移动'\0'的前一位,即字符串最后一个字符的位置,再减去首字符地址
}
int main() {
char arr[] = "abcde";
printf("%d", my_strlen(arr));
return 0;
}
1.2 strcpy
strcpy库函数用于将源字符串的值复制到目标字符串中。接下来,我们继续看一下其传参和返回值。
因为我们是往目标字符串拷贝字符,所以目标字符串值需要改变,而源字符串则用const修饰,避免被改变。
实现代码及注释:
char* my_strcpy(char* des, const char* src) {
assert(des && src);//des和src指针都不能为空
char* ret = des;//保存字符串首字符地址
//直接将src中字符赋值到des中,并当*src='\0'时,赋值给*des时,循环结束
while (*des++ = *src++) {
;
}
return ret;//返回ret
}
注:
1.使用此库函数时目标字符串长度不可小于源字符串长度,否则会造成越界。
2.目标字符串不可以是常量字符串,因为常量字符串的值不可被修改。
1.3 strcmp
strcmp库函数用于比较两个字符串的大小(实质是比较每个字符的ASCII码值),其入参和返回值如下:
因为我们只是对两个字符串进行比较,本身两个字符串都不需要改变。所以需要用const修饰,避免两个字符串发生改变。
实现代码及注释:
int my_strcmp(const char* str1, const char* str2) {
assert(str1 && str2); //避免两个字符串为空
while (*str1 == *str2) {//相等则继续比较直到*str1不等于*str2跳出循环
if (*str1 == '\0') return 0;//等于'\0',则两个字符串相等,也停止比较跳出循环
str1++;
str2++;
}
return *str1 - *str2;//若*str1>*str2,则返回大于零的数,反之,返回小于零的数
}
1.4 strcat
strcat库函数用于将一个源字符串连接到目标字符串之后,其入参和返回值如下:
这里只对目标字符串进行连接,而源字符串不做改变(直接用const修饰)。
实现代码及注释:
char* my_strcat(char* des, const char* src) {
assert(des && src);//断言目标字符串及源字符串皆不能为空
char* ret = des;//保存目标字符串首字符地址
while (*des != '\0') {//将目标字符串定位到其'\0'位置
des++;
}
while (*des++ = *src++) {//将源字符串赋值到目标字符串后
;
}
return ret;//返回目标字符串首字符地址
}
注:目标字符串长度一定要等于或大于连接后的字符串,不能小于连接后的字符串,不然会发生越界错误。
1.5 strstr
strstr库函数用于在str1字符串中查找str2字符串,其入参及返回值如下:
这里我们用第一种,因为定位字符串不需要改变其两个字符串的值,返回第一次str2出现的地址。
实现代码及注释:
char* my_strstr(const char* str1, const char* str2) {
const char* s1 = str1, *s2 = str2;//这里习惯保存首字符地址,也可直接对str1和str2操作
const char* temp = str1;//使用中间指针变量保存str1首字符地址并进行指针控制
if (*s2 == '\0') return (char*)s1;//如果str2为空直接返回str1
//暴力求解,遍历字符串
while (*temp) {
s1 = temp;//每次指针后移更新str1
s2 = str2;//失败将str2再次变回原来的值
//一直循环到*str1或*str2为'\0'或*str1!=*str2时
while (*s1 != '\0' && *s2 != '\0' && (*s1 == *s2)) {
s1++;
s2++;
}
//循环结束条件如果是*s2='\0',代表str2为str1的子串,这时候temp指向位置为str2第一次在
//str1中出现的位置
if (*s2 == '\0') return (char*)temp;
temp++;//没有找到但遍历未完成,继续遍历
}
return NULL;//str1字符串遍历完毕后还没有找到返回NULL
}
注:1.这里str1的长度不能小于str2的长度。
2.可以用KMP算法提高效率,后期会专门写一篇关于KMP算法的博客,欢迎大家跟我一起学习
这里逻辑比较复杂,不懂的码友可以看看我画的图,方便大家理解:
画的是成功查找到的例子,查不到也是这样的思路,在循环里首先更新s1,s2,然后看内循环是否能成功执行,再思考*s2是否为\0,能否成功返回temp。
2.可以对任意类型数据操作的库函数模拟实现
2.1 memcpy
memcpy库函数用于将源一块连续空间的数据拷贝到另一块连续d目标空间处,其入参和返回值如下:
void* 指针可接受任意类型数据,源空间数据无需改变,用const修饰它
实现代码及注释:
//num是所需拷贝的字节个数
void* my_memcpy(void* des, void* src, size_t num) {
void* ret = des;//保存目标空间的首元素地址
assert(des != NULL && src != NULL);//断言目标空间及源空间皆不能为NULL
while (num--) {//循环次数为所需拷贝的字节个数
//char*类型能保证源空间地址的每个字节的数据都能被读到,并赋值给目标空间的每个字节
*(char*)des = *(char*)src;
des = (char*)des + 1;//一个字节一个字节的读
src = (char*)src + 1;
}
return ret;//返回保存目标空间的首元素地址
}
注:为了避免溢出,目标空间大小和源空间大小不能小于所需拷贝的字节个数。
2.2 memmove
memmove库函数用于目标空间与源空间发生重叠时的拷贝数据(将一块空间的某个子块的数据拷贝到另一个子块中),其入参和返回值如下:
这时候码友们也许有一个疑问,memmove的函数体几乎一模一样,它们之间有什么关系呢?大家可以看看我这个实例,就会明白它们之间的区别:
当des与src不发生重叠时,数据拷贝方向是任意的,不会发生数据覆盖,所以为方便实现我们直接分为两种操作,第一种若des<src时,memmove的拷贝方式与memcpy相同,当des>src和des,src不发生重叠时,memmove的拷贝方式就从后往前拷贝。
实现代码及注释:
void* my_memmove(void* des, const void* src, size_t num) {
void* ret = des;//保存目标空间的首元素地址
assert(des != NULL && src != NULL);//断言目标空间与源空间指针不能NULL
if (des < src) {//两种操作
//操作一:与memcpy相同,从前向后拷贝
while (num--) {
*(char*)des = *(char*)src;
des = (char*)des + 1;
src = (char*)src + 1;
}
}else {
//操作二:从后向前拷贝
while (num--) {
*((char*)des + num) = *((char*)src + num);
}
}
return ret;//返回目标空间起始地址
}
注:为了避免溢出,目标空间大小和源空间大小不能小于所需拷贝的字节个数。
3.知识点拓展
3.1 str(n)中n加与不加的区别
码友们一定发现了我的注意事项,一般都是对字符串的长度进行限制,而strncpy,strncat等等有关字符串的操作库函数都不对长度进行限制。反之,不加n的则是长度受限的字符串操作库函数。
3.2 strtok,strerror,memcmp的使用
strtok库函数用于分隔字符串,其入参及返回值如下:
第一个参数是所需分隔字符串的首元素地址,第二个参数是存放分隔符号的字符串首元素地址。返回值为分隔后的字符串的首元素地址。
注:该函数具有记忆功能,当第一个参数传入NULL时,则会在上一次分隔位置再一次进行分隔。
strerror库函数用于输出错误信息,其入参及返回值如下:
输入错误码,返回一串错误信息的首元素地址,大家可以直接传errno错误码变量(引用<errno.h>头文件),也就是这样:
memcmp库函数用于比较两块空间的值大小,其入参及返回值如下:
返回值与strcmp一样,0(相等),1(前者大于后者),-1(后者小于前者),num为所需比较空间的字节个数。长度也受限,与strcmp不一样的是,memcmp遇到'\0'不会停止比较