實現個算法,懶得手寫鏈表,於是用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