C 封裝一個簡單二叉樹基庫

来源:http://www.cnblogs.com/life2refuel/archive/2016/02/03/5178534.html
-Advertisement-
Play Games

C介面與設計,需要註意的流程還是比較多的. 從演算法設計到演算法測試,到基礎庫封裝, 到基礎庫測試,後面是投入生產, 和框架融合再測試. 等等,都特別的耗時間, 今天分享的 是一個關於二叉樹基庫的封裝測試.下一個版本投入到生產環節.


引文

  今天分享一個喜歡佩服的偉人,應該算人類文明極大突破者.收藏過一張紙幣類型如下

那我們繼續科普一段關於他的簡介

  '高斯有些孤傲,但令人驚奇的是,他春風得意地度過了中產階級的一生,而 
沒有遭受到冷酷現實的打擊;這種打擊常無情地加諸於每個脫離現實環境生活的 
人。或許高斯講求實效和追求完美的性格,有助於讓他抓住生活中的簡單現實。 
高斯22歲獲博士學位,25歲當選聖彼德堡科學院外籍院士,30歲任哥廷根大學數 
學教授兼天文臺台長。雖說高斯不喜歡浮華榮耀,但在他成名後的五十年間,這 
些東西就像雨點似的落在他身上,幾乎整個歐洲都卷入了這場授獎的風潮,他一 
生共獲得75種形形色色的榮譽,包括1818年英王喬治三世賜封的“參議員”, 
1845年又被賜封為“首席參議員”。高斯的兩次婚姻也都非常幸福,第一個妻子 
死於難產後,不到十個月,高斯又娶了第二個妻子。心理學和生理學上有一個常 
見的現象,婚姻生活過得幸福的人,常在喪偶之後很快再婚,
他的晚年不幸福,

孩子和他關係不好...'

  關於他的專業知識 業界評價如下

  '能從九霄雲外的高度按照某種觀點掌握星空和深奧數學的天才。'地球上搞數學人類中公認前三diao. 

有時候我們所有的一切 都是自己抉擇的過程,

不是自己選擇,就是別人選擇. 很公平, 就看每個人覺醒的早晚,覺醒能力的 不同而已. 

 

推薦參照

    沒有什麼不同 http://music.163.com/#/song?id=25713024

再扯一點, '孤傲'的話題, 生活中有時候遇到一類人, 第一次見他覺得太傲了, 接觸了一段時間

發現這人了不起, 後面瞭解多了, 還是很喜歡和他交朋友. 人不錯.

  人是最複雜的,也是最容易改變的.關鍵需要多瞭解.

 

前言

   到這裡逐漸切入正題了, 當一個構想 投入生產環境一般 需要下麵幾個步驟.

1演算法/思路 構思

2演算法實現 測試

3.封裝基礎演算法結構庫

4.演算法/思路結構庫 測試

5. 投入生產環境輕微重構

6.生產環境測試

7.實戰檢測.

  所以封裝一個庫還是有些流程比較耗時的. 我們這裡分享的是關於一個二叉樹基礎庫的分享. 原先花了2天使用紅黑樹實現,

但是最後磕磕碰碰,抄抄補補搞出來但是代碼很不好維護,最後退而求其次採用 二叉查找樹構造了一個基礎庫. 並測試了一下基本可以.

等下一次直接用到實戰環境中.

  首選學習這個二叉樹 庫封裝 需要

    1.瞭解二叉樹基礎原理

    2.瞭解C介面的簡單設計

  能夠學到

    1.C介面設計的一些技巧

    2.介面簡單測試

首先看下麵介面文檔 tree.h

#ifndef _H_TREE
#define _H_TREE

//4.0 控制台列印錯誤信息, fmt必須是雙引號括起來的巨集
#ifndef CERR
#define CERR(fmt, ...) \
    fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
         __FILE__, __func__, __LINE__, errno, strerror(errno),##__VA_ARGS__)
#endif/* !CERR */

