勇敢牛牛,不怕困难!!!
文章目录
前言
重点介绍字符和字符串的库函数使用及其注意事项
以下库函数均可在(https://www.cplusplus.com)中查看到
思维导图
1 strlen
1.1 strlen函数的功能及用法
size_t strlen ( const char * str );
函数功能:求字符串长度(\0之前的)
注意事项:
1:字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2:参数指向的字符串必须要以 ‘\0’ 结束。
3:函数的返回值为size_t,是无符号的( 易错 )
<1>函数用法如下:
<2>关于strlen的返回值size_t .
打开vs编译器,双击size_t,按F12即可转到定义。
如上图可知:typedef是重命名,将无符号整型重命名为 size_t 所以 strlen的返回值是无符号整型。
<3>关于strlen的返回值的例题:
#include <stdio.h>
#include<string.h>
int main()
{
if (strlen("abc") - strlen("abcd") > 0)
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
若你以为strlen的返回值是有符号整形的话应该打印<号。
正因为strlen的返回值是无符号整形,把-1的补码认为是一个无符号型的数,即非常大的正数。
执行结果:
1.2 strlen函数的模拟实现
思想:采用指针-指针->结果为之间的元素个数
#include<assert.h>
//方法一:计数器方式
size_t my_strlen1(const char* str)
{
assert(str != NULL); //断言,str为空时报错。
size_t count = 0;
while(*str++ != '\0')
{
count++;
}
return count;
}
//方法二:递归(不能创建临时变量)
size_t my_strlen2(const char* str)
{
assert(str != NULL); //断言,str为空时报错。
if (*str == '\0')
{
return 0;
}
else
{
return 1 + my_strlen2(str + 1);
}
}
//方法三:指针-指针的方式
size_t my_strlen3(const char* str)
{
assert(str != NULL); //断言,str为空时报错。
char* end = str;
while (*end != '\0')
{
end++;
}
return end - str;
}
2 strcpy
2.1 strcpy函数的功能及用法
char * strcpy ( char * destination, const char * source );
函数功能:将source指向的C字符串复制到destination指向的数组中,包括终止空字符(并在该点停止)。
注意事项:
1:源字符串必须以 ‘\0’ 结束。
2:会将源字符串中的 ‘\0’ 拷贝到目标空间。
3:目标空间必须足够大,以确保能存放源字符串。
4:目标空间必须可变。
<1>函数用法如下:
<2>目标空间必须可变
例如:
int main()
{
//错误代码:arr是常量字符串,不能被修改
const char* arr = "abcdert";
const char* p = "abcd";
strcpy(arr, p);
printf("%s", arr);
return 0;
}
上述代码无法运行,因为arr为常量字符串,常量是不能被修改的!
2.2 strcpy函数的模拟实现
char* my_strcpy(char* dest, const char* sorc)
{
//只要解引用s了,就需要在前面加上assert防止其空指针
assert(dest);
assert(sorc);
//while (*dest = *sorc) //先赋值,再判断的,\0也拷贝进去了
//{
// dest++;
// sorc++;
//}
char* ret = dest;
while (*dest++ = *sorc++) //这样更好,后置++,先赋值,再++
{
;
}
//return dest; 此时dest已经改变了,不能直接返回
return ret;
}
int main()
{
char arr[10] = "xxxxxxxxx";
const char* p = "abcdefg";
printf("%s", my_strcpy(arr, p));
return 0;
}
3 strcat
3.1 strcat函数的功能及用法
char * strcat ( char * destination, const char * source );
函数功能:将源字符串的副本追加到目标字符串。目标中的终止空字符被源中的第一个字符覆盖,空字符包含在由目标中的两个字符串联而成的新字符串的末尾。
注意事项:
1:源字符串必须以 ‘\0’ 结束。
2:目标空间必须有足够的大,能容纳下源字符串的内容。
3:目标空间必须可修改。
<1>函数用法如下:
3.2 strcat函数的模拟实现
思路:先找到目标空间的\0,再追加
char* my_strcat(char* dest, const char* sorc)
{
//先找\0
assert(dest && sorc);
char* cur = dest;
while (*cur !='\0')
{
cur++;
}
//再追加
while (*cur++ = *sorc++)
{
;
}
return dest;
}
int main()
{
char arr[10] = "hello ";
const char* p = "bit";
printf("%s", my_strcat(arr, p)); //函数的链式访问,将一个函数的返回值作为参数传递给另外一个函数
return 0;
}
用strcat
模拟实现的代码思考一个问题,能不能自己给自己追加?
其实是不可以的,他有一定的缺陷,例如
arr1[20]="we"
自己给自己追加时,此时其在内存中存储的是w e \0
。
我们知道strcat
是将源头的数据拷贝(包含源头的\0)到目标空间,目标空间的\0
会被覆盖。
此时源头数据为w e \0
,将其拷贝的话,w
将覆盖\0
,e
继续拷贝,变成w e w e
,此时我们再拷贝源头的\0
时发现已经被覆盖了,这回导致没有结束标志,会死循环
4 strcmp
4.1 strcmp函数的功能及用法
int strcmp ( const char * str1, const char * str2 );
函数功能:字符串比较函数,这个函数开始比较每个字符串的第一个字符(ASCII值)。如果它们彼此相等,它将继续处理后面的字符对,直到字符不同或到达终止空字符。
参数:
const char * str1
:要比较的 C 字符串。
const char *str2
:要比较的 C 字符串
返回值:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
比较对应位置的字符,比较他们的ASCⅡ
值,如果相同就比较下一个,直到分出大小。
<1>函数用法如下:
//使用strcmp()
int main()
{
//char arr1[10] = "abcd";
//char arr2[10] = "abq";
char arr1[10] = "abcd";
char arr2[10] = "abcd";
if (strcmp(arr1, arr2) > 0)
{
printf("arr1>arr2\n");
}
else if (strcmp(arr1, arr2) < 0)
{
printf("arr1<arr2\n");
}
else
printf("arr1=arr2\n");
printf("%d\n", strcmp(arr1, arr2)); //打印返回值
return 0;
}
可以看到arr1和arr2一模一样,故结果为arr1=arr2,且最终打印的返回值为0。
4.2 strcmp函数的模拟实现
思路:写一个循环,条件为两个字符是否相等,相等则为真,进入循环,地址++,不相等,则返回退出循环的地址处的两个值做差。其中,如果两个字符串一直到结束位置都相等,则在循环内部返回0,表示两个字符串相等
// 退出循环条件
// 1:两个不相等时,退出后将两个地址处的元素相减(结果是ASCII值),
// >0表示arr1>arr2;
// <0表述arr1<arr2;
// =0表示arr1=arr2
//2:两个都走到头了,这个时候直接返回0,因为一直都相等
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2) //只有相等才会进来,当都为\0的时候还会进入,判断一下
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
return *s1 - *s2;
}
int main()
{
char arr1[10] = "abcd";
char arr2[10] = "abq";
//char arr1[10] = "abcd";
//char arr2[10] = "abcd";
if (my_strcmp(arr1, arr2) > 0)
{
printf("arr1>arr2\n");
}
else if (my_strcmp(arr1, arr2) < 0)
{
printf("arr1<arr2\n");
}
else
printf("arr1=arr2\n");
printf("%d\n", my_strcmp(arr1, arr2));
return 0;
}
小总结
我们上面这些函数
strcmp
,strcpy
,strcat
,这些函数都必须依赖\0
,都看\0
来进行他们的功能
所以我们叫这些函数是长度不受限制的字符串,不关注对几个字符进行拷贝,追加,和比较。
因此还存在一些函数是长度受限制的字符串,能对几个字符进行拷贝,追加,和比较。
比如strncmp
,strncpy
,strncat
,下面我们就介绍这些函数。
5 strncpy
5.1 strncpy函数的功能及用法
char * strncpy ( char * destination, const char * source, size_t num );
函数功能:由上面的strcpy函数可知这是字符串拷贝函数,但是比他多了一个num,是长度受限制的字符串函数,比如:拷贝2个字符,不需要看 \0。其拷贝方法与 strcpy相同。
注意事项:
1:拷贝num个字符从源字符串到目标空间。
2:如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
3:目标空间必须足够大,以确保能存放源字符串。
<1>函数用法如下:
int main()
{
char arr1[10] = "abcdefg";
char arr2[10] = "xxx";
printf("%s\n", strncpy(arr1, arr2, 4));
return 0;
}
解析:由于
strncpy(arr1, arr2, 3)
,设置的为3个字节,即,将arr2中的xxx拷贝到arr1中,不包括\0。
5.2 strncpy函数的模拟实现
思路:首先要有一个循环将*dest++ = *src++,还要有一个count去记录拷贝了几个字节的数据,拷贝一次count–
退出循环条件
1:count=0,说明指定的字节拷贝完了,此时退出循环即可
2:src=\0了,此时也要退出循环,因为函数介绍里说如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
此时还需要一个条件判断count是否为0,不为0,再写一个while循环,将\0赋值给dest,同时–count
char* my_strncpy(char* dest, const char* src, int count)
{
assert(dest && src);
char* ret = dest;
while (count && (*dest++ = *src++) && *src != '\0') //这里**dest++ = *src++的次数比count多一次
{
count--;
}
if (count)
{
while (--count)
{
*dest = '\0';
}
}
return ret;
}
6 strncat
6.1 strncat函数的功能及用法
char * strncat ( char * destination, const char * source, size_t num );
函数功能:将源的前num个字符追加到目标,加上一个终止空字符(源字符串后会自动加一个\0)。 如果source中C字符串的长度小于num,则只复制终止空字符之前的内容可以自己给自己追加了!!!
函数用法如下:
int main()
{
char arr1[] = "abcdefg\0qqqqqq";
char arr2[] = "xxxx";
//printf("%s\n", strncat(arr1, arr2, 3));
printf("%s\n", strncat(arr1, arr1, 3));
return 0;
}
7 strncmp
7.1函数的功能及用法
int strncmp ( const char * str1, const char * str2, size_t num );
函数功能:// 将C字符串str1
的最多num
个字符与C字符串str2
的字符进行比较。 这个函数开始比较每个字符串的第一个字符。
如果它们彼此相等,它继续处理后面的字符对,直到字符不同,直到到达终止空字符,或者直到num
个字符在两个字符串中匹配,以先发生的为准。
返回值:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
函数用法如下:
int main()
{
char arr1[] = "abcde";
char arr2[] = "abcd";
printf("%d\n", strncmp(arr1, arr2, 4));
printf("%d\n", strncmp(arr1, arr2, 5));
return 0;
}
解析: 可以看到当比较4
个字节时,arr1=arr2
,故结果为0
。
当比较5
个字节时,arr1
的第5
个字节为e
,arr2
的第5
个字节为\0
,e
的ASCII
值大于\0
的ASCII
值,故结果为大于0的数。
7.2函数的模拟实现
思路:和strcmp
实现大致相同,只是限制条件有些许不同
strcmp
:元素不等时,有一个为\0是退出
strncmp
:元素不等时,num
减为0时
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
while (num && *str1 == *str2)
{
num--;
if (num != 0)
{
str1++;
str2++;
}
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcde";
char arr2[] = "abcd";
printf("%d\n", my_strncmp(arr1, arr2, 4));
printf("%d\n", my_strncmp(arr1, arr2, 5));
return 0;
}
8 strstr
8.1函数的功能及用法
char * strstr ( const char * str1, const char * str2 );
函数功能在一个字符串找另外一个字符串
返回指向str1中第一个str2的指针,如果str2不是str1的一部分,则返回空指针。匹配过程不包括终止的空字符,但是它在那里停止。
函数用法如下:
8.1函数的模拟实现
char* my_strstr(const char* str1, const char* str2)
{
const char* s1 = str1;
const char* s2 = str2;
char* p = str1;
while (*p) //p找到后记录 子字串 第一次出现的位置
{
s1 = p;
s2 = str2;
//跳出下面这个循环有3种情况
//1:(*s1)!=(*s2)
//2:如果找到了,还会进入循环,s2++后会指向\0,两个可能相等(当s1处于末尾时会相等,再次进入循环++时,会越界),这时应该跳出循环
//3:当s1为\0,或者s2为\0时候。当s1为\0时跳出,说明到头了(可能找到,也可能没找到)。 s2为\0时候说明找到了!!!!
while ((*s1 != '\0') && (*s2 != '\0') && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)p;
}
p++;
}
return NULL; //如果*p到最后了还没找到
}
8 strtok
8.1函数的功能及用法
char * strtok ( char * str, const char * delimiters );
**函数功能:**这个函数有点不好理解,其实可以理解成字符串分割函数,将一个字符串通过分割符分割成几个单个的字符,从而得到这几个单个的字符,但是需要注意的是使用 strtok
会改变原字符串,所以一般先将字符串拷贝,然后将拷贝的字符串进行分割。
比如:现在有一个字符串 123456@QQ.com
分隔符是 '@和.'
就可以使用这个函数将字符串分割成 123456
, QQ
, com
,三个字符串。
但是需要注意的一点就是:再找第二个分割字符串时,他已经将第一个字符串的分割符变为 \0
,这样打印时就能直接打印,所以在分割第二个字符串时,传空指针直到找到下一个分割符。
函数的参数:char * str, const char * delimiters
,第一个字符指针指向是要被分割的字符的地址。而第二个字符指针指向分隔符的地址。
返回值:strtok
的返回值是 char *
,返回一个字符型指针,返回是分割字符串的起始位置
函数用法如下:
代码很罗嗦,如分隔很多的话,要点用很多次,考虑用for循环改进
改进后的代码:
8.2函数注意事项
1、delimiters参数是个字符串,定义了用作分隔符的字符集合。
2、第一个参数指定一个字符串,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
3、strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针
(注: strtok函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容并且可修改。)
4、 strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记, strtok 函数将保存它在字符串 中的位置。
5、 strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
6、如果字符串中不存在更多的标记,则返回 NULL 指针。
9 strerror
9.1函数的功能及用法
char * strerror ( int errnum );
函数功能:返回错误码,所对应的错误信息。我们平常上网遇到的404就是错误码。这个错误码就是c语言自己规定的
返回值:指向描述错误的错误字符串的指针
函数用法如下:我们要了解 errno, errno就是将错误信息记录到错误码里的东西,是C语言提供的库函数的全局变量。
1:错误码含义展示
2:具体应用场景
补充:perror
,也是打印错误信息的函数其实相当于 printf+strerror.
10 字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
---|---|
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 | 任何可打印字符,包括图形字符和空白字符 |
11 memcpy
11.1函数的功能及用法
void * memcpy ( void * destination, const void * source, size_t num );
函数功能:
其实这个函数很好理解,可以从字面上理解,memory+copy
,就是内存拷贝函数,也是内存函数。和strcpy
函数的区别:emcpy
的函数参数是 void*
类型,说明他什么都能拷贝,而strcpy
不一样,只能拷贝字符串。
函数参数:
因为是只是移动,源头参数都不能修改,所以用const修饰。
size_t
就是拷贝 多少字节个数,这个很重要。如果拷贝一个整型,那么就是4个字节
函数的返回值:
memcpy
的返回值是void*
,因为不知道返回的是什么类型所以我们用 void*
类型接收。
函数用法如下:
可以看到,memcpy可以拷贝任意数据类型
11.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*)src);
//dest = (char*)dest + 1;
//src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9};
int arr2[20] = { 0 };
my_memcpy(arr2, arr1, 12); //将345拷贝到123位置上
return 0;
}
运行结果:
注意:
如果我们想自己拷贝自己几个字符,你觉得用上面这个代码合适吗。
显然这个是不太合适的。当我们自己拷贝自己时,可能会改变我们需要拷贝的字符,进而我们最后拷贝的字符就变成了拷贝完的字符。
所以memcpy
一般是不能拷贝重叠的字符,但是在vs
编译器下,比较智能,可以实现但是在其他编译器上就有可能不会实现
因此就又有一个函数memmove
,内存移动函数,就可移动重叠的字符。
12 memmove
12.1函数的功能及用法
void * memmove ( void * destination, const void * source, size_t num );
函数功能:
其实这个函数很好理解,可以从字面上理解,memory+move,就是内存移动函数,也是内存函数。如果源空间和目标空间出现重叠,就得使用 memmove
函数处理
函数参数:
因为是只是移动,源头参数都不能修改,所以用const修饰。
size_t
就是拷贝 多少字节个数,这个很重要。如果拷贝一个整型,那么就是4个字节
函数的返回值:
memcpy
的返回值是void*
,因为不知道返回的是什么类型所以我们用 void*
类型接收。
函数用法如下:
12.2函数的模拟实现
思路:分2种情况拷贝
1:dest<src
2:dest>=src
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = (char*)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;
}
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9};
my_memcpy(arr1+2, arr1, 20); //将12345拷贝到34567位置上
//memmove(arr1 + 2, arr1, 20);
//float arr1[] = { 1.0f,2.0f,3.0f,4.0f };
//float arr2[20] = { 0 };
//my_memcpy(arr2, arr1, 8);
return 0;
}
运行结果:
因此:
和memcpy
的差别就是memmove
函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove
函数处理。
13 memcmp
13.1函数的功能及用法
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
函数功能:
其实这个函数很好理解,可以参考memcpy
,相当于字符串比较的拓展,就是对于什么都可以比较,也是内存函数,比较方法和strcmp
比较类似。
函数参数:
因为是只是移动,源头参数都不能修改,所以用const修饰。
size_t
就是拷贝 多少字节个数,这个很重要。如果拷贝一个整型,那么就是4个字节
函数的返回值:
memcmy
的返回值是 int
,和strcmp
的返回一模一样
函数用法如下:
14 memset
14.1函数的功能及用法
void * memset ( void * ptr, int value, size_t num );
函数功能:
其实这个函数很好理解,memset
,就是内存设置函数,将指定的内存设置为指定的值。将 ptr
指向的内存块的第一个字节数设置为指定的值(解释为无符号字符)
函数参数:
int value
就是要将内存设置成什么类型,可以是任意类型,不一定要int
型
size_t
就是拷贝 多少字节个数,这个很重要。如果拷贝一个整型,那么就是4个字节
函数的返回值:
memset
的返回值是void*
,因为不知道返回的是什么类型所以我们用 void*
类型接收。
函数用法如下: