字符串的三種存儲方式

工科生一枚,熱衷於底層技術開發,有強烈的好奇心,感興趣內容包括單片機,嵌入式Linux,Uboot等,歡迎學習交流!
愛好跑步,打籃球,睡覺。 歡迎加我QQ1500836631(備註CSDN),一起學習交流問題,分享各種學習資料,電子書籍,學習視頻等。

  在數據結構中,字符串要單獨用一種存儲結構來存儲,稱爲串存儲結構。這裏的串指的就是字符串。無論學習哪種編程語言,操作最多的總是字符串。我們平常使用最多的存儲結構無疑是利用定長數組存儲。但是這種存儲結構需要提前分配空間,當我們不知道字符串長度的時候,過大的分配內存無疑是一種浪費。因此,合理的選擇字符串的存儲方式顯得格外重要。下面將依次介紹三種存儲方式。

定長順序存儲

  字符串的定長順序存儲結構,可以理解爲採用 “固定長度的順序存儲結構” 來存儲字符串,因此限定了其底層實現只能使用靜態數組。
  使用定長順序存儲結構存儲字符串時,需結合目標字符串的長度,預先申請足夠大的內存空間。
  例如,採用定長順序存儲結構存儲 “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語言)
  文中代碼均已測試,有任何意見或者建議均可聯繫我。歡迎學習交流!
  如果覺得寫的不錯,請點個贊再走,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章