字符串的函数学习,我们大致要明白,学习一个新的函数时,我们首先要清楚它的功能、传参、返回类型、实际场景,了解前面这些后,我们想更深一步了解一个函数,我们可以试着去自己实现它,因此接下来介绍的一些关于字符串类型的函数,部分我会将实现过程和思路,以及我自己实现时候遇到的难点和易错点分享给大家,供大家参考和学习,如有错误也欢迎指出。
定义:size_t strlen (const char* str)
ps:size_t 就是 unsigned int
功能:计算字符串的大小
原理:一个个数字符,直到遇到‘\0’才会返回
易错点:
a.由于这个函数遇到‘\0’,才返回值,因此如果给过去的字符串没有\0,则返回随机值
b.函数传参时注意传过去的是字符串的地址
c.由于返回类型是无符号整形,strlen(arr1)- strlen(arr2)的值不是负数
关于my_strlen的两种实现方法:
非递归:
int my_strlen(const char* p)
{
int count=0;//计数
assert(p);
while(*p++)//先解引用判断,然后在++
count++;
return count;
}
递归(不创建临时变量实现):
int my_strlen(const char* p)
{
assert(p);
if(*p)
return 1+my_strlen(p+1);
else
return 0;
}
定义:char* strcpy ( char*str1 , const char* str2 )
功能:将str2里的字符串拷贝到str1的字符串中
原理:在str2中逐一取一个字符往str1里放,直到在str2中遇到‘\0’为止,‘\0’也会被放到arr1,返回str1的初始地址
易错点:
a. str2要存在‘\0’
b. str1在定义时,空间要足够大,要比str2大,才能容下str2.
c.str1可修改,str2一般不可修改
d.返回地址是返回一开始str1的值,由于str1会被修改,因此一开始用一个临时变量先存放。
模拟实现 my_strcpy
char* my_strcpy(char* str1,const char* str2)
{
char* ren=str1;
assert(str1 && str2);
while(*str1++=*str2++);//先赋值,再各自加一,当str2为\0时,完成\0的赋值后,赋值表达式返回0,不再执行循环
return ren;
}
定义:char* strncpy ( char*str1 , const char* str2 ,size_t num )
功能:同样是字符串拷贝,相比strcpy,它能够指定在str2中拷贝多少个元素放到str1中,但不会自动补‘\0’,当n大于str2的字符串长度时,多出来的赋值为‘\0’
易错点:
a.在你指定一个str2想拷贝到str1时,要知道strncpy是不会自动在末尾补'\0'的
b.在你n超过str2的时候,拷贝到str1的操作继续执行,拷贝‘\0’,直到执行完n次
模拟实现—my_strncpy
char* my_strncpy(char* str1,const char* str2,size_t n)
{
char* ren=str1;
assert(str1 && str2);
while((*str1++=*str2++) && n--);//当str2或者n为0时,不再执行赋值操作
while(n--)//若n还不为0,对str1继续补\0
*str1++='\0';
return ren;
}
定义:char* strcat ( char* str1 , const char* str2 )
功能:在str1后面追加一个字符串str2,追加结束后自动补‘\0’
原理:找str1中‘\0’的位置,将str2的字符从str1中‘\0’的位置开始一个个放,直到str2遇到\0停下
易错点:
a.str1和str2不能是同一个字符串地址,也就是不能自己追加自己
b.str1要足够大,且要有\0存在
模拟实现—strcat
char* my_strcat(char* str1,const char* str2)
{
char* ret=str1;
assert(str1&&str2);
while(*str1)
str1++;//先让str1指在\0位置
while(*str1++ = *str2++);//追加
return ret;
}
定义:char* strncat ( char* str1 , const char* str2 ,size_t n )
功能:可以指定追加的个数,追加结束后自动补‘\0’,当n超过str2时,不做其他的操作。
易错点:
a.追加结束后会自动补\0
b.n超过str2时,不做多余的操作,只会补一次\0
模拟实现—strncat
char* my_strncat(char* str1,const char* str2,size_t n)
{
char* ret=str1;
assert(str1&&str2);
while(*str1)
str1++;//先让str1指在\0位置
while(n&&(*str2))//当执行n次后或者str2已经指向\0时不再循环
{
*str1++=*str2++;
n--;
}
if(n==0 || (*str2 =='\0'))//补\0
*str1='\0';
return ret;
}
定义:int strcmp ( const char* str1,const char* str2 )
功能:实现字符串str1和str2的比较
比较规则:
两个字符串都从第一个开始,逐个逐个比较,若第一个相同则同时找到下一个字符比较,直到两个字符串都遇到‘\0’,也就是比完的情况,都相同则认为是两个字符串相等;
当某次比较出现不同时,比较结束,大小按字符的acsll码值算;
当其中一个字符串前面都相同,但其中一个较短,例如“abc\0”和“abcdf\0”,则在比到\0时也会视为前者小于后者,因此是较短的字符小
当str1 > str2时,返回一个大于0的数
当str1 = str2时,返回0
当str1 < str2时,返回一个小于0的数
根据上面的比较规则,我们来试着自己设计这个函数
模拟实现—my_strcmp
int my_strcmp(const char* str1,const char* str2)
{
assert(str1 && str2);
while(*str1 == *str2)
{
if(*str1 == '\0')
return 0;//顺利比完,str1和str2都指向‘\0’
str1++;
str2++;
}
//比较出现不同
return *str1-*str2;
}
定义:int strncmp ( const char* str1,const char* str2, size_t n )
功能:指定两个字符串的前n位进行比较,比较规则和strcmp一样
模拟实现—my_strncmp
int my_strncmp(const char* str1,const char* str2,size_t n)
{
assert(str1 && str2);
while(n&&*str1 == *str2)//当比完n次或者提前比较出结果了
{
n--;//n--一定要放在判断前面
//当n次比完后,还能进入循坏,说明前面的都相等,因此先n--
if(*str1=='\0'||n==0)//若n为0则说明比完n次,且都相同
return 0;
str1++;
str2++;
}
return *str1 - *str2;
}
定义:char* strstr ( const char* str1,const char* str2 )
功能:在str1里找是否存在子串和str2相同,如果找到了,则返回第一次在str1里找到str2的起始位置的地址,如果没找到返回空指针。
模拟实现—my_strstr
设计思路:
//实现在str1中找到str2的首字符
//当找到以后开始判断是否为子串
//当不是子串时,指针位置返回:str1返回到刚刚的下一个,str2返回到首字符
//继续执行查找,若是找到子串,则str2会指向\0,若始终没找到,str1会指向\0
char* my_strstr(const char* str1,const char* str2)
{
const char* str_1=str1;//作为记住str1开始判断时的地址的一个指针
const char* str_2=str2;//str_2作为不变量,始终指向str2的首元素
assert(str1 && str2);
if(*str2 == 0)
return (char*)str1;//若str1或str2是‘\0’则直接返回str1首元素地址
while(*str1)//str1指向\0时意为着找完了也没有
{
while(*str1!=*str2)//在str1中找到str2的首字符
{
if(*str1 == 0)
return NULL;//若一开始就不存在和str2匹配的字符,直接返回NULL
str1++;
}
str_1=str1;//记住起始位置
while(*str1==*str2)
{
str1++;
str2++;
if(*str2 == 0)//找到了返回str1中开始判断的起始位置
return (char*)str_1;
}
//没找到时,str1重新指向开始判断时的下一个字符,str2指向首字符
str1=str_1+1;
str2=str_2;
}
return NULL;
}
定义:char* strtok ( char* str1, const char* str2 )
功能:用来切分字符串,str1是要被切割的字符串,str2是告诉函数要切哪些地方(从哪下刀的问题)
使用方法:这个函数能够记住上一次切完的位置,只需要第一次给数组首地址str1,每次执行切一段即返回,会改变str1里面的数据,因此通常将拷贝后的数据放到函数里切。
例子:123.452*[email protected]
我如果想把里面的数字和字母单独拿出来,我调用strtok去切,第一次str1给这个字符串的首地址,str2给“.*@”(要以什么为分割),第一次调用结果返回会得到:
123\0452*[email protected]
因此只会打印123,此时它会记录指针(不销毁的参数)指向4的位置,若再次调用,可切出452,并把*改成\0。
巧用for循环去使用这个函数会方便的多
int main()
{
char str1[100]="123.456&1325@com";//被分割的字符串
const char *p=".@&";//分割点
char *len;//接受返回值
//for的第一个语句只执行一次,并且完成切割并把结果给len
//如果len不为‘\0’,说明切割下了一段数据,将其可以用printf输出
//输出后再到第三个条件语句,此时用空指针作为str1的参数,再次进行切割操作,切割完成后赋值给len
//随后再次进行判断,len若为‘\0’,说明刚刚字符串已经切完了,跳出循环,若不是,则继续执行
for(len=strtok(str1,p);len;len=strtok(NULL,p))
printf("%s\n",len);
system("pause");
return 0;
}
执行结果:
本函数要求会用即可,暂时不进行模拟实现
定义:char* strerror(int errnum)
功能:在一些程序执行过程中,可能出现一些错误是我们无法知道的,但代码又能顺利跑,可实际又达不到我们要的效果,于是,要想知道具体哪一部分出了问题,我们通过系统返回的错误码,使用这个函数去转译成错误信息,方便找到bug
ps:要使用错误码得引用头文件#include
暂时来说这个函数还用不上,目前理解就行
功能:字面意思,tolower就是将大写字母转换成小写字母,tolower就是将小写变大写,如果你是小写的放到tolower里去,结果不变。
没啥好说的这个,要求遇到这两个函数能知道干嘛的就ok,由于功能过于简单,自己用的时候忘了也随便能写一个,但不要遇到了却不知道干嘛的就行。
函数 如果他的参数符合下列条件就返回真
iscntrl 任何控制字符
isspace 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit 十进制数字 0~9
isxdigit 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower 小写字母a~z
isupper 大写字母A~Z
isalpha 字母a~z或A~Z
isalnum 字母或者数字,a~z,A~Z,0~9
ispunct 标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 任何图形字符
isprint 任何可打印字符,包括图形字符和空白字符ps:遇到时认得出来就ok
定义:void* memcpy(void* arr1,const void* arr2,size_t sz)
功能:基本功能和strcpy一样是拷贝数据的函数,但功能上扩大了,这个函数能拷贝不同类型的数据,因此参数和返回类型也是void*这个万金油,sz的单位是字节,在使用时要额外注意。
易错点:当两个数组地址出现重叠时,不能进行交叉拷贝
模拟实现—my_memcpy
void* my_memcpy(void* arr1,const void* arr2,size_t sz)
{
void* ren=arr1;
assert(arr1&&arr2);
//进入循环sz为字节数,循环执行多少次,arr1被赋值多少次
while(sz--)
{
*(char*)arr1 = *(char*)arr2;
arr1=(char*)arr1+1;
arr2=(char*)arr2+1;
}
return ren;
}
定义:void* memmove(void* arr1,const void* arr2,size_t sz)
功能:也是拷贝数据,但它能实现交叉拷贝,当arr1和arr2有交叉的时候也能拷贝。
原理:通过画图可以知道,之所以不能交叉拷贝是因为拷贝可能会篡改后面还未执行拷贝的数据,因此要分情况讨论从前向后拷贝,还是从后向前拷贝:
arr1>arr2时 arr2的指针位于arr1前面时 从后向前拷贝
arr1=arr2时 arr2的指针位于arr1同一位置 随便拷
arr1
模拟实现—my_memmove
void* my_memmove(void* arr1,const void* arr2,size_t sz)
{
char* ren=(char*)arr1;
assert(arr1 && arr2);
//从后向前拷贝
if(arr1>arr2)
while(sz--)//此处sz先减1,刚好指向最后一个元素的位置
*((char*)arr1+sz)=*((char*)arr2+sz);
//从后向前
else
while(sz--)
{
*(char*)arr1=*(char*)arr2;
arr1=(char*)arr1+1;
arr2=(char*)arr2+1;
}
return ren;
}
定义:int memcmp(const void* arr1,const void* arr2,size_t sz)
功能:和strncmp的比较规则是一样的,这里所有的数据类型都可以放进去比,一个字节一个字节的比较,比较内存中的ascll码值的大小,需要注意的是,第三个参数是字节为单位的。
模拟实现—my_memcmp
int my_memcmp(const void* arr1,const void* arr2,size_t sz)
{
assert(arr1 && arr2);
while(sz--&&(*(char*)arr1==*(char*)arr2))//要么比较sz次后跳出循环,要么遇到不同提前跳出
{
arr1=(char*)arr1+1;
arr2=(char*)arr2+1;
}
//如果循环被执行了sz次,则此时*arr1和*arr2相同,相减返回0
//如果提前跳出,相减可估计返回值比大小
return *(char*)arr1 - *(char*)arr2;
}
定义:void* memset(void* arr,int c,size_t sz)
功能:在arr中的内存里每次取一个字节的c放到arr内存中,按顺序一直放,放sz次
易错点:
a.每次放c中的一个字节的内容
b.第三个参数单位是字节
本章学习了字符串有关的一些函数,并通过理解原理和尝试模拟实现函数去加深对函数的理解和记忆,希望这篇文章看了对你能有所帮助,有哪里不足和错误也欢迎指出。