C 封裝一個通用鏈表 和 一個簡單字元串開發庫

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

今天分享一個 企業級的通用鏈表庫設計和一個簡單字元串設計. 封裝一個簡單的開發框架還是比較麻煩的, 或者說說封裝一個簡單的基庫都挺麻煩的. 全當分享吧. 簡單扯一點,封裝庫其實難點在 前期是設計,中期是演算法,瓶頸在結構和語法解析上.


引言

  這裡需要分享的是一個 簡單字元串庫和 鏈表的基庫,代碼也許用到特定技巧.有時候回想一下,

如果我讀書的時候有人告訴我這些關於C開發的積澱, 那麼會走的多直啊.剛參加工作的時候做桌面開發,

服務是C++寫,界面是C#寫.那時候剛進去評級我是中級,因為他問我關於系統鎖和信號量都答出來.開發一段

時間,寫C#也寫的很溜.後面招我那個人讓我轉行就寫C++和php,那時候就開始學習C++有關知識.

後面去四川工作了,開發安卓,用eclipse + java語法 + android jdk,開發前端,用起來,我的感受,都相似,就是api名字

有點長. 都是那老套路,後來四川公司黃了. 輾轉又來了北京做C系列還有php開發. 說了這麼多, 我想說下麵幾個問題.

  1. 你寫的是 C# 和 java嗎,還只是.net/jdk的積木 , 寫了那麼多這樣的代碼,你感到疑惑嗎?

  2.假如你也感到過疑惑, 推薦去看看 Linux程式開發 或 unix環境編程, 網路編程

      //2.1 不推薦認真學C++, 學了好多,看了好多書,還是不明覺歷,覺得是在雜耍! 如果喜歡C,把市面上好的C書都看一遍,敲一遍!

      3. 因為隨著年紀增長,效率太重要了, 需要去找到學到那些一招鮮吃遍天的東西, 其它的讓年起人去拼搏吧.

有時候想想,做軟體開發,初中就夠了,高中綽綽有餘,大學研究生都暴遣天物. 大家覺得呢.

 

又扯了一會兒蛋, 今天分享的還是很有用的,但是感覺沒接觸這樣黑科技的還是有點難. 或者說封裝一個框架還是有難度的,

或者,哪怕再小的一個庫封裝完畢都是不容易的.而我們分享的是封裝庫的庫. 個人比較水,打臉要輕打.

 

用到的資源

  list 測試demo http://download.csdn.net/detail/wangzhione/9428243

     入行第一篇博文 C的回歸(國內超一線)   http://blog.codingnow.com/2007/09/c_vs_cplusplus.html

 

再扯一點, 2015 北京平均工資最高的三個職業 IT 特殊服務業 電子設備. 如果你窮你真的需要 認真學習編程,不要太沉迷於框架的學習中.

真的 人窮就應該多編程, 別人搶紅包,你 需要寫代碼, , ,

 

前言

  終於到這裡了,扯的有點多. 首先來看一下今天主要寫的通用鏈表的介面,看設計

#ifndef _H_LIST
#define _H_LIST

#include <schead.h>

/*
 *    這個萬能單鏈表庫 前提所有結點都是堆上分配的,設計的比較老了,能用
 *註意
 *    1.使用的時候,需要加上 _LIST_HEAD; 巨集
 *    2.創建的第一句話就是 list head = NULL; 開始從空鏈表開始list的生涯
 */

struct __lnode {
    struct __lnode* next;
};

// 不多說了一定放在想使用鏈表結構的結構體頭部
#define _LIST_HEAD \
    struct __lnode __ln;

// 簡單鏈表結構, 當你使用這個鏈表的時候 需要 list_t head = NULL; 開始使用之旅
typedef void* list_t;

/*
 *  採用頭查法插入結點, 第一使用需要 list_t head = NULL;
 *返回 _RT_OK 表示成功!
 * ph        : 指向頭結點的指針
 * node        : 待插入的結點對象
 */
extern int list_add(list_t* ph, void* node);

