不会这些字符串操作,你怎么精通C语言?如何玩转C++?

简介: 对于学习C语言或者C++的同学,如果对字符串的操作不熟悉,那就无法在学习的过程中更进一步,因为这是非常基础,也是非常重要的一个环节,希望博主这篇文章中的例子,可以教你去搞定字符串操作!对于初学者来说,一下要记住这么多东西是很难的,所以【建议收藏】,方便以后用到的时候查找。

废话不多说,进入正题。字符串操作几乎是我们在C或C++学习的过程中都会遇到困惑的地方,我们得不到自己想要的输出结果, 或者得到了,但没完全得到。就比如我们得到了我们想要的字符串,但是紧接着也会输出一些乱码。类似如图所示的情况:

image.png

那么我们应该如何去搞定这些字符串操作的bug呢?接下来咱们来细细讨论:


对于一个字符串数组或者char型指针,我们一定应该给它初始化,因为不初始化的话,里面都是一些默认初始化的值,可以认为里面是一些“垃圾”数据,那么我们首先来介绍我们的第一个函数:


1. 内存初始化memset


我一般用memset这个函数来给动态申请的内存进行初始化,因为如果是栈区内存,我们一般就在变量声明的同时就进行初始化,所以在堆区申请的内存,我们需要手动来给它初始化,我们来看一下函数的声明:

void *
memset (
      void            *Dest,
      int              Value,
      ACPI_SIZE        Count)

从函数的声明来看,总共有三个参数,我们来解释一下这三个参数:

void   *Dest:这是我们要进行初始化的指针,在这里被默认为void*指针
int    Value:一个int型变量,也就是我们需要给这片内存初始化的值,一般传0
ACPI_SIZE  Count:这是我们需要初始化的内存长度,一般是和申请内存的大小相同

我们一般把一块申请到的内存,使用之前,初始化为0,以便后续使用,特别说明:memset 不止针对字符串指针,其它的指针也可以使用。我们来看一个简单的使用举例:

//对于栈区内存,我们在申请到的时候,就对其初始化
  char Des[15] = { 0 };
  //堆区内存,我们申请到之后,使用memset初始化为0
  char* ptr1 = (char*)malloc(15);
  memset(ptr1, 0, 15);//将我们申请到的15个字节初始化为0

对于栈区内存初始化,我们看一下这个图:

image.png

对于堆区内存初始化,我们来看一下初始化执行的效果:

image.png


2. 字符串长度计算strlen


一个字符串长度的计算会被用来干什么呢?也许拷贝字符串的时候会用到的吧,或者说来计算一下输入了多少字符?反正不管怎么说,这都是非常通用的字符串操作函数,来看一下它的声明:

ACPI_SIZE
strlen (
    const char     *String)

我们可以看到返回值是一个数值类型,而这个函数的参数只有一个,多少有点敷衍了。。。但是流程还是要走的哈,所以我们再来看一下参数代表的意义:

const char   *String:特别注意是一个const char*。有关这个的概念,
在我之前讲C语言指针的时候讲过,意思就是字符串内容不允许修改,这是肯定的,我们传入字符串指针是为了让它计算长度,肯定不能被修改。

其实strlen的实现非常简单,在内部申请了一个局部变量用来计数,然后用一个while循环来判断,如果这个字符串不为 ‘\0’ ,那就指针后移,计数器++,否则,字符串长度计算结束,返回这个计数器变量的值。

现在我们来用它计算一个字符串的长度来看一下:

//初始化为HelloWorld
  char Des[] = "HelloWorld";//注意这是11个字节,因为还有 \0 结尾
  //计算字符串的长度
  size_t StringLength = strlen(Des);
  return 0;

计算这个字符串的结果:

image.png

从上图中我们应该能看到两个问题,首先我们默认初始化的Des字符串,里面有11个字符,但是最后我们发现计算出来的StringLength只算出了10,这个原因就是因为在strlen实现的时候,没有把 \0 的长度计算进去,所以需要注意:strlen计算出来的字符串长度不包含 \0 ,这个要切记,以后在写代码过程中会经常用到的。


3. 字符串复制strcpy


当我们想要将一个源字符串拷贝一个备份出来的时候,我们依然有可以使用的字符串操作函数,我们也可以预想的到,如果想要实现一个复制操作,那最起码得有源字符串和目标字符串,这样才能说是拷贝吧,我们在这里先说strcpy函数,我们还是惯例先来看看它的声明:

char *
strcpy (
      char           *DstString,
      const char     *SrcString)

我们看到这个函数的返回值是一个char型指针,但是我们一般不取它的返回值,因为在传参时候的DstString指向的就是我们要传回的字符串值,来看一下这两个参数分别代表什么:

