嵌入式开发之C语言基础(六.字符串详解)

        本章节我们进入C语言字符串部分,内容不算太难但也挺重要的,那我们就开始进入字符串的学习。

        字符串基础

        首先,我们必须知道什么是字符串,字符串实际就是由若干个有效字符构成且以字符'\0'作为结束的一个字符序列。但是我们要怎么存储字符串呢,在代码上要怎么体现呢?我们来一段代码看看:

大家可以看到我们一般是用数组的形式存储字符串的,用cdata2和cdata3这两种存储方式都是一样的,但我们还写了一个cdata1,和cdata2的形式很像,就是结尾少了一个'\0',虽然结果输出来是一样的,但是cdata1不是字符串,它只是一个字符串数组。所以我们得出一个结论:一个字符串可以存于字符数组中,但一个字符型数组中存储的不一定是字符串,这要看它的最后一个元素是不是'\0'这里要注意的是'\0'是不会输出来的

        既然字符串可以由数组来存储,那么我们是不是可以用指针来指向其地址从而来访问该字符串呢,我们也来段代码来看看:

这里我们也给出了两种用指针访问字符串的方式,第一种就是用直接用指针指向该字符串常量,因为字符串常量本身代表的就是存放它的常量存储区的首地址,是一个地址常量。第二种就是数组指针的形式,指针指向数组名,数组名代表首地址。 两者遍历字符串的方式是一样的。

         如果我们要修改字符串中的某一个字符呢?如果是用数组形式存储字符串的应该不难,找到对应下标进行修改就行,但是如果是字符指针呢?我们来看一看:

这里应该没问题,但是不知道大家发现问题没用,我注释了两行代码,这两行代码如果不注释代码就会出问题,因为pdata1是指向字符串常量地址的指针,因为字符串常量地址是不能被修改的,所以不能像上述方法进行修改字符串。

        接下来我们来讨论一下字符串长度的问题。长度问题无非就是'\0'的问题,它到底计不计入字符串的实际长度呢?

我们通过sizeof可得:字符串中的'\0'是计入数组的长度的。这里我们顺便验证了字符数组加'\0'和不加'\0'输出的区别,我们可以发现不加'\0'输出的结果会有点乱码的,因为不加'\0'不是字符串,但是我们用的%s来输出,所以会出现乱码。 

         我们如果想知道字符串的实际长度我们就需要借助一个函数—strlen( ),我们在代码上看看:

我们发现字符串的实际长度是5,即不包括'\0',那为什么sizeof出来128呢,因为我们规定了数组的大小为128,即使我们没用用到这么多。而且我们发现字符指针的大小为8,这是为什么呢,在之前的文章我们也讲过,指针的大小就是8个字节,不论是什么类型。所以总而言之:'\0'占1个字节的内存,但不计入字符串的实际长度,只计入数组的长度。

        字符串的输入和输出

        对于普通的输入输出也没啥好讲的,我们经常用,这里我们主要是讲一下对于字符串输入时scanf( )和gets()。先来段代码看看:

对于puts( )和printf( )我们应该很熟了,简单一下puts( ):函数puts( )用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个'\0'时结束输出,并且自动输出一个换行符只能输出字符串常量。

        接着我们来看scanf( ),我们运行时输入了两次,第一次字符串中没带空格,可以正常输出,第二次字符串中带了空格,只输出了空格前的字符串这是因为用函数scanf( )按%s格式符不能输入带空格的的字符串。那么带空格的字符串怎么输入呢?这时,我们就要引入gets( )这个函数:

大家通过对比,发现用gets( )就可以输出带空格的字符串。 gets( )以回车符作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分。而scanf( )不读走回车符,回车符仍留在缓冲区中。

        malloc函数的realloc函数

        malloc是动态内存分配函数,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址函数原型:void* malloc(unsigned int num_bytes)要注意的是我们得包括头文件—#include <stdlib.h>。我们来看看具体怎么使用malloc函数。

这段代码就是为pdata开辟了1个字节的内存空间,接着再把字符'c'放进去,所以我们打印*pdata就可以得到字符'c',仅仅这样大家可能不能很好理解到malloc函数的作用,我们再来看一段代码:

这段代码就是在上一段代码的基础上注释掉了malloc函数,我们可以看到输出结果为无现象,即不能输出字符'c'。这是因为单单定义一个字符指针(即char *pdata),并没有指向具体的空间地址,所以其为野指针,不能直接对*pdata进行赋值。 

        在使用malloc的时候我们一定要注意溢出的问题,我们接着往下看:

我们在开辟了1个字节空间之后再对pdata开辟12个字节,并存放占12个字节的字符串,结果和我们预想的一样,那么我们如果把字符串长度加长一点: 

我们发现该字符串并不能输出,这是因为溢出了,我们开辟的12个字节的空间不能存放这么长的字符串。所以我们得进行扩容操作,但是在扩容之前补充一个函数—free( ),在使用malloc开辟空间时,使用完成一定要释放空间,如果不释放会造内存泄漏和悬挂指针(野指针)。

        进行扩容操作我们就得用到realloc函数,原型: void *realloc(void *mem_address, unsigned int newsize);功能:改变mem_address所指内存区域的大小为newsize长度,同样要注意的是我们得包括头文件—#include <stdlib.h>。我们在代码上看看:

