字符和字符串函数的这些知识你知道吗?超硬核3w字文章带你领略C语言字符与字符串的美!!!
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字符和字符串函数的这些知识你知道吗?超硬核3w字文章带你领略C语言字符与字符串的美!!!相关的知识,希望对你有一定的参考价值。
字符函数和字符串函数
我们这篇文章重点介绍以下内容:
-
求字符串长度
- strlen
-
长度不受限制的字符串函数
- strcpy
- strcat
- strcmp
-
长度受限制的字符串函数介绍
- strncpy
- strncat
- strncmp
-
字符串查找
- strstr
- strtok
-
错误信息报告
- strerror
- perror
-
字符操作
-
内存操作函数
- memcpy
- memmove
- memset
- memcmp
字符串基础知识
- 字符串就是一串或多个字符,并且以一个位模式全0的NULL字节结尾。因此我们字符串所包含的字符内部不能出现NULL字节,这个限制很少会引起问题,因为NULL字节并不存在与它相关联的可打印字符,这也是它被选为终止符的原因。NULL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不句括NULL字节,头文件 string.h 包含了使用字符串函数所需的原型和声明。尽管并非必需,但在程序中包含这个头文件确实是个好主意,因为有了它所包含的原型,编译器可以更好地为你的程序执行错误检查。
函数介绍
长度不受限制的字符串函数
strlen(字符串长度)
- 库函数strlen函数的原型如下:
size_t strlen(const char*string);
- 字符串结束标志为’\\0’,strlen函数返回的是在结束标志之前的字符个数。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\\n",len);
return 0;
}
char arr[]={"hello"};
解释:
我们在这样初始化时,后面是有\\0的。strlen返回的是字符串个数,所以是5
- 参数指向的字符串必须以’\\0’结束,不然得出的结果是随机值。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[]={'h','e','l','l','o'};
int len = strlen(arr);
printf("%d\\n",len);
return 0;
}
char arr[]={'h','e','l','l','o'};
解释:
我们这样初始化时,arr数组里没有\\0,strlen找不到结束标志,所以他一直往后数,所以打印到的是随机值
- 注意函数的返回值为size_t,size_t是无符号整形。
#include<stdio.h>
#include<string.h>
int main()
{
if(strlen("abcdef")-strlen("abcdefgh")>0)
{
printf(">\\0");
}
else
{
printf("<");
}
return 0;
}
解释:
我们发现这条语句和我们预想的不一样,为什么是大于呢?原因是strlen的结果是无符号数,无符号数-无符号数得到的还是无符号数,无符号数不可能是负的,所以它的结果永远都是大于0。
strlen函数的模拟实现
- 计数器法
#include<stdio.h>
int my_strlen(const char*str)
{
assert(str);
int count=0;
while(*str++)
{
count++;
}
return count;
}
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\\n",len);
return 0;
}
解释:
函数循环体判断部分*str++,*str为’\\0’时退出循环,因为’\\0’的ascii码值为0,为假。str指向字符串第一个字符,我们的想法是当它指向内容不为\\0时,计数器++,最后返回count,我们看这个代码:*str++,++操作符的优先级比*高,我们先使用后置++,后置++是先使用后++。然后我们判断是不是\\0,不是\\0进入循环,计数器加加,要是\\0,则退出循环,返回count。
- 递归实现
#include<stdio.h>
int my_strlen(const char*str)
{
assert(str);
if(*str!='\\0')
{
return 1+my_strlen(str+1);
}
else
{
return 0;
}
}
int main()
{
char arr[]={"hello"};
int len = strlen(arr);
printf("%d\\n",len);
return 0;
}
递归如何实现呢?我们利用大事化小的思想,看下图:代码解释:
刚开始进入函数,str指向’h’,‘h’不等于\\0,return 1+my_strlen(str+1),递归调用,str+1指向了’e’,b不等于\\0,继续递归调用,str+1指向了’l’,l不等于\\0,继续递归调用,str+1指向了’l’,l不等于\\0,继续递归调用,str+1指向了’o’,o不等于\\0,继续递归调用,str+1指向了’\\0’,不会再进行调用,此时就到了归的环节了,归的是调用它的地方,所以依次return 1,return2,return 3,return 4,return 5,最后返回给main函数中调用它的地方。
- 指针-指针
#include<stdio.h>
int my_strlen(char* str)
{
char* start=str;
while(*str!='\\0')
{
str++;
}
return str-start;
}
int main()
{
char arr[]="abcdef";
int len=my_strlen(arr);
printf("%d\\n",len);//6
return 0;
}
解释:
指针-指针,得到的是指针和指针之间的元素个数
讲到这里,我们求字符串长度函数就到这里了,下面我们看这个代码:
int main()
{
char arr[20]={0};
arr="hello";///error
return 0;
}
这个代码是错误的,arr是数组名,数组名是首元素地址,把hello放入这个(编号)地址上去吗?不行的,我们是要放进arr的内存空间的,那么我们怎么放进去呢?
此时我们就要用到strcpy
strcpy(字符串拷贝)
- 库函数strcpy函数的原型如下:
char *strcpy(char *destination,const char *source);
我们首先来看一下它的使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20]={0};
strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
printf("%s\\n",arr);
return 0;
}
- 源字符串必须以 ‘\\0’ 结束。
int main()
{
char arr1[20]={0};
char arr2[]={'a','b','c'};
strcpy(arr,arr2);
return 0;
}
arr2能拷贝进arr1吗?
答案是不行的,我们拷贝是要将’\\0’也拷贝进去的,源字符串没有’\\0’,我们不知道啥时候拷贝停止
- 会将源字符串中的 ‘\\0’ 拷贝到目标空间。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[20]={0};
strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
printf("%s\\n",arr);
return 0;
}
如果你不相信我们可以调试:
调试后发现确实要将\\0也拷贝进去的
- 目标空间足够大,能够存入源字符串长度的空间
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[5]={0};
strcpy(arr1,"hello world");
return 0;
}
目标空间不够时,他会帮你拷贝进去,但是他会出问题
而程序运行是会报错的
我们进行打印arr1是他会打印出来,但是会报错:
- 目标空间必须可变。
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "************";
char arr[]="hello";
strcpy(p,arr);
return 0;
}
字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变
我们调试发现,程序会崩掉:
strcpy函数的模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* scr)
{
char* temp = dest;//创建一个临时变量存储dest,以便后面返回目标字符串的地址
assert(dest && scr);
while (*dest++ = *scr++)
{
;
}
return temp;
}
int main()
{
char arr1[] = "**************";
char arr2[] = "hello";
printf("%s\\n", my_strcpy(arr1, arr2));
return 0;
}
解释:
循环判断条件是*dest++ = *scr++,首先我们要知道,a=b这个表达式的值是a的值,我们将arr1中的每个字符赋值给arr2中时,其实整个表达式的值在每次循环时的值分别为’h’e’l’l’o’\\0’字符的ASSIC码值,当我们把\\0赋值到arr2中时,同时整个表达式的值也为0了,所以退出循环。
- 注意:
我们必须保证目标字符数组的空间足以容纳需要复制的字符串。若字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,它无法判断目标字符数组的长度。
接下来我们看strcat连接字符串
strcat(连接字符串)
- 库函数strcpy函数的原型如下:
char * strcat ( char * destination, const char * source );
- 我们首先来看看strcat函数的使用:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20]={"hello "};//假设我们要追加world
char arr2[]="world";
//strcat(arr1,"world");//字符串追加或连接
strcat(arr1,arr2);
printf("%s\\n",arr1);
return 0;
}
strcat(arr1,“world”); strcat(arr1,arr2);这两种方式都可以字符串连接。
- 源字符串必须以 ‘\\0’ 结束。
#include<stdio.h>
#include<string.h>
int main()
{
char arr[]={'h','e','l','l','o'};
char arr2[]="world";
strcat(arr1,arr2);//字符串追加或连接
printf("%s\\n",arr1);
return 0;
}
如果不以\\0结束,是不能进行连接的,为什么不能连接呢?慢慢往下看,看到strcat函数是如何实现的你就知道啦!
- 目标空间必须有足够的大,能容纳下源字符串的内容。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[8] = { "hello "};
char arr2[] = "world";
strcat(arr1, arr2);//字符串追加或连接
printf("%s\\n", arr1);
return 0;
}
如果目标空间不足够大,这里是会发生访问越界的,这里只能拷贝进wo。
- 目标空间必须可修改。
#include<stdio.h>
#include<string.h>
int main()
{
char *p = "hello ";
char arr2[] = "world";
strcat(p, arr2);//字符串追加或连接
return 0;
}
字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变
- 字符串自己给自己追加,如何?
#include<stdio.h>
#include<string.h>
int main()
{
char arr[10]={"abcd"};
strcat(arr,arr);
printf("%s\\n",arr);
return 0;
}
看下图解释:
我们调试发现:确实进行了死循环的连接,因为没有了连接的结束标志\\0,不知道啥时候停止,直到空间满时会报错。
这里将追加的结束标志修改了,程序陷入了死循环,所以strcat函数是不能自己连接自己的
- 目的字符串和源字符串必须要有\\0
我们这里来看一下strcat是如何实现的,我们首先看下面代码测试:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20]={"hello \\0*************"};//假设我们要追加world
char arr2[]="world";
strcat(arr1,arr2);//字符串追加或连接
printf("%s\\n",arr1);
return 0;
}
strcat函数的实现是目标字符串中的结束标志\\0被源字符串的首字符覆盖,然后依次进行追加,直到追加到\\0,即追加结束。
arr1中的\\0是开始追加的标志,arr2中的\\0是追加结束的标志,所以目的字符串和源字符串必须要有\\0
strcat函数的模拟实现
- 1.找到目标字符串中的\\0。
- 2.源数据追加过去,包含\\0。
#include<stdio.h>
void my_strcat(char *dest,const char*src)
{
assert(dest&&src);
//1.找到目标字符串中的\\0
while(*dest)
{
dest++;
}
//2.源字符串的追加,包含\\0
while(*dest++=*src++)
{
;
}
}
int main()
{
char arr1[20]={"hello "};//假设我们要追加world
char arr2[]="world";
my_strcat(arr1,arr2);//字符串追加或连接
printf("%s\\n",arr1);
return 0;
}
关于返回值:
库函数里strcat返回的是目标空间的起始地址,所以我们也将目标空间的起始地址返回,my_strcat函数返回的是指针,那么我们在打印时就可以这样打印:
printf("%s\\n",my_strcat(arr1,arr2));
看如下代码:
#include<stdio.h>
char* my_strcat(char *dest,const char*src)
{
char *ret=dest;//利用ret变量保存目标空间的起始地址
assert(dest&&src)以上是关于字符和字符串函数的这些知识你知道吗?超硬核3w字文章带你领略C语言字符与字符串的美!!!的主要内容,如果未能解决你的问题,请参考以下文章
自定义类型的这些知识你知道吗?C语言超硬核结构体枚举联合体画图+文字详细讲解
超硬核学习手册系列3事务视图篇——深入浅出MySQL的知识点,学习收藏必备