在前面我们已经学习了strlen、strcpy、strcat、strcmp几个库函数,今天我们继续学习剩余的库函数。
上期链接:
C语言进阶——字符函数和字符串函数(上)_wangjiushun的博客-CSDN博客
三、长度受限制的函数字符串
1、 strncpy
函数原型:char* strncpy(char* destination, const char* source, size_t num);
1、从源字符串拷贝num个字符到目标空间
2、如果源字符串的长度大于num,则只拷贝num个字符,并不会在目标空间后追加‘\0’字符
3、如果源字符串的长度小于num,则拷贝玩源字符串之后(包括‘\0’),在目标空间的后面追加‘\0’,直到num个。
4、源指向的空间与目标空间不能重叠
5、目标空间必须可变
讲解:
(1)strncpy函数的返回类型和参数是怎么设计的?
strncpy与strcpy相似,只是多了一个参数size_t num,可以自己决定拷贝几个字符源字符串到目标空间中(在决定拷贝几个字符时,我们会考虑目标空间是否足够大),所以相对strcpy函数安全一些。
(2)拷贝num个字符从源字符串到目标空间
①如果源字符串长度大于num,则只拷贝num个字符,并不会在目标后追加‘\0’
代码实例:
#include<stdio.h> #include<string.h> int main() { char arr1[20] = "################"; char arr2[] = "abcdef"; //把arr2中的以首字符开始的前3个字符拷贝到arr1中 strncpy(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
F10调试之后,观察目标空间arr1的内存:
②如果源字符串的长度小于num,则拷贝玩源字符串之后(包括‘\0’),在目标空间的后面追加‘\0’,直到num个。
代码实例:
#include<stdio.h> #include<string.h> int main() { char arr1[20] = "################"; char arr2[] = "abcdef"; //把arr2中的以首字符开始的前10个字符拷贝到arr1中 strncpy(arr1, arr2, 10); printf("%s\n", arr1); return 0; }
F10调试之后,观察目标空间arr1的内存:
(3)模拟实现strncpy
分析:
strncpy函数是从源字符串拷贝num个字符到目标空间的,模拟strncpy,那函数的返回类型、参数类型应该一致。
函数体的实现:
①定义char* start变量存储目标空间的起始地址;
②将源字符串拷贝num个字符到目标空间:
a. 拷贝源字符串:num <= 源字符串
b. 判断num是否为0,不为0在目标后追加‘\0’
③返回目标空间的起始地址。
代码演示:
#include<stdio.h> #include<assert.h> char* my_strncpy(char* dest, const char* src, size_t num) { //断言指针的有效性 assert(dest && src); //存储目标空间的起始地址 char* start = dest; //将源指向的字符串拷贝num个字符到目标空间 //①num <= 源字符串,拷贝源字符串 while (num)//注意num是无符号类型,写成后置--,易错,所以在后面单独成一条语句 { if ((*dest++ = *src++) == '\0') { break; } --num; } //②num > 源字符串,在目标后面追加'\0',直至拷贝num个字符 if (num) { while (--num) { *dest++ = '\0'; } } //返回目标空间起始地址 return start; } int main() { char arr1[10] = "#########"; char arr2[] = "abcde"; //从arr2中拷贝3个字符到arr1 printf("%s\n", my_strncpy(arr1, arr2, 3)); //从arr2中拷贝8个字符到arr1 printf("%s\n", my_strncpy(arr1, arr2, 8)); return 0; }
积累:当类型为无符号类型,后置--且为循环条件循环两次是,易错!(后置--,先赋值再-1,可能减到负数,负数在无符号类型下是很大的正数)
2、 strncat
函数原型:char* strncat(char* destination, const char* source, size_t num);
1、从源字符串的第一个字符开始拷贝num个字符到目标空间字符串的尾部,追加完后再外加一个‘\0’字符;(说明:①目标空间的‘\0’被源字符串的第一个字符覆盖②注意只拷贝源字符串‘\0’之前的字符,不拷贝‘\0’)
2、注意:如果源字符串的长度小于num,则只拷贝‘\0’之前的内容;
3、源指向的空间与目标空间不可以重叠;
4、目标空间可变。
讲解:
(1)模拟strncat
分析:
strncat函数是从源指向的第一个字符开始拷贝num个字符到目标空间字符串的尾部,追加完后再外加终止‘\0’字符,模拟strncat,那函数的返回类型、参数类型应该一致。
函数体的实现:
①定义char* start变量存储目标空间的起始地址;
②从源字符串开始拷贝num个字符到目标空间的尾部,追加完后在加终止‘\0’字符:
a. 找到目标空间的终止‘\0’字符
b. 追加num个字符到目标空间,当满足条件num > 源字符串长度时,则拷贝完源字符串(包括‘\0’)后,直接返回目标空间起始地址
c. num < 源字符串长度,追加完num个字符后,再外加终止‘\0’字符
d. 返回目标空间起始地址(num < 源字符串)
代码演示:
//模拟strncat #include<stdio.h> #include<assert.h> char* my_strncat(char* dest, const char* src, size_t num) { //断言指针的有效性 assert(dest && src); //存储目标空间的起始地址 char* start = dest; //追加num个字符到目标空间 //①找到目标空间的\0 while (*dest) { dest++; } //②追加num个字符到目标空间 while (num) { //如果num > 源字符串的长度,则拷贝到\0退出函数 if ((*dest++ = *src++) == '\0') { return start; } num--; } //num < 源字符串长度,追加完后附加\0 *dest = '\0'; return start; } int main() { char arr1[10] = "######"; char arr2[] = "abc"; printf("%s\n", my_strncat(arr1, arr2, 2)); return 0; }
3、 strncmp
函数原型:int strncmp(const char* str1, const char* str2, size_t num);
1、比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
2、返回值与strncmp一样
讲解:
(1)模拟strncmp
分析:
strncmp函数是str1和str2比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完,模拟strncmp,那函数的返回类型、参数类型应该一致。
函数体的实现:
①while循环比较num个对应字符:
a. 当满足情况——比较到不同字符,*str1 > *str2,返回1,*str1 < *str2,返回-1
b. 当满足情况——一个字符串结束(即相等),返回0
②比较完num个字符,均未出现不同字符,即相等,返回0
#include<stdio.h> #include<assert.h> int my_strncmp(const char* str1, const char* str2, size_t num) { //断言指针的有效性 assert(str1 && str2); //比较num个对应字符的ASCII值 while (num) { //①比较到不同的字符 if (*str1 != *str2) { //如果str1 > str2,返回1,否则返回-1 if (*str1 > *str2) { return 1; } else { return -1; } } //②一个字符串结束,返回0 if (*str1 == '\0') { return 0; } str1++; str2++; --num; } //比较完num个字符,均相同,返回0 return 0; } int main() { char arr1[] = "abcqw"; char arr2[] = "abcaw"; int ret = my_strncmp(arr1, arr2, 4); if (ret > 0) { printf("arr1 > arr2\n"); } else if (ret == 0) { printf("arr1 = arr2\n"); } else { printf("arr1 < arr2\n"); } return 0; }
四、字符串查找
1、 strstr
函数原型:char* strstr(const char* str1, const char* str2);
1、strstr函数查找子字符串;
2、在str1中找str2中第一次出现的位置,如果找到返回一个指针,指向str2在str1中第一次出现的位置;如果找不到(即str2不是str1的子集),就返回一个NULL指针。
讲解:
(1)代码演示:strstr
#include <stdio.h> #include <string.h> int main() { char str[] = "This is a simple string"; //接收strstr的返回值 char* pch; //将str字符串中的simple子串修改为sample //①得到子串在str中第一次出现的位置 pch = strstr(str, "simple"); if (pch != NULL) { //②将sample拷贝到子串的位置 strncpy(pch, "sample", 6); //输出修改后的str puts(str); } else { printf("str中子串不存在\n"); } return 0; }
(2)模拟strstr
分析:
strstr函数是查找字符串,判断str2是否为str1的子集,是则返回str1中str2第一次出现得我位置,否则返回NULL。
函数体的实现:
①特殊情况:str为‘\0’是,直接返回str1(“特事特办”)
②遍历str1,当满足情况str2是str1的子集或str1结束:
a. cp指针用来遍历str1,给s1赋值;
b. c. s1指针用来每次判断该位置是否为str1中子集str2的位置,不是则str1遍历;
c. s2指针用来遍历str2是否与s1相等,相等则返回s1的起始地址(即cp),不相等则重新回到str2的位置;
代码演示:
//模拟strstr /* 分析:strstr函数查找子字符串,判断str2是否为str1的子集。是则返回子字符串的位置,否则返回NULL */ #include<stdio.h> #include<assert.h> char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); //特殊情况:str2为'\0'时,直接返回str1 if (*str2 == '\0') { return (char*)str1; } //s1用于从主字符串的第一个字符开始遍历主字符串是否包含子串, //遍历到不同于子串的字符,又从主字符串的第二个字符开始遍历; //直至遍历到包含,或遍历到主字符串的\0 const char* s1 = NULL; //s2每一次新遍历时都会重新回到str2的位置 const char* s2 = NULL; //存放子串可能的位置,从主字符串的首字符开始 const char* cp = str1; while (*cp) { s1 = cp; s2 = str2; while (*s1 != '\0' && *s2 != '\0' && * s1 == *s2) { s1++; s2++; } if (*s2 == '\0') { return (char*)cp; } else if (*s1 == '\0') { return NULL; } cp++; } return NULL; } int main() { char arr1[] = "abbcdbba"; char* ret = my_strstr(arr1, "bbbd"); if (ret != NULL) { printf("%s\n", ret); } else { printf("没有找到\n"); } return 0; }
2、 strtok
函数原型:char* strtok(char* str,const char* sep);
1、sep参数是个字符串,定义了用作分隔符的字符集合;
2、第一个参数指定一个字符串,它包含了0个或多个由sep字符串中一个或者多个分隔符分割的标记;
3、strtok函数找到str中的下一个标记,并将其用‘\0’结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。);
4、strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;
5、strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记;
6、如果字符串不存在更多的标记,则返回NULL指针。
代码演示:
代码1:
#include<stdio.h> #include<string.h> int main() { char arr[] = "2809552931@qq.com"; //分割邮箱 //2809552931 qq com //分割符集合:@. char* sep = "@."; //注意:strtok函数会改变被操作的字符串,所以在使用strtok分割的字符串 //一般都是临时拷贝的内容并且可修改。 char arr1[20]; strcpy(arr1, arr); //strtok函数找到arr中的标记,并将其用\0结尾,返回一个指向这个标记的指针 //①strtok函数的第一个参数不为NULL,函数将找到arr1中第一个标记, //strtok函数将保存它在字符串中的位置 char* ret = strtok(arr1, sep); printf("%s\n", ret); //②strtok函数的第一个参数为NULL,函数将在同一个字符串中 //被保存的位置开始,开始查找下一个标记 ret = strtok(NULL, sep); printf("%s\n", ret); //③如果字符串不存在更多的标记,则返回NULL指针 ret = strtok(NULL, sep); printf("%s\n", ret); return 0; }
代码2:
#include<stdio.h> #include<string.h> int main() { char arr[] = "2809552931@qq.com"; //分割邮箱 //2809552931 qq com //分割符集合:@. char* sep = "@."; //注意:strtok函数会改变被操作的字符串,所以在使用strtok分割的字符串 //一般都是临时拷贝的内容并且可修改。 char arr1[20]; strcpy(arr1, arr); char* ret = NULL; //strtok函数:除第一次调用,其余调用都是传null,这样才会继续向后寻找分隔符 for (ret = strtok(arr1, sep); ret != NULL; ret = strtok(NULL, sep)) { printf("%s\n", ret); } return 0; }
五、错误信息报告
1、strerror
头文件:“string.h”
函数原型:char* strerror(int errnum);
1、返回错误码,所对应的错误信息
2、注意:要配合printf才能打印出错误信息。
讲解:
(1)函数的返回类型和参数:
返回类型:char* ——字符指针,指向错误码所对应的错误信息的字符串首字符地址
参数:int ——错误码是一些整数
(2)返回错误码,所对应的错误信息
C语言的库函数在调用的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno(头文件:“errno.h”)。
错误码是一些数字:0 1 2 3 4 5
每一个错误码都对应一个错误信息。
代码演示:
#include<stdio.h> #include<string.h> int main() { printf("%s\n", strerror(0)); printf("%s\n", strerror(1)); printf("%s\n", strerror(2)); printf("%s\n", strerror(3)); printf("%s\n", strerror(4)); printf("%s\n", strerror(5)); return 0; }
运行结果:
(3)strerror的正确使用:
当C语言的库函数调用时发生错误,会将错误码存放在(全局)变量errno中,使用strerror可以获得错误码所对应的错误信息的字符串首地址,配合printf将错误码打印出来。
代码示例:如打开文件失败,想知道为什么打开失败
#include<stdio.h> #include<string.h> #include<errno.h> int main() { //打开文件 FILE* pf = fopen("test.txt", "r"); if (pf == NULL) { printf("%s\n", strerror(errno)); return 1; } //读文件 //关闭文件 fclose(pf); pf = NULL; return 0; }
运行结果:
2、perror
头文件:“stdio.h”
函数原型:void perror(const char* str);
1、直接打印错误信息,在打印错误信息前,会先打印自定义的信息。
2、可理解为:perror = printf + strerror
讲解:
(1)函数的返回类型和参数:
返回类型:void —— 没有返回值
参数:C字符串,包含要在错误信息本身之前打印的自定义信息。如果是NULL,则不会打印前面的自定义信息,但仍会打印错误信息。按照惯例,应用程序本身的名称通常用作参数。
(2)代码演示:如打开文件失败,想知道为什么打开失败
#include<stdio.h> int main() { //打开文件 //‘r’——打开方式:只读(如果该文件路径下,没有该文件则读取失败) FILE* pf = fopen("test.txt", "r"); //如果文件打开失败,打印失败原因 if (NULL == pf) { perror("fopen"); return 1; } //读文件 //关闭文件 fclose(pf); pf = NULL; return 0; }
运行结果:
六、字符操作
1、字符分类函数:(头文件:“ctype.h”)
函数 如果他的参数符合下列条件就返回真
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 任何可打印字符,包括图形字符和空白字符
代码演示:islower
#include<stdio.h> #include<ctype.h> int main() { char ch = 0; //多组输入,判断输入值是否为小写字母 while ((scanf("%c", &ch)) != EOF) { //islower函数判断它的参数是否为小写字母 //是小写字母,为真返回非零的数字 //不是小写字母,为假返回0 printf("%d\n", islower(ch)); //清空缓存区 getchar(); } return 0; }
运行结果:
2、字符转换
头文件:ctype.h
1、大写转小写
int tolower(int c);
2、小写转大写
int toupper(int c);
代码演示:
代码1:
#include<stdio.h> #include<ctype.h> int main() { //大写字母转小写字母 printf("%c\n", tolower('A')); //小写字母转大写字母 printf("%c\n", toupper('b')); return 0; }
运行结果:
代码2:将字符串中的小写字母转换为大写字母
#include<stdio.h> #include<ctype.h> int main() { char arr[] = "I Have An Apple."; int i = 0; while (arr[i]) { //如果为小写字母转换为大写字母 if (islower(arr[i])) { arr[i] = toupper(arr[i]); } printf("%c", arr[i]); i++; } return 0; }
运行结果:
七、内存操作函数
之前我们学习的库函数只能操作字符串函数,那我们要操作其他类型的数组该怎么办呢?
答案是:内存操作函数。
1、memcpy
函数原型:void* memcpy(void* destination, const void* source, size_t num);
1、函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存空间;
2、这个函数在遇到‘\0’的时候并不会停下来;
3、如果source和destination有任何的重叠,复制的结果都是未定义的。(在VS的平台上memcpy进行了优化与memmove一样,但是不保证所有平台都进行优化)
讲解:
(1)函数的返回类型和参数:
返回类型:void* —— 指向目标空间的起始地址
参数:①void* dest —— 不知道是指向什么类型地址
②const char* src —— 不知道是指向什么类型地址,且源指向的内容不变
③size_t num —— 拷贝的字节数
(2)如果source和destination有任何的重叠,复制的结果都是未定义的。(在VS的平台上memcpy进行了优化与memmove一样,但是不保证所有平台都进行优化)
#include<stdio.h> #include<string.h> int main() { int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 }; //将arr1中的1,2,3,4,5拷贝到arr1+2中 memcpy(arr1 + 2, arr1 , 20); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
图示:
(3)模拟实现memcpy
#include<stdio.h> #include<assert.h> void* my_memcpy(void* dest, const void* src, size_t num) { //断言指针的有效性 assert(dest && src); //存储目标空间的起始地址 void* ret = dest; //从src指向的位置拷贝num个字节的数据到dest //void* 不能直接使用,必须转化为确定的类型才能使用 while (num) { *(char*)dest = *(char*)src; dest = (char*)dest + 1; src = (char*)src + 1; --num; } return ret; } int main() { int arr1[10] = { 0 }; int arr2[10] = { 1,2,3,4,5,6,7,8 }; //将arr2中的3,4,5,6,7拷贝到arr1中 my_memcpy(arr1, arr2+2, 20); //打印arr1 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
2、memmove
函数原型:void* memmove(void* destination, const void* source, size_t num);
1、和memcpy的差别就是memmove函数处理的源内存块和目标空间内存块是可以重叠的;
2、如果源空间和目标空间出现重叠,就得使用memmove函数处理。
讲解:
(1)模拟memmove
分析:
memmove函数是实现源指向的内容拷贝到目标空间,源空间和目标空间可以重叠。
函数体实现:(如图)
代码演示:
#include<stdio.h> #include<assert.h> void* my_memmove(void* dest, const void* src, size_t num) { //断言指针有效性 assert(dest && src); //保存目标空间的起始地址 void* ret = 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[10] = { 1,2,3,4,5,6,7,8,9,10 }; //将arr1中的1,2,3,4,5拷贝到arr1+2中 my_memmove(arr1 + 2, arr1 , 20); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } return 0; }
3、memcmp
函数原型:int memcmp(const void* ptr1,const void* ptr2,size_t num);
①比较从ptr1和ptr2指针开始的num个字节、
②标准规定:
ptr1 > ptr2,返回正数
ptr1 = ptr2,返回零
ptr1 < ptr2,返回负数
代码演示:
#include<stdio.h> #include<string.h> int main() { int arr1[] = { 1,2,3 }; //VS——小端存储:低位存放低地址 //0x:01 00 00 00 02 00 00 00 03 00 00 00 int arr2[] = { 1,2,5 }; //0x:01 00 00 00 02 00 00 00 05 00 00 00 //比较整形数组,从arr1和arr2开始的9个字节 int ret = memcmp(arr1, arr2, 9); printf("%d\n", ret); return 0; }
运行结果:
4、memset
函数原型:void* memset(void* ptr,int value,size_t num);
1、以字节为单位来设置内存中的数据;
2、填充内存块,将某一块内存中的内容全部设置为指定的值, 通常为新申请内存做初始化工作。
讲解:
(1)函数返回类型和参数:
返回类型:void* ——返回ptr指向的地址
参数:①void* ptr —— 指向要填充内存块的指针(因为不知道类型,所以设为空指针)
②int value —— 要设置的值(为整形家族的值且要注意不要太大,因为是以字节为单位来填充内存的)
③size_t num —— 去设置填充的字节数(因为不知道是什么类型)
代码演示:
代码1:可以使用memset把整形数组初始化为1吗
#include<stdio.h> #include<string.h> int main() { int arr[5] = { 1,2,3,4,5 }; memset(arr, 1, 20); int i = 0; for (i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
运行结果:
我们发现结果和我们预期结果不同,因为memset函数是以字节为单位来设置内存中的数据的。
我们F10调试起来,观察arr内存
代码2:
#include<stdio.h> #include<string.h> int main() { char arr[] = "hello world"; //将arr中的world改为yyyyy memset(arr + 6, 'y', 5); printf("%s\n", arr); //将arr中的hello改为xxxxx memset(arr, 'x', 5); printf("%s\n", arr); return 0; }
运行结果:
字符串函数、内存函数就完结了,希望对大家有帮助。