文章目录
- 1.字符串的定义
- 2.函数的使用
- 3.strlen使用与实现
- 4.strcpy使用与实现
- 5.strcat的使用与实现
- 6.strcmp的使用与实现
- 7.strstr的使用与实现
- 8.memcpy的使用和实现
- 9.memmove的使用和实现
1.字符串的定义
字符串是一系列字符组成的序列,C语言中字符串以\0
结尾。由""
引起的的字符串常量系统默认会添加\0
,区分而由''
引起的是字符常量。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{// 方式1:char* str1 = "";// 方式2:char arr1[] = "hello"; // 系统会默认添加一个\0,注意比较arr1和arr2char arr2[] = {'h','e','l','l','o'};char arr3[] = ""; // 默认一个\0// 方式3:char* str2 = (char*)malloc(128 * sizeof(char));strcpy(str2,"hello world");// 注意比较:strlen 和 sizeof的不同printf("use sizeof to calculate : char arr1[] = %ld\n",sizeof(arr1));printf("use strlen to calculate : char arr1[] = %ld\n",strlen(arr1));printf("use sizeof to calculate : char arr2[] = %ld\n",sizeof(arr2));printf("use sizeof to calculate : char arr3[] = %ld\n",sizeof(arr3));printf("%s\n",str2);return 0;
}
2.函数的使用
字符串函数传入参数的一个特点类似A of B
的格式,A of B
表示B
的A
。以strcpy
为例,传入的参数第一个是destination
,第二个是source
。是将source
拷贝到destination
中。
char * strcpy ( char * destination, const char * source )
这里和Linux命令是相反的,例如,cp
命令:前面的是原文件(source_file),后面的是目标文件(destination_directory)
cp [OPTION]... source... directory
3.strlen使用与实现
strlen
的使用很简单,只需要传入一个char*
的指针即可。
size_t strlen ( const char * str )
示例代码:
int main()
{const char* str1 = "hello world";char str2[] = "hello";// %u 是用于格式化输出无符号整数,%zu 格式化输出size_tprintf("%u %u\n",strlen(str1),strlen(str2));return 0;
}
其实strlen
的原理也比较简单,字符串是一个字符序列,strlen
的工作就是统计一个一个字符,直到遇到\0
。实现strlen
有三种方式:
方式1:可以使用一个count计数器来统计。一层循环直到遇到\0
结束。
size_t my_strlen1(const char* str)
{size_t count = 0;while(*str != '\0'){count++; // 计数器++str++; // 指针++}return count;
}
方式2:利用指针的加减特性,记录当前位置的地址,然后将指针指到\0
位置,减去起始位置。
size_t my_strlen2(const char* str)
{const char* start = str; // 记录起始位置// 求'\0'的位置while (*str != '\0'){str++;}return str - start; // 指针相减
}
方式3:使用函数递归,根据递归的三部曲:1.确定递归函数的参数和返回值 2.确定终止条件 3.确定单层递归的逻辑。这里可以简单的思考,让my_strlen3
是一个黑盒,给一个char*的指针就能帮我求出字符串的长度。比如求:求"abc"的长度就要,求"bc"长度 + 1,如果求"bc"就可以又交给my_strlen3
函数。
size_t my_strlen3(const char* str)
{if(*str == '\0') return 0;return 1 + my_strlen3(str + 1);
}
4.strcpy使用与实现
strcpy需要注意的点在于,确保destination的空间足够大,并且可以被修改。当然既然是指针就要保证不能使用空指针。
char * strcpy ( char * destination, const char * source )
函数的实现:
实现函数困惑的点,soure
以\0
标志结尾,destination
又要把\0
拷贝,怎么实现。
void my_strcpy(char* distination,const char* sourse)
{while(sourse != '\0'){*(distination++) = *(sourse++);}
}
如果像上面这样实现程序,并没有把结束标志的\0
拷贝到distination
,如果打印distination会出现Segmentation fault (core dumped)
printf("%s\n",distination);
让sourse赋值给distination然后再去判断\0。其实\0、0、NULL、false本质都是0,0值会判断为假,就退出循环。这里就很巧妙。
void my_strcpy(char* distination,const char* sourse)
{while(*(distination++) = *(sourse++)){; }
}
其实上面的版本就能实现字符串的拷贝,但如果传入一个NULL,就发生空指针解引用。不能指望程序员来严格遵守规则,需要在程序中避免错误。改进版本:
void my_strcpy(char* distination,const char* sourse)
{const char* ret = sourse; // 好习惯assert(distination != NULL); // 断言,false就进入函数就报错,错就是错,对就不报错assert(sourse != NULL);while(*(distination++) = *(ret++)){; }
}
5.strcat的使用与实现
strcat是一个字符串处理函数,当然要遵守C字符串的风格的特点,如:字符串的结尾是\0
。使用strcat和strcpy的使用特点基本一样,要求destination可以修改,并且要求空间足够大。当然如果详细的用法还是需要查看文档!
char * strcat ( char * destination, const char * source )
strcat函数的使用:
int main ()
{char arr[128] = "hello";strcat(arr," world");printf("%s\n",arr);return 0;
}
strcat函数的实现
char *my_strcat(char* destination,const char* source)
{// 记录destination其实位置,最后返回。char * ret = destination;assert(destination != NULL);assert(source != NULL);// 将destination移动到\0处while (*destination != '\0'){destination++;}// strcpy拷贝的逻辑while(*(destination++) = *(source++)){}return ret;
}
6.strcmp的使用与实现
strcmp用来比较字符串的大小,标准规定,标准是这样规定,但编译器尊不遵守那就不一定了!
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
int strcmp ( const char * str1, const char * str2 )
模拟实现:
int my_strcmp(const char* str1,const char* str2)
{assert(str1 != NULL);assert(str2 != NULL);while(* str1 != '\0' && *str2 != '\0'){if(*str1 > * str2) {return 1;}else if(*str1 < *str2) {return -1;}else {str1++;str2++;}}if(str1 == '\0' && str2 =='\0') return 0;else if(str1 == '\0' && str2 !='\0') return -1;else return 1;
}
上面一看就是我写的,而下面的版本更加的巧妙
int my_strcmp(const char *src, const char *dst)
{int ret = 0;assert(src != NULL);assert(dst != NULL);while (!(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)++src, ++dst;if (ret < 0)ret = -1;else if (ret > 0)ret = 1;return ret;
}
7.strstr的使用与实现
const char * strstr ( const char * str1, const char * str2 );char * strstr ( char * str1, const char * str2 );
简单使用:
int main()
{const char* str1 = "take it easy,the all things will be ok!";const char* str2 = "easy";if(strstr(str1,str2) != NULL){printf("存在\n");} else {printf("不存在\n");}return 0;
}
模拟实现:
char* my_strstr(const char* str1,const char* str2)
{char* ret = (char*)str1;if(*str2 == '\0') return ret;char *s1,*s2; while (* ret != '\0'){ // 给第二层循环使用s1 = ret;s2 = (char*)str2; while(*s1 != '\0' && *s2 != '\0' && *s1 == *s2){s1++;s2++;}// s2到结尾了,说明已经找到了if(*s2 == '\0'){return ret;} // ret表示返回匹配的第一个字符的地址ret++;}return NULL;
}
8.memcpy的使用和实现
memcpy函数是按照字节的方式来拷贝的,遇到 \0
的时候并不会停下来。如果source和destination有任何的重叠,复制的结果都是未定义的。
函数的使用:
struct person
{char name[128];int age;
}person;int main()
{const char* name = "i am lihua";memcpy(&person.name,name,strlen(name) + 1);int age = 0;memcpy(&person.age,&age,sizeof(int));printf("name = %s : age = %d\n",person.name,person.age);return 0;
}
模拟实现:
实现的思路也比较简单,source按照一个字节一个字节拷贝到destination,需要注意一点是要将void*
强转成char*
,char
就是一个字节。
void *my_memcpy(void* destination,const void* source,size_t num)
{void* ret = destination;while(num--){*(char*)destination = *(char*)source;destination++;source++;}return ret;
}
9.memmove的使用和实现
int main()
{char str[] = "memmove can allow memory cover";char *ret = (char *)my_memmove(str + 20, str + 15, 10);puts(ret);return 0;
}
模拟实现
void *my_memmove(void *destination, void *sourse, size_t num)
{void *ret = destination;if (destination <= sourse || destination >= sourse + num){while (num--){*(char *)destination = *(char *)sourse;destination++;sourse++;}}else{destination = destination + num - 1;sourse = sourse + num - 1;while (num--){*(char *)destination = *(char *)sourse;destination--;sourse--;}}return ret;
}