长度不受限制的字符串函数
strlen部分
strlen函数的易错小知识
我们知道,strlen函数是用来求字符串的长度的。
size_t strlen( const char *string );
Each of these functions returns the number of characters in string, excluding the terminal NULL.
其中每个函数都返回字符串中的字符数,到\0为止(不包括\0),不包括NULL指针。
需要注意的是,strlen的返回类型是size_t.
size_t 是一些C/C++标准在stddef.h中定义的,size_t 类型表示C中任何对象所能达到的最大长度,它是无符号整数。
初学者很容易误以为strlen的返回类型是int,这点需要特别注意,看下面的代码来巩固。
#include <stdio.h> #include<string.h> int main() { const char* str1 = "chendada"; const char* str2 = "chen"; if (strlen(str2) - strlen(str1) > 0) { printf("str2>str1\n"); } else { printf("srt1>str2\n"); } return 0; }
运行结果如下:
嘻嘻,不知道有多少老铁踩坑了?😋可不能想当然地认为结果是个负数,切记strlen的返回类型是无符号整形!
这两个字符串长度相减的结果,如果从整形的角度来看,是-4没错,但是我们的返回类型是无符号整形,而无符号整形减去无符号整形的结果也是一个无符号整形。
也就是说,-4储存在内存中的补码并没有转换成源码来参与运算,而是直接被当成是一个数字来参与运算,我们知道,这个运算的结果一定是一个正数。
strlen函数的实现
strlen函数的实现更是多种多样,我在这里列出以下三种。
1.计数器
2.递归
3.指针-指针
//计数器方式 int my_strlen(const char * str) { int count = 0; while(*str) { count++; str++; } return count; }
//递归,不需要创建临时变量计数器 int my_strlen(const char * str) { if(*str == '\0') return 0; else return 1+my_strlen(str+1); }
//指针-指针的方式 int my_strlen(char* str) { char* p = str; while (*p != '\0') { p++; } return p - str; }
我们在这里将返回类型改成int,这样可以有效规避上面无符号整形导致误判的情况。
在以上代码里,我们都可以加入const和assert断言函数来让程序更安全。
strcpy部分
函数的参数形式char* strcpy(char*destination,const char*source);
该参数说明了strcpy返回类型是char类型的指针,将源头(不能被改)拷贝到目的地。
strcpy特点和strlen类似,遇到‘\0’就停止。
看下面的代码,最后打印的是什么呢?
#include <stdio.h> #include<string.h>} int main() { char str1[100] = "chen\0dada"; char str2[100] = "xxxxxxxxx"; strcpy(str2, str1); printf("%s", str2); return 0; }
运行结果如下:
正如上面所说,遇到\0就停止。
可以看到,strcpy会将\0也一并拷过去。
需要特别注意的是,目标空间必须可变,且必须足够大,用来确保能存放源字符串。
自己实现strcpy的方式非常多,在自制strcpy函数之前,我们需要知道,strcpy函数返回的是目标空间的起始地址,所以它的返回类型是一个指针,例如char *。
我们知道源头的地址不能改变,所以我们要加上const来修饰。
同时,为了确保指针有效,我们使用断言函数。
char* my_strcpy(char* dest, const char* src) { char* ret = dest;//目标空间的起始地址 assert(dest && src); while ((*dest++ = *src++)) { ; } return ret; }
strcpy函数返回的是目标空间的起始地址,所以它的返回类型是一个指针,例如char *。
我们可以用这一点实现下面的代码。
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<assert.h> char* my_strcpy(char* dest, const char* src) { char* ret = dest; assert(dest != NULL); while ((*dest++ = *src++)) { ; } return ret; } int main() { char a[10] = ""; char b[] = "dada"; printf("%s", my_strcpy(a, b)); return 0; }
strcat部分
char * strcat ( char * destination , const char * source );
Appends a copy of the source string to the destination string. The terminating null character
in destination is overwritten by the first character of source, and a null-character is included
at the end of the new string formed by the concatenation of both in destination.
源字符串必须以 '\0' 结束。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
简单的例子如下:
#include <string.h> #include <stdio.h> void main( void ) { char string[80]; strcpy( string, "Hello world from " ); strcat( string, "strcpy " ); strcat( string, "and " ); strcat( string, "strcat!" ); printf( "String = %s\n", string ); }
strcat函数是从\0的位置开始,会将\0用第一个元素覆盖。
那strcat能不能将自己拷贝到自己后面,即函数的参数为同一字符串呢?
我们看下面的代码。
自己实现strcat
char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest != NULL); assert(src != NULL); while (*dest) { dest++; } while ((*dest++ = *src++)) { ; } return ret; }
答案自然是不行的,调试会一直卡在这个位置,程序是死循环的结果。
为什么会死循环呢?请看我做的图。
一开始是这样,dest和src全都指向第一个字符。
然后dest找到\0的位置,准备开始拷贝。
紧接着\0被转化为c,且src向后位移一个字节。
这样下去的结果是。
dest应该去找下一个\0,可是\0已经在上面被字符'C'覆盖,也就是说,\0已经不复存在,程序崩溃。