字符和字符串函数的这些知识你知道吗?超硬核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);

image-20210606192935816

  • 字符串结束标志为’\\0’,strlen函数返回的是在结束标志之前的字符个数。
#include<stdio.h>
#include<string.h>
int main()
{
    char arr[]={"hello"};
    int len = strlen(arr);
    printf("%d\\n",len);
    return 0;
}

image-20210606154349546

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;
}

image-20210606154641540

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;
}

image-20210606155128810

解释:

我们发现这条语句和我们预想的不一样,为什么是大于呢?原因是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;
}

递归如何实现呢?我们利用大事化小的思想,看下图:image-20210606162923630代码解释:

刚开始进入函数,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);

image-20210606193209887

我们首先来看一下它的使用:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr[20]={0};
    strcpy(arr,"hello");//传字符串实际上是将hello字符串的首字符地址h传过去了
    printf("%s\\n",arr);
    return 0;
}

image-20210606170320427

  • 源字符串必须以 ‘\\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;
}

如果你不相信我们可以调试:

image-20210606171305712

调试后发现确实要将\\0也拷贝进去的

  • 目标空间足够大,能够存入源字符串长度的空间
#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[5]={0};
    strcpy(arr1,"hello world");
    return 0;
}

image-20210606173112716

目标空间不够时,他会帮你拷贝进去,但是他会出问题

而程序运行是会报错的
image-20210606173219454

我们进行打印arr1是他会打印出来,但是会报错:

image-20210606232501412

  • 目标空间必须可变。
  #include<stdio.h>
  #include<string.h>
  int main()
  {
      char* p = "************";
      char arr[]="hello";
      strcpy(p,arr);
      return 0;
  }

字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变

我们调试发现,程序会崩掉:

image-20210606173741998

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 );

image-20210606192705457

  • 我们首先来看看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;
}

image-20210606174630815

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函数是如何实现的你就知道啦!

image-20210606180926715

  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  #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。

image-20210606181444256

  • 目标空间必须可修改。
  #include<stdio.h>
  #include<string.h>
  int main()
  {
      char *p = "hello ";
      char arr2[] = "world";
      strcat(p, arr2);//字符串追加或连接
      return 0;
  }

image-20210606181708006

字符指针p指向的字符串是常量字符串,是不能修改的,所以我们的目标空间必须可变

  • 字符串自己给自己追加,如何?
 #include<stdio.h>
 #include<string.h>
 int main()
 {
     char arr[10]={"abcd"};
     strcat(arr,arr);
     printf("%s\\n",arr);
     return 0;
 }

看下图解释:

image-20210607005200037

我们调试发现:确实进行了死循环的连接,因为没有了连接的结束标志\\0,不知道啥时候停止,直到空间满时会报错。

image-20210607005541404

这里将追加的结束标志修改了,程序陷入了死循环,所以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;
}  

image-20210606182517104

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;
} 

image-20210607085826442

关于返回值:

image-20210606095812112

库函数里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语言超硬核结构体枚举联合体画图+文字详细讲解

超硬核:Linux系统内存知识

超硬核!苏州同程旅游学长给我的全面的面试知识库

超硬核学习手册系列3事务视图篇——深入浅出MySQL的知识点,学习收藏必备

超硬核学习手册系列1——深入浅出MySQL的知识点,学习收藏必备

超硬核!16000 字 Redis 面试知识点总结,这还不赶紧收藏?