字符串的三种存储方式
在数据结构中,字符串要单独用一种存储结构来存储,称为串存储结构。这里的串指的就是字符串。无论学习哪种编程语言,操作最多的总是字符串。我们平常使用最多的存储结构无疑是利用定长数组存储。但是这种存储结构需要提前分配空间,当我们不知道字符串长度的时候,过大的分配内存无疑是一种浪费。因此,合理的选择字符串的存储方式显得格外重要。下面将依次介绍三种存储方式。
定长顺序存储
字符串的定长顺序存储结构,可以理解为采用 "固定长度的顺序存储结构" 来存储字符串,因此限定了其底层实现只能使用静态数组。
使用定长顺序存储结构存储字符串时,需结合目标字符串的长度,预先申请足够大的内存空间。
例如,采用定长顺序存储结构存储 "feizhufeifei",通过目测得知此字符串长度为12(不包含结束符 '\0'),因此我们申请的数组空间长度至少为 12,用 C 语言表示为:
char str[18] = "feizhufeifei";
下面是具体的C语言实现
#include<stdio.h> int main() { char str[15]="feizhufeifei"; printf("%s\r\n",str); return 0; }
这种存储方式基本是初学者都应该掌握的。下面介绍第二种存储方式。
动态数组存储
首先我们应该明确两个概念:堆和栈。
堆是由我们程序员自己管理的,当进程调用malloc等函数分配内存时,新分配的内存就被动态分配到堆上,当利用free等函数释放内存时,被释放的内存从堆中被剔除。
栈又称堆栈,是用户存放程序临时创建的变量,也就是我们函数{}中定义的变量,但不包括static声明的变量,static意味着在数据段中存放变量。除此之外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中,由于栈的先进后出特点,所以栈特别方便用来保存、恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存,交换临时数据的内存区。
当我们调用malloc时,就会在堆上划分一块空间给我们使用,具体代码如下:
//创建了一个动态数组str,通过使用 malloc 申请了 10个 char 类型大小的堆存储空间。 char * str = (char*)malloc(10*sizeof(char));
动态数组的优势是长度可变,根据需要动态进行分配。当我不想申请新的变量,但是又想要扩大str的空间怎么办呢?这个时候realloc函数就起作用了。
//通过使用这行代码,之前具有10 个 char 型存储空间的动态数组,其容量扩大为可存储 20 个 char 型数据。 str = (char*)realloc(str, 20*sizeof(char));
下面通过一个合并两个字符串的例子来更好地理解下动态分配过程。
/* * @Description: 字符串的堆动态堆分配内存 * @Version: V1.0 * @Autor: Carlos * @Date: 2020-05-25 * @LastEditors: Carlos * @LastEditTime: 2020-05-25 */ #include <stdio.h> #include <stdlib.h> #include <string.h> //打印测试语句 #define DEBUG 0 #if DEBUG #define DBG_PRINTF(fmt, args...) \ do\ {\ printf("<<File:%s Line:%d Function:%s>> ", __FILE__, __LINE__, __FUNCTION__);\ printf(fmt, ##args);\ }while(0) # else # define DBG_PRINTF(fmt, args...) #endif int main() { char *s1 = NULL; char *s2 = NULL; s1 = (char *)malloc(5*sizeof(char *)); strcpy(s1,"test"); DBG_PRINTF("s1:%s\r\n",s1); s2 = (char *)malloc(7*sizeof(char *)); strcpy(s2,"string"); DBG_PRINTF("s2:%s\r\n",s2); int length1 = strlen(s1); int length2 = strlen(s2); //尝试将合并的串存储在 s1 中,如果 s1 空间不够,则用realloc动态申请 if(length1<length1+length2) s1 =(char*) realloc(s1,(length1 + length2+1) * sizeof(char)); //合并两个串到 s1 中 for(int i = length1; i < length1 + length2;i++) s1[i] = s2[i - length1]; //串的末尾要添加 \0,避免出错 s1[length1 + length2] = '\0'; printf("s1+s2:%s", s1); //用完动态数组要立即释放 free(s1); free(s2); return 0; }
块链存储
块链存储就是利用链表来存储字符串。本文使用的是无头结点的链表结构(即链表的第一个头结点也存储数据)。
我们知道,单链表中的 "单" 强调的仅仅是链表各个节点只能有一个指针,并没有限制数据域中存储数据的具体个数。因此在设计链表节点的结构时,可以令各节点存储多个数据。
例如,我们要用链表存储feizhu字符串,链表结构如下所示:
我们也可以每个链表存储四个字符,那么最后一个节点肯定不会占满。这时,我们可以使用#或者其他符号将其填满。
怎样确定链表中每个节点存储数据的个数呢?
链表各节点存储数据个数的多少可参考以下几个因素:
串的长度和存储空间的大小:若串包含数据量很大,且链表申请的存储空间有限,此时应尽可能的让各节点存储更多的数据,提高空间的利用率(每多一个节点,就要多申请一个指针域的空间);反之,如果串不是特别长,或者存储空间足够,就需要再结合其他因素综合考虑;
程序实现的功能:如果实际场景中需要对存储的串做大量的插入或删除操作,则应尽可能减少各节点存储数据的数量;反之,就需要再结合其他因素。
下面是具体的代码实现。
/* * @Description: 字符串的块链表存储(无头结点的链表) * @Version: V1.0 * @Autor: Carlos * @Date: 2020-05-25 * @LastEditors: Carlos * @LastEditTime: 2020-05-25 */ #include<stdio.h> #include<stdlib.h> #include<string.h> //全局设置链表中节点存储数据的个数 #define linkNum 3 typedef struct link { //数据域可存放 linkNum 个数据 char a[linkNum]; //代表指针域,指向直接后继元素 struct link * next; }Link; /** * @Description: 遍历链表,打印 * @Param: Link * head 结构体头结点指针 * @Return: 无 * @Author: Carlos */ void PrintLink(Link * head) { Link * p = head; while (p) { for (int i = 0; (i < linkNum) &&(p->a[i]!='#'); i++) { printf("%c", p->a[i]); } p = p->next; } } /** * @Description: 初始化链表 * @Param: Link * head 结构体头结点指针。char * str 要操作的字符串 * @Return: Link *结构体指针 * @Author: Carlos */ Link * InitLink(Link * head, char * str) { int length = strlen(str); //需要的节点个数 向上取整 int nodenum = length/linkNum; Link *p = head; int j; //将数据存放到每个节点的数组中 for(int i = 0;i<=nodenum;i++) { for( j = 0;j < linkNum;j++) { if (i*linkNum + j < length) { p->a[j] = str[i*linkNum+j]; } //使用#填充未满的节点数组空间 else { p->a[j] = '#'; } } //链接新旧两个节点 if (i*linkNum + j < length) { Link* q = (Link*)malloc(sizeof(Link)); q->next = NULL; p->next = q; p = q; } } return head; } int main() { Link* head = (Link*)malloc(sizeof(Link)); head->next=NULL; InitLink(head,"blockchain"); PrintLink(head); return 0; }
关于链表不明白的可以参考这篇博客史上最全单链表的增删改查反转等操作汇总以及5种排序算法(C语言)
文中代码均已测试,有任何意见或者建议均可联系我。欢迎学习交流!
如果觉得写的不错,请点个赞再走,谢谢!
有任何问题,均可通过公告中的二维码联系我
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架