/*
 * 鏈表中查找函數,查找失敗返回NULL,查找成功直接返回那個結點,推薦不要亂改,否則就崩了.
 *如果需要改的話,推薦 用 list_findpop, 找到並彈出
 * h        : 鏈表頭結點
 * cmp        : 查找的比較函數
 * left        : cmp(left, right) 用的左結點
 *            : 返回查找的結點對象
 */
extern void* list_find(list_t h, icmp_f cmp, const void* left);

/*
 * 查找到要的結點,並彈出,需要你自己回收
 * ph        : 指向頭結點的指針
 * cmp        : 比較函數,將left同 *ph中對象按個比較
 * left        : cmp(left, x) 比較返回 0 >0 <0
 *            : 找到了退出/返回結點, 否則返回NULL
 */
extern void* list_findpop(list_t *ph, icmp_f cmp, const void* left);

/*
 * 這裡獲取當前鏈表長度, 推薦調用一次就記住len
 * h        : 當前鏈表的頭結點
 *            : 返回 鏈表長度 >=0
 */
extern int list_len(list_t h);

/*
 * 查找索引位置為idx的結點,找不見返回NULL
 * h        : 當前結點
 * idx        : 查找的索引值[0,len)
 *            : 返回查到的結點,如果需要刪除的推薦調用 list_pop(&h, idx);
 */
extern void* list_get(list_t h, int idx);

/*
 * 按照索引彈出並返回結點, 需要自己回收這個結點 推薦 free(list_pop...);
 * ph        : 指向鏈表結點的指針
 * idx        : 彈出的索引
 * return    : 無效的彈出,返回NULL
 */
void* list_pop(list_t* ph, int idx);

/*
 * 返回結點node 的上一個結點,如果node = NULL, 返回最後一個結點
 * h        : 當前鏈表結點
 * node        : 待查找的結點信息
 * return    : 返回查找到的結點,不存在返回NULL
 */
void* list_front(list_t h, void* node);

/*
 * 這個巨集推薦不使用, 主要返回結點n的下一個結點
 * 第一種使用方法 node->next = (void*)list_node(n), 另一種是 list_node(n) = node;
 * n        : 當前結點
 */
#define list_next(n) \
    (((struct __lnode*)n)->next)

/*
 * 和 list_add 功能相似,但是插入位置在尾巴那
 * ph        : 待插入結點的指針
 * node        : 待插入的當前結點
 */ 
int list_addlast(list_t* ph, void* node);

/*
 * 在鏈表的第idx索引處插入結點,也必須需要 list_t head = NULL; 在idx過大的時候
 *插入尾巴處,如果<0直接返回 _RT_EP. 成功了返回 _RT_OK
 * ph        : 指向頭結點的指針
 * idx        : 結點的索引處
 * node        : 待插入的結點
*/
int list_addidx(list_t* ph, int idx, void* node);

/*
 * 這裡的銷毀函數,只有這些數據都是棧上的才推薦這麼做,會自動讓其指向NULL
 * ph         : 指向當前鏈表結點的指針
 */
void list_destroy(list_t* ph);

#endif // !_H_LIST

這裡介面使用的 extern聲明的希望外部直接使用, 沒有extern的外部可以使用,屬於擴展功能.
對於上面介面 簡單的測試 代碼如下

#include <list.h>

struct lint {
    _LIST_HEAD;
    int node;
};

//簡單創建函數
static struct lint* __lint_new(int node)
{
    struct lint* ln = malloc(sizeof(struct lint));
    if(ln){
        ln->node = node;
    }
    return ln;
} 

//簡單列印函數
static void __lint_puts(list_t head)
{
    int len = list_len(head);
    int i;

    printf("當前鏈表中數據結果如下:");
    for(i=0; i<len; ++i){
        struct lint* tl = list_get(head, i);
        printf("%d ", tl->node);
    }
    putchar('\n');
}

/*
 * 這裡簡單測試一下 關於鏈表的常用介面
 */