char  *DstString:目标字符串指针,需要拷贝到的地方
const char  *SrcString:源字符串指针,被拷贝的对象

看一下使用举例:

//对于栈区内存,我们在申请到的时候,就对其初始化
  char SrcString[] = "HelloWorld";
  //堆区内存,15个字节的长度
  char* DstString = (char*)malloc(15);
  memset(DstString, 0, 15);//将我们申请到的15个字节初始化为0
  strcpy(DstString, SrcString);

再来看一下拷贝字符串的效果:

image.png

在使用strcpy的过程中需要注意:那就是对于目标内存长度的申请,因为在之前strlen计算长度的时候,并没有把字符串的结尾null给计算进去,所以申请内存时候要多申请一个字节,用来存放字符串结尾字符。这是因为strcpy在内部实现的时候,就会在字符串复制结束之后,在DstString的最后不上\0,也就是null字符来结尾,这个需要占用一个字节。


4. 字符串复制memcpy


在使用strcpy的过程中,我们发现我们只能按照字符串的null字符结尾来控制复制操作的停止,这显得有点被动了,那我们能不能变得主动出击呢?当然是可以的,我们接下来介绍的memcpy就是可以控制我们对于复制长度的控制:

void *
memcpy (
      void               *Dest,
      const void         *Src,
      ACPI_SIZE          Count)

我们还是来看一下各个参数的详细意思吧:

void         *Dest:需要拷贝到的目标指针首地址
const void   *Src:被拷贝的指针首地址
ACPI_SIZE    Count:当前指针类型被拷贝的个数


比如说我们指向从源字符串里面拷贝5个字符过来,那我们只需要将第三参数传个5就OK了,看一下下面这个例子:

//对于栈区内存,我们在申请到的时候,就对其初始化
  char SrcString[] = "HelloWorld";
  //堆区内存,15个字节的长度
  char* DstString = (char*)malloc(15);
  memset(DstString, 0, 15);//将我们申请到的15个字节初始化为0
  //我们只想复制前五个字节 Hello,所以这样写
  memcpy(DstString, SrcString,5);

我们可以使用memcpy只复制Hello,但是不能使用strcpy来达到这个效果:

image.png

但是这个函数就不会像strcpy一样,在复制完成后给补上 \0 来作为结尾,因为它不止针对字符串操作,所以不会默认给添加\0结尾。


那对于这个函数一定要注意,它不仅仅针对的是字符串系列的操作,我们可以从参数类型看出来,它并不是和str系列的函数一样,str系列都是char型参数,这个函数的参数都是void型参数,那就意味着什么类型,它都可以接受。其实不止当前函数,所有的mem系列函数都不是仅仅针对字符串操作。


5. 字符串复制memmove


好的,在这里我们来提出新的问题,因为有的同学喜欢玩,现在各个领域内卷态势严重,那字符串拷贝当然也要来帮帮场子,如果我们想实现一个自己拷贝自己,那我们使用什么函数呢?如果使用memcpy会有什么样的效果呢?我记得在很久之前一直被大家津津乐道的内存重叠问题,memcpy就是典型的例子,但是因为我很久没测试了,今天测试时候,居然发现memcpy内存重叠问题被解决掉了??

//对于栈区内存,我们在申请到的时候,就对其初始化
  char SrcString[] = "HelloWorld";
  //我希望拷贝HelloWorld 中的前8个字节给字符串本身
  //也就是我希望拷贝之后字符串变为HeHelloWor 
  //但是我预计它会出现HeHeHeHeHe的情况
  memcpy(SrcString + 2, SrcString, 8);

本来这里我是预计自己搞自己,然后会发生内存重叠,输出HeHeHeHeHe,然后再引出memmove来解决这个问题,没想到。。。它居然行了??也就是说正确输出了结果:

image.png

我都无语了,没想到微软居然把这个问题解决了,而我浑然不知!!!但是,但是,我肯定不甘心,我得告诉大家它之前是怎么实现的:

void *
my_memcpy(
  void                    *Dest,
  const void              *Src,
  size_t               Count)
{
  char                    *New = (char *)Dest;
  char                    *Old = (char *)Src;
  while (Count)
  {
  *New = *Old;
  New++;
  Old++;
  Count--;
  }
  return (Dest);
}

这就是它原本的实现逻辑,而按照这个函数的实现逻辑,我们再来测试一番:

image.png

OK!!nice!达到我要的错误示范的效果了,而且,它确实之前就是这么实现的!


接下来我们来“顺理成章”的引出我们的主角memmove吧!先来看看声明:

void *
memmove (
      void           *Dest,
      const void     *Src,
      ACPI_SIZE      Count)

对于这个函数的三个参数我们就不解释了,因为这三个参数和memcpy的三个参数是一样的,所以我们直接来看使用举例以及效果图吧:

image.png

