目录
1.6.strncmp()、strncpy()和strncat()
1.字符串函数
此类函数的头文件为 <string.h>
1.1.字符串
在讲字符串函数前,我们先了解一下字符串,字符串是一串字符的集合,其特点是:以'\0'结尾;
这也是区分字符与字符串的关键。
字符串函数,字符串函数,意思就是对字符串进行操作的函数,为了安全起见,那么就要求参数得和字符串相关,那就得有字符串的标志:'\0'。
1.2.strlen()
1.2.1.函数介绍
unsigned int strlen(const char* str)
计算字符串有效长度的函数,只有一个参数,参数的类型是char*,指代要计算字符串的首元素地址,返回值类型是unsigned int,计算过程是从参数所指地址开始一个一个字节地向后访问,当被访问空间为 '\0' 时,就停止访问,返回 '\0' 前的字符个数。
1.2.2使用时要注意的一些点
当两个strlen()相减为负数的情况:前面我们提到了strlen()返回值的类型为 unsigned int ,两个这个类型的数进行加减运算时,计算的结果也会以%u 的形式解读,如果让两个strlen()函数相减,此时我们再判断其正负,就会得到,错误的结果,因为%u解读的数都是大于0的,不可能小于0!!!
1.2.3.my_strlen()的实现
实现的方法有三种:1.计数器法 2,指针法 3,不创建临时变量的递归法
由于计算字符串长度时,不需要进行修改,为了提高代码的健壮性,我们要在指针前加上const修饰。
1.计数器法
int My_strlen(const char* str)
{
int count = 0;
while (*str++)
{
count++;
}
return count;
}
2.指针法
int My_strlen(const char* str)
{
char* p = str;
while (*str)
{
str++;
}
return str - p;
}
//看到这要回忆一下 指针的运算 有哪些
3.递归实现
//这里不用创建临时变量
int My_strlen(const char* str)
{
if (*str)
{
return 1 + My_strlen(++str);
}
else
{
return 0;
}
}
1.3.strcpy()
1.3.1.函数介绍
char* strcpy(char *strDestination, const char *strSource)
在目标空间内复制字符串,有俩个参数,第一个参数的类型为char*,指向复制的目标空间,第二个参数的类型是char*,指向待复制内容的空间,返回值的类型是char*,指向目标空间的首元素地址,复制过程是:依次将待复制内容的数据赋给目标空间,当指向待复制内容的指针指向'\0'时,就结束。
1.3.2使用时要注意的一些点
空间大小问题:当目标空间的大小不足以存放待复制的内容的话,会越界访问,就存在数据被非法覆盖的危机了,所以在使用strcpy()前,请确定目标空间足够大。
1.3.2.my_strcpy()的实现
char* My_strcpy(char* dest, const char* sor)
{
char* p = dest;
while (*dest++ = *sor++)
{
;
}
return p;
}
1.4.strcat()
1.4.1.函数介绍
char* strcat(char *strDestination, const char *strSource)
在目标字符串的末尾追加字符串,有俩个参数,第一个参数的类型为char*,指向待被追加的目标空间,第二个参数的类型是char*,指向要追加内容,返回值的类型是char*,指向待被追加空间的首元素地址,追加的过程是:先访问待被追加的空间,当访问空间的内容为'\0'时停止向后访问,接着就从此位置开始,逐次将要追加的内容进行复制,复制的结束以要复制的空间内容为'\0'时停止。
1.4.2使用时要注意的一些点
在自己后面追加自己:当想使用strcat()实现在自己后头再追加自己时,就会出现一个bug,在清楚strcat()函数的执行逻辑后,在末尾进行复制追加的操作就会进入一个无尽循环,要追加内容永远不会时'\0'。
1.4.3.my_strcat()的实现
//有点像strlen()和strcpy()的结合
char* My_strcat(char* dest, const char* sor)
{
char* p = dest;
while (*dest)
{
dest++;
}
while (*dest = *sor)
{
dest++;
sor++;
}
return p;
}
1.5.strcmp()
1.5.1.函数介绍
int strcmp(const char* str1, const char* str2)
str1与str2的先后顺序没有太大关系,只有当两者不同时会影响返回值的正负。
字符串比较函数,比较的不是字符串长度,而是逐个比较字符的ascii码值的大小,如果两个字符串都比较到末尾'\0'时,就会返回0,表示两个字符串相等,反之如果遇到两个不相等的字符时,就返回参数1字符串与参数2字符串的差值,差值大于0说明两个不相等字符中,前者的ascii码值更大,反之说明后者的更大,类似于按姓名拼音进行排序的大小比较。
1.5.2使用时要注意的一些点
1.5.3.my_strcmp()的实现
int My_strcmp(const char* str1, const char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0') //这一步很关键
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
---------------------------------------------------------------分割线---------------------------------------------------------
分割线以上的字符串操作函数都存在一个问题,就是当参数所指的“字符串”的最后如果不存在'\0',就会出现越界的风险,要是正好要操作的字符串后面是重要的数据,被函数一修改,那就麻烦了,
下面的函数在一定程度上降低了这种风险(加上了限制访问范围的n参数,统一加在最后)
1.6.strncmp()、strncpy()和strncat()
1.6.1.函数介绍
int strncmp( const char *string1, const char *string2, size_t count )
char *strncpy( char *strDest, const char *strSource, size_t count )
char *strncat( char *strDest, const char *strSource, size_t count )
就是在原来的基础上加上参数n作为函数功能结束的控制条件,不再以'\0'作为“灯塔”,而是n字节后的位置为“灯塔”,函数操作字符串的过程基本是一样的。
补:strncpy()和strncat()中,如果原字符的长度小于count时,在拷贝完原字符串后,后面默认补0,直至count个
1.6.2使用时要注意的一些点
n的单位是字节,虽然在字符串中一个元素就是一个字节,但不要就把它当成元素的个数看了。
1.7.strstr()
1.7.1.函数介绍
char* strstr(const char *string, const char *strCharSet )
在主串(string指向的字符串)中找子串(strCharSet指向的字符串)是否存在的函数,存在则返回子串的第一个字符在主串中的位置,不存在则返回NULL。
1.7.2.my_strstr()的实现
char* My_strstr(const char* str1, const char* str2)
{
char* p1 = str1; //用于每次比较后的复位
char* p1_c = str1; //用于做比较
char* p2 = str2; //用于作比较
//循环的终点是主串被遍历完
//其实可以考虑先求出两个字符串的长度,然后就可以避免一些重复的比较了
//因为当主串中剩余未遍历的字符串长度小于子串时,就没有比较的必要了
//复位操作也能优化一下,让子串尽可能地“右移”(等我学完数据结构中的串后再来修改)
while (*p1)
{
if (*p1 = *p2)
{
while (*p1_c == *p2)
{
p1_c++;
p2++;
}
if (*p2 == '\0')
{
return p1;
}
p2 = str2;
}
p1++;
p1_c = p1;
}
return NULL;
}
1.8.strerror()
char *strerror( int errnum )
识别错误码并返回表示错误信息的字符串,返回类型是char*,指向表示错误信息的字符串的第一个字符,参数的类型为int,需是能表示错误码的整型数。
可是要在程序运行后才能知道错误码是多少啊,我们怎么能在敲程序代码的时候就未卜先知了呢?这个时候就需要引用一个特殊的全局变量:errno,这个变量的作用是存放上次程序运行错误对应的错误码,如果程序运行过程中没有错误,则其值为默认的初始值0,而错误代码0指的是 no error,注意!在使用这个变量的时候,得引用一个库:<errno.h>。
1.9.strtok()
char* strtok(char* str, const char div)
字符串分割函数,将字符串按所给字符进行分割,返回的是遇到此次的第一个分割符处之前的字符串,分割参数div可以是单个字符,或者是分割符的字符串,字符串的每个字符都代表一个分割符。
注意:如果你上次引用strtok(),strtok()会记忆上次分割过的字符串位置,当想要获取第二段分割串时,参数1char* str,传NULL即可,当字符串不能再分割时,函数返回NULL。
1.9.字符分类函数
所需要引用的头文件为<ctype.h>
这类函数以 is 开头
函数结构:int isxxx(int x)
相当于英语中的疑问句,用于判断字符的属性:
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 任何可打印字符,包括图形字符和空白字符
上述函数的返回类型为bool类型,只有一个参数,类型为int,参数如果符合相应条件就返回ture,反之返回false。
1.10.字符转换函数
所需要引用的头文件为<ctype.h>
用来转换 字符 的大小写的功能:
int tolower ( int c );
int toupper ( int c );
如果参数合理,则返回转化后的字符对应的ascii码值。
------------------------------------------------------内存函数---------------------------------------------------------------
2.内存函数
所需引用的头文件:<string.h>
相较于字符串操作函数,内存函数在使用上显得更加顺手一点,因为其操作的对象是更加底层的内存,也就意味着它能操作任何存储在内存中的数据(只要有权限的话)
补:下列标红的文字
1.void*
注意函数返回值为void*时,虽然void*可以转化成任意类型的指针,但也不能直接用带类型的指针去接收,而是应该把返回值强制类型转化一下再赋给对应类型的指针。
2.size_t num
这里的单位时字节,不是指元素的个数!!!
2.1.memcpy()
void * memcpy ( void * destination, const void * source, size_t num )
从低地址到高地址,将source指向内存块的后size_t个字节空间的数据,拷贝到destination指向的内存块中,类似strncpy()。
这样的拷贝方式存在一个缺陷,试分析以下代码输出的结果:
int main()
{
char string[10] = "memcpy";
memcpy(string+2, string, 4);
printf("%s\n", string);
return 0;
}
这是因为destination指向的内存空间与source指向的内存空间发生了重叠,在顺序拷贝的过程中,也将source指向的内存空间进行了修改,但是你不先备份一份数据,在不申请额外空间的条件下,想将自身的某个部分拷贝到自身,这种现象是不可避免的吧?
其实是有解决办法的,下面的函数memmove()就是不申请额外空间,但是完成了这个要求。
2.2.memmove()
void * memmove ( void * destination, const void * source, size_t num )
与memcpy()所不同的是,memmove()函数不是一贯以从左向右进行拷贝的,而是先判断dest指向空间的地址与source指向空间的地址的先后顺序,如果dest指向的空间的地址低于source指向的地址,就从左向右进行拷贝,反之则从右向左进行拷贝。
规则巧记:
如果记不住这个规则,我这里做个比喻,将拷贝的动作想象成向目标地埋雷,埋雷者不能经过自己埋过雷的地方,否则就会有危险,当要埋雷的区域与埋雷者活动的区域重叠时,埋雷者应从自己活动范围的两端中选则在重叠部分的一端开始埋雷,这样就不会踩到自己埋的雷了。
2.3.memcmp()
int memcmp ( const void * ptr1, const void * ptr2, size_t num )
同strncmp()
2.4.memset()
void * memset ( void * ptr, int value, size_t num )
内存设置函数,在ptr指向地址后num个字节的空间中,将每一字节空间的数据设置为value。