int main(int argc, char* argv[])
{
    list_t head = NULL;    
    int arrs[] = { 1, 2, 3, 4, 5, 6, 7, 8};    
    int i;

    //這裡添加結點
    for(i=0; i<sizeof(arrs)/sizeof(*arrs); ++i)
        list_add(&head, __lint_new(arrs[i]));
    
    __lint_puts(head);
    //這裡刪除一個結點
    free(list_pop(&head, 0));
    __lint_puts(head);

    //刪除第二個結點
    free(list_pop(&head, 1));    
    __lint_puts(head);        

    list_destroy(&head);
    return 0;
}

測試了幾個簡答介面. 註釋比較詳細, 寫的也比較簡單相對於Linux內核的數據結構而言. 這裡是個開門紅.
臨摹幾遍都能理解C介面的簡單設計.

 

正文

  其實呀上面代碼主要突出一個設計, 實現而言還是比較容易,因為結構有了,演算法就能夠寫好了. 例如 獲取某個結點的源碼

/*
 * 這裡獲取當前鏈表長度, 推薦調用一次就記住len
 * h        : 當前鏈表的頭結點
 *            : 返回 鏈表長度 >=0
 */
int list_len(list_t h)
{
    int len = 0;
    while(h){
        ++len;
        h = list_next(h);
    }
    return len;
}

很基礎也容易理解, 大多數代碼其實結構設計好實現也就是時間問題, 也等同於業務了. 精妙的東西沒有那麼多, 魔鬼藏在細節里.向那些這個池那個組,都名次解釋.
很普通.現在我們只談設計, 最後會給出完整的代碼. 同樣還有一種結構, (狀態不好,加班太多了,寫的很水望見諒,因為很多東西說出來還是做不出來,做出來說的不好.)

看下麵關於簡單字元串設計代碼

#ifndef _H_TSTRING
#define _H_TSTRING

#include <schead.h>

//------------------------------------------------簡單字元串輔助操作----------------------------------

/*
* 主要採用jshash 返回計算後的hash值
* 不衝突率在 80% 左右還可以, 不要傳入NULL
*/
extern unsigned str_hash(const char* str);

//------------------------------------------------簡單文本字元串輔助操作----------------------------------

#ifndef _STRUCT_TSTRING
#define _STRUCT_TSTRING
//簡單字元串結構,並定義文本字元串類型tstring
struct tstring {
    char* str;        //字元串實際保存的內容
    int len;        //當前字元串大小
    int size;        //字元池大小
};
typedef struct  tstring* tstring;
#endif // !_STRUCT_TSTRING

//文本串棧上創建內容,不想用那些技巧了,就這樣吧
#define TSTRING_CREATE(var) \
    struct tstring var = { NULL, 0, 0}
#define TSTRING_DESTROY(var) \
    free(var.str)

/*
 * tstring 的創建函數, 會根據str創建一個 tstring結構的字元串
 * 
 * str : 待創建的字元串
 * 
 * ret : 返回創建好的字元串,如果創建失敗返回NULL
 */
extern tstring tstring_create(const char* str);

/*
 * tstring 完全銷毀函數
 * tstr : 指向tsting字元串指針量的指針
 */
extern void tstring_destroy(tstring* tstr);

/*
 *  向簡單文本字元串tstr中添加 一個字元c
 * tstr : 簡單字元串對象
 * c : 待添加的字元
 * ret : 返回狀態碼 見 schead 中 _RT_EB 碼等
 */
extern int tstring_append(tstring tstr, int c);

/*
 *  向簡單文本串中添加只讀字元串 
 * tstr : 文本串
 * str : 待添加的素材串
 * ret : 返回狀態碼主要是 _RT_EP _RT_EM
 */
extern int tstring_appends(tstring tstr, const char* str);

//------------------------------------------------簡單文件輔助操作----------------------------------

/*
 * 簡單的文件幫助類,會讀取完畢這個文件內容返回,失敗返回NULL.
 * 需要事後使用 tstring_destroy(&ret); 銷毀這個字元串對象
 * path : 文件路徑
 * ret : 返回創建好的字元串內容,返回NULL表示讀取失敗
 */
extern tstring file_malloc_readend(const char* path);

