第0章
第1章
dlist.h
#include <stdio.h>
#include "locker.h"
#ifndef DLIST_H
#define DLIST_H
DECLS_BEGIN //对c++支持的封装 extern "c" {}
struct _DList;//对局部数据结构的隐藏,实现在c文件中;原则,尽量少暴露数据。
typedef struct _DList DList;
typedef void (*DListDataDestroyFunc)(void* ctx, void* data); //3)
typedef int (*DListDataCompareFunc)(void* ctx, void* data);
typedef Ret (*DListDataVisitFunc)(void* ctx, void* data);
DList* dlist_create(DListDataDestroyFunc data_destroy, void* ctx, Locker* locker);//提供创建函数
Ret dlist_insert(DList* thiz, size_t index, void* data);
Ret dlist_prepend(DList* thiz, void* data);
Ret dlist_append(DList* thiz, void* data);
Ret dlist_delete(DList* thiz, size_t index);
Ret dlist_get_by_index(DList* thiz, size_t index, void** data);
Ret dlist_set_by_index(DList* thiz, size_t index, void* data);
size_t dlist_length(DList* thiz);
int dlist_find(DList* thiz, DListDataCompareFunc cmp, void* ctx);
Ret dlist_foreach(DList* thiz, DListDataVisitFunc visit, void* ctx);
void dlist_destroy(DList* thiz);
DECLS_END
#endif/*DLIST*/
1)内部函数前加static,头文件中放最少的结构声明。
2)通用链表的实现可以在node的数据部分放置一个void* data,然后实际数据与链表分离在其他部分;此外,可以将链表的最后一个元素定义为char data[0],前面再包含一个计数器,就可以在C语言中强制改变结构大小;此方法移植性问题存在。但是是一个极其常用的手法。
3)如上代码,函数指针的使用(回调函数),可以实现多态的手段。函数指针的灵活性是一样极其强大的东西。注意,因为回调函数总是有上下文的(用脚指头都能想到——每次回调它时的出发点)
2、你的数据放在哪儿
1)bss:存放没有初始化的全局变量,或者初始化为0的全局变量。全部初始化为0.这就是为什么全局变量总是为0.因为在bss段。bss段特点是只占运行时内存空间,而不占文件空间(程序本身的大小)。
2)data:初始化为非0的全局变量。特点是即占用内存空间,也占用文件空间(也即这些数据作为程序一部分)
3)rodata:常量,只占硬盘空间,不占内存。可以多进程共享(不可修改嘛!)
4)text:和rodata段很像,只是text段是我们可执行的程序,而rodata只是数据不可执行。
5)stack:存放临时变量和函数的,想必读了《Python源码剖析》这点深有体会吧。
6)heap:malloc使用的地方,是内存中比例最大的(因为栈一般都是比较小的,而程序本身和数据并不会特别大,剩下的自然全是堆了)。而我们所指的内存泄漏和缓冲区溢出也就出现在这里。
3、本章程序注:
1)本章主要代码示例给出了一个通用链表的实现。在此之前,我基本没有仔细观察过c代码的架构,从作者的代码中可以习得一些优秀的品质,也是每个人字里行间的风格;还有经验或者正确的布局方法。
2)
typedef enum _DListRet
{
DLIST_RET_OK,
DLIST_RET_OOM,
DLIST_RET_STOP,
DLIST_RET_PARAMS,
DLIST_RET_FAIL
}DListRet;
返回值的定义,提高程序的移植性和可阅读性。这点要注意,尤其当程序规模稍微大点的时候,要约定好一套返回的规则。
3)注意程序接口的实现,见前面的代码。接口的实现是一门学问,实现的好坏关乎程序的易用性、稳健性、封装等等;如何习得?读代码,经验,灵感;->
4)至于代码的实现,注意对程序符号的约定,清爽易懂最佳。看过写得很简洁的程序,都是有字母做变量,不佳。当然,数学算法的实现,约定俗成或者刻意隐藏的除外。
5)测试代码的编写。不知道是哪种开发中提到(敏捷开发),测试先行。在开始编码之前(应该也是设计好程序接口之后),首先写好测试程序,把主要接口的功能测试函数写好。然后开始编码。great。注意assert的使用,还有一些辅助测试用的宏。
第2章
1、一些话
1)SPEC工程师(需求分析)。测试。调试。记住:调试器是最后一招,迫不得已采用。一次性写好最佳,多用中间log。
2)写完程序后,先花时间去阅读它,一遍两遍后才开始编译。阅读方法:检查常见错误,模拟计算机执行,假象讲给朋友听。
3)避免常见错误:内存泄漏与越界(未经历过大工程,表示没感觉 ,惭愧),野指针(用完就释放,拴到原位),引用未初始化变量(一旦申明就定义,如 int* p = NULL)、不清楚指针的复杂运算(细节决定成败)……
第3章
1、动态数组与双向链表 <