前言: 学习C语言的过程中模拟实现各种库函数可以让我们了解更加底层的原理,在这里helloworld社区为您整理了部分函数的模拟实现和注意事项
一、strlen
1.函数介绍
strlen的原型为
#include<string.h> . int strlen(char *str);
.
其作用和原理是
传入要统计字符串的地址 统计该字符串'\0'(字符串结束标志)前的字符个数
2.模拟实现
① 指针计数器方式
int my_strlen(const char *str) { assert(str); //判定不为空指针再运行 int count = 0; //count作为计数器 while(*str != '\0') //若str解引用不为字符串中的'\0' 则停止 { count++; str++; } return count; }
② 函数递归方式
int my_strlen(const char *str) { assert(str); if(*str != '\0') { return (1 + my_strlen(str + 1) ); //注意 这里由一种易错的写法,如下" //return (1 + my_strlen(++str) ); //此时会改变递归进行中str的值,导致计算中途出现错误 } else { return 0; } }
③ 指针减指针方法
我们要知道
同类型的指针减指针得到的结果为两个指针间的元素个数
所以我们可以通过指针减指针的方法来模拟实现strlen
具体实现方法如下
int my_strlen(const char * str) { assert(str); char * mem = str; //通过 mem 记录 str 的起始位置 while( *str != '\0') { str++; } return str - mem; //指针减指针得到中间元素的个数 }
二、strcpy
1.函数介绍
strcpy的原型为
#include<string.h> . char *strcpy(char* dest, const char *src);
. strcpy的作用和原理是
先后传入空间的目标空间的起始地址和源空间的起始地址 将源空间的内容拷贝至目标空间
该函数的脾气
如果目标空间不够,会越界访问,导致报错 所以目标空间必须足够大,确保能放进去; 源空间的'\0'也会被拷贝过去; 目标空间必须可以改变(const,指针指向字符串常量字符串就不行)
2.模拟实现
具体实现方法如下:
void my_strcpy(char *destination,const char * source) { assert( destination && source); //断言一下两者都不为空指针 while( *source != '\0') { *destination = *source; destination++; source++; } *destination = *source; }
略微观察一下这个代码,我们可以写出更简洁的版本,如下
void my_strcpy(char *destination,const char * source) { assert( destination && source); while( *destination++ = *source) //当碰到'\0'(即为0,判定为假) { ; } }
我一开始有疑问,当source字符串里面有0这个函数功能是不是就失效了呢?
原来,判断的根据是ASCII码值为0 ,我所想的'0' ASCII值为48 而ASCII值为0的就是 '\0' (空字符)
以上
三、qsort
1.函数介绍
qsort函数的原型
#include<stdlib.h> void qsort( void*base, //要被排序的第一个元素的地址 size_t num, //排序数据元素的个数 size_t size, //排序数据中一个元素的大小 int(*compar)(const void* e1, const void* e2) //用来比较待排序数据中的两个元素 的函数 );
我们以整形排序实例来看一下qsort的使用
int Cmp_int(const void*e1,const void*e2) //比较两个元素大小的函数 { return *(int*)e1 - *(int*)e2; } int main() { int arr[] = {9,8,7,6,5,4,3,2,1}; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr,sz,sizeof(int),Cmp_int); //打印出的结果为 // 1,2,3,4,5,6,7,8,9 }
通过qsort排序结构体
struct stu { char name[20]; int age; }; int sortby_age(const void*e1,const void*e2) { return (struct stu *)e1 -> age - (struct stu *)e2 -> age; } int sortby_name(const void*e1,const void*e2) { return strcmp((struct stu *)e1 -> name, (struct stu *)e2 -> name); } //若想改变排序的升序或降序,改变一下 e1 和 e2 的位置即可 int main() { struct stu s[3] = {{"zhang",30},{"lisi",34},{"wang",20}}; int sz = sizeof(s) / sizeof(s[0]); //年龄排序 qsort(s,sz,sizeof(s[0]),sortby_age); //结果为 20,30,34 方式排列出结构体 //名字排序 qsort(s,sz,sizeof(s[0],sortby_name); //结果为 lisi,wang,zhang 方式排列出结构体
.
2.模拟实现
void swap(char* buf1, char * buf2 , int width) { int i = 0; for(i = 0 ; i < width ; i++) { char tem = *buf1; *buf1 = *buf2; *buf2 = tem; buf1++; buf2++; } } void my_qsort( void*base, size_t sz, size_t width, int(*cmp)(const void* e1, const void* e2) ) { int i = 0; //趟数 for(i = 0; i < sz-1 ; i++) { //每一趟的排序 int j = 0; for(j = 0 ; j < sz - 1 - i ; j++) { //开始比较两个元素 if( cmp( (char*)base + j*width , (char*)base + (j+1)*width)>0) //一个字节一个字节进行比较和交换 { //交换 swap( (char*)base + j*width , (char*)base + (j+1)*width) } } } }
四、strcat
1.函数介绍
strcat原型
#include<string.h> . char *strcat(char *des, const char *src);
strcat的作用
将源空间中的内容追加到目标空间后 目标空间中的 '\0' 被替换为源空间中第一个元素
注意:strcat不能自己追加自己,会使'\0'被覆盖,导致无法终止
2.模拟实现
char* my_strcat(char * dest, const char * src) { assert(dest && src); char * ret = dest; //寻找目标空间中的'\0' while(*dest) { dest++; } //开始追加 while( *dest++ = *src++); return ret; }
五、strcmp
1.函数介绍
函数原型为
#include<string.h> . int strcmp(const char* str1, const char* str1);
函数的作用
我们知道,两个字符串无法直接用大于小于号来进行比较 因此就存在了strcmp这个函数 . 对字符串逐一比较字符ASCII大小 一旦有大于或小于便停止,返回结果,若都相等则返回0 若前面都相等,则较长的win
2.模拟实现
int my_strcmp(const char* s1,const char* s2) { assert(s1 && s2); while(*s1 == *s2) { if(*s1 == '\0') { return 0; } s1++; s2++; } return *s1 - *s2; }
六、strncpy
1.函数介绍
该函数原型为
#include<string.h> . char* strncpy(char* dest, const char* src, size_t num);
该函数作用和原理为
选择一定数量的字符拷贝至目标空间 . 若传递数量超过源空间含有的数量,并且空间足够 那么就会先将源空间内所有字符传进去后,按照规定数量补'\0'
2.模拟实现
char* my_strncpy(char * dest,const char * src,int num) { char* ret = dest; while(num && (*dest++ = *src++) != '\0') { num--; } if(num) { while(--num) { *dest++ = '\0'; } } return ret; }
七、strncat
1.函数介绍
函数的原型为
#include<string.h> . char *strncat(char *dest, const char *src, size_t num);
函数作用为
选择固定数量的字符从源空间中追加到目标空间内
2.模拟实现
char* strncat(char *dest, const char *src, size_t num) { char *ret = dest; while(*dest++) { dest--; } while(num--) { if((*dest++ == *src++)==0) //发现'\0'直接结束,不再继续进行循环 { return ret; } } *dest='\0'; //提前结束 return ret; }
八、strncmp
1.函数介绍
strncmp的原型为
#include<string.h> . int strncmp(const char* str1, const char* str2, size_t num);
strncmp的作用为
固定一个数量 使两个字符串的前几个数量的字符进行比较
2.模拟实现
int My_strncmp(const char* str1, const char* str2, size_t n) { if (n==0) { return 0; } while (n-- && *str1 != '\0' && *str1 == *str2) { str1++; str2++; } return *str1 - *str2; }
九、strstr
1.函数介绍
strstr函数原型为
#include<string,h> . char*strstr(const char* str1,const char* str2);
该函数作用为
在str1中寻找str2 返回str1中出现str2首元素的地址 若找不到则返回空指针
2.模拟实现
char * my_strstr ( const char * str1, const char * str2) { assert(str1 && str2); const char * s1 = NULL; const char * s2 = NULL; const char * cp = str1; if(*str2 == '\0') { return (char*)str1; while(*cp) { s1 = cp; s2 = str2; while(*s1 && *s2 &&(*s1 == *s2) ) { s1++; s2++; } if(*S2 == '\0') { return (char*)cp; } cp++; } return NULL; }
十、strtok
1.函数介绍
该函数原型为
#include<string.h> . char* strtok(char * str, const char * sep);
该函数的介绍
在str中 以sep中的字符分割str sep中可以有一个或多个分隔符标记 . strtok函数找到str中的下一个标记,并将其用'\0'结尾,返回一个指向这个标记的指针 所以该函数会改变被操作的字符串,所以在使用时一般面向对象都是临时拷贝的内容 . strtok函数第一个参数不为空指针,函数将找到str中第一个标记,strtok会保存它在字符串中的位置 strtok函数第一个参数为空指针,函数将在同一字符串中被保存的位置开始,查找下一个标记 如果字符串中不存在标记则会返回空指针
我们以一个实例来加深理解
char arr[30] = "9TSe@qq.com"; char *p = "@."; char tmp[30] = {0}; //创建临时拷贝份 strcpy(tmp,arr); //9TSe@qq.com\0 strtok(tmp,p); //返回9TSe 9TSe\0qq.com\0 strtok(NULL,p);//返回qq 9TSe\0qq\0com\0 strtok(NULL,p);//返回com 9TSe\0qq\0com\0
那么实际上使用该函数的用法为
char arr[30] = "9TSe@qq.com"; char *p = "@."; char tmp[30] = {0}; strcpy(tmp,arr); char * ret = NULL; for(ret = strtok(tmp,p); ret != NULL ; ret = strtok(NULL,p) ) { printf("%s\n",ret); }
该函数由于使用情况不多,暂且不做模拟实现
十一、strerror
1.函数介绍
该函数原型为
#include<string.h> #include<errno.h> . char * strerror(int errnum);
该函数的作用为
返回错误码所对应的错误信息
使用情况一般如下
printf("%s",strerror(errno)); //errno是库函数里本有的全局变量,加入头文件就不用再创建
模拟暂不实现
十二、perror
1.函数介绍
该函数原型为
#include<stdio.h> . void perror(const char * str);
该函数的作用为
perror可以直接转换错误信息并且打印 比如 perror("fopen") ; fopen: No such file or directory
暂不模拟实现
十三、字符分类函数和字符转换函数
1.函数介绍
他们公用原型
#include<ctype.h> . int xxx(int c);
函数功能为
为真返回非0 为假返回0
函数 | 如果符合以下条件返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格' ' ,换页'\f',换行'\n',回车'\r',制表符'\t',垂直制表符'\v' |
isdigit | 十进制数字 0 ~ 9 |
isxdigit | 十六进制数字,包括所有十进制数字和大小写字母 a |
islower | 小写字母 a ~ z |
isuper | 大写字母 A ~ Z |
isalpha | 字母 a ~ z 和 A ~ Z |
isalnum | 字母或数字 a ~ z , A ~ Z , 0 ~ 9 |
ispunct | 标点符号,任何不属于数字或字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
字符转换函数 | 功能 |
---|---|
tolower | 将字符大写转换为小写 |
toupper | 将字符小写转换为大写 |
十四、memcpy
1.函数介绍
该函数原型为
#include<string.h> . void * memcpy(void * dest , const void * src , size_t num);
该函数的功能
memcpy为内存拷贝,将src中的内容拷贝至dest num中存放的为拷贝的字节大小
2.模拟实现
void * my_memcpy(void * dest , const void * src , size_t num) { assert(dest && src); void * ret = dest; while(num--) { *(char*)dest = *(char*)src; //一个字节一个字节拷贝,所以强制转换为char*再解引用 dest = (char*)dest + 1; src = (char*)src + 1; //那能不能写如下的更加简便呢 //*(char*)dest++ = *(char*)src++; //很遗憾并不可以 ++在生效时dest和src还是void*类型,不能++; } return ret; }
注意:memcpy不能拷贝重叠的内存
int arr[10] = {1,2,3,4,5,6,7,8,9,10}; memcpy(arr+2,arr,20); //结果并不为 {1,2,1,2,3,4,5,8,9,10} //而是为 {1,2,1,2,1,2,1,8,9,10}
但是memmove却可以做到
十五、memmove
1.函数介绍
该函数原型为
#include<string.h> . void * memmove( void * dest, const void * src , size_t num);
2.模拟实现
void * memmove( void * dest, const void * src , size_t num) { assert(dest && src); void * ret = dest; if(dest<src) { //前->后 while(num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; } } else { //后->前 while(num--) { *((char*)dest+num) = *((char*)src+num); } } return ret; }
十六、memcmp
1.函数介绍
该函数原型为
#include<string.h> . int memcmp(const void * str1 , const void * str2 , size_t num);
该函数作用
对比str1和str2中前 num 字节的内容
该函数较strcmp较相似,不再模拟实现
十七、memset
1.函数介绍
该函数原型为
#include<string.h> . void * memset(void * ptr , int value , size_t num);
该函数作用为
设置ptr内前num字节 全部初始化为 value
该函数不进行模拟实现