/*
 * 文件寫入,沒有好說的,會返回 _RT_EP _RT_EM _RT_OK
 * path : 文件路徑
 * str : 待寫入的字元串
 * ret : 返回寫入的結果
 */
extern int file_writes(const char* path, const char* str);

/*
 * 文件追加內容, 添加str內同
 * path : 文件路徑
 * str : 待追加的文件內同
 *     : 返回值,主要是 _RT_EP _RT_EM _RT_OK 這些狀態
 */
extern int file_append(const char* path, const char* str);

#endif // !_H_TSTRING

這個串可以用在讀取一個大串,主要解決的問題是記憶體空間分配問題,還可以用.最大浪費就50%.

現在我們簡單說一下具體實現,其實一看

#ifndef _STRUCT_TSTRING
#define _STRUCT_TSTRING
//簡單字元串結構,並定義文本字元串類型tstring
struct tstring {
    char* str;        //字元串實際保存的內容
    int len;        //當前字元串大小
    int size;        //字元池大小
};
typedef struct  tstring* tstring;
#endif // !_STRUCT_TSTRING

全部明白了. 就是 len表現當前str中保存的長度, size表現當前str的容量.分配代碼如下

//簡單分配函數,智力一定會分配記憶體的, len > size的時候調用這個函數
static int __tstring_realloc(tstring tstr, int len)
{
    int size = tstr->size;
    for (size = size < _INT_TSTRING ? _INT_TSTRING : size; size < len; size <<= 1)
        ;
    //分配記憶體
    char *nstr = realloc(tstr->str, size);
    if (NULL == nstr) {
        SL_NOTICE("realloc(tstr->str:0x%p, size:%d) is error!", tstr->str, size);
        return _RT_EM;
    }
    tstr->str = nstr;
    tstr->size = size;
    return _RT_OK;
}

len是新的str大小.後面展現 全部的演示代碼.

#include <tstring.h>
#include <sclog.h>

/*
* 主要採用jshash 返回計算後的hash值
* 不衝突率在 80% 左右還可以, 不要傳入NULL
*/
unsigned 
str_hash(const char* str)
{
    size_t i, h = strlen(str), sp = (h >> 5) + 1;
    unsigned char* ptr = (unsigned char*)str;

    for (i = h; i >= sp; i -= sp)
        h ^= ((h<<5) + (h>>2) + ptr[i-1]);

    return h ? h : 1;
}

/*
* tstring 的創建函數, 會根據str創建一個 tstring結構的字元串
*
* str : 待創建的字元串
*
* ret : 返回創建好的字元串,如果創建失敗返回NULL
*/
tstring 
tstring_create(const char* str)
{
    tstring tstr = calloc(1, sizeof(struct tstring));
    if (NULL == tstr) {
        SL_NOTICE("calloc is sizeof struct tstring error!");
        return NULL;
    }
    tstring_appends(tstr, str);

    return tstr;
}

/*
* tstring 完全銷毀函數
* tstr : 指向tsting字元串指針量的指針
*/
void tstring_destroy(tstring* tstr)
{
    if (tstr && *tstr) { //展現內容
        free((*tstr)->str);
        free(*tstr);
        *tstr = NULL;
    }
}

//文本字元串創建的度量值
#define _INT_TSTRING (32)

//簡單分配函數,智力一定會分配記憶體的, len > size的時候調用這個函數
static int __tstring_realloc(tstring tstr, int len)
{
    int size = tstr->size;
    for (size = size < _INT_TSTRING ? _INT_TSTRING : size; size < len; size <<= 1)
        ;
    //分配記憶體
    char *nstr = realloc(tstr->str, size);
    if (NULL == nstr) {
        SL_NOTICE("realloc(tstr->str:0x%p, size:%d) is error!", tstr->str, size);
        return _RT_EM;
    }
    tstr->str = nstr;
    tstr->size = size;
    return _RT_OK;
}