这里也给大家看一下这个memmove的实现逻辑,来帮助大家更好的理解为什么能够避免内存重叠:

void *
memmove (
    void                    *Dest,
    const void              *Src,
    ACPI_SIZE               Count)
{
    char                    *New = (char *) Dest;
    char                    *Old = (char *) Src;
  //在这里对于两个地址的大小进行了比较,用来确定该如何去复制
    if (Old > New)
    {
        /*从头部复制*/
        while (Count)
        {
            *New = *Old;
            New++;
            Old++;
            Count--;
        }
    }
    else if (Old < New)
    {
        /*从尾部开始复制*/
        New = New + Count - 1;
        Old = Old + Count - 1;
        while (Count)
        {
            *New = *Old;
            New--;
            Old--;
            Count--;
        }
    }
    return (Dest);
}


6. 字符串比较strcmp


在两个字符串出现的时候,有时候我们需要比较两个字符串是否相同,或者看一下从哪儿开始不同的,那我们就用到了strcmp函数:

int
strcmp (
      const char     *String1,
      const char     *String2)

这个函数的返回值是我们需要注意的地方,它的两个参数倒也容易理解:

返回值:如果两个字符串相等,返回0,若String1>String2则返回 1,若String1<String2,则返回 -1
const char     *String1:第一个需要比较的字符串
const char     *String2:第二个需要比较的字符串

这个函数相对来说比较好理解,让我们来看一个小例子:

//初始化为HelloWorld
  char String1[] = "HelloWorld";
  //初始化为HelloChina
  char String2[] = "HelloChina";
  //预期返回1
  int index = strcmp(String1, String2);

比较的办法,就是从两个传入的指针第一个字符开始比较,然后比较标准就是按照ASCII值的大小,比如’W’就大于’C’,这个就根据ASCII表比较了。如上的代码我们预期index值是1:

image.png


7. 字符串比较memcmp


我们使用strcmp比较的时候也是有个不好的地方,那就是无法控制比较字符串的长短,字符串长短还是自己能够控制比较舒服,毕竟有时候不需要比较太长。那现在提供给了大家机会,那还愣着干什么,赶紧来看看memcmp:

int __cdecl 
memcmp(
  const void *s1, 
  const void *s2, 
  size_t n)

mem系列的函数我们之前就说过了不止针对字符串,从它的参数就可以看出,那这三个参数又是什么意思呢?其实也和memcpy差不多:

返回值:如果两个字符串相等,返回0,若String1>String2则返回 1,若String1<String2,则返回 -1
void         *s1:第一个需要比较的指针地址
const void   *s2:第二个需要比较的指针地址
size_t    n:比较的个数

它的返回值和strcmp是一样的,0,-1或者1,对于我们来说,我们就想比较两个字符串的前5个字节,因为前5个字符是一样的,我们来看看效果:

//初始化为HelloWorld
  char String1[] = "HelloWorld";
  //初始化为HelloChina
  char String2[] = "HelloChina";
  //预期输出0
  int index = memcmp(String1, String2, 5);

image.png

对于以上两种字符串比较函数,这个需要我们去衡量,在什么时候用哪个,如果说我们需要比较两个完整的字符串,可以选择使用strcmp,如果我们比较两个字符串的一部分,那我们可以使用memcmp。


8. 字符串拼接strcat


我们总希望能有改变自己的机会,字符串又何必不是这样呢??它也许也想变得强大,变的无坚不摧,但是苦于没有办法,一直在沉淀自己,终于,等到了strcat函数的到来:

char *
strcat (
      char           *DstString,
      const char     *SrcString)

我们可以看到这个函数的两个参数和strcpy的参数的一样的,第一参数是个char型指针,第二参数是个const char*指针,也就是说第二个参数指向的字符串不允许被修改:

char  *DstString:目标字符串指针,需要拼接到的目标字符串地址
const char  *SrcString:源字符串指针,拼接在DstString之后

返回值是一个指针,指向目标字符串的首地址,我们来看看经过拼接之后,能不能顺利的让字符串完成变身,强大自我呢?

//初始化为Hello
  char String1[] = "Hello";
  //初始化为KookNut39
  char String2[] = "KookNut39";
  //预期最终String1变为HelloKookNut39
  strcat(String1, String2);

image.png

但是!!!必须要说的是,这样做是不对的,这是一个栈区内存,我们本身只有6个字节的空间,现在加上这个字符串之后,很明显栈区内存越界了!!!这是肯定不被允许的。

想要强大自己,这没错,但是得看有没有这个能力,所以我们现在需要让它首先具备强大自己的能力,那就是具有足够的内存空间!!