要注意的是realloc中的长度参数是需要增加的长度,加1是为了给'\0'留有空间,尽管'\0'不占字符串长度。而且这里我们打印了扩容前后的地址,结果我们发现是一样的,故扩容操作不会改变地址。

        这里我们也用到两个API,memset( )和strcpy( ),它们的作用很简单,memest( )是一个初始化函数,作用是将某一块内存中的全部设置为指定的值,一般用于清零作用。strcpy( )就是复制的作用,把一个字符串复制到另一个字符串,接下来我们会详解。

        接下来我们来聊一聊几个常见字符串的API。

        strcpy函数

         strcpy函数的作用是把含有转义字符\0即空字符作为结束符,然后把src该字符串复制到dest,且返回值的类型为“char*”;原型:char *strcpy(char *dest, const char *src) ;把 src 所指向的字符串复制到 dest要记得包括头文件—#include <string.h>,我们在代码上体现一下:

大家可以看到p1所指向的字符串拷贝到了str数组里,用法不是很难。那我们能不能自己来写一个拷贝字符串的函数呢? 

这里我们自己写了写了一个Mystrcpy函数,从结果上来看也实现了字符串的拷贝,Mystrcpy函数功能得以成功实现while(*src != '\0')这个判断条件起到至关重要的作用,充分利用了字符串结束标志'是\0'这一性质,这也是为什么最后要把'\0'赋值给*des。 拷贝字符串这个函数的实现逻辑应该不是很难,关键在于利用'\0'这一特性和掌握指针偏移的内容,关于指针的内容大家不懂的可以去看我的上一篇内容—《嵌入式开发之C语言基础(五.指针详解)》这里我就不过多赘述了。

        到这里不知道大家发现一个问题没,为什么我们要保证拷贝和被拷贝的两个指针不能为NULL呢?我们在代码上验证一下:

这段代码中我们定义了一个空指针p2,并且把des != NULL的条件去掉了,从运行结果来看p2并不能打印出我们想要的字符串,这是因为当指针指向NULL时,代表该指针不被使用,除非我们对其malloc一下。 

        大家可以看到代码中其实还有一个assert断言函数,其作用和下面的if语句差不多。assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,,如果括号里的条件为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。

        当然,这只是其中一种写法,我们再补充几种Mystrcpy( ):

 这两种Mystrcpy同样是可以实现拷贝字符串的功能,大家可能会对Mystrcpy3中while((*des++ = *src++) != '\0');有点难以理解,经过验证得知判断条件里面可以进行赋值操作

        讲到strcpy函数其实还有一个类似的函数—strncpy函数,顾名思义就是拷贝指定个数的字符串,我们也来看看其是怎么实现的:

大家可以看到其实和strcpy函数差别不大,就是多了一个个数条件的判断,下面那个if (count > 0)的判断就是防止count大于字符串的长度,代码的具体实现大家应该能看明白。 

        strcat函数

         strcat函数就是把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,原型:char *strcat(char *dest, const char *src),我们在代码上来看一看:

很明显我们通过strcat函数将两个字符串拼接在一起了,同样要用头文件— <string.h>,同样,有了对strcpy函数的开发基础,我们也来自己写一写Mystrcat函数:

实现拼接字符串的功能关键在于把被拼接的字符串指针偏移到'\0'前面,再进行和sctcpy函数类似的功能,当然这也只是其中一种写法,下面我们再来补充两种: 

这两种也是可以实现字符串拼接功能的,只是写法不一样而已。像Mystrcat2,指针偏移后直接就可以调用scrcpy函数,但是这里为什么没用将'\0'赋值给最后一个字符呢?因为strcpy函数就已经实现这步操作。 

         strcmp函数

        strcmp函数作用是比较字符串str1和str2是否相同,如果相同则返回0,如果不同,前者大于后者则返回1,否则返回-1。原型:int strcmp(char *str1,char *str2)。我们也来用一下:

我们在两个字符串后面分别加了字符'a'和字符'b',所以前者的ASCII码小于后者,故返回值为-1。但是大家如果去看一看strcmp函数的源码就会发现bug,源码里比较的大小不是整个字符串的ASCLL码,而是第一处字符不同所处位置的两个字符的ASCLL码, 意思就是如果在现在代码的基础上在这两个字符串前面分别加字符'b'和字符'a',按道理应该是返回0,但是结果会是1,不信我们来看看:

其实纠结这个问题意义不大,因为在一般的项目开发中我们只关注返回值是否为0,但是从严谨的角度出发,这个strcmp函数我们可以对其进行修改,改成我们所理解的样子:

代码应该不难理解,关键就是指针偏移完后要重新指向字符串。正确性在这里我就不在这展现了,我运行的结果是正确的。

        至此,我们这篇关于字符串的内容就讲完了,希望大家好好理解消化,估计大家有问题的地方还是在指针,如果对指针不是很了解的同学可以看看我上一篇的内容,对指针有很详细的讲解。如果我的文章有不足或错误之处欢迎大家批评指正,共勉!

 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

STRIVE1151

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值