/*
*  向簡單文本字元串tstr中添加 一個字元c
* tstr : 簡單字元串對象
* c : 待添加的字元
* ret : 返回狀態碼 見 schead 中 _RT_EM 碼等
*/
int tstring_append(tstring tstr, int c)
{
    //不做安全檢查
    int len = tstr->len + 2; // c + '\0' 而len只指向 字元串strlen長度

    //需要進行記憶體分配,唯一損失
    if ((len > tstr->size) && (_RT_EM == __tstring_realloc(tstr, len)))
            return _RT_EM;

    tstr->len = --len;
    tstr->str[len - 1] = c;
    tstr->str[len] = '\0';

    return _RT_OK;
}

/*
*  向簡單文本串中添加只讀字元串
* tstr : 文本串
* str : 待添加的素材串
* ret : 返回狀態碼主要是 _RT_EP _RT_EM
*/
int tstring_appends(tstring tstr, const char* str)
{
    int len;
    if (!tstr || !str || !*str) {
        SL_NOTICE("check param '!tstr || !str || !*str'");
        return _RT_EP;
    }

    len = tstr->len + strlen(str) + 1;
    if ((len > tstr->size) && (_RT_EM == __tstring_realloc(tstr, len)))
        return _RT_EM;

    //這裡複製內容
    strcpy(tstr->str + tstr->len, str);
    tstr->len = len - 1;

    return _RT_OK;
}

//------------------------------------------------簡單文件輔助操作----------------------------------

/*
* 簡單的文件幫助類,會讀取完畢這個文件內容返回,失敗返回NULL.
* 需要事後使用 tstring_destroy(&ret); 銷毀這個字元串對象
* path : 文件路徑
* ret : 返回創建好的字元串內容,返回NULL表示讀取失敗
*/
tstring file_malloc_readend(const char* path)
{
    int c;
    tstring tstr;
    FILE* txt = fopen(path, "r");
    if (NULL == txt) {
        SL_NOTICE("fopen r path = '%s' error!", path);
        return NULL;
    }

    //這裡創建文件對象,創建失敗直接返回
    if ((tstr = tstring_create(NULL)) == NULL) {
        fclose(txt);
        return NULL;
    }

    //這裡讀取文本內容
    while ((c = fgetc(txt))!=EOF)
        if (_RT_OK != tstring_append(tstr, c))
            break;

    fclose(txt);//很重要創建了就要釋放,否則會出現隱藏的句柄bug
    return tstr;
}

/*
* 文件寫入,沒有好說的,會返回 _RT_EP _RT_EM _RT_OK
* path : 文件路徑
* str : 待寫入的字元串
* ret : 返回寫入的結果
*/
int file_writes(const char* path, const char* str)
{
    FILE* txt;
    //檢查參數問題
    if (!path || !str) {
        SL_NOTICE("check is '!path || !str'");
        return _RT_EP;
    }

    if ((txt = fopen(path, "w")) == NULL) {
        SL_NOTICE("fopen w path = '%s' error!", path);
        return _RT_EF;
    }

    //這裡寫入信息
    fputs(str, txt);

    fclose(txt);
    return _RT_OK;
}

/*
* 文件追加內容, 添加str內同
* path : 文件路徑
* str : 待追加的文件內同
*     : 返回值,主要是 _RT_EP _RT_EM _RT_OK 這些狀態
*/
int 
file_append(const char* path, const char* str)
{
    FILE* txt;
    //檢查參數問題
    if (!path || !str) {
        SL_NOTICE("check is '!path || !str'");
        return _RT_EP;
    }
    if ((txt = fopen(path, "a")) == NULL) {
        SL_NOTICE("fopen a path = '%s' error!", path);
        return _RT_EF;
    }
    //這裡寫入信息
    fputs(str, txt);

    fclose(txt);
    return _RT_OK;
}

相比雲風的那個玩具要簡單的多,而且針對性很強,就為了大字元串. 轉存用.還可以一試.

到這裡就到了今天一個主題. 主要測試list demo. 首先看運行的結果圖

首先看Makefile 文件

main.out:main.c list.c schead.c
    gcc -g -Wall -o $@ $^ -I.

再看schead.h 文件

#ifndef _H_CHEAD
#define _H_CHEAD

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <stddef.h>

