提示: 本篇文章涉及到以下内容:
求字符串长度
strlen
长度不受限制的字符串函数(被VS认为不安全,就像scanf)–>非法也要完成任务
strcpy 拷贝(将原字符串内容和\0全拷贝过去)
strcat 追加(先找到目标空间中的\0,然后把原字符串中的内容直到\0全拷贝过去,原字符串中的\0也会被追加过去)
strcmp 字符串内容比较
长度受限制的字符串函数介绍
strncpy 拷贝(字符不够,0来凑)
strncat 追加(追加不够,不追了)
strncmp 字符串比较(可控制比较字符的个数)
字符串查找
strstr
strtok
错误信息报告
strerror
字符分类函数
iscntrl
isspace
isdigit
isxdigit
islower
isupper
isalpha
isalnum
ispunct
isgraph
isprint
字符转换函数
tolower
toupper
内存操作函数
memcpy
memmove
memset
memcmp
文章目录
1.strlen 求字符串的长度
头文件 #include<string.h>
注意要点:
(1)字符串已经以\0作为结束标志,strlen函数返回的是在字符串中\0前面出现的字符个数(不包含\0)
char arr[]="abc\0def"; int len=strlen(arr); printf("%d\n",len); //3
(2)函数指向的字符串必须要以\0结束
char arr[3] = { 'a','b','c' }; int len = strlen(arr); printf("%d\n", len); //随机值
(3)注意函数的返回值为size_t,实物符号的(易错)
const char* str1 = "abcdef"; const char* str2 = "bcd"; if (strlen(str2) - strlen(str1) > 0) { printf("str2>str1\n"); } else { printf("str2<str1\n"); } return 0;
运行结果
明明看着str2的长度比str1的长度短,为什么这里是str2>str1呢?
因为strlen函数的返回类型是size_t(无符号整型)
按说是3-6=-3,但是在内存中把-3这个结果当做无符号整型,也就是默认它为正数,正数的原码也就是它的补码,所以直接把它的补码当做原码打印出来(正数的原码反码补码全都一样,如果是负数要先转换成原码打印出来),所以结果一定是个>0的数字
三种方法模拟实现strlen函数
(1)计数器
//1.计数器 #include<assert.h> #include<stdio.h> #include<string.h> //这里将my_strlen自定义函数的返回类型定义为int,当遇到strlen("abdcf")-strlen("shfudgfu")这种情况计算结果时不容易出错 int my_strlen(const char* str) { //求长度时只需要遍历字符串,不需要修改它,所以用const来保护指针所指向的内容 int count = 0; assert(str); //担心str是空指针,用assert()来断言一下str是不是空指针,保证指针的有效性 while (*str != '\0') { count++; str++; } return count; } int main() { char arr[] = "bit"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
(2)递归
int my_strlen(const char* str) { if (*str != '\0') { return 1 + my_strlen(str+1); } else { return 0; } } int main() { char arr[] = "bit"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
(3)指针-指针
int my_strlen(char* s) { char* p = s; while (*p != '\0') { p++; } return p - s; } int main() { char arr[] = "bit"; int len = my_strlen(arr); printf("%d\n", len); return 0; }
2.strcpy 字符串拷贝
头文件#include<string.h>
注意要点
(1)源字符串必须以\0结束
比如char arr1[ ]=“abc\0def”;
将arr1拷贝到一个数组里面的时候,拷贝的是abc
(2)会将源字符串中的\0拷贝到目标空间
比如char arr2[3]={‘a’,‘b’,‘c’}; 无法正确拷贝
(3)目标空间必须足够大,以确保能存放源字符串(不够大时事实上也可以拷贝,运行之后程序会崩溃,非法也要完成任务)
(4)目标空间必须可变
(注意数组可以被改变,变量也可以被改变)
不能写成
char arr3[20]=arr1;//error
也不能写成
arr4=arr5;//error
注意是把数组的内容放到地址所指向的空间里面去,而不是放地址
正确示范
#include<stdio.h> #include<string.h> int main() { char arr1[] = "abcdef"; char arr2[20] = {0}; strcpy(arr2, arr1); printf("%s\n", arr2); return 0; }
错误示范—>目标空间必须是可以修改的
int main() { char* p = "abcdefghi"; //p指向的是常量字符串,不能被修改 char arr2[20] = "hehe"; strcpy(p, arr2); printf("%s\n", p); return 0; }
strcpy的模拟实现
模拟实现的功能:
(1)希望源头拷贝到目的地,目的地发生变化,感知它的变化,应返回目标空间的起始地址
(2)目的地里面数据发生变化,源头里数据不发生变化,所以用const保护起来
#include<assert.h> char* my_strcpy(char* dest, const char* src) { char* ret = dest; //在最开始的时候把目的地址保存起来 assert(dest && src); //断言保证这两个指针的有效性 while (*dest++ == *src++) { // \0的ASCII值是0,就不执行*dest=*src这条指令了 ; } return ret; } int main() { char arr1[] ="hehe"; char arr2[20] = {0}; my_strcpy(arr2,arr1); printf("%s\n", arr2); //printf("%s\n", my_strcpy(arr2,arr1);); return 0; }
3.strcat 字符串追加
追加的时候是在目的地\0处,把\0覆盖,源头中的\0也会被追加过去–>所以目标空间内必须有\0(知道从哪里开始追加
注意要点
(1)源字符串必须以\0结束
(2)目标空间必须足够大,能容纳下源字符串的内容
(3)目标空间必须可修改
(4)字符串自己给自己追加,用strcat不适合
简单的应用
int main() { char arr1[20] = "hello"; char arr2[] = "world"; strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
模拟实现strcat
my_strcat 实现思路:
先找到目的空间的第一个\0,然后拷贝字符串
#include<assert.h> char* my_strcat(char* dest, const char* src) { assert(dest && src); //assert断言保证两个指针的有效性 char* ret = dest; //最开始把目的地址保存起来 while (*dest != '\0') { dest++; } while (*dest++ = *src++) { //拷贝 ; } return ret; }
主函数:(一个字符串在另一个字符串后面追加,追加成功)
int main() { char arr1[20] = "hello"; char arr2[] = "world"; my_strcat(arr1, arr2); printf("%s\n", arr1); return 0; }
主函数:(自己给自己追加,追加失败)
int main() { char arr1[20] = "bit"; my_strcat(arr1, arr1); printf("%s\n", arr1); return 0; } //error
在自己给自己追加的时候用strcat是有问题的,因为在追加的时候原字符串中的\0被覆盖,指针在原字符串中找不到\0,会一直陷入死循环追加,停不下来
4.strcmp 字符串内容比较
注意比较两个字符串的内容时不能用==,应使用strcmp
if(“abcdef”==“bcdefg”) //这里比较的是两个字符串首字符的地址,并不是字符串的内容
标准规定
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
所以strcmp函数返回的结果要用有符号整型int来接收
如何判断两个字符串?
比的是对应位置上的ASCII值大小如果发现有一个不相等的,就可以比较出来了
a < b (a的ASCII值小于b的ASCII值)
模拟实现strcmp
#include<assert.h> int my_strcmp(const char* str1, const char* str2) { assert(str1&&str2); //保证两个指针非空 while (*str1 == *str2) { //2个字符串相等的情况在while循环里面 if (*str1 == '\0') { return 0; } str1++; str2++; } if (*str1 > *str2) return 1; else return -1; //上面的if...else...太啰嗦,可以改为return *str1-*str2; } int main() { char arr1[] = "abcdef"; char arr2[] = "acdf"; int ret = my_strcmp(arr1, arr2); printf("%d\n", ret); return 0; }
注意:
不要固定思维,函数返回的是大于0,小于0,0的数字,strcmp并不是只返回1,-1,0
5.strncpy 字符串拷贝( 控制拷贝个数)
(字符不够,0来凑)
注意要点
(1)拷贝num个字符从原字符串到目标空间
(2)如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后面追加0.直到num个
简单应用
int main() { char arr1[] = "abcdef"; char arr2[20] = "xxxx"; strncpy(arr2, arr1, 3); printf("%s\n", arr2); return 0; }
6.strncat 追加(控制追加几个字符)
追加不够个数,就不追加了
可以自己给自己追加
简单应用
int main() { char arr1[20] = "hello"; char arr2[] = "abcdef"; strncat(arr1, arr2, 3);//将arr2中的3个字符追加到arr1后面 printf("%s\n", arr1); return 0; }
int main() { char arr1[20] = "hello \0xxxxxxxx"; char arr2[] = "abcdef"; strncat(arr1, arr2, 8); printf("%s\n", arr1); return 0; }
追加不够个数就不追加了
7. strncmp 字符串比较(控制比较的字符个数)
如果num大于要比较的2个字符串的长度,实际上根本不用比到num个就比出大小了
注意要点
比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完
strcat_s函数
定义
strcat_s(char* destination,size_t num,const char* source)
8.strstr 在一个字符串中找另一个字符串第一次出现的位置
在str1字符串中找str2字符串出现的位置
如果在str1中没有找到str,那么返回的就是空指针
简单应用
int main(){ char arr1[] = "abcdef"; char arr2[] = "bcd"; char* p = strstr(arr1, arr2); if (p == NULL) { printf("找不到\n"); } else { printf("%s\n", p); } return 0; }
模拟实现strstr
char* my_strstr(const char* str1, const char* str2) { //在查找的过程中不会修改str1,str2字符串,用const保护起来 char* s1 = NULL;//用来维护遍历第一个字符串 char* s2 = NULL;//用来维护遍历第二个字符串 char* cp = (char*)str1; //cp用来记录每次开始匹配的位置 //str1是const修饰的指针`,是一个相对安全的指针.cp是char*类型,将相对安全的指针交给不安全的指针,权限被放大,编译器报警告,因此这里需要将str1进行强制类型转换一下 while (*cp) { //*cp指向被遍历的串,当*cp不是\0说明这个串还没完 s1 = cp; //将开始匹配的位置交给s1,让s1从这里开始遍历 s2 = (char*)str2; //将arr2的起始地址交给s2;str2这里也需要进行强制类型转换 while (*s1 && *s2 && *s1 == *s2) {//当*s1指向\0会停下来,*s2指向\0也会停下来 //s1,s2指向的内容相等那么遍历指针同时++ s1++; s2++; } if (*s2 == '\0') { //当*s2指向\0说明子串匹配完 return cp; //返回最开始匹配的位置 } cp++; //s1内容和s2内容不相等,说明当前位置匹配是失败的,cp++,从下一个位置再开始匹配 } //上面的循环结束说明第一个字符串里面的字符全都匹配了一遍也没有找到第二个字符串,那么此时结果就是找不到 return NULL; //如果在循环中始终没有找到子串,没有返回cp,return空指针说明找不到 } int main() { char arr1[] = "abcdef"; char arr2[] = "bcd"; char* p = my_strstr(arr1, arr2); if (p == NULL) { printf("找不到\n"); } else { printf("%s\n", p); } return 0; }
思路分析:
(1)
1次匹配成功的情况:
首先看str2指向的内容和str1指向的内容是否相等,发现不相等,被查找的字符串str1中的指针++,继续查找,相等的情况下,两个指针同时往后走
(2)
多次匹配成功的情况:
在这种情况下,当第一个b匹配相等时,指针各自往后走.此时不相等,但是不意味着在后面就找不到arr2,而是说明从arr1中当前指针所在位置找不到arr2,但并不代表他的后面没有.所以应该从arr1当前指针所在位置的下一个位置处开始再匹配.arr1中的指针应回到刚才开始匹配的位置,arr2中的指针应回到arr2字符串的起始位置.
注意点(要记住的位置)
(1)arr1:要记住上一次是从哪里开始匹配的,如果匹配不相等,应从上一次开始匹配的位置的下一个位置开始匹配 -->设置cp指针记住这个位置
(2)arr2:当指针不断往后走应该记住这个字符串的起始位置,一旦匹配失败了,让它直接返回起始位置重新开始匹配 -->str2就是arr2字符串的起始位置,将它的地址赋值给遍历指针s2
9.strtok 切割有标记符的字符串
注意要点
(1)sep参数是个字符串,定义了用作分隔符的字符集合
(2)第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分隔的标记
(3)strtok函数找到str中的下一个标记,并将其用\0结尾(也就是说将标记符改为\0),返回一个指向这个标记的指针
(注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
(4)第一次调用:
strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置
(5)第二次到第n次调用:
strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
(注意:NULL空指针要包含头文件#include<stdio.h>)
(6)最后一次调用的时候,字符串中不存在更多的标记,则返回NULL指针
(7)strtok函数特殊的地方在于:
它具有记忆的功能.调用之后还记得原字符串位置等信息
使用场景
例如:
(1)IP地址:192.168.10.5
能否将192和168和10和5这四个部分提取出来呢?
(2)邮箱:zhuimin@yeah.net
这里的@ 和. 叫做分隔符.
能否将被分割开的三部分提取出来呢?
简单应用
int main() { char arr[] = "zhuimin@yeah.net"; char buf[30] = { 0 }; strcpy(buf, arr); //拷贝一份 const char* p = "@."; //标记符集合的字符串 char* str = strtok(buf, p); //第一次分割:在buf中找到p中的第一个标记符,将其修改为\0,保存并记住这个标记符的位置,函数返回标记段的起始位置 printf("%s\n", str); str = strtok(NULL, p); //第二次分割:strtok从第一次切割时保存的标记符位置处查找下一个标记 printf("%s\n", str); str = strtok(NULL, p); //第三次分割:strtok从第二次分割后保存的位置处查找下一个标记,发现此时字符串中没有更多的标记,结果返回空指针 printf("%s\n", str); return 0; }
如果是n次,也要写n次调用函数太麻烦了
优化一下
int main() { char arr[] = "zhuimin@yeah.net"; char buf[30] = { 0 }; strcpy(buf, arr); //拷贝一份 const char* p = "@."; //标记符集合的字符串 char* str = NULL; for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p)) { printf("%s\n", str); } return 0; }
错误示范
因为这里的buf是常量字符串,有const修饰不能被修改,所以调用strtok时会出错.
10.strerror 把错误码翻译成错误信息,返回的是错误信息的起始地址
注意要点
其实:
(1)C语言的库函数在调用失败的时候,会将一个错误码存放在一个叫errno的变量中,当我们想知道调用哪个函数的时候发生了什么错误信息,就可以将errno中的错误码翻译成错误信息
(2)调用多个库函数的时候,大家是共用errno的,要及时去观察errno,下一次调用它的值会被修改
比如说:
库函数1调用失败//errno:3
库函数2调用成功//errno:0
简单应用
int main() { char* p = strerror(0); printf("%s\n", p); p = strerror(1); printf("%s\n", p); p = strerror(2); printf("%s\n", p); p = strerror(3); printf("%s\n", p); p = strerror(4); printf("%s\n", p); return 0; }
int main() { //打开文件 //打开文件的时候,如果文件的打开方式是"r" (读) //文件存在则打开成功,文件不存在则打开失败 //打开失败的时候,会返回NULL FILE* pf = fopen("text.txt", "r"); if (pf == NULL) { printf("打开文件失败,原因是%s\n", strerror(errno)); return 1; } //读写文件 //.... //关闭文件 fclose(pf); pf = NULL; return 0; }
注意:新建一个文本文档时看一下有没有有开启文件扩展名,又饿能创建成test.txt.txt
11.字符分类函数
头文件 <ctype.h>
函数 判断一个字符是不是
iscntrl 任何控制字符
isspace 空白字符:空格’ ‘;换页’\f’;换行’\n’;回车’\r’,制表符’\t’或者垂直制表符’\v’
isdigit 十进制数字’0’~‘9’(是数字字符的话,返回非0数字,不是数字字符,返回0)
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 任何可打印字符,包括图形字符和空白字符
字符转换
tolower 转换成小写字符
toupper 转换成大写字符
例如:
printf(“%c\n”,tolower(‘X’)); //x
printf(“%c\n”,toupper(‘x’)); //X
字符转换函数的简单使用
#include<stdio.h> #include<ctype.h> void test() { char arr[120] = { 0 }; gets(arr); int i = 0; while (arr[i]){ if (isupper(arr[i])) { arr[i] = tolower(arr[i]); } printf("%c", arr[i]); i++; } } int main() { test(); return 0; }
12.memcpy 内存拷贝(仅两个不重叠的内存空间可用)
注意要点
(1)函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置
(2)这个函数在遇到’\0’的时候并不会停下来
(3)如果source和destination有任何的重叠,复制的结果都是未定义的
(4)void* 通用类型指针,可以接受任意类型数据的地址,但是这种指针不能直接解引用和±操作
简单应用
将arr1中的前五个元素拷贝到arr2中
void test1() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[8] = { 0 }; //将arr1中的前五个元素拷贝到arr2中 //元素类型是int类型,五个元素总共是20字节 memcpy(arr2, arr1, 20); int i = 0; while (arr2[i]){ printf("%d ",arr2[i]); i++; } } int main() { test1(); return 0; }
将arr1中的前3个元素拷贝到arr2中
void test2() { float arr1[] = { 1.0f,2.0f,3.0f,4.0f,5.0f,6.0f,7.0f,8.0f,9.0f,10.0f }; float arr2[8] = { 0 }; //将arr1中的前3个元素拷贝到arr2中 memcpy(arr2, arr1, 12); int i = 0; while (arr2[i]){ printf("%f ",arr2[i]); i++; } } int main() { test2(); return 0; }
模拟实现memecpy
#include<assert.h> void* my_memcpy(void* dst, const void* src, size_t count) { void* ret = dst; //临时变量保存dst地址 assert(dst && src); //断言两个指针不能为空指针 while (count--) { //每次处理一个字节,将指针强制类型转换成char* *(char*)dst = *(char*)src; //一个字节一个字节的拷贝(注意:强制类型转换是临时的,转换完解引用dst还是void*类型 dst = (char*)dst + 1; //跳过一个字节后的地址给dst,dst是void类型,可以接收 src = (char*)src + 1; } return ret; }
想将1,2,3,4,5拷贝到3,4,5,6,7的位置
#include<assert.h> void* my_memcpy(void* dst, const void* src, size_t count) { void* ret = dst; //临时变量保存dst地址 assert(dst && src); //断言两个指针不能为空指针 while (count--) { //每次处理一个字节,将指针强制类型转换成char* *(char*)dst = *(char*)src; //一个字节一个字节的拷贝(注意:强制类型转换是临时的,转换完解引用dst还是void*类型 dst = (char*)dst + 1; //跳过一个字节后的地址给dst,dst是void类型,可以接收 src = (char*)src + 1; } return ret; } void test3() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; my_memcpy(arr1 + 2, arr1, 20); //想要把1,2,3,4,5拷贝到3,4,5,6,7的位置 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr1[i]); } } int main() { test3(); return 0; }
预想效果: 1 2 1 2 3 4 5 8 9 10
实际效果: 1 2 1 2 1 2 1 8 9 10
为什么会是现在这样的效果呢?
在拷贝3到5的位置时的时候,其实原来3位置已经被之前拷贝的数据覆盖修改过了,变成1了
所以,我们发现在内存重叠的时候,使用memcpy会出现意想不到的效果
13.memmove 内存拷贝(两个重叠内存和不重叠内存都可以使用)
注意要点
(1)和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
(2)如果源空间和目标空间出现重叠,就得使用memmove函数处理
memmove的功能包含memcpy
总的来说,mommove的功能100分,memcpy功能有60分
但是在vs编译器上二者都做到了100分的程度(也就是说在vs上二者功能一致)
模拟实现memmove
实现思路
(1)首先我们应该清楚:
(2)然后我们固定源数据位置,根据上面的图讨论dst在不同位置时应该是从前往后拷贝还是从后往前拷贝?
(3)分情况讨论:
方案A:
1,3区域:前–>后拷贝
2区域:后–>前拷贝
方案B:
1区域:前–>后拷贝
2,3区域:后–>前拷贝
上面两个方案,显然方案B比较简单
方案B代码规划:
if(dst<src)
{
前–>后拷贝; //==偷懒小技巧:my_memcpy模拟函数就是从前往后拷贝,可以把代码直接拿过来使用
}
else{
后–>前拷贝;
}
(4)代码实现
void my_memmove(void* dst,void* src,size_t num) { void* ret = dst; assert(dst && src); if (dst < src) { //从前往后拷贝 while (num--) { *(char*)dst = *(char*)src; dst = (char*)dst+1; src = (char*)src+1; } } else { //从后往前拷贝 while (num--) { *((char*)dst + num) = *((char*)src + num); } } return ret; }
14.memcmp 内存比较
(任意给两块内存,就可以比较这两块内存中放的数据是否一样)
注意要点
(1)比较从ptr1和ptr2指针开始的num个字节
(2)返回值如下:
ptr1指针处的数据<ptr2指针处的数据 返回<0的数字
ptr1指针处的数据=ptr2指针处的数据 返回0
ptr1指针处的数据>ptr2指针处的数据 返回>0的数字
简单应用
void test5() { int arr1[] = { 1,2,3,4,5 }; int arr2[] = { 1,2,3,4,6 }; int ret1 = memcmp(arr1, arr2, 16); int ret2 = memcmp(arr1, arr2, 17); printf("%d\n", ret1); printf("%d\n", ret2); } int main() { test5(); return 0; }
15.memset 内存设置函数
把ptr指向的后面num个字节每个的内存块设置为value
简单应用
//将hello改为xxxxx void* test6() { char arr[] = "hello world"; memset(arr, 'x', 5); printf("%s\n", arr); } int main() { test6(); return 0; }
//将数组的10个元素全改为1 void* test7() { int arr[10] = { 0 }; memset(arr, 1, sizeof(arr)); int i = 0; for (i = 0; i < 10; i++) { printf("%s\n", arr[i]); } } int main() { test7(); return 0; }
这种写法是错误的,无法将数组的每个元素设置为1
因为arr中每个元素是整型(4字节),将每个字节的数据改为01,该元素(int类型)不是1
验证一下
(以%p打印2进制数字,8个一组;
以%x打印2进制数字,二进制数字前面的0不打印)
总结
本篇内容就介绍到这里啦,如果对大家有帮助的话,记得点赞收藏博客,关注后续的C语言学习内容哦~😉😉