//4.1 控制台列印錯誤信息並退出, t同樣fmt必須是 ""括起來的字元串常量
#ifndef CERR_EXIT
#define CERR_EXIT(fmt,...) \
    CERR(fmt,##__VA_ARGS__),exit(EXIT_FAILURE)
#endif/* !ERR */

/*
*  這裡是簡單二叉查找樹封裝的基庫,封裝庫的庫
*  需要用的的一些輔助結構,主要是通用結構和申請釋放的函數指針
*/
typedef struct tree* tree_t;
typedef void* (*pnew_f)();
typedef void (*vdel_f)(void* node);
typedef int (*icmp_f)(void* ln, void* rn);


// __開頭一般意思是不希望你使用,私有的,系統使用
struct __tnode {
    struct __tnode* lc;
    struct __tnode* rc;
};
/*
*   這個巨集必須放在使用的結構體開頭,如下
*  struct persion {
        _TREE_HEAD;
        char* name;
        int age;
        ...
*  }
*
*/
#define _TREE_HEAD \
    struct __tnode __tn


/*
* new   : 結點申請記憶體用的函數指針, 對映參數中是 特定結構體指針
* acmp  : 用於添加比較 
* gdcmp : 兩個結點比較函數,用戶查找和刪除
* del   : 結點回收函數,第一個參數就是 二叉樹中保存的結點地址
* ret   : 返回創建好的二叉樹結構, 這裡是 tree_t 結構
*/
tree_t tree_create(pnew_f new, icmp_f acmp, icmp_f gdcmp, vdel_f del);

/*
* proot  : 指向tree_t 根結點的指針,
* node     : 待處理的結點對象, 會調用new(node) 創建新結點
* ret    : proot 即是輸入參數也是返回參數,返回根結點返回狀況
*/
void tree_add(tree_t* proot, void* node);

/*
* proot  : 輸入和輸出參數,指向根結點的指針
* node   : 刪除結點,這裡會調用 cmp(node 左參數, foreach) 找見,通過del(find) 刪除
*/
void tree_del(tree_t* proot, void* node);

/*
* root   : 根結點,查找的總對象
* node   : 查找條件,會通過cmp(node, foreach)去查找
* parent : 返回查找到的父親結點
* ret     : 返回查找到的結點對象
*/
void* tree_get(tree_t root, void* node, void** parent);

/*
* proot  : 指向二叉樹數結點指針
* 會調用 del(foreach) 去刪除所有結點,並將所有還原到NULL
*/
void tree_destroy(tree_t* proot);

#endif // !_H_TREE

上面有些代碼在實戰環節是要去掉和統一修改的.這裡是為了降低耦合性,方便測試,就放在一起了.

介面比較精簡,還可以更精簡,下次再優化.應該一看都明白上面代碼是乾什麼的. 需要註意的是

在你想使用二叉樹性質的 結構體中 需要在第一個 成員位置 加入

_TREE_NODE;

舉例如下

//通用結構體變數
struct dict {
    _TREE_HEAD;
    char* key;
    char* value;
};

至於為什麼,想一想也都明白了,這樣的代碼或者說技巧 太多了, Linux內核中結構喜歡 將其放在最末的位置,會有一個

typeof 巨集 判斷位置.那下麵我們開始說說具體設計. 扯一點,一個需要C入門選手,要麼把C語言之父的書看一遍,倒著看一遍.

寫一遍或理解會用上面的結構體設計,基本C這塊語法都明白了.

  一定要多寫代碼, 因為未來不清楚, 但可以知道的是不好好寫代碼, 那現在都不清楚了. 大家覺得呢.

 

正文

1.說細節實現 

  首先看創建函數定義,這裡主要用到函數指針技巧,比較直白.

//內部使用的主要結構
struct tree {
    //保存二叉樹的頭結點
    struct __tnode* root;

    //構建,釋放,刪除操作的函數指針
    pnew_f new;
    icmp_f acmp;
    icmp_f gdcmp;
    vdel_f del;
};


/*
* new   : 結點申請記憶體用的函數指針, 對映參數中是 特定結構體指針
* acmp  : 用於添加比較
* gdcmp : 兩個結點比較函數,用戶查找和刪除
* del   : 結點回收函數,第一個參數就是 二叉樹中保存的結點地址
* ret   : 返回創建好的二叉樹結構, 這裡是 tree_t 結構
*/
tree_t 
tree_create(pnew_f new, icmp_f acmp, icmp_f gdcmp, vdel_f del)
{
    tree_t root = malloc(sizeof(struct tree));
    if (NULL == root)
        CERR_EXIT("malloc struct tree error!");

    //初始化挨個操作
    memset(root, 0, sizeof(struct tree));
    root->new = new;
    root->acmp = acmp;
    root->gdcmp = gdcmp;
    root->del = del;

    return root;
}

上面主要是需要註冊4個函數, 第一個new自然是分配記憶體的操作返回void*就是構造好的記憶體, acmp是添加結點的時候比較函數,

gdcmp 是 get 和 del 時候需要調用的查找函數指針, 對於del可以沒有這個時候,可以傳入NULL,表示不需要幫忙回收記憶體.

大家可以仔細考慮一下為什麼要這些. 

首先創建和銷毀是必須的,後面 add的時候添加的是 node 結點, 而查找的時候是比較的是 關鍵字key結構是不一樣的.

同樣看一下回收函數

static void __tree_destroy(struct __tnode* root, vdel_f del)
{
    if (root) {
        __tree_destroy(root->lc, del);
        __tree_destroy(root->rc, del);
        del(root); //結點刪除採用註冊方法
    }
}

/*
 * proot  : 指向二叉樹數結點指針
 * 會調用 del(foreach) 去刪除所有結點,並將所有還原到NULL
 */
void 
tree_destroy(tree_t* proot)
{
    tree_t root;
    if ((!proot) || !(root = *proot))
        return;
    if (root->root && root->del)
        __tree_destroy(root->root, root->del);
    free(*proot); //單獨釋放最外層內容
    *proot = NULL;
}

比較質朴沒有好解釋的,最後會讓釋放的指針指向NULL.

後面就是二叉查找樹插入查找和刪除演算法實現了,比較基礎,對著書翻譯就可以了.添加代碼如下

/*
* proot  : 指向tree_t 根結點的指針,
* node      : 待處理的結點對象, 會調用new(node) 創建新結點
* ret    : proot 即是輸入參數也是返回參數,返回根結點返回狀況
*/
void 
tree_add(tree_t* proot, void* node)
{
    tree_t tm;
    struct __tnode *n, *p = NULL;
    icmp_f cmp;
    int tmp = 0;

    if ((!proot) || (!node) || !(tm = *proot)) //參數無效直接返回
        return;
    if (!(n = tm->root)) { //插入的結點為頭結點,直接賦值返回
        tm->root = tm->new(node);
        return;
    }
    //下麵開始找 待插入結點
    cmp = tm->acmp;
    while (n) {
        if ((tmp = cmp(node, n)) == 0) //這種情況是不允許插入的
            return;
        p = n;
        if (tmp < 0)
            n = n->lc;
        else
            n = n->rc;
    }

    //找見了開始插入結點
    if (tmp < 0)
        p->lc = tm->new(node);
    else
        p->rc = tm->new(node);
}

對於cmp

typedef int (*icmp_f)(void* ln, void* rn);

這裡有點約定, ln == rn 返回0, ln>rn 返回 >0 反之返回<0. 其中 傳入的基參數 .都是做第一個參數.

下一版本想改為

//int cmp(void* node, void* rn); 必須是這樣格式
typedef int (*icmp_f)();

弱約束,可以用.畢竟底層庫應該靈活,上層庫應該寫死. 這樣在難處學習成本高,簡單處學習成本低. 等同於紅黑樹的添加和查找.

後面還有一個刪除代碼

/*
* proot  : 輸入和輸出參數,指向根結點的指針
* node   : 刪除結點,這裡會調用 cmp(node 左參數, foreach) 找見,通過del(find) 刪除
*/
void 
tree_del(tree_t* proot, void* node)
{
    tree_t tm;
    struct __tnode *n, *p, *t, *tp;
    if ((!proot) || (!node) || !(tm = *proot) || !(tm->root))
        return;
    //查找一下這個結點,如果不存在直接返回
    if (!(n = tree_get(tm, node, (void**)&p)))
        return;
    //第一種刪除和操作
    if ((!n->lc || !n->rc) && !(t = n->lc)) 
            t = n->rc;
    else { //第二種情況,將右子樹最小結點和當前刪除結點交換
        for (tp = n, t = tp->rc; (t->lc); tp = t, t = t->lc)
            ; //找見了最小的左子樹結點n 和父結點p
        if (tp->lc == t)
            tp->lc = t->rc;
        else
            tp->rc = t->rc;
        //移動孩子關係
        t->lc = n->lc;
        t->rc = n->rc;
    }

    if (!p) //設置新的root結點
        tm->root = t;
    else {
        if (p->lc == n) //調整父親和孩子關係,需要你理解二叉查找樹,否則那就相信我吧
            p->lc = t;
        else
            p->rc = t;
    }
    //這裡釋放那個結點
    if (tm->del)
        tm->del(n);
}

刪除思路解釋,單節點刪除,父節點指向後繼, 多結點找到右子樹中最小的結點當做新結點,再刪除它.上一個版本用尾遞歸,這裡採用的是非遞歸實現.

對於查找是這樣的,也會一起找到父節點

/*
* root   : 根結點,查找的總對象
* node   : 查找條件,會通過cmp(node, foreach)去查找
* parent : 返回查找到的父親結點
* ret     : 返回查找到的結點對象
*/
void* 
tree_get(tree_t root, void* node, void** parent)
{
    struct __tnode *n, *p = NULL;
    icmp_f cmp;
    int tmp;

    if(parent) //初始化功能
        *parent = NULL;
    if ((!node) || (!root) || !(n = root->root))
        return NULL;
    //查找結點
    cmp = root->gdcmp;
    while (n) {
        if ((tmp = cmp(node, n)) == 0){ //這種情況是不允許插入的        
            //返回父親結點,沒有就置空
            if (parent)
                *parent = p;    
            break;
        }
        p = n;
        if (tmp < 0)
            n = n->lc;
        else
            n = n->rc;
    }

    return n;
}

特別是開頭的

    if(parent) //初始化功能
        *parent = NULL;

為了是查找返回數據都是正常數據,沒有意外.

到這裡基本上二叉樹基庫就整理完畢了. 主要是一些C介面設計的技巧 + 二叉樹查找樹的簡單演算法.

還是比較直白的.下一個版本 將公有頭文件內容移除去,會更簡約一點.

 

2.tree.c 代碼完整展示

  完整代碼展示如下

#include "tree.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

//內部使用的主要結構
struct tree {
    //保存二叉樹的頭結點
    struct __tnode* root;

    //構建,釋放,刪除操作的函數指針
    pnew_f new;
    icmp_f acmp;
    icmp_f gdcmp;
    vdel_f del;
};


/*
* new   : 結點申請記憶體用的函數指針, 對映參數中是 特定結構體指針
* acmp  : 用於添加比較
* gdcmp : 兩個結點比較函數,用戶查找和刪除
* del   : 結點回收函數,第一個參數就是 二叉樹中保存的結點地址
* ret   : 返回創建好的二叉樹結構, 這裡是 tree_t 結構
*/
tree_t 
tree_create(pnew_f new, icmp_f acmp, icmp_f gdcmp, vdel_f del)
{
    tree_t root = malloc(sizeof(struct tree));
    if (NULL == root)
        CERR_EXIT("malloc struct tree error!");

    //初始化挨個操作
    memset(root, 0, sizeof(struct tree));
    root->new = new;
    root->acmp = acmp;
    root->gdcmp = gdcmp;
    root->del = del;

    return root;
}

/*
* proot  : 指向tree_t 根結點的指針,
* node      : 待處理的結點對象, 會調用new(node) 創建新結點
* ret    : proot 即是輸入參數也是返回參數,返回根結點返回狀況
*/
void 
tree_add(tree_t* proot, void* node)
{
    tree_t tm;
    struct __tnode *n, *p = NULL;
    icmp_f cmp;
    int tmp = 0;

    if ((!proot) || (!node) || !(tm = *proot)) //參數無效直接返回
        return;
    if (!(n = tm->root)) { //插入的結點為頭結點,直接賦值返回
        tm->root = tm->new(node);
        return;
    }
    //下麵開始找 待插入結點
    cmp = tm->acmp;
    while (n) {
        if ((tmp = cmp(node, n)) == 0) //這種情況是不允許插入的
            return;
        p = n;
        if (tmp < 0)
            n = n->lc;
        else
            n = n->rc;
    }

    //找見了開始插入結點
    if (tmp < 0)
        p->lc = tm->new(node);
    else
        p->rc = tm->new(node);
}

/*
* proot  : 輸入和輸出參數,指向根結點的指針
* node   : 刪除結點,這裡會調用 cmp(node 左參數, foreach) 找見,通過del(find) 刪除
*/
void 
tree_del(tree_t* proot, void* node)
{
    tree_t tm;
    struct __tnode *n, *p, *t, *tp;
    if ((!proot) || (!node) || !(tm = *proot) || !(tm->root))
        return;
    //查找一下這個結點,如果不存在直接返回
    if (!(n = tree_get(tm, node, (void**)&p)))
        return;
    //第一種刪除和操作
    if ((!n->lc || !n->rc) && !(t = n->lc)) 
            t = n->rc;
    else { //第二種情況,將右子樹最小結點和當前刪除結點交換
        for (tp = n, t = tp->rc; (t->lc); tp = t, t = t->lc)
            ; //找見了最小的左子樹結點n 和父結點p
        if (tp->lc == t)
            tp->lc = t->rc;
        else
            tp->rc = t->rc;
        //移動孩子關係
        t->lc = n->lc;
        t->rc = n->rc;
    }

    if (!p) //設置新的root結點
        tm->root = t;
    else {
        if (p->lc == n) //調整父親和孩子關係,需要你理解二叉查找樹,否則那就相信我吧
            p->lc = t;
        else
            p->rc = t;
    }
    //這裡釋放那個結點
    if (tm->del)
        tm->del(n);
}

/*
* root   : 根結點,查找的總對象
* node   : 查找條件,會通過cmp(node, foreach)去查找
* parent : 返回查找到的父親結點
* ret     : 返回查找到的結點對象
*/
void* 
tree_get(tree_t root, void* node, void** parent)
{
    struct __tnode *n, *p = NULL;
    icmp_f cmp;
    int tmp;

    if(parent) //初始化功能
        *parent = NULL;
    if ((!node) || (!root) || !(n = root->root))
        return NULL;
    //查找結點
    cmp = root->gdcmp;
    while (n) {
        if ((tmp = cmp(node, n)) == 0){ //這種情況是不允許插入的        
            //返回父親結點,沒有就置空
            if (parent)
                *parent = p;    
            break;
        }
        p = n;
        if (tmp < 0)
            n = n->lc;
        else
            n = n->rc;
    }

    return n;
}

//實際的刪除函數,採用後續刪除
static void __tree_destroy(struct __tnode* root, vdel_f del)
{
    if (root) {
        __tree_destroy(root->lc, del);
        __tree_destroy(root->rc, del);
        del(root); //結點刪除採用註冊方法
    }
}

/*
 * proot  : 指向二叉樹數結點指針
 * 會調用 del(foreach) 去刪除所有結點,並將所有還原到NULL
 */
void 
tree_destroy(tree_t* proot)
{
    tree_t root;
    if ((!proot) || !(root = *proot))
        return;
    if (root->root && root->del)
        __tree_destroy(root->root, root->del);
    free(*proot); //單獨釋放最外層內容
    *proot = NULL;
}

總長度還是比較短的.上面代碼寫了幾遍,都沒有加測試介面. 後面單獨寫測試demo.因為是封裝庫的庫,測試代碼會多一點.

 

3.說測試結果

  到這裡就是說測試的時候,先簡單看一個test.c 測試,編譯命令是

gcc -g -Wall -o test.out test.c tree.c

源碼如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tree.h"

//通用結構體變數
struct dict {
    _TREE_HEAD;
    char* key;
    char* value;
};

static void* __dict_new(void* arg)
{
    return arg;
}

//為了通用庫,這種比較演算法比較不好,採用hash不能夠唯一確定
static int __dict_acmp(struct dict* ln, struct dict* rn)
{
    return strcmp(ln->key, rn->key);
}
static int __dict_gdcmp(const char* ln, struct dict* rn)
{
    return strcmp(ln, rn->key);
}


/*
 * 這裡測試 tree.c 基類型測試的
 */
int main(int argc, char* argv[])
{
    struct dict *pd , *pp;
    struct dict dt1 = { { 0, 0 }, "123", "123" };
    struct dict dt2 = { { 0, 0 }, "1","1" };
    struct dict dt3 = { { 0, 0 }, "2","2" };
    struct dict dt4 = { { 0, 0 }, "456", "456" };
    struct dict dt5 = { { 0, 0 }, "7","7" };

    //創建一個結點,後面創建刪除
    tree_t root = tree_create(__dict_new, (icmp_f)__dict_acmp, (icmp_f)__dict_gdcmp, NULL);

    //開始添加結點
    tree_add(&root, &dt1);
    tree_add(&root, &dt2);
    tree_add(&root, &dt3);
    tree_add(&root, &dt4);
    tree_add(&root, &dt5);

    //得到這個結點,並返回
    pd = tree_get(root, "123", NULL);
    printf("key:[%s], value:[%s].\n", pd->key, pd->value);

    pd = tree_get(root, "456", (void**)&pp);
    printf("key:[%s], value:[%s].\n", pd->key, pd->value);
    printf("key:[%s], value:[%s].\n", pp->key, pp->value);

    //刪除結點測試,這個普通樹型結構確實不好
    tree_del(&root, "123");
    pd = tree_get(root, "456", (void**)&pp);
    printf("key:[%s], value:[%s].\n", pd->key, pd->value);
    if (!pp)
        puts("應該不存在的!");

    //通過單點調試,記憶體檢測一切正常
    tree_destroy(&root);

    system("pause");
    return 0;
}

測試結果,原先是在window上,後面在Linux上測試了.結果如下

一切正常.

第二個測試,測試在堆上分配是否正常 main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "tree.h"

//繼續測試堆上分配
struct node {
    _TREE_HEAD;
    char* key;
    char* value;
};

//構建運用到的函數
static void* __node_new(struct node* n)
{
    struct node* nn = calloc(1, sizeof(struct node));
    if(NULL == nn)
        CERR_EXIT("malloc struct node error!");    
    nn->key = n->key;    
    nn->value = n->value;    
    //返回最終結果
    return nn;
}

//添加時候查找函數
static int __node_acmp(void* ln, void* rn)
{
    return strcmp(((struct node*)ln)->key, ((struct node*)rn)->key);
}

//查找和刪除的查找函數
static int __node_gdcmp(void* ln, void* rn)
{
    return strcmp(ln, ((struct node*)rn)->key);
}

//簡單測試函數
static void __node_puts(void* arg)
{
    struct node* n = arg;
    if(NULL == n)
        puts("now node is empty!");
    else
        printf("key:%s, value:%s.\n", n->key, n->value);
}

//簡單釋放函數
static void __node_delete(void* arg)
{
    __node_puts(arg);
    free(arg);
}

//寫到這裡自己都想抱怨一句前戲太長了, tree.c 其實本質是個通用演算法庫,...

/*
 * 這裡繼續測試一下 tree 基類庫介面
 */
int main(int argc, char* argv[])
{
    tree_t root = tree_create(__node_new, __node_acmp, __node_gdcmp, __node_delete);            

    //這裡就添加結點
    struct node ntmp = { {NULL, NULL}, "a", "123"};
    tree_add(&root, &ntmp);
    
    ntmp.key = "bb";
    ntmp.value = "ccccccc";
    tree_add(&root, &ntmp);
    
        
    ntmp.key = "bbc";
    ntmp.value = "ccccccc";
    tree_add(&root, &ntmp);
        
    ntmp.key = "bbcc";
    ntmp.value = "ccccccc";
    tree_add(&root, &ntmp);
    
    ntmp.key = "bbcccc";
    ntmp.value = "dd你好ccc";
    tree_add(&root, &ntmp);
    //tree_destroy(&root);    

    if(NULL == root)
        puts("root is null");
    ntmp.key = "好的";
    ntmp.value = "cccok就這樣c";
    tree_add(&root, &ntmp);

    //這裡查找結點
    void *p, *n;
    n = tree_get(root, "好的", &p);
    if(p)
        __node_puts(p);
    else
        puts("沒有父結點");    
    __node_puts(n);

    //刪除結點
    tree_del(&root, "好的");    

    tree_destroy(&root);

    return 0;
}

編譯命令,Makefile文件內容如下

main.out:main.c tree.c
    gcc -g -Wall -o $@ $^

運行結果截圖如下

一切正常沒有記憶體泄露.

後面準備到庫再進行生產測試.

 

後記

  這裡,這個基礎tree C庫基本封裝了,根據庫簡單修改一下基本就可以用在開發中了.下一個版本利用這個庫 構造一個 C 配置文件讀取介面.

讓框架具備簡單配置文件熱讀取的能力.扯一點,像這些解析配置的引擎難點都在 語法解析上.其它都好搞.以後有機會帶大家手把手寫json,csv 解析'引擎'.

這裡就這樣了. 錯誤是難免的, 因為經歷的太少, 拜~.

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • function readyDo() {// alert(xhr.readyState + "分" + xhr.Status); if (xhr.readyState==4 && xhr.status==200 ) { var result = xhr.responseText; if (resul
  • 在Nancy中,給我們的網站換個自定義的圖標。
  • System類代表系統,系統級的很多屬性和控制方法都放置在該類的內部。該類位於java.lang包。 由於該類的構造方法是private的,所以無法創建該類的對象,也就是無法實例化該類。其內部的成員變數和成員方法都是static的,所以也可以很方便的進行調用。 1、成員變數 System類內部包含i
  • 指定迴圈次數,使用計數器重覆運行語句,語法結構如下: 1 2 3 4 5 For counter = start To end [Step step] [statements] [Exit For] [statements] Next 主要參數: counter:用做迴圈計數器的數值變數。這個變數不
  • scrapy關於日誌文件的記錄
  • JSON字元串的最上一層,肯定是一個JSONObject,JSONObject的下一層,可以包含JSONArray,JSONArray又包含了若幹個JSONObject。用例子來說明: package myJson; import net.sf.json.JSONArray; import net.
  • EXTENDED LIGHTS OUT 題意:給一個5*6的01矩陣,對一個位置操作(0->1開燈或者1->0關燈)會影響到(包括自己)周邊燈狀態反轉。問最後要使得所有的燈關掉的操作矩陣(1表示該位置的燈操作了) 提示:01矩陣,題目給了說是操作兩次就相當於沒操作,但是還有隱含的意思就是這就是一個異
  • 用eclipse創建一個WebService應用非常方便,步驟如下: 1.安裝WebService插件。www.eclipse.org網站上有下載,我下載的是wtp-all-in-one-sdk-R-1.5.4-win32,裡面就包括了。(我下載了好像沒什麼用,估計就跟之前安裝的Eclipse是同一
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...