/*
 * 1.0 錯誤定義巨集 用於判斷返回值狀態的狀態碼 _RF表示返回標誌
 *    使用舉例 : 
         int flag = scconf_get("pursue");
         if(flag != _RT_OK){
            sclog_error("get config %s error! flag = %d.", "pursue", flag);
            exit(EXIT_FAILURE);
        }
 * 這裡是內部 使用的通用返回值 標誌
 */
#define _RT_OK        (0)                //結果正確的返回巨集
#define _RT_EB        (-1)            //錯誤基類型,所有錯誤都可用它,在不清楚的情況下
#define _RT_EP        (-2)            //參數錯誤
#define _RT_EM        (-3)            //記憶體分配錯誤
#define _RT_EC        (-4)            //文件已經讀取完畢或表示鏈接關閉
#define _RT_EF        (-5)            //文件打開失敗

/*
 * 1.1 定義一些 通用的函數指針幫助,主要用於基庫的封裝中
 * 有構造函數, 釋放函數, 比較函數等
 */
typedef void* (*pnew_f)();
typedef void(*vdel_f)(void* node);
// icmp_f 最好 是 int cmp(const void* ln,const void* rn); 標準結構
typedef int(*icmp_f)();

/*
 * 1.2 最簡單的 判斷字元串是否為空白字元代碼, true為真
 */
#define sh_isspace(c) \
    (c==' '||c=='\t'||c=='\n'||c=='\r'||c=='\v'||c=='\f')

/*
 *    2.0 如果定義了 __GNUC__ 就假定是 使用gcc 編譯器,為Linux平臺
 * 否則 認為是 Window 平臺,不可否認巨集是醜陋的
 */
#if defined(__GNUC__)
//下麵是依賴 Linux 實現,等待毫秒數
#include <unistd.h>
#include <sys/time.h>
#define SLEEPMS(m) \
        usleep(m * 1000)
#else 
// 這裡創建等待函數 以毫秒為單位 , 需要依賴操作系統實現
#include <Windows.h>
#include <direct.h> // 載入多餘的頭文件在 編譯階段會去掉
#define inline __inline    //附加一個內聯函數巨集
#define rmdir  _rmdir

/**
*    Linux sys/time.h 中獲取時間函數在Windows上一種移植實現
**tv    :    返回結果包含秒數和微秒數
**tz    :    包含的時區,在window上這個變數沒有用不返回
**        :   預設返回0
**/
extern int gettimeofday(struct timeval* tv, void* tz);

//為瞭解決 不通用功能
#define localtime_r(t, tm) localtime_s(tm, t)

#define SLEEPMS(m) \
        Sleep(m)
#endif /*__GNUC__ 跨平臺的代碼都很醜陋 */

//3.0 浮點數據判斷巨集幫助, __開頭表示不希望你使用的巨集
#define __DIFF(x, y)                ((x)-(y))                    //兩個表達式做差巨集
#define __IF_X(x, z)                ((x)<z&&(x)>-z)                //判斷巨集,z必須是巨集常量
#define EQ(x, y, c)                    EQ_ZERO(__DIFF(x,y), c)        //判斷x和y是否在誤差範圍內相等

//3.1 float判斷定義的巨集
#define _FLOAT_ZERO                (0.000001f)                        //float 0的誤差判斷值
#define EQ_FLOAT_ZERO(x)        __IF_X(x,_FLOAT_ZERO)            //float 判斷x是否為零是返回true
#define EQ_FLOAT(x, y)            EQ(x, y, _FLOAT_ZERO)            //判斷表達式x與y是否相等

//3.2 double判斷定義的巨集
#define _DOUBLE_ZERO            (0.000000000001)                //double 0誤差判斷值
#define EQ_DOUBLE_ZERO(x)        __IF_X(x,_DOUBLE_ZERO)            //double 判斷x是否為零是返回true
#define EQ_DOUBLE(x,y)            EQ(x, y, _DOUBLE_ZERO)            //判斷表達式x與y是否相等

//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 */

