C語言實現簡單的單向鏈表(創建、插入、刪除)及等效STL實現代碼


實現個算法,懶得手寫鏈表,於是用C++的forward_list,沒有next()方法感覺很不好使,比如一個對單向鏈表的最簡單功能要求:

input:

1 2 5 3 4

output:

1->2->5->3->4

相當於僅僅實現了插入、遍歷2個功能(當然遍歷功能稍微修改就是銷毀鏈表了)

用純C寫了份測試代碼

/* 基本數據結構的定義以及函數的聲明 */
typedef int ElemType;

typedef struct Node
{
    ElemType elem;
    struct Node* next;
} Node, * NodePtr, **ForwardList;

NodePtr createNode(ElemType x);

void showList(ForwardList lst);

void destroyList(ForwardList lst);

// 創建元素為x的節點並插入到節點where后面
// 若where為NULL, 則插入到鏈表lst的首部作為首節點
// 返回新節點的指針
NodePtr insertAfterNode(NodePtr where, ElemType x,
        ForwardList lst);
/* 鏈表相關函數的具體實現 */
NodePtr createNode(ElemType x)
{
    NodePtr pNode = (NodePtr) malloc(sizeof(Node));
    if (pNode == NULL) {
        perror("malloc");
        exit(1);
    }

    pNode->elem = x;
    pNode->next = NULL;
    return pNode;
}

NodePtr insertAfterNode(const NodePtr where,
        ElemType x, ForwardList lst)
{
    NodePtr pNode = createNode(x);

    if (where == NULL) {
        *lst = pNode;
    } else {
        pNode->next = where->next;
        where->next = pNode;
    }

    return pNode;
}

void showList(ForwardList lst)
{
    printf("顯示鏈表: ");
    NodePtr curr = *lst;
    while (curr->next != NULL) {
        printf("%d->", curr->elem);
        curr = curr->next;
    }
    printf("%d\n", curr->elem);
}

void destroyList(ForwardList lst)
{
    printf("銷毀鏈表: ");
    NodePtr curr = *lst;
    while (curr != NULL) {
        NodePtr next = curr->next;
        printf("%d ", curr->elem);
        free(curr);
        curr = next;
    }
    printf("\n");
}
/* 測試代碼 */
int main()
{
    NodePtr head = NULL;
    initListFromStdin(&head);
    showList(&head);
    destroyList(&head);
    return 0;
}

三個部分都是寫在一份代碼里(forward_list.c)的,測試結果如下

$ ls
data.in  forward_list.c
$ cat data.in 
1 2 5 3 4
$ gcc forward_list.c -std=c99 
$ ./a.out <data.in 
顯示鏈表: 1->2->5->3->4
銷毀鏈表: 1 2 5 3 4 

由於是不需要考慮周全的C代碼,所以很多C++的一些工程性的技巧不需考慮,比如模板、const,說起來之前沒把C代碼封裝成函數的時候就曾經導致鏈表的頭節點被修改,最后銷毀鏈表時,遍歷后頭節點直接指向了最后一個節點,導致前4個節點都沒被銷毀。如果合理地使用const,在編譯期就能檢查出來。

嘛,其實這么一寫下來,C++的forward_list版本也就寫出來了,畢竟我的鏈表插入函數就是模仿forward_list的,但是寫出來有點別扭。因為需要遍歷到倒數第2個節點停止,最后代碼如下

#include <cstdio>
#include <forward_list>
using namespace std;

// 取得前向迭代器it的下一個迭代器
template <typename FwIter>
FwIter nextIter(FwIter it)
{
    return ++it;
}

int main()
{
    forward_list<int> lst;
    int x;

    for (auto it = lst.before_begin();
        fscanf(stdin, "%d", &x) == 1;
        )
    {
        it = lst.emplace_after(it, x);
    }
    
    // 按照a0->a1->...->an的格式輸出
    auto it = lst.begin();
    while (nextIter(it) != lst.end())
    {
        printf("%d->", *it++);
    }
    printf("%d\n", *it);
    return 0;
}

 既然C++不提供next()函數那就只有手寫一個,因為迭代器傳參數時拷貝了一份,所以nextIter()直接返回++it並不會對原迭代器進行修改,而是修改的原迭代器的拷貝。

注意一點就是,在順序插入構建鏈表時需要記錄鏈表最后一個節點,跟我的C代碼實現風格一致(好吧其實我本來就是仿STL實現的)。

那么初始值就是before_begin()而不是begin(),因為空鏈表不存在begin(),確切的說空鏈表的初始節點為NULL。

測試代碼,這里_M_node是glibc++的forward_list迭代器底層實現部分,並不是跨平台代碼。迭代器相當於把節點地址進行了一層封裝,而_M_node則是節點地址。

#include <forward_list>                                                                            
#include <stdio.h>
              
int main()   
{            
    std::forward_list<int> lst;
    printf("begin()地址:        %p\n", lst.begin()._M_node);
    printf("before_begin()地址: %p\n", lst.before_begin()._M_node);
    return 0;
}      

結果如下:

$ g++ test.cc -std=c++11
$ ./a.out
begin()地址:        (nil)
before_begin()地址: 0x7fffb0896b60

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM