前言
在编程的过程中,我们经常要对字符串和内存进行各种各样的处理,c语言提供了一系列字符串函数和内存函数,便于我们对字符串或者内存空间进行操作。本篇文章我们就来学习其中的一些函数。
一、字符串函数
1.strlen的使用和模拟实现
c语言中,strlen函数用于计算一个字符串的长度。它的原型如下:
size_t strlen ( const char * str ) ;
这里需要注意的是:
1.字符串以\0为结束标志,这个函数返回值是字符串的长度,不包括\0。
2.传参时要传入字符、0串的首元素地址,指向的字符串要以\0为结尾。
3.函数返回值类型是size_t,是一个无符号整数
4.strlen函数使用需要引头文件string.h
接下来我们试着使用一下strlen函数:
int main() { char str[] = "hello"; int ret = strlen(str); printf("%d\n", ret); return 0; }
运行结果:
可以看到,"hello"一共有五个字符,所以字符串的长度就是5。也就是说,这个函数的原理就是统计出\0之前的字符个数。这样,我们尝试模拟实现一下它:
方法1:计数器
size_t my_strlen(const char* str) { size_t count = 0;//定义变量统计字符个数 while (*str != '\0') { str++; count++; } return count; }
这里我们定义了一个变量count,负责统计\0之前的字符个数。当指针走到\0处时,循环停止。
方法2:递归
size_t my_strlen(const char* str) { size_t count = 0;//定义变量统计字符个数 while (*str != '\0') { str++; count++; } return count; }
方法3:指针减指针
size_t my_strlen(const char* str) { char* p = str; while (*str++);//指针持续往后走,知道遇到\0为止 return str - p - 1;//后置++使得指针多走了一步,多减一个1 }
这里先记录一下首元素地址,然后让str跑到字符串末尾,再减去首元素地址就得到两地址之间的元素个数,也就是字符串长度。
2.strcpy的使用和模拟实现
strcpy这个函数的作用是将源字符串中的内容拷贝到目标字符串中。它的原型如下:
char* strcpy ( char* dest, const char* src ) ;
这里需要注意:
1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。
2.源字符串必须以\0结尾。
3.目标字符串的空间必须足够大,能够包含整个源字符串;目标字符串不能是常量字符串。
4.函数返回值是目标字符串的首元素地址。
5.源字符串中的\0也会一同拷贝。
我们尝试使用一下它:
int main() { char str1[] = "xxxxxxxxxxxx"; char str2[] = "hello"; printf("%s\n", strcpy(str1, str2)); return 0; }
运行结果:
模拟实现:
char* my_strcpy(char* dest,const char* src) { char* ret = dest;//记录首元素地址 while (*dest++ = *src++); return ret; }
这里我们首先记录了原字符串的首地址,然后循环赋值完成拷贝,最后返回首地址。
3.strcat的使用和模拟实现
strcat函数的作用是将源字符串内容追加到目标字符串上。原型如下:
char * strcat ( char * dest, const char * src ) ;
要注意的是:
1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。
2.源字符串和目标字符串都必须以\0结尾。
3.目标字符串的空间必须足够大;目标字符串不能是常量字符串。
4.函数返回值是目标字符串的首地址。
接着我们使用一下这个函数:
int main() { char str1[20] = "hello "; char str2[] = "world"; printf("%s\n", strcat(str1, str2)); return 0; }
运行结果:
模拟实现:
char* my_strcat(char* dest, const char* src) { char* ret = dest;//记录目标字符串首地址 while (*dest) { dest++; }//使指针指向目标字符串末尾 while (*dest++ = *src++);//循环追加,直到源字符串末尾 return ret; }
4.strcmp的使用和模拟实现
strcmp函数是一个很重要的函数,它用于比较两个字符串的大小。字符串的比较在很多实例中会使用到,它的比较规则如下:
在了解了比较规则之后,我们来看一下函数原型:
int strcmp ( const char* str1, const char* str2 ) ;
1.参数传入要比较的两个字符串的首地址。
2.两个字符串都要以\0结尾。
3.如果str1大于str2,函数返回正数,否则返回负数,相等则返回0。
我们写一个程序使用该函数,实现两个字符串比较:
int main() { char str1[] = "abcd"; char str2[] = "abdc"; int flag = strcmp(str1, str2); if (flag > 0) { printf("str1大于str2\n"); } else if (flag < 0) { printf("str1小于str2\n"); } else { printf("两者相等\n"); } return 0; }
运行结果:
接着,我们尝试模拟实现strcmp:
int my_strcmp(const char* str1, const char* str2) { while (*str1 == *str2) { if (*str1 == '\0')//处理出现空字符串或者字符比较到末尾的情况 { return 0; } str1++; str2++; } return *str1 - *str2; }
5.strstr的使用和模拟实现
接下来,我们学习一下strstr函数。strstr函数的作用是判断一个字符串是否是另一个字符串的子字符串(是否为包含关系)。
它的原型如下:
char * strstr ( const char * str1, const char * str2) ;
1.两个字符串必须要以\0结尾。
2.函数的返回值是str2在str1中第一次出现的位置,如果没找到,返回空指针。
我们尝试使用这个函数:
int main() { char str1[] = "123456789"; char str2[] = "3456"; printf("%s\n", strstr(str1, str2)); return 0; }
运行结果:
可以看到,函数确实找到了第二个字符串在第一个字符串中的位置。接下来我们模拟实现一下它:
char* my_strstr(const char* str1, const char* str2) { char* cur = str1; if (*str2 == '\0')//空字符串的情况 { return str1; } while (*cur != '\0') { char* s1 = cur; char *s2 = str2; while (*s1 && *s2 && (*s1 - *s2) == '\0')//遍历相等的部分 { s1++; s2++; } if (*s2 == '\0')//遍历结束后如果str2已结束,说明是子串 { return cur; } cur++; } return NULL; }
6.strncmp、strncpy、strncat的使用
这三个函数是在原来功能的基础之上,限定了比较/拷贝/追加的字符个数。函数的参数部分多了一个num,表示限定的字符个数。举个例子:
int main() { char str1[] = "xxxxxxxxx"; char str2[] = "hehe"; strncpy(str1, str2, 2); printf("%s\n", str1); char str3[20] = "hello "; char str4[] = "world"; strncat(str3, str4, 3); printf("%s\n", str3); return 0; }
运行结果:
可以看到,程序根据我们限定的字符个数完成了拷贝和追加。
7.strtok的使用
strtok函数也叫做字符串分割函数。顾名思义,它的作用就是根据给出的特定字符来分割字符串。下面是它的原型:
char* strtok ( char* str, const char* sep ) ;
这里需要注意以下几点:
1.第一个参数是要被分割的字符串,第二个参数是由分隔符组成的字符串。
2.strtok函数会在str中寻找限定的分隔符,遇到分隔符就会在字符串中将其变为\0,完成分割。函数返回上一次分割后\0的下一个字符的地址;如果是第一次分割,则返回字符串首地址。
3.对一个字符串进行多次分割时,需要多次调用该函数:第一次调用时str传入要被分割的字符串,函数会使用静态变量保存被分割的位置,之后调用时str要传入NULL,函数就会从保存的位置开始进行分割。
4.当字符串按照分隔符完成了所有分割之后,就会返回NULL。
我们写代码体现一下它的使用效果:
int main() { char str[] = "123.12.14.1.50"; char* p = NULL; printf("%s\n", str); for (p = strtok(str, "."); p != NULL; p = strtok(NULL, ".")) { printf("%s\n", p); } return 0; }
运行结果:
可以看到字符串成功被分割成几部分。不难发现,strtok函数可以和for循环结合使用,达到分割字符串的效果。
二、内存函数
在学习了这些字符串函数之后,我们可以发现,它们虽然实用,但是却只能对字符串进行操作。那么是否有一些函数可以让我们对任何类型也进行相似操作呢?它就是内存函数。这些内存函数的使用也需要引头文件string.h。
1.memcpy的使用和模拟实现
memcpy函数也叫做内存拷贝函数,它能够将特定字节的内存值拷贝到其他内存位置中。它的原型:
void * memcpy ( void * destination, const void * source, size_t num );
其中的前两个参数分别表示目标空间和源空间的首地址,第三个参数表示要拷贝的字节数。函数的返回值是目标空间的首地址。我们来使用一下它:
int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[10] = { 0 }; memcpy(arr2, arr1, 5 * sizeof(int)); for (int i = 0; i < 10; i++) { printf("%d ", arr2[i]); } return 0; }
运行结果:
可以看到,有五个元素的内容被拷贝到arr2当中。
接下来我们尝试模拟实现:
void* my_memcpy(void* dest, const void* src, size_t num) { void* ret = dest; while (num--) { *(char*)dest = *(char*)src; (char*)dest += 1;//这里由于强制类型转换是临时的,不能直接写++ (char*)src += 1; } return ret; }
可以看出,我们这里使用了泛型编程的思想,将void*指针强转为char*类型,然后根据给定的字节数一个字节一个字节赋值。
2.memmove的使用和模拟实现
对于刚才的memcpy函数,如果想要拷贝的两个内存空间之间出现重叠情况,那么函数就无法完成正常的拷贝。而memmove函数就弥补了这个缺陷。原型如下:
void * memmove ( void * destination, const void * source, size_t num );
它的参数含义和返回值和memcpy是完全相同的。我们尝试使用它来拷贝重叠的内存:
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; memmove(arr, arr + 2, 5 * sizeof(int)); for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }
运行结果:
可以看到,它成功地完成了重叠内存的拷贝。那我们是否可以模拟实现这个函数呢?
我们首先来思考一下,现在有一个数组arr,它的内容如下:
现在我们想要将1,2,3,4,5这五个元素拷贝到3,4,5,6,7的位置上,能否完成呢?
将1赋值到3的位置上,将2赋值到4的位置上,到赋值3的时候,就会发现,此时的3已经被覆盖成1了,这样下去,最终结果就会变成1,2,1,2,1,2,1,8,9,10。
聪明的你肯定会想到,既然从前往后不行,那么我们就从后往前进行拷贝:先将5赋值到7的位置,再将4赋值到6的位置,以此类推...你就会发现结果就是1,2,1,2,3,4,5,8,9,10,说明成功了。
难道所有的情况都可以通过从后往前拷贝的方法赋值成功吗?当我们反过来将3,4,5,6,7拷贝到1,2,3,4,5的位置上,我们就发现从后往前的拷贝就不行了,就要用从前往后拷贝的方式。
所以我们就可以得出以下结论:在有重叠空间的情况下,如果dest的地址小于src,那么就从前向后拷贝;如果dest的地址大于src,就从后向前拷贝。没有重叠空间时怎样都行。图示:
这里由于右边非重叠空间的部分和中间部分是连续的,所以我们使用从后向前拷贝的方法会更加方便编写代码。
代码如下:
void* my_memmove(void* dest, const void* src, size_t num) { void* ret = dest; if (dest < src)//从前向后 { while (num--) { *(char*)dest = *(char*)src; (char*)dest += 1; (char*)src += 1; } } else//从后向前 { while (num--) { *((char*)dest + num) = *((char*)src + num); } } return ret; }
3.memset的使用
memset函数也叫做内存设置函数,它可以对特定区域的内存全部赋成你想要的值。它的原型如下:
void * memset ( void * ptr, int value, size_t num ) ;
它的第一个参数是要设置的内存空间的首元素地址,第二个参数是要赋的值,第三个参数是内存空间的大小(字节)。
它的返回值是内存空间的首元素地址。
我们来使用一下这个函数:
int main() { char str[] = "hello world"; memset(str, 'x', 6); printf(str); return 0; }
运行结果:
可以看到,前六个字符都被赋值成了'x'。
总结
本篇文章我们学习了字符串函数和内存函数的相关知识,它们在我们的编程当中十分常见和实用。同时,我们也学会了如何思考问题,解决问题。之后博主会更新数据存储方式相关的内容。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