#ifndef IF_CERR
/*
 *4.2 if 的 代碼檢測
 *
 * 舉例:
 *        IF_CERR(fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP), "socket create error!");
 * 遇到問題列印日誌直接退出,可以認為是一種簡單模板
 *    code : 要檢測的代碼 
 *  fmt     : 必須是""括起來的字元串巨集
 *    ...     : 後面的參數,參照printf
 */
#define IF_CERR(code, fmt, ...)    \
    if((code) < 0) \
        CERR_EXIT(fmt, ##__VA_ARGS__)
#endif //!IF_CERR

//5.0 獲取數組長度,只能是數組類型或""字元串常量,後者包含'\0'
#ifndef LEN
#define LEN(arr) \
    (sizeof(arr)/sizeof(*(arr)))
#endif/* !ARRLEN */

//6.0 程式清空屏幕函數
#ifndef CONSOLE_CLEAR
#ifndef _WIN32
#define CONSOLE_CLEAR() \
        system("printf '\ec'")
#else
#define CONSOLE_CLEAR() \
        system("cls")
#endif/* _WIN32 */
#endif /*!CONSOLE_CLEAR*/

//7.0 置空操作
#ifndef BZERO
//v必須是個變數
#define BZERO(v) \
    memset(&v,0,sizeof(v))
#endif/* !BZERO */    

//9.0 scanf 健壯的
#ifndef SAFETY_SCANF
#define SAFETY_SCANF(scanf_code,...) \
    while(printf(__VA_ARGS__),scanf_code){\
        while(getchar()!='\n');\
        puts("輸入出錯,請按照提示重新操作!");\
    }\
    while(getchar()!='\n')
#endif /*!SAFETY_SCANF*/

//10.0 簡單的time幫助巨集
#ifndef TIME_PRINT
#define TIME_PRINT(code) {\
    clock_t __st,__et;\
    __st=clock();\
    code\
    __et=clock();\
    printf("當前代碼塊運行時間是:%lf秒\n",(0.0+__et-__st)/CLOCKS_PER_SEC);\
}
#endif /*!TIME_PRINT*/

//11.0 等待的巨集 這裡 已經處理好了
#define _STR_PAUSEMSG "請按任意鍵繼續. . ."
extern void sh_pause(void);
#ifndef INIT_PAUSE

#    ifdef _DEBUG
#        define INIT_PAUSE() atexit(sh_pause)
#    else
#        define INIT_PAUSE()    (void)316 /* 別說了,都重新開始吧 */
#    endif

#endif/* !INIT_PAUSE */


//12.0 判斷是大端序還是小端序,大端序返回true
extern bool sh_isbig(void);

/**
*    sh_free - 簡單的釋放記憶體函數,對free再封裝了一下
**可以避免野指針
**pobj:指向待釋放記憶體的指針(void*)
**/
extern void sh_free(void** pobj);

/**
*    獲取 當前時間串,並塞入tstr中長度並返回
**    使用舉例
    char tstr[64];
    puts(gettimes(tstr, LEN(tstr)));
**tstr    : 保存最後生成的最後串
**len    : tstr數組的長度
**        : 返回tstr首地址
**/
extern int sh_times(char tstr[], int len);

#endif/* ! _H_CHEAD */

主要是跨平臺的一些幫助巨集,開發中用到的通用巨集.具體schead.c實現如下

#include <schead.h>

//簡單通用的等待函數
void 
sh_pause(void)
{
    rewind(stdin);
    printf(_STR_PAUSEMSG);
    getchar();
}

//12.0 判斷是大端序還是小端序,大端序返回true
bool 
sh_isbig(void)
{
    static union {
        unsigned short _s;
        unsigned char _cs[sizeof(unsigned short)];
    } __ut = { 1 };
    return __ut._cs[0] == 0;
}

/**
*    sh_free - 簡單的釋放記憶體函數,對free再封裝了一下
**可以避免野指針
**@pobj:指向待釋放記憶體的指針(void*)
**/
void 
sh_free(void** pobj)
{
    if (pobj == NULL || *pobj == NULL)
        return;
    free(*pobj);
    *pobj = NULL;
}

