学习网站
在学习字符函数和字符串函数之前,先给各位推荐一个c语言学习网站:cplusplus.com
--在这个网站里面我们可以查看c语言中各种函数的原型,最好先选用旧版之后再进行查阅--
字符函数
字符分类函数
作用:判断一个字符属于哪种类型的
包含头文件:<ctype.h>
它包含以下多种函数:
这些函数的使用方法都是相似的,我们来选取其中一个函数分析一下下:
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() {/* int i = islower('Q');*/ int i = islower('q'); printf("%d\n", i); return 0; }
练习:将字符串中小写字母转成大写,其余字符不变
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> int main() { char str[] = "i am A sTUDENT"; size_t len = strlen(str); //strlen函数的返回值应该是无符号类型size_t int i = 0; for (i = 0; i < len; i++) { if (islower(str[i])) { str[i] = str[i] - 32; //利用ASCII码将小写字母转换为大写字母 } } printf("%s\n", str); return 0; }
字符转换函数
tolower函数:
函数原型:int tolower ( int c );
包含头文件:<ctype.h>
作用:将参数传进去的大写字⺟转小写
toupper函数:
函数原型:int toupper ( int c );
包含头文件:<ctype.h>
作用:将参数传进去的小写字⺟转大写
实例:
#include <stdio.h> #include <ctype.h> int main() { int i = 0; char str[] = "Test String.\n"; char c; while (str[i]) { c = str[i]; if (islower(c)) //如果传入字母为小写,就利用toupper函数直接小写转大写 c = toupper(c); else c = tolower(c); putchar(c); i++; } return 0; }
与ACSII码方式的比较:
利用toupper函数和tolower函数转换字符大小写与利用 ASCII 码加 32 后强制类型转换字符大小写在功能上是相似的,然而它们之间存在一些区别:
tolower与toupper
函数是标准库提供的函数,具有平台可移植性。这意味着无论你在哪个操作系统或编译器上运行代码,该函数应该都能正常工作。而直接使用 ASCII 码加 32 并进行强制类型转换则依赖于特定的字符编码和实现。tolower与toupper
函数能够处理更多字符集和本地化设置。它不仅适用于基本 Latin 字母(A-Z),还可以处理扩展字母、非拉丁字母以及其他语言中的特殊字符。这使得它们更适合国际化环境下对字符串进行大小写转换。- 直接使用 ASCII 码加 32 并进行强制类型转换可能会导致溢出或未定义行为。如果输入不是大写字母,在某些情况下可能会产生错误结果或导致程序崩溃。
结论:尽管 ASCII 码加 32 的方式在某些简单场景下可行,并且比较高效,但为了保证代码的可移植性、兼容性和正确性,在大多数情况下建议使用标准库提供的 toupper函数和tolower函数。
字符串函数
strlen函数
函数原型:size_t strlen (const char * str)
头文件:<string.h>
作用:求字符串长度
注意事项:
1、strlen的返回类型是无符号类型size_t
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> int main() { const char* str = "abcdef"; const char* str1 = "bbb"; if(strlen(str) - strlen(str1) > 0) { printf("str1>str\n"); } else { printf("str1<str\n"); } return 0; }
输出结果与我们想的好像不太一样,这是因为返回类型为两个无符号类型的3和6,无符号类型之间的运算结果肯定还是无符号数,而无符号数的定义是大于等于0的,所以str1>str。如果将两个strlen函数的返回值强制类型转换为int型结果就为str1<str了
2、返回字符串'\0'前出现的字符个数但不包含'\0',字符串必须以 '\0'结尾
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> int main() { char str[] = { 'a','v','b','c' }; size_t len = strlen(str); printf("%zd\n", len); //打印无符号型不能使用%d了,要用%zd return 0; }
预期输出结果为4,但是实际输出结果为74远超预期,这就是因为字符串结束后后面没有添加'\0'导致的,如果添加一个'\0',结果就会是4了。
~~注意:常量字符串系统默认后面有\0~~
练习:strlen函数模拟实现
方法一:利用指针解引用,assert断言,指针+整数
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> size_t my_strlen(const char* str) { int count = 0; assert(*str != NULL); //加入assert断言,保证传入的字符串非空 while (*str != '\0') //当str指向的那个数字*str不是\0时就接着循环,然后count++ { count++; str++; //str是指针,++代表指向的地址加1,指向下一个字符 } return count; } int main() { char str[] = "abcdef"; size_t len = my_strlen(str); printf("%zd\n", len); return 0; }
方法二:指针-指针 ==> 地址-地址,得到指针之间元素个数
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> size_t my_strlen(const char* str) { const char* start = str; while (*str != '\0') { str++; } return str-start; } int main() { char str[] = "abcdef"; size_t len = my_strlen(str); printf("%zd\n", len); return 0; }
方法三:递归方式
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> size_t my_strlen(const char* str) { if(*str != '\0') return 1 + my_strlen(str+1) //这里不能写成str++,或者++str,因为++后的确是获得新地址 else //但是str指向地址仍不变 return 0; } int main() { char str[] = "abcdef"; size_t len = my_strlen(str); printf("%zd\n", len); return 0; }
学前提醒:
!!不会拷贝或追加空字符!!
strcpy函数
函数原型:char* strcpy(char* destination,const char* source)
作用:拷贝字符串
头文件:<string.h>
注意事项:
1、从目标空间的起始地址拷贝,且源字符串必须以'\0'结尾
因为strcpy只有拷贝到’\0‘才会停下来,不加’\0‘就会出现访问冲突问题。
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> int main() { char arr1[] = {'a','b','c'} char arr2[4] = "xxx"; strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
2、目标空间要足够大,且目标可以修改
练习:strcpy函数模拟实现
#include <stdio.h> #include <assert.h> char* my_strcpy(char* dest,const char* src) //加const修饰源字符串,保证传递过程中源 { //字符串指向的空间不变,char*是因为函数原型的返回类型是char* char* ret = dest; //定义一个用于存放目标空间起始地址的一级指针ret assert(dest && src); //保证dest和src都不为空指针 while (*dest++ = *src++) { ; } return ret; //返回目标空间的起始地址,stcpy功能是将源字符串的内容拷贝到目标空间,期望目标空间的内容发生变化,所以返回目标空间的起始地址,方便观察目标空间的数据 } int main() { char arr1[] = "abcdef"; char arr2[20] = { 0 }; printf("%s\n",my_strcpy(arr2,arr1)); return 0; }
常量字符串
①常量字符串是指在程序中无法被修改的字符串
②通常由双引号或单引号包围,例如:“Hello, World!”或'42'
③常量字符串有效字符个数 < 规定字符个数,因为还要为\0保留位置
比如:char arr2[4] = ”xxx“;
由于常量字符串无法修改,所以在使用*p = 'w'时,系统会出现访问权限冲突问题,但是在程序运行之前系统是不会报错的,所以这时候我们可以增加一个const修饰*p从而使其从源头上就不能修改*p的值,此时再书写*p = 'w'时系统就会系统就会报错:
strcat函数
函数原型:char* strcat(char* destination,const char* source)
作用:字符串追加
头文件:<string.h>
注意事项:
1、从目标空间的第一个\0处开始追加,同时覆盖掉该\0,且源字符串必须以'\0'结尾
追加位置规定从目标字符串的\0处开始,但\0可以人为添加,故这里说从第一个\0处开始追加
#include <stdio.h> #include <string.h> int main() { char arr1[20] = "hello \0xxxxxxxxx"; char* p = "world"; strcat(arr1, p); printf("%s\n", arr1); return 0; }
2、目标空间要足够大,且目标可以修改
3、不能实现对自身的追加
char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest && src); //找到目标空间的'\0' while (*dest != '\0') { dest++; } //拷贝数据 while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[20] = "abcdef"; my_strcat(arr1, arr1); printf("%s\n", arr1); return 0; }
通过画图来理解一下,刚开始的时候dest和src是这样的:
dest在经历了while (*dest != '\0')后:
然后开始执行while (*dest++ = *src++)后:
如果arr1的空间足够大,src是永远也不可能追上dest的,同时当dest指向数组边界时,由于src还没有追上dest,就会导致数组越界访问了
练习:strcat函数模拟实现
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> char* my_strcat(char* dest, const char* src) { char* ret = dest; assert(dest && src); //找到目标空间的'\0' while (*dest != '\0') { dest++; } //拷贝数据 while (*dest++ = *src++) { ; } return ret; } int main() { char arr1[20] = "hello \0xxxxxxxxx"; char* p = "world"; my_strcat(arr1, p); printf("%s\n", arr1); return 0; }
strcmp函数
函数原型:int strcmp (char* str1,char* str2)
作用:比较两个字符串,依据比较结果返回整数
头文件:<string.h>
注意事项:
1、第一个字符串大于第二个字符串,返回大于零的整数
2、第一个字符串等于第二个字符串,返回等于零的整数
3、第一个字符串小于第二个字符串,返回小于零的整数
4、只能返回第一次在同一位置上两个字符不相等时,两字符的ASCII码差值,并不会返回两个字符串ACSII码的整体差值
练习:strcmp函数模拟实现:
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> int my_strcmp(const char* s1,const char* s2) //比较时str1和str2不能被改变 { assert(s1 != NULL); assert(s2 != NULL); while (*s1 == *s2) //如果两个字符串在同一位置的字符上相同,进入循环 { if (*s1 == '\0') //如果同时s1在该位置上的字符为'\0',证明两个数完全相等,返回零 return 0; s1++; //++继续向后面逐位比较 s2++; } return *s1 - *s2; //如果此时s1和s2指向的字符不同,就返回两个字符在ASCII码中的差值 } int main() { int ret = my_strcmp("abg", "abc"); printf("%d\n", ret); return 0; }
思考:
我们在上面学习了strcpy函数、strcat函数、strcmp函数,我们发现它们在拷贝、追加、比较的时候是直接将提供的字符串全部进行操作,并没有明确规定到底要操作多少内容, 我们统称这类函数是不受长度限制的字符串函数,下面我们就来学习它们三个的升级版,受长度限制的字符串函数:strncpy函数、strncat函数、strncmp函数。
strncpy函数
函数原型:char* strcpy(char* destination,const char* source,size_t num)
作用:拷贝字符串(将source字符串中num个字节的字符拷贝给destination字符串)
头文件:<string.h>
注意事项:
1、从目标空间起始地址开始拷贝,源字符串长度 >= num,有多少拷贝多少
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "xxxxxxxxxx"; char arr2[] = "abcdef"; strncpy(arr1, arr2, 3); printf("%s\n", arr1); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "xxxxxxxxxx"; char arr2[7] = "abcdef"; strncpy(arr1, arr2, 7); printf("%s\n", arr1); return 0; }
2、源字符串长度 < num,不足部分自动补'\0'
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "xxxxxxxxxx"; char arr2[] = "abc"; strncpy(arr1, arr2, 5); printf("%s\n", arr1); return 0; }
练习:strncpy函数模拟实现
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> char* my_strcpy(char* dest,const char* src,size_t num) { int i = 0; for (i = 0; *(src + i) && i < num; i++) { *(dest + i) = *(src + i); } while (i < num) { *(dest + i) = '\0'; //由于是自定义函数末尾加'\0'需要依靠循环人为添加,系统不会自己添加 i++; } return dest; } int main() { char arr1[20] = "abc\0xxxxxxx"; char arr2[] = "def"; size_t len = strlen(arr2); printf("%s\n",my_strcpy(arr1,arr2,5)); return 0; }
strncat函数
函数原型:char* strcat(char* destination,const char* source,size_t num)
作用:字符串追加(将source字符串中num个字节的字符在destination字符串第一个\0后追加)
头文件:<string.h>
注意事项:
1、源字符串长度不论与num的大小关系为多少,追加完成后末尾必定补'\0'
源自符串长度大于num:
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "abc\0xxxxxxx"; //目标空间从/0后开始追加 char arr2[] = "defghi"; strncat(arr1, arr2, 5); printf("%s\n", arr1); return 0; }
源自符串长度等于num:
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "abc\0xxxxxxx"; //目标空间从/0后开始追加 char arr2[] = "defghi"; strncat(arr1, arr2, 7); printf("%s\n", arr1); return 0; }
源自符串长度小于num:
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "abc\0xxxxxxx"; char arr2[] = "def"; strncat(arr1, arr2, 5); printf("%s\n", arr1); return 0; }
练习:strncat函数模拟实现
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> char* my_strncat(char* dest, const char* src,size_t num) { char* ret = dest; assert(dest && src); while (*dest != '\0') { dest++; } for (int i = 0; i < num; i++) { *dest = *src; dest++; src++; } return ret; } int main() { char arr1[20] = "hello \0xxxxxxxxx"; char* p = "world"; my_strncat(arr1, p,3); printf("%s\n", arr1); return 0; }
strncmp函数
函数原型:int strcmp (char* str1,char* str2,size_t num)
作用:比较两个字符串,依据比较结果返回整数
头文件:<string.h>
注意事项:
1、比较两个字符串中前num个字符是否相等,相比于strcmp函数它能规定比较范围,返回值情况与strcmp函数相同,小于零证明s1小于s2,大于零证明s1大于s2,等于零证明s1等于s2
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> int main() { char arr1[20] = "abcdef"; char arr2[] = "abcdge"; int ret = strncmp(arr1, arr2, 5); printf("%d\n", ret); return 0; }
练习:strncmp函数模拟实现
这次的模拟并不是简单的模拟了,我们让它实现了一些功能,只依靠随机返回的大于小于或者等于这些值看起来没多大用处,我们让他能返回两字符串前num个字符ASCII码总值的差值,这样既能起到一定的实际用处又能跟原来一样通过返回的值判断两个字符串是否相同
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> int my_strncmp(const char* s1, const char* s2,size_t num) //比较时str1和str2不能被改变 { assert(s1 != NULL); assert(s2 != NULL); int sum = 0; for (int i = 0; i < num; i++) { if (*s1 == *s2) { if (*s1 == '\0') return 0; } else { sum += *s1 - *s2; } s1++; s2++; } return sum; } int main() { int ret = my_strncmp("zqp", "yyf",2); printf("%d\n", ret); return 0; }
strstr函数
函数原型:char * strstr ( const char *str1, const char *str2 )
作用:返回str2在str1第一次出现的位置,如果没有出现,就返回空指针NULL
头文件:<string.h>
注意事项:无
练习:strstr函数模拟实现
~~请点开所有代码,以便查看全部注释~~
const char* my_strstr(const char* str1, const char* str2)//str1指向arr1数组的首字符地址,str2指向arr2数组的首字符地址 { //判断str1和str2是否为空 assert(str1); assert(str2); const char* cp = str1; //cp用于记录可能找到匹配的字符串的起始位置,当cp位于有可能匹配成功的位置,这时候就轮到s1和s2开始动了 const char* s1 = NULL; const char* s2 = NULL; //如果子串是空字符串,直接返回str1 if (*str2 == '\0') return str1; while (*cp) //如果str1非空,那么就会从str1的第一个字符开始 { s1 = cp; //起始时s1也指向arr1数组首字符地址 s2 = str2; //起始时s2也指向arr2数组首字符地址,同时也是防止在可能找到匹配字符的起始位置之后的几个位置寻找后并未找到可以完全匹配的内容,方便下一次的比对,总之就是为了方便每次匹配失败后的再次匹配 while (*s1 == *s2 && *s1 && *s2) //如果s1和s2指向的字符不相等那么不会进入循环 { s1++; //如果s1和s2指向内容相等且s1和s2均不为'\0'时,证明还可以继续向后走所以++ s2++; } if (*s2 == '\0') //当子字符串此时指的位置为'/0'时,证明此时子字符串已经读取完了 return cp; cp++; //如果一直找不到cp一直++,最后*cp就会指向arr1数组的'\0'了,循环结束返回一个空值 } return NULL; } int main() { char arr1[] = "abbbcdef"; char arr2[] = "bbc"; char* ret = my_strstr(arr1,arr2); if (ret != NULL) printf("%s\n", ret); else printf("找不到\n"); return 0; }
tips:return char* 类型的指针后,系统会自动将以该指针指向的位置为起始位置,然后自动向后面访问整个字符串,这也就是为什么return cp后虽然是一个地址但是最后打印的内容是bbcdef了
可能对模拟实现还有疑问,下面我们通过画图的办法模拟指针的运动帮助大家理解:
1、第一次循环前
2、第一次循环后,第二次循环前
3、第二次循环内部过程
4、第二次循环后,第三次循环前
5、第三次循环过程
最后一次while (*s1 == *s2 && *s1 && *s2)后,s2++满足*s2 == '\0',此时返回存储的首地址
strtok函数
函数原型:char * strtok ( char * str, const char * sep)
作用:分割字符串
头文件:<string.h>
注意事项:
1、sep指向由多个分隔符组成的字符串;str指向待分割字符串;
2、找到待分割字符串中的首个分隔符后用\0代替,此时\0与前面的一段字符都会被看作一个标记
3、函数的第二个参数一直都是sep即由分隔符组成的字符串
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main() { char arr[] = "192.168.6.111"; char* sep = "."; char* str = NULL; for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep)) { printf("%s\n", str); } return 0; }
4、被分割的字符串一般都先放在创建的临时数组中
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> int main() { char arr[] = "192.168.12.102"; char buf[30] = {0}; //将字符串拷贝给buf字符数组,然后想怎么改就怎么改了,最后结果 strcpy(buf,arr); //也不会影响arr字符数组里的字符串 char* sep = "."; //.为分隔符 char* s = strtok(buf,sep); printf("%s\n", s); return 0; }
5、strtok函数第一次传入的都是非空指针即原始字符串作为第一个参数传递给函数,但是第二次之后传入的都是空指针,这样函数会从上一次找到标记位置的下一个字符开始查找下一个标记
练习:strtok函数的使用(for循环分割很长内容)
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> int main() { char arr[] = "yanhyf@yeah.net"; char buf[60] = { 0 }; strcpy(buf, arr); char* p = "@."; //.为分隔符 char* r = NULL; //使用前先置为空 for (r = strtok(buf, p); r != NULL; r = strtok(NULL, p)) { //(初始化部分 ; 循环判断条件 ; 再次调用) printf("%s\n", r); } return 0; }
streeor函数
函数原型:char * strerror ( int errnum)
作用:返回错误码(类似于http中的整数404)所对应的错误信息字符串的起始地址
头文件:<errno.h>
注意事项:
1、c语言每个错误码都有其所对应的错误信息字符串
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> int main() { int i = 0; for (i = 0;i<10; i++) { char* ret = strerror(i); printf("%d : %s\n", i, ret); } return 0; }
strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来,在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 <errno.h> 这个头⽂件中说明的,C语⾔程序启动的时候就会使⽤⼀个全局变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表⽰没有错误,当我们在使⽤标准库中的函数时发⽣了某种错误,就会将对应的错误码存放在errno中,⽽⼀个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误码对应的错误信息字符串的地址返回,从而让我们开到具体的错误信息。
2、当库函数调用失败的时候,会将错误码记录到errno这全局个变量中
#define _CRT_SECURE_NO_WARNINGS 1 #include<ctype.h> #include<stdio.h> #include<string.h> #include<assert.h> #include<errno.h> int main() { FILE* pf = fopen("add.txt", "r"); if (pf == NULL) //当pf指向的文件不存在时,系统就会自动感受到错误从而生成错误码并将错误码交给全局变量errno { /*printf("打开文件失败,失败的原因:%s\n", strerror(errno));*/ perror("fopen"); printf("fopen:%s\n", strerror(errno)); return 1; } else { printf("打开文件成功\n"); //...... } return 0; }
perror的作用就相当于下一行的printf("fopen:%s\n", strerror(errno)) 二者打印结果一样:
对fopen()函数的简单理解:
fopen()函数会获取文件信息,包括文件名、文件状态、当前读写位置等,并将这些信息保存到一个 FILE 类型的结构体变量中,然后将该变量的地址返回。
如果希望接收 fopen() 的返回值,就需要定义一个 FILE 类型的指针。例如:
FILE* fp = fopen("demo.txt", "r");
表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针
再来看一个例子:
FILE* fp = fopen("D:\\demo.txt", "rb+");
表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。
3、在创建文件时,记得将文件夹 "查看" 里面的"文件扩展名"勾选上