//动态申请20个字节,并且初始化为0
  char* String1 = (char*)malloc(20);
  memset(String1, 0, 20);
  //修改String1为Hello
  strcpy(String1, "Hello");
  //初始化为KookNut39
  char String2[] = "KookNut39";
  //预期最终String1变为HelloKookNut39
  strcat(String1, String2);

稍作修改,动态申请内存,让我们拥有足够的内存空间来存放加上来的字符串,结果如下:

image.png


9. 字符串中搜索字符strchr


字符串中搜索特定字符也是我们也许会用到的小功能,其实这个函数相比前面的那些函数来说,也简单很多,我们先来看一下这个函数的声明:

char *
strchr (
      const char    *String,
      int           ch)

对于其中的两个参数和返回值,我们这么理解它:

返回值,当字符ch与源串String第一次匹配的时候的地址,如果串中所有字符都不匹配,那么返回NULL地址
const char    *String:源串,匹配的来源
int           ch:目标字符,用在再源串中匹配的

我们写一个简单的小例子来结束这个函数的讲解,我们涉及到了匹配成功和不成功两种情况:

char String1[] = "KookNut39";
  //字符串中没有 Y 预期返回NULL
  char* ptr1 = strchr(String1, 'Y');
  //预期返回 N 第一次出现的地址
  char* ptr2 = strchr(String1, 'N');

看一下运行结果:

image.png


10. 字符串中搜索子串strstr


字符串中可以搜索匹配指定字符?那如果我要搜索一个字符串,有没有什么办法来实现呢?当然有啦,直接来一个strstr就可以找到字符串中子串出现的位置啦:

char *
strstr (
      const char     *String1,
      const char     *String2)

我们来看一下这个字符串中这些参数的具体意思:

返回值:如果String2是String1的子串,那么返回第一次匹配的地址,否则返回NULL
const char     *String1:目标字符串
const char     *String2:需要在目标串中寻找的子串

我们还是一样,在这里进行能匹配的子串和不能匹配的子串进行两次测试,看一下返回值是不是符合我们的预期:

//初始化为HelloKookNut39
  char String1[] = "HelloKookNut39";
  //寻找我的名字KookNut39
  char String2[] = "KookNut39";
  //预期返回第一次匹配到的地址
  char* ptr1 = strstr(String1, String2);
  //预期返回NULL
  ptr1 = strstr(String1, "Kt39");
  return 0;

image.png

行文至此,终于结束了有关常用字符串操作函数的介绍,授人以鱼不如授人以渔,这句话我觉得始终是没问题的,在这里我希望通过给大家介绍简单的使用例子,能够让大家在以后的学习日子里,对于字符串操作可以游刃有余!!如果这篇博文能够给您的学习带来帮助,麻烦您赏博主一个点赞+评论+收藏,给博主继续创作提供动力,谢谢!


今日份与君共勉:“待到秋来九月八,我花开后百花杀”


目录
相关文章
|
2月前
|
安全 编译器 C语言
C++入门1——从C语言到C++的过渡
C++入门1——从C语言到C++的过渡
72 2
|
3月前
|
搜索推荐 编译器 C语言
【C++核心】特殊的元素集合-数组与字符串详解
这篇文章详细讲解了C++中数组和字符串的基本概念、操作和应用,包括一维数组、二维数组的定义和使用,以及C风格字符串和C++字符串类的对比。
103 4
|
24天前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
49 10
|
28天前
|
存储 算法 C语言
C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项
本文深入探讨了C语言中常见的字符串处理技巧,包括字符串的定义、初始化、输入输出、长度计算、比较、查找与替换、拼接、截取、转换、遍历及注意事项,并通过案例分析展示了实际应用,旨在帮助读者提高编程效率和代码质量。
88 4
|
25天前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
49 0
|
2月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
69 10
|
2月前
|
C语言 C++
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
【C语言】解决不同场景字符串问题:巧妙运用字符串函数
|
2月前
|
缓存 网络协议 API
C/C++ StringToAddress(字符串转 boost::asio::ip::address)
通过上述步骤和示例代码,你可以轻松地在C++项目中实现从字符串到 `boost::asio::ip::address`的转换,从而充分利用Boost.Asio库进行网络编程。
89 0
|
3月前
|
存储 C语言
【C语言基础考研向】10 字符数组初始化及传递和scanf 读取字符串
本文介绍了C语言中字符数组的初始化方法及其在函数间传递的注意事项。字符数组初始化有两种方式:逐个字符赋值或整体初始化字符串。实际工作中常用后者,如`char c[10]=&quot;hello&quot;`。示例代码展示了如何初始化及传递字符数组,并解释了为何未正确添加结束符`\0`会导致乱码。此外,还讨论了`scanf`函数读取字符串时忽略空格和回车的特点。
113 8
|
3月前
|
算法 机器人 C语言
ROS仿真支持C++和C语言
ROS仿真支持C++和C语言
98 1