前言
本文重点介绍处理字符和字符串的库函数的使用和注意事项
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在
常量字符串
中或者 字符数组
中。
字符串常量
适用于那些对它不做修改的字符串函数。
●求字符串长度
▷strlen
①函数介绍
计算字符串长度。
size_t strlen ( const char * str );
注意事项:
⛳字符串以 ‘\0’ 作为结束标志(遇见‘\0’就停止遍历),strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
⛳参数指向的字符串必须要以 ‘\0’ 结束。
⛳注意函数的返回值为size_t,是无符号的!!!( 易错 )。
第三个错误我给大家举例说明一下:
int main()
{
char arr1[20]="abc";
char arr2[20]="abcde";
if(strlen(arr1)-strlen(arr2)>0)
{
printf(">");
}
else if(strlen(arr1)-strlen(arr2)==0)
{
printf("=");
}
else
{
printf(">");
}
return 0;
}
💦结果竟然是大于!!!
原因就是因为strlen函数
的返回类型是无符号整数
,而负数被看作无符号整数时是一个非常大的数!
所以我们使用strlen
时应该直接比较大小,而不是依靠减法!
②strlen函数的模拟实现
1.计数器
unsigned int my_strlen(const char* elem)
{
assert(elem);
int len = 0;
while(*elem)
{
elem++;
len++;
}
return len;
}
2.递归方法(不允许建立临时变量)
unsigned int my_strlen(const char* elem)
{
assert(elem);
if(*elem)
{
return 1 + my_strlen(elem+1);
}
else
{
return 0;
}
}
3.指针-指针的方法
unsigned int my_strlen(const char* elem)
{
assert(elem);
char* ret = elem;
while(*elem)
{
elem++;
}
return elem - ret;//elem和ret中间相距的字节即为长度
}
❗第三种方法解释了为什么strlen
只能计算字符串的个数而不能计算整型的字数:
是因为字符指针每次访问一个字节,字符指针与字符指针相减也是得到的字节总数,而不是字节总数/4!
这虽然是一个很简单的函数,但也很容易出错,希望大家要牢牢记住这几点。
●长度不受限制的字符串函数
▷strcpy
①函数介绍
字符串copy函数,把一个函数拷贝到另一个函数中去。
char* strcpy(char * destination, const char * source );
注意事项
:
🎀源字符串必须以 ‘\0’ 结束。
🎀会将源字符串中的 ‘\0’ 拷贝到目标空间。
🎀目标空间必须足够大,以确保能存放源字符串。
🎀目标空间必须可变。
②strcpy函数的模拟实现
char* my_strcpy(char* elem1,const char* elem2)//将不用改变的量定义为const常变量,使*elem2不可改变,提高代码的健壮性
{
assert(elem1&&elem2);//断言
char* src = elem1;//保存字符串1的起始地址,便于链式访问
while(*elem1++ = *elem2++)//先把*elem2的值赋给*elem1,再对elem1 elem2加1,如果*elem1 *elem2相等之后都为'\0',跳出循环。
{
;
}
return src;
}
▷strcat
①函数介绍
strcat函数
的作用是复制一个字符串,将这个字符串拼接在另一个字符串后面。strcat()函数
接受两个字符串作为参数,会把第二个字符串的备份附加在第一个字符串末尾,并把拼接后形成的新字符串作为第一个字符串,第二个字符串不变。
char * strcat ( char * destination, const char * source );
注意事项
:
🎐源字符串必须以 ‘\0’ 结束。
🎐目标空间必须有足够的大,能容纳下源字符串的内容。
🎐目标空间必须可修改。
🎐如果自己给自己拼接会出问题,因为在拼接的过程中’\0’被覆盖掉了。
②strcat函数的模拟实现
char* my_strcat(char* elem1,const char* elem2)
{
assert(elem1&&elem2);//断言
char* ret = elem1;//保存字符串1的起始地址,便于链式访问
while(*elem1)//找到第一个字符串为'\0'的位置开始改
{
elem1++;
}
while(*elem1++ = *elem2++)//先把*elem2的值赋给*elem1,再对elem1 elem2加1,如果*elem1 *elem2相等之后都为'\0',跳出循环。
{
;
}
return ret;
}
▷strcmp
①函数介绍
strcmp函数是比较两个字符串的大小,返回比较的结果。
其中,字符串1、字符串2均可为字符串常量或变量;
int strcmp ( const char * str1, const char * str2 );
返回方式
:
✨字符串1小于字符串2,strcmp函数返回一个负值;
✨字符串1等于字符串2,strcmp函数返回零;
✨字符串1大于字符串2,strcmp函数返回一个正值;
②strcmp函数的模拟实现
int my_strcmp(const char* elem1,const char* elem2)
{
assert(elem1&&elem2);
while(*elem1 == *elem2&&*elem1&&*elem2)
{
elem1++;
elem2++;
}
return *elem1 - *elem2;
}
●长度受限制的字符串函数介绍
▷strncpy
函数介绍
拷贝num个字符从源字符串到目标空间。
char * strncpy ( char * destination, const char * source, size_t num );
注意事项
:
🎄如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
▷strncat
函数介绍
把src所指字符串的前n个字符添加到dest结尾处(覆盖dest结尾处的’\0’)并添加’\0’。
char *strncat(char *dest, const char *src, size_t n)
▷strncmp
函数介绍
此函数功能即比较字符串str1和str2的前num个字符。如果前num字节完全相等,返回值就=0;在前num字节比较过程中,如果出现str1[n]与str2[n]不等,则返回(str1[n]-str2[n])。
int strncmp ( const char * str1, const char * str2, size_t num );
注意事项
:
🎡比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
●字符串查找
▷strstr
①函数介绍
strstr 函数
用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
char * strstr ( const char *str1, const char * str2);
②strstr函数的模拟实现
char* my_strstr(const char* elem1,const char* elem2)//找到子字符串并返回子字符串开始的位置
{
assert(elem1&&elem2);
while(*elem1)
{
char* src = elem1;
char* dest = elem2;
while(*src == *dest&&*src&&*dest)
{
src++;
dest++;
}
if(*dest=='\0')
{
return elem1;
}
elem1++;
}
return NULL;
}
▷strtok
①函数介绍
strtok函数
用来将字符串分割成一个个片段。参数str指向欲分割的字符串,参数sep则为分割字符串中包含的所有字符。当strtok()在参数str的字符串中发现参数sep中包涵的分割字符时,则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数str字符串,往后的调用则将参数str设置成NULL。每次调用成功则返回指向被分割出片段的指针。
char * strtok ( char * str, const char * sep );
注意事项
:
🏈sep参数是个字符串,定义了用作分隔符的字符集合。
🏈第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
🏈strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
🏈strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
🏈strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
🏈如果字符串中不存在更多的标记,则返回 NULL 指针。
②strtok最笨笨用法(在公司上班这么写会被开除的那种)
int main()
{
char arr[] = "TvT@yeah.net.hehe@haha nihao";
char buf[50] = { 0 }; //TvT@yeah.net.hehe@haha nihao
strcpy(buf, arr);
const char* sep = "@. ";
char* str = NULL;
printf("%s\n", strtok(buf, sep)); //只找第一个标记
printf("%s\n", strtok(NULL, sep)); //是从保存的好的位置开始继续往后找
printf("%s\n", strtok(NULL, sep)); //是从保存的好的位置开始继续往后找
return 0;
}
能看出来只打印了3组分隔符之后的字符串,因为我只写了三个printf()…(⊙_⊙;)…
③strtok聪明的用法
这个就是把整个字符串都遍历并且把分隔符之外的元素都打印出来的方法:
int main()
{
char arr[] = "TvT@yeah.net.hehe@haha nihao";
char buf[50] = { 0 }; //TvT@yeah.net.hehe@haha nihao
strcpy(buf, arr);
const char* sep = "@. ";
char* str = NULL;
for (str=strtok(buf, sep); str!=NULL; str=strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
🔎看结果:
●错误信息报告
▷strerror
①函数介绍
返回错误码,所对应的错误信息。
char * strerror ( int errnum );
②streror用法
errno
是错误码,会自动根据错误信息改变errno的值,这里由于我向堆区申请内存过大,导致p成为空指针,错误信息调用错误码之后就正确得到了没有足够的内存这一错误。
#include <errno.h>//错误码的头文件
#include <string.h>//strerror的头文件
int main()
{
int* p = (int*)malloc(99999999999999); //向堆区申请内存的
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
return 0;
}
③peror用法
void perror( const char *string );
就是自动找到errno
并打印出对应的信息,并输出一个前缀的解释(解释是自己定义的),可以看到这个函数要求传一个字符地址。
#include <errno.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main()
{
int* p = (int*)malloc(99999999999999); //向堆区申请内存的
if (p == NULL)
{
perror("Malloc");
return 1;
}
return 0;
}
●字符操作
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v' |
isdigit | git 十进制数字 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 | 任何可打印字符,包括图形字符和空白字符 |
●内存操作函数
▷memcpy
①函数介绍
函数memcpy
从source的位置开始向后复制num个字节的数据到destination的内存位置。
void * memcpy ( void * destination, const void * source, size_t num );
注意事项:
🥗这个函数在遇到 ‘\0’ 的时候并不会停下来。
🥗如果source和destination有任何的重叠,复制的结果都是未定义的。
②memcpy函数的模拟实现
代码如下(示例):
void* my_memcpy(void* elem1, void* elem2, size_t width)
{
void* ret = elem1;
while (width--)
{
*(char*)elem1 = *(char*)elem2;//一个字节一个字节移动
elem1 = (char*)elem1 + 1;
elem2 = (char*)elem2 + 1;
}
return ret;
}
▷memove
①函数介绍
和memcpy
的差别就是memmove
函数处理的源内存块和目标内存块是可以重叠的。
void * memcpy ( void * destination, const void * source, size_t num );
注意事项:
🍷如果源空间和目标空间出现重叠,就得使用memmove
函数处理。
②memmove函数的模拟实现
当我们想把1234放到3456的地方时,使用memcpy
就会产生重叠,因为把1放到3的位置之后,再往后遍历就会把原来3的位置上的数据1放在位置5上,导致数据重叠。
如何解决重叠问题呢?
我们通过观察可以发现,很简单,只要把从前往后复制改成从后往前就可以了,先把4放在6的位置上,再把3放在5的位置上,依次类推。
那是不是就所有的数据都可以反着放了呢?
当然不是。
比如当我们想把3456放到1234位置时,反着放就又会发生重叠。
这可怎么办?
🏳🌈 有一个很妙的方法:
我们判断要拷贝的元素位置是在目标位置的前面还是后面就可以了。
假如以3为临界点,如果想拷贝3左边的元素,比如1234到3456的位置,就需要倒着拷贝
才不会发生重叠。
如果像拷贝右边的元素,比如5678到3456的位置,就需要正着拷贝
才不会发生重叠。
而倒着拷贝的时候我们还要注意电脑的大小端存储问题:
我目前的电脑是小端存储的,即把低位数据存储在低地址中。又由于在内存中数组是从高位存到低位的,所以有这样一张图:
从后往前存储的时候为了正确的对应上每个字节,我们需要先给width-1,才可以正确的取到这里7的最后一位(第19位),因为第20位是8的第一位了。
由此我们可以写出代码:
void* my_memmove(void* elem1,void* elem2,size_t width)
{
void* src = elem1;
while (width--)
{
if (elem2 > elem1)//从前往后迭代
{
*((char*)elem1) = *((char*)elem2);
elem1 = (char*)elem1 + 1;
elem2 = (char*)elem2 + 1;
}
else
{
*((char*)elem1 + width) = *((char*)elem2 + width);
}
}
return src;
}
}
总结
💕以上就是今天的内容啦~ 本文仅仅介绍了字符函数和字符串函数的使用,而想让自身取得提升还需要大家多方面的练习~那我就在这里祝大家越来越牛,年入百万!