一.字符串函数
1.1.strlen
1.1.1.strlen剖析
strlen:求字符串长度函数
1.首先,我们先看一下strlen这个函数的返回值和参数类型
size_t strlen ( const char * str ); C语言中VS编译器下是这样定义size_t的: typedef unsigned __int64 size_t; 也就是说:size_t实际上是对unsigned int类型起的一个别名, 我们可以理解为size_t就是一种unsigned int 其中的const char* str:说明传入的参数需要是一个地址, strlen函数通过所传入的一个地址来找到所对应的字符串, 然后从对应地址处开始读取计算字符个数,一直计算到'\0'为止. !!!注意: 1.strlen函数是计算'\0'之前的字符个数, 也就是说'\0'本身并不会计算在strlen所计算的个数之内 2.sizeof操作符计算字节大小时也会计算上'\0' 3."abcd"这种类型的字符数组在末尾处会隐含'\0', 而{'a','b','c','d'}这种类型的字符数组在末尾处则不会隐含'\0' 也就是说除非在末尾处人工添加'\0',否则这种字符数组末尾是没有'\0'的 4.关于const的作用是:限制str所指向的内容不能被修改, 也就是*str不能被修改,即不能充当左值 例如:后面给了大家答案,大家可以参照一下 int main() { char arr1[] = "abcdef"; char arr2[] = { 'a','b','c','d','e','f' }; printf("arr1的strlen:%d\n", strlen(arr1));//6 printf("arr1的sizeof:%d\n", sizeof(arr1));//7 printf("arr2的strlen:%d\n", strlen(arr2));//x printf("arr2的sizeof:%d\n", sizeof(arr2));//6 return 0; }
1.1.2 模拟实现strlen函数
接下来让我们模拟实现一下strlen函数,
1.在这里我们使用int类型作为返回值,实现完后会跟大家说明原因
2.在这里我们使用assert宏来保证arr所指向的空间不是NULL,
便于函数调用者传入参数错误时能够精确定位到错误的位置和原因
3.assert宏会在后面给大家详细说明
法一:计数器方法
思想是这样的:
让指针从数组首元素开始一次右移读取字符,
记录下读取到的字符个数,即为’\0’之前的字符个数
从strlen函数的功能上面可以很好的理解
//法一:计数器 int my_strlen1(const char* arr) { assert(arr != NULL); int count = 0; while (*arr++ != '\0') { count++; } //注意:这两种对arr++的操作完全相同, //大家可以思考一下为什么这两种方法得到的最终答案相同 //while (*arr != '\0') //{ // count++; // arr++; //} return count; }
下面我们解释一下为什么这两种方法得到的最终答案相同
1.注意:两种while循环的循环次数是相等的
但是不同点在于
1.第一种while循环中arr在循环条件判断结束之后就进行了自增操作,
2.而第二种while循环中arr的自增操作是在循环体内部完成的
不过这个函数返回的是count,而count只与while循环的循环次数有关,与arr无关,所以最终答案相同
2.递归方法
与上面的具体实现方式相同,只不过算法实现方式并不相同
上面是计数器方式,这里是递归调用的方式
这里的递归思想是
把一个字符串(假设长度为len)分割为
1.第一个字符
2.后面那个长度为len-1的字符串
第一步:
我们判断第一部分是否为’\0’,
如果为’\0’,则终止计算,返回0
第二步:
如果不为’\0’,则返回1+计算第二部分的长度
//法二:递归 int my_strlen2(const char* arr) { assert(arr != NULL); if (*arr == '\0') { return 0; } return 1 + my_strlen2(arr + 1); }
3,指针-指针的方法:
补充:
指针-指针:得到的是两个指针之间的元素个数!!!
指针之间可以进行减法(前提:必须是指向同一结构的指针,例如;数组)
指针之间不可以进行加法
思路:
让end指针指向’\0’的位置,然后只需要返回end-arr即可
//法三:指针-指针 int my_strlen3(const char* arr) { assert(arr != NULL); char* end = arr; while (*end != '\0') { end++; } //下面的方法是错误的,得到的字符串长度为实际长度+1 //大家可以思考一下为什么这两种方法得到的最终答案不同 //while (*end++ != '\0') //{ // ; //} return end - arr; }
前面说明了这两种while循环的区别之处和相同之处
下面我们来看一下这个题为什么得到的答案就不一样了呢?
1.这题返回的是end-arr,又因为arr在该函数内部并不会变动
所以最终答案与end有直接联系
2.上面提到过end是在while()循环的条件判断出进行的,
而我们又知道:
在for循环和while循环中,循环体执行的次数是循环条件执行的次数-1,所以错误的方法中end多自增了一次,所以返回的答案是实际答案+1
下面我们说一下用int类型作为返回值的好处 大家可以做一下这道题 int main() { if (strlen("abc") - strlen("abcdef") > 0) { printf(">\n"); } else { printf("<=\n"); } return 0; } 最终的答案是 > ,而不是 <= 其实最终的答案是挺出人意料的, 因为strlen这个库函数的返回值为size_t(可以理解为unsigned int类型) 所以最终得到的"-3"是size_t类型,而不是int类型, 所以"-3"在内存中被取出时是以无符号整型的视角去取出的,所以-3被取出时是一个非常大的正数,所以答案是> 所以使用size_t作为返回值的话无法用来这样直接判断两个字符串的长度, 不太方便,也正是因为这个原因我们使用了int类型作为返回值, 这样的话就可以很方便的直接判断两个字符串的长度了 那是不是说库函数实现的strlen函数用size_t函数作为返回值就不好呢? 当然不是,strlen这个函数设计的本意就是求字符串的长度 显然结果不可能为负数,所以我们使用了size_t作为返回值是很好的 这也就说明了不同的人在设计相同功能的函数时,出发的角度不同,所设计出的函数也会不同,也就是说,这两种设计方式各有各的好处,我们要灵活使用
下面我们说一下assert这个宏 void assert (int expression); assert翻译过来就是断言的意思, 也就是说assert会执行expression中的语句, 如果这个语句的计算结果为0,那么断言失败,程序终止, 并且在屏幕上指出错误位置和具体原因 我们可以把assert中的语句和if中的语句联系起来看待, 例如: int main() { int a = 2; int b = 0; if (b = a) { printf("you can see me\n");//会执行该条语句 } printf("%d", b);//2 } 举一个例子,没有什么意义,只是为了说明assert中的语句会执行 例如: int main() { int a = 2; assert(a=1); printf("%d", a);//结果为1,说明assert中的语句会执行 }
1.2 strcpy
strcpy:字符串拷贝函数
1.2.1 strcpy函数剖析
strcpy(字符串拷贝) char* strcpy(char* destination,char* source) 英语好的老铁可以看一下: Copies the C string pointed by source into the array pointed by destination, including the terminating null character(and stopping at that point). 翻译后: 源字符串必须以 '\0' 结束。 会将源字符串中的 '\0' 拷贝到目标空间。 目标空间必须足够大,以确保能存放源字符串。 目标空间必须可变。
1.2.2 strcpy函数模拟实现
char* my_strcpy(char* dest,const char* src) { char* ret = dest; assert(dest != NULL); assert(src != NULL); while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[20] = "xxxxxxxxxxxxxxx"; char arr2[] = "hello world"; my_strcpy(arr1, arr2); //arr2中'\0'及'\0'之前的元素拷贝到arr1中 printf("%s\n", arr1); return 0; }
1.因为strcpy函数要返回目标空间首元素的地址,所以先用ret来保存目标字符串dest的首元素地址
2.采取逐个赋值,当*src==‘\0’时进行完对应的赋值操作后(*dest++=*src++)这个表达式整体的值为’\0’,对应的ASCII码值即为0,所以跳出while循环,字符串拷贝操作结束
1.3. strcat
strcat:字符串追加函数
1.3.1 strcat函数剖析
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' 结束。 目标空间必须有足够的大,能容纳下源字符串的内容。 目标空间必须可修改。 字符串自己给自己追加,如何? 这个问题等到我们模拟实现完strcat函数后再进行解释
1.3.2 strcat模拟实现
char* my_strcat(char* dest, const char* src) { assert(dest != NULL); assert(src != NULL); char* ret = dest; while (*dest != '\0') { dest++; } while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[30] = "hello "; char arr2[] = "world"; my_strcat(arr1, arr2); printf("%s\n", arr1);//hello world return 0; }
方法:
1.先找到dest即目标字符串的末尾位置(即’\0’的位置)
2.将src即源头字符串中的数据拷贝到目标字符串dest中
注意:该函数的返回值为目标字符串的首元素地址,所以需要用ret来保存dest字符串的首元素地址