strlen的使用和模拟实现
strlen用来计算字符串的长度,从给定起始位置到'\0'。
函数的返回值是size_t,是无符号的。
传递给strlen函数的形参是字符串的首元素地址。
想使用字符串函数,需要增加头文件#include<string.h>
对于库函数strlen,使用方法如下:
我们可以看到,strlen计算得到arr字符型数组的长度为6,可知strlen计算的结果不包括'\0'。
模拟实现strlen函数,我们有多种方法:
方法1,通过指针变量字符数组,指针每遍历一个字符,计数器自加,最后返回计数器的值就是字符数组的长度。
int my_strlen(const char*arr) {
int num = 0;
assert(arr);
while (*arr) {
num++;
arr++;
}
return num;
}
int main() {
char arr[] = { "hellow" };
int len=my_strlen(arr);
printf("%d\n", len);
}
方法2,在函数时,我们学习了递归的思想,方法1的想法其实可以转化成递归的方式:
在初始的返回值我们可以设置成1,在返回值通过递归可以获得字符串的长度。
但是在这里我们需要注意一个问题,如果字符串的长度本身就为0,那么我们就不能返回1,因此我们可以通过assert断言来判断传递给函数的指针是否为空指针,如果是空指针就返回0,非空指针就通过递归获取字符串的长度。
int my_strlen2(char* arr) {
assert(arr);
if (*arr == '\0') {
return 0;
}
else {
return 1 + my_strlen2(arr+1);
}
}
注:使用assert断言需要增加头文件#include<assert>。
方法3:因为指针-指针返回的是一个整数,所以我们可以通过两个指针来计算字符串的长度
我们可以设置一个指针指向字符串的首元素,然后让指针形参进行遍历,当遍历到'\0'时跳出循环,此时指针指向'\0'前一个字符。最后返回指针与形参相减的结果,这个结果就是字符串的长度。
int my_strlen3(char*arr) {
char* p = arr;
while (*p!='\0') {
p++;
}
return p - arr;
}
int main() {
char arr[] = { "hellow" };
int len = my_strlen3(arr);
printf("%d", len);
}
strcpy的使用和模拟实现
strcpy函数的作用是将源字符串中的数据复制到目标字符串。
在cpluscplus中我们可以了解到,strcpy函数的形参应该是目标空间的起始地址和源字符串的起始地址。
目标字符串中的大小应该足够长。
strcpy函数返回的是目标空间的起始地址。
源字符串中的'\0',也会被拷贝到目标空间中。
对于strcpy的使用:
库函数strcpy的使用
int main() {
char arr1[] = { "hellow" };
char arr2[10] = { 0 };//目标字符串中的大小应该足够长。
strcpy(arr2, arr1);
for (int i = 0; i < strlen(arr2); i++) {
printf("%c", arr2[i]);
}
}
我们看到,arr1已经成功被拷贝到arr2中,下面让我们通过自己的代码模拟实现strcpy。
在模拟实现中,我们需要用一个指针记录目标空间的初始地址,因为一旦开始交换空间时,目标空间的指针就会发生改变,当我们拷贝完后返回目标空间的指针时就无法正确找到目标空间,因此记录目标空间的初始地址很有必要。
char* my_strcpy(char* arr2, const char* arr1) {//被复制的一方不需要改变,用const修饰
char* ret = arr2;//先记录下拷贝数组的首地址,因为数组是连续存放的,知道数组首元素地址等于知道数组地址
while ((*arr2++ = *arr1++)) {
//当 *arr1 是 '\0'(即字符串的终结符)时,赋值操作会将 '\0' 赋值给 *arr2,
//并且 '\0' 的值是 0。while 循环的条件是 0(即假),因此循环会退出。
;
}
return ret;
}
int main() {
char arr1[] = { "hellow" };
char arr2[10] = { 0 };
my_strcpy(arr2, arr1);
for (int i = 0; i < strlen(arr2); i++) {
printf("%c", arr2[i]);
}
}
strcat的使用和模拟实现
strcat函数可以将一个字符串拼接到另一个字符串上,但是两个字符串的空间不能有重叠。
下面是库函数中的使用例子:我们可以通过strcat自己给自己追加。
#include<stdio.h>
#include<string.h>
//库函数使用
int main() {
char arr1[20] = { "hellow" };//追加的数组要确定长度,不然会错误
/*char arr2[] = { "nihao" };*/
strcat(arr1, arr1);
printf("%s", arr1);
}
也可以插入别的字符串:
int main() {
char arr1[20] = { "hellow" };//追加的数组要确定长度,不然会错误
//char arr2[] = { "nihao" };
strcat(arr1, "abc");
printf("%s", arr1);
}
我们需要注意:
源字符串必须以'\0',结束。
函数需要确定从哪个位置开始追加,所以目标字符串中也必须要有'\0'。
目标空间必须可以修改。
目标空间必须要大,否则无法容纳下源字符串。
而对于模拟实现,其思想与strcpy基本一致:
目标空间指针遍历自身找到'\0'的位置,源字符串拷贝从目标空间的'\0'位置开始拷贝直到完成。
相较于strcpy的模拟实现,多了一步先让目标空间的遍历。
char* my_strcat(char* arr1, const char* arr2) {
char* ret = arr1;
while (*arr1) {
arr1++;
}
while ((*arr1++) = (*arr2++)) {
;
}
return ret;
}
int main() {
char arr1[20] = { "hellow" };
char arr2[] = { "nihao" };
my_strcat(arr1, arr2);
printf("%s", arr1);
}
对于模拟实现的结果也与库函数中的strcat一致。
strcmp的使用和模拟实现
strcmp函数的作用是比较两个字符串的大小,比较两个字符串的大小,其实是比较对应位置上的ASCII码值的大小。
规定:第一个字符串>第二个字符串,返回大于0的数字;
第一个字符串=第二个字符串,返回等于0的数字;
第一个字符串<第二个字符串,返回小于0的数字;
库函数中strcmp的使用:.
int main() {
char arr1[] = { "abc" };
char arr2[] = { "bcd" };
int num=strcmp(arr1, arr2);
printf("%d", num);
}
由于首个字符不一致,所以比较ASCII码,返回的结果是<0的数,可知arr1<arr2 。
对于模拟实现的方法:
通过指针找到两者对应位置不一样的指针指向,再通过当前指针的解引用相减来比较大小。函数的返回类型是int类型。
int my_strcmp(const char* arr1, const char* arr2) {
//先通过循环找到不相等的位置,如果此时
while ((*arr1 == *arr2)) {
//如果遍历到末尾两者都相等,证明两个字符串相等,此时返回0
if (*arr1 == '\0' && *arr2 == '\0') {
return 0;
}
arr1++;
arr2++;
}
//通过解引用获取当前指针指向的字符,字符的相减在计算机中自动转换成ASCII码进行计算
return *arr1 - *arr2;
}
int main() {
char arr1[] = { "abc" };
char arr2[] = { "abc" };
int len=my_strcmp(arr1, arr2);
printf("%d", len);
}
得到的结果与库函数一致,模拟实现成功。
strstr的使用和模拟实现
strstr函数的作用是在主串中寻找子串,,并返回子串第一次出现的位置。
传递给函数的两个形参是主串的地址与待寻自串的地址。
strstr库函数的使用:
int main() {
char arr1[]{ "abcdefgh" };
char*p=strstr(arr1, "def");
printf("%s", p);
}
返回的是子串第一次出现的位置:
而对于strstr的模拟实现就稍微有点复杂了。
假设下面两串字符串,第二行字符串是要寻找的子串。
对两串字符串都进行遍历,当第一行与第二行同时指向第一个c,此时可能是子串出现的位置;另外再用一个指针指向不确定是否为子串位置的,我们将这个指针称为指针p。
随后继续向后遍历,发现第一行字符串的位置还不是子串出现的位置,这时我们需要将第二行指针进行重置,将第二行指针回到初始位置!
此时待确定位置的指针p向后走一位(因为此处不是子串开始出现的位置,所以向后移动),而对于第一行的指针,则需要返回指针p的位置,然后重置后的第一行指针与第二行指针继续向后遍历。
至此,函数的主逻辑基本实现。但是有一种特殊情况,如果子串本来就是空字符串,那么我们就应该返回主串的初始位置。
const char* my_strstr(const char* str1, const char* str2) {
const char* p1 = str1;//记录str1的初始位置
const char* p2 = str2;//记录str2的初始位置
const char* ret = str1;///记录可能开始相同的位置
//特殊情况,查找的子串是空子串
if (*p2 == '\0') {
return p1;
}
while (p1) {
//第一次循环
p1 = ret;//第一次开始是首元素的位置,后面不断更新
p2 = str2;//后续如果查找到一半发现不是子串,通过循环可以直接重置指针
while (p1 && p2 && *p1 == *p2) {
p1++;
p2++;
}
//如果p2刚好是'\0',则子串已经找到了,返回记录可能相同位置的指针ret
if (*p2 == '\0') {
return ret;
}
ret++;
}
return NULL;
}
int main() {
char arr1[] = { "abbbcdef" };
char arr2[] = { "bbc" };
const char*len=my_strstr(arr1,arr2);
printf("%s", len);
}
输出结果与库函数一致,模拟实现成功。