#if defined(_MSC_VER)
/**
*    Linux sys/time.h 中獲取時間函數在Windows上一種移植實現
**tv    :    返回結果包含秒數和微秒數
**tz    :    包含的時區,在window上這個變數沒有用不返回
**        :   預設返回0
**/
int 
gettimeofday(struct timeval* tv, void* tz)
{
    time_t clock;
    struct tm tm;
    SYSTEMTIME wtm;

    GetLocalTime(&wtm);
    tm.tm_year = wtm.wYear - 1900;
    tm.tm_mon = wtm.wMonth - 1; //window的計數更好寫
    tm.tm_mday = wtm.wDay;
    tm.tm_hour = wtm.wHour;
    tm.tm_min = wtm.wMinute;
    tm.tm_sec = wtm.wSecond;
    tm.tm_isdst = -1; //不考慮夏令時
    clock = mktime(&tm);
    tv->tv_sec = (long)clock; //32位使用,介面已經老了
    tv->tv_usec = wtm.wMilliseconds * 1000;

    return _RT_OK;
}
#endif

/**
*    獲取 當前時間串,並塞入tstr中C長度並返回
**    使用舉例
char tstr[64];
puts(gettimes(tstr, LEN(tstr)));
**tstr    : 保存最後生成的最後串
**len    : tstr數組的長度
**        : 返回tstr首地址
**/
int 
sh_times(char tstr[], int len)
{
    struct tm st;
    time_t    t = time(NULL);
    localtime_r(&t, &st);
    return (int)strftime(tstr, len, "%F %X", &st);
}

後面是list.c的具體實現了

#include <list.h>

/*
 *  採用頭查法插入結點, 第一使用需要 list_t head = NULL;
 *返回 _RT_OK 表示成功!
 * ph        : 指向頭結點的指針
 * node        : 待插入的結點對象
 	   

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

-Advertisement-
Play Games
更多相關文章
  • 這裡是簡要的一些微信支付,公眾號支付的一些流程,包括以下配置信息,錯誤信息等,並不全面,但是希望能夠幫助到大家,不喜勿噴,我也是新手,也當是給自己寫了一個筆記,加深一下影響,以後再遇到,也能方便自己的學習
  • 本篇文章的主旨是使用 .NET/C# 實現 TCP 高性能服務的不同方式,包括但不限於如下內容:APM 方式,即 Asynchronous Programming Model;TAP 方式,即 Task-based Asynchronous Pattern;SAEA 方式,即 SocketAsync...
  • 集合概述 集合簡單的說就是數組的升級版。他可以動態的對集合的長度(也就是集合內最大元素的個數)進行定義和維護! ArrayList ArrayList非常類似於數組。也有人稱他為數組列表,它的容量可以根據需要進行動態擴充,而它的索引也會根據集合容量的擴展而重新分配和調整。也就是說ArrayList集...
  • ASP.NET使用StructureMap等依賴註入組件時最重要就是EntityFramework的DbContext對象要保證在每次HttpRequest只有一個DbContext實例,這裡將使用第三方提供的HttpSimulator進行測試。 1.定義IDependency介面 創建屏蔽不同依賴
  • 由快速排序引發對Java方法參數的思考
  • 一、三元運算 我們在上章學習的if,,else,,有一種簡便的方法 他的表達式是這樣的:變數 = 值1 if 條件 else 值2 解釋過來就是如果aaa等於sss則輸出值1否則輸出值2 二、類的概念 類是面向對象編程的核心, 它扮演相關數據及邏輯的容器角色。它們提供了創建“真實” 對象(也就是實例
  • 練習: 輸入詩的名稱查詢出詩的內容,當輸入exit時,退出程式,“春曉”,“靜夜思”,“鵝”。 package CollectionPart; public class Poetry { private String title; private String poet; private Strin
  • 說明:垃圾回收演算法是理論,垃圾收集器是回收演算法的實現,關於回收演算法,見《第四章 JVM垃圾回收演算法》 1、七種垃圾收集器 Serial(串列GC) ParNew(並行GC) Parallel Scavenge(並行回收GC) Serial Old(MSC)(串列GC) CMS(併發GC) Paral
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...