前言:
字符,字符串操作函数和内存操作函数一直是我学习过程中思路特别乱,总是记不住的一块知识点,所以在这里进行一个系统的复习,方便进行复习和整理
1.字符篇:
1.字符函数:
1.分类:
字符函数主要包括两个类型的函数:
1.字符分类函数 2.字符转换函数
2.字符分类函数:
字符分类函数是专门用来进行字符分类的函数,这类函数的头文件统一引用#include<ctype.h>
这类函数的参数和返回类型都是相同的,其具体如下:
以islower为例:
int islower(char c)
其参数为相应的字符或者相应的字符对应的ASCII值,其返回值为:如果为假,则返回0,若为真,则返回1.
字符分类函数的具体函数类型如下:
1.islower:用来判断参数是否为小写字母
2.isupper:用来判断是否为大写字母
3.iscntrl:用来判断任何控制字符
4.isspace:用来判断空白字符
5.isdigit:用来判断十进制数字
6.isxdigit:用来十六进制数字
7.isalpha:用来判断字母A-Z或者a-z
8.isalnum:用来判断字母或者数字
9.ispunct:用来判断标点符号
10.isgraph:用来判断任何图形字符
11.isprint:用来判断任何可打印字符,包括图形字符和空白字符
#include<ctype.h> int main() { int a = islower('C'); printf("%d", a); }
结果如下:
C不是小写字母,所以判断返回为空,即0.
3.字符转换函数:
字符转换函数主要是负责将字符转换成其他字符的函数,其头文件也是#include<ctype.h>
这类函数的参数和返回类型都是相同的,具体如下:
int tolower(int c)
其参数为对应的字符的ASCII值,我们直接传入字符即可,返回值为转换完的字符的ASCII值,故我们直接拿一个char类型的变量接收即可。
字符转换函数的具体函数如下:
1.tolower:大写转小写字母的函数
2.toupper:小写转大写字母的函数
注意:如果本身传的就是对应的大写小写字符,则直接返回这个字符且不会做任何处理,例如toupper(A),则返回A,不进行任何操作。
#include<ctype.h> int main() { char c = 'A'; c = tolower(c); printf("%c", c); }
结果如下:
将大写字母A转换为a,执行过的结果如图。
2.字符串函数:
字符串函数的操作的主要对象就是字符串,其中包括对字符串字符个数的统计,对字符进行操作等等,下面让我们详解一下,并模拟一些常见字符串函数的模拟实现:
首先注意,字符串函数的头文件包含一律用:#include<string.h>
1.strlen函数:
stren函数是用来计算字符串中字符个数的函数,其具体的函数形式如下:
size_t strlen(const char*str)
strlen函数的特点,自动计数直到遇到’\0’为止,然后返回\0之前的字符串的字符个数。
注意:strlen不会把\0自己计入到字符的个数里,注意别搞错个数。
2.strcpy函数:
strcpy函数是用来拷贝字符串的函数,具体的函数形式如下:
char* strcpy(char* destination,const char*source)
此函数的第一个参数是拷贝的目的地,第二个参数是拷贝的源字符串,这个函数最后会返回拷贝完后的字符串的指针。
strcop函数的特点:1.strcpy函数拷贝时,\0也会被拷贝过去,且当把\0拷贝过去后,整个拷贝也结束了
2.目标空间必须要足够大,以确保能存放原字符串,目标空间必须可变,不能是常量字符串
3.倘若源字符串为空,则直接返回第目标字符串的地址
模拟实现如下:
#include<string.h> char* my_strcpy(char* dest, const char* src)//第一个参数为目标空间,第二个参数为源字符串 { char*tmp=dest; if (src == NULL) { return dest; } while (*dest++ = *src++)//这里很巧妙,不仅仅可以在\0处停止,同时还可以把\0也拷贝到目标字符串中,一举两得的一步 { ; } return tmp; } int main() { char arr1[] = "ABCDEF"; char arr2[] = "HE"; printf("%s", my_strcpy(arr1, arr2)); }
结果如下:
即将HE拷贝到目标字符串中,且拷贝\0,所以我打印第一个字符串的时候遇到\0就自动停止了
3.strcat函数:
strcat是用来追加在字符串后面追加字符串的函数,具体的函数形式如下:
charstract(chardestination,char*source)
此函数的参数和strcpy基本类似,也是传入一个目的字符串和一个源字符串,返回的类型也是追加后的目的字符串的首元素的地址。
strcat的函数特点:
1.strcat会从目的地字符串的\0开始,向后继续将想要追加的字符串追加上来,且会覆盖这个\0,字符串的追加也会在遇到\0后停止。
2.目标空间必须要足够大,方便源字符串追加,否则会越界访问。
3.目标空间必须是可修改的。
模拟实现的过程如下:
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; }
与strcpy函数模拟实现不同的是,它首先需要找到目的字符串的\0,然后从这个\0开始覆盖,直到遇到源字符串的\0为止。
注意!!!!!!:strcat函数是不能追加自己本身的,因为由于覆盖,追加自己的\0被覆盖了,导致循环不能停止,所以strcat不能追加自己,但是strncat限制长度的就可以追加。
4.strcmp函数:
strcmp适用于进行字符串的比较,比较的基准是字符串对应的ASCII值得先后,其基本的函数具体形式如下:
int strcmp(const charsrc,const chardest)
同前面的strcpy和strstr一样,strcmp传参也是传入两个字符串的地址,然后依次比较这两个字符串相应位置的字符的大小,一旦比较出来直接返回结果,不进行后续的比较了,倘若一直没结果就直到得到dest的\0为止。
返回值:1.当字符串1的字符小于字符串2的字符时,返回值<0
2. 当字符串1的字符等于字符串2的字符时,返回值=0
3.当字符串1的字符大于字符串2的字符时,返回值>0
模拟实现的过程如下:
int my_strcmp(const char* str1, const char* str2) { assert(str1); assert(str2); while (*str1 == *str2)//这里的逻辑很妙,循环用两个相等,这样就可以处理相等但长度不相等的情况 { if (*str1 == '\0') { return 0; } str1++; str2++;//注意,这里str1和str2是同时变化的,所以我判断一个为\0的时候,由于相等,则另一个也为\0了,故相等。 } return *str1 - *str2; }
注意思考这段的代码的逻辑,假如两者一直相等直到\0时,则str1到\0的时候,另一个也到\0了,此时两者是相等的,但假若一个到\0一个,没到,则就会按不相等跳出循环,然后直接比较两个字符串ASCII值,这便是strcmp的比较逻辑。
5.strncpy,strncat,strncmp函数:
这三个函数可以理解为限制长度的strcpy,strcat,strcmp函数,用法相同只是限制长度,在这里就不过多赘述了。
特别注意的是:此时的strnact是可以自己追加自己的,但要控制好长度,让其正好在\0之前一位停止。
6.strstr函数:
strstr函数是用来寻找目的字符串内部是否有子字符串的,其函数的具体格式如下:
charstrstr(const charstr1,const char*str2)
第一个参数为目的字符串,第二个参数为源字符串,这里的目的就是判断目的字符串内部是否有源字符串。
倘若寻找到,则返回目的字符串中的源字符串的第一个元素的地址,反之,如果找不到,就返回NULL。
strstr函数的一些细节:若strstr中的目的字符串str2中只有\0,则返回str1即目的字符串的地址。
模拟实现的代码如下:
char* my_strstr(const char*str1,const char*str2) { if(str2== NULL) { return str1; } char*s1=NULL; char*s2=NULL; char*cp=str1; while(*cp) { s1=cp;//倘如循环结束,cp++,然后重置s1的位置重新进行按次序比较的过程,这样就能让s1实时记录cp之后的位置 s2=str2; while(*s1==*s2) { s1++; s2++; if(*s2=='\0') { return cp; } } cp++; } return NULL; }
注意,这个函数模拟实现的逻辑,首先定义s1和s2,s2作为源字符串的起始地址,s1作为目的字符串的起始地址,控制cp的进度向后移动,倘如cp到了\0证明没有子字符串,直接返回NULL,每次对s1都开始刷新为cp,s2刷新为str2的值,一旦进入相等的情况,s1和s2就向后移动去寻找,倘若s2到达\0证明有子字符串,反之就直接跳出循环,进行下一次循环即可。
KMP算法后续我会学完之后解析一下!!!!!!
7.strtok函数:
strtok是一个非常怪异的函数,其目的主要是用来切割字符串,它的具体函数参数如下:
charstrtok(charstr,const char*sep)
函数解析:
1.第一个参数指定一个字符串,它包含0个或者多个由sep字符串中的一个或多个分隔符分割的标记。
2.第二个参数指定另一个字符串,它包含了用作分隔符的各种符号的字符串集合
3.strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝内容并且可以修改)
3.strtok函数的第一个参数不能为NULL,函数将找到str中的第一个标记,并且会保存它的位置(存\0的地址)
4.strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
5.如果不存在更多的标记,则返回NULL指针。
下面是一个strtok函数的应用实例:
char* r = NULL; for (r = strtok(buf, p); r != NULL; r = strtok(NULL, p)) { printf("%s\n", r); }
由于其strtok会自动保存上一次分隔符的地址,所以我们除了第一次传字符串的地址外,后续传只要传NULL即可,然后让strtok从上一次的位置开始找下一个分隔符。
8.sterror和perror函数:
这两个函数都是可以用来判断错误的函数,sterror函数引用头文件为:#include<stdio.h>,当我们在调用某些库函数时一旦失败就会设置错误码,这些错误码errno是全局的,引用头文件#include<errno.h>,这些错误码通过sterror函数就可以由数字变成相应的字符串。
sterror的函数具体格式:
char* sterror(int errno)
当我们使用perror则比sterror更加智能,它可以直接输出相应的错误,只需要传入相应的文件名即可。
perror的函数格式:
void perror("文件名“)
sterror和perror的关系就是:
perror==printf(”文件名:%s\n",sterror(errno));
在我们动态开辟和操作文件的时候,经常要使用perror来进行测试和对报错进行判断。
2.内存篇:
内存操作之所以和字符串操作放在一起,主要是因为两者有很多共通之处,所以接下来让我们再看看内存操作函数:
!!!!!必须要强调,在内存操作函数里,size_t类型规定的都是字节数,即内存操作的基本单位而不是元素个数,这个别搞错,千万别搞错!!!!
1.memcpy函数:
如同strcpy函数一样,不过不同的是,这一次操作的是内存而非字符串。且memmcpy是不能用拷贝重叠的内存的,那个需要用memmove函数
基本格式:
void* memcpy(voiddestination,const void source,int num)
参数的分析如下:memcpy是可以拷贝任何类型的数据的,所以我们用void*,void来规定其类型,memcpy拷贝了num个字节的数据就会停止,即将source的数据拷贝到destination中,对于void*,void类型的数据,我们完全可以将其强制转换转为我们想要的数据类型。拷贝数据我们虽然不知道数据类型是什么,但只要利用char类型数据一个一个拷贝即可,正好对应一个字节。
函数实现如下:
void * memcpy ( void * dst, const void * src, size_t count) { void * ret = dst; assert(dst); assert(src); while (count) { *(char *)dst = *(char *)src; dst = (char *)dst + 1;//注意,一定要先将其转化为char*类型后,在对其相加减 src = (char *)src + 1; count--; } return ret; }
2.memcmp函数:
memcmp是用来进行内存比较的,整体的思路与strcmp大体相同,但不同的地方在于,它是一个一个字节比较的而非一个字符一个字符去比较的,
格式如下:
int memcmp(const voidptr1,const voidptr2,size_t num)
模拟实现的函数如下:
int memcmp(const void*ptr1,const void*ptr2,size_t num) { assert(ptr1); assert(ptr2); while(*((char*)ptr1)==*((char*)ptr2),num) { (char*)ptr1)++; (char*)ptr2)++; num--; } return *((char*)ptr1)-*((char*)ptr2); }
3.memmove函数:
memmove函数是用来对内存数据自身进行覆盖的函数,其格式如下:
void* memmove(voidddestination,const voidsource,size_t num)
!!!!拷贝的思路就是!!!!:
if(dest<src):
数据从前向后拷贝
else
数据从后向前拷贝
具体实现的代码如下:
void* my_memmove(void* dest, void* src, int num) { void* ret = dest; assert(src); assert(dest); if (dest < src)//当dest<src时,从前向后操作 { while(num--) { *(char*)dest = *(char*)src; dest = (char*)dest + 1;//注意,先转化其类型再进行整数加减操作 src = (char*)src + 1; } } else//反之,当dest>=src时,从后向前操作 { while(num--) { *((char*)dest + num) = *((char*)src + num);//从后向前操作,利用num调整即可 } } return ret; }
4.memset函数:
memset函数是用来将空间指定范围的位置改成相应的符号的函数,且这里指定的字节数而不是元素的个数。
函数基本格式:
void* memset(void*ptr,int value,size_t num)
格式解析:注意,这里的value绝对不仅仅是数字,它也可以是相应的字符,只是这里直接转化为ASCII值形式展现出来的参数,实际上我们传字符也可以,传数字也可以,然后就会把ptr内部的对应num个字节数的内容改成我们相应的value。
3.总结:
内存操作和字符串操作函数的模拟实现对于代码能力有很强的提升,同时也希望通过这几个函数能够进一步提高我们学习函数时的思路,即看参数,看返回值,看实现功能三个方面,后续我也会更新KMP算法的内容,希望经过这篇文章,我们对于内存和字符串,字符的操作能力能不断增强!!!!!