前言

   最经想改写C用的配置读取接口, 准备采用hash或二叉树提到原先用的链表,提高查找效率.
就回顾一下二叉树,这里分享一下二叉查找树,代码很精简的, 适合学习运用二叉树查找.

需要基础

1.具备C基础知识

2.知道数据结构,最好知道一点二叉树结构

能够学到

1.稳固二叉查找树

2.C良好编码格式习惯

3.tree 数据结构几种流行套路(设计)

参照

1.二叉查找树简单分析 http://www.cppblog.com/cxiaojia/archive/2012/08/09/186752.html

(上面那个博文, 图形讲解的恨透,但是那种tree数据结构,不要参照)

正文

1 直接奔主题 说二叉查找树难点

1.1 简单说一下二叉查找树原理和突破

  二叉树也是个经典的数据结构,但是工作中用的场景不多,但是我们常用过,例如map,自带排序的k-v结构.

二叉树相比双向链表在改变了插入和删除方式,使查找代价变小.因而适用领域在快速查找的领域.对于那种快速删除,

快速插入的领域并不适合.

  我们今天主要回顾的是二叉查找(搜索)树. 首先看看数据结构如下

  1. /*
  2. * 这里简单的温故一下 , 二叉查找树
  3. *一切从简单的来吧
  4. */
  5. typedef int node_t;
  6. typedef struct tree {
  7. node_t v; //这里简单测试一下吧,从简单做起
  8.  
  9. struct tree* lc;
  10. struct tree* rc;
  11. } *tree_t;

上面比较简陋,不是很通用,方便了解原理设计,最后会带大家设计一些通用的二叉树结构. 这里扯一点,

结构会影响算法,算法依赖特定的结构.蛋和鸡的问题,先有一个不好的蛋,孵化一个不好的鸡,后来鸡下了很多蛋,其中一个蛋很好,

孵化了一个很好的鸡,最后蛋鸡良好循环出现了.

对于二叉查找树,除了删除比较复杂一点,其它的还是很大众的代码,这里从如何找到一个结点的父节点出发.看下面代码

  1. /*
  2. * 查找这个结点的父结点
  3. * root : 头结点
  4. * v : 查找的结点
  5. * : 返回这个v值的父亲结点,找不见返回NULL,可以返回孩子结点
  6. */
  7. tree_t
  8. tree_parent(tree_t root, node_t v, tree_t* pn)
  9. {
  10. tree_t p = NULL;
  11. while (root) {
  12. if (root->v == v)
  13. break;
  14. p = root;
  15. if (root->v > v)
  16. root = root->lc;
  17. else
  18. root = root->rc;
  19. }
  20.  
  21. if (pn) //返回它孩子结点
  22. *pn = root;
  23.  
  24. return p;
  25. }

本质思路是,构建一个指针p保存上一个结点.这个函数相比其它函数 tree_parent 这里多返回当前的孩子结点.一个函数顺带做了两件事.

这是一个突破.推荐学习,同等代价做的事多了,价值也就提升了.

下面说一下 二叉查找树 删除原理(从上面参照文中截得,这个比较详细,但是代码写的水)

代码实现如下,有点精简,多看几遍,或者调试几遍理解更容易写.

  1. /*
  2. * 删除结点
  3. * proot : 指向头结点的结点
  4. * v : 待删除的值
  5. */
  6. void
  7. tree_delete(tree_t* proot, node_t v)
  8. {
  9. tree_t root, n, p, t;//n表示v结点,p表示父亲结点
  10. if ((!proot) || !(root = *proot))
  11. return;
  12. //这里就找见 v结点 n和它的父亲结点p
  13. p = tree_parent(root, v, &n);
  14. if (!n) //第零情况 没有找见这个结点直接返回
  15. return;
  16.  
  17. //第一种情况,删除叶子结点,直接删除就可以此时t=NULL; 第二情况 只有一个叶子结点
  18. if (!n->lc || !n->rc) {
  19. if (!(t = n->lc)) //找见当前结点n的唯一孩子结点
  20. t = n->rc;
  21. if (!p)
  22. *proot = NULL;
  23. else {
  24. if (p->lc == n) //让当前结点的父亲收养这个它唯一的孩子
  25. p->lc = t;
  26. else
  27. p->rc = t;
  28. }
  29. //删除当前结点并返回,C要是支持 return void; 语法就好了
  30. free(n);
  31. return;
  32. }
  33.  
  34. //第三种情况, 删除的结点有两个孩子
  35. //将当前结点 右子树中最小值替代为它,继承王位,它没有左儿子
  36. for (t = n->rc; t->lc; t = t->lc)
  37. ;
  38. n->v = t->v;//用nr替代n了,高效,并让n指向找到t的唯一右子树,
  39. tree_delete(&n->rc, t->v);//递归删除n右子树中最小值, 从t开始,很高效
  40. }

第一步找见这个结点和它父亲结点,没找见它直接返回,父亲结点为了重新配置继承关系.

对于 要删除 叶子结点或只有孩子的结点, 删除 走 if(!n->lc || !n->rc) 分支不同是t

当只为叶子结点 t = NULL, 当有一个孩子结点, t = 后继结点,将其二和一了,是一个突破.

最后 删除 有两个孩子的结点, 我们的做法,将 它 右子树中最小值找到,让其替代自己, 后面在右子树中删除 那个结点.

1.2 简单扩展一下 递归的潜规则

  递归大多数流程如下

  1. //数据打印函数,全部输出,不会打印回车,中序递归
  2. void
  3. tree_print(tree_t root)
  4. {
  5. if (root) { //简单中序找到最左结点,打印
  6. tree_print(root->lc);
  7. printf("%d ", root->v);
  8. tree_print(root->rc);
  9. }
  10. }

这样的递归的方式 是

 tree_print_0 => tree_print_1 => tree_print_2 => tree_print_3 => tree_print_2 => tree_print_1 => tree_print_0

先入函数栈后出函数栈,递归深度太长会爆栈.上面就是大多数递归的方式.

递归中有一种特殊的尾递归.不需要依赖递归返回结果.一般递归代码在函数最尾端.例如上 删除代码,结构如下

tree_delete_0 => tree_delete_0 => tree_delete_1 =>  tree_delete_1 =>  tree_delete_2 =>  tree_delete_2 =>  tree_delete_3 =>

这里代码就是入栈出栈,跳转到新的递归中.属于编译器关于递归的优化,不依赖递归返回的结果,最后一行,一般都优化为尾递归很安全.

入不同行开发,潜规则还是比较多的.扯一点, 一天晚上出租车回来和司机瞎扯淡, 他说有一天带一个导演,那个导演打电话给一个女孩父亲,

告诉他,他女儿今天晚上来他房间,痛斥一顿让她走了,后面就联系女孩父亲,女孩父亲神回复,导演你该潜你就潜. 估计当时那个导演心里就有

一万个草泥马奔过,怎么就有这么一对活宝父女.

人生活宝多才欢乐,快乐才会笑着带着'class'.

1.3 说一下接口和测试代码

  一般良好安全的编程喜欢是,先写接口,再写总的测试代码,后面代码接口打桩挨个测试. 这里总的接口和测试代码如下

  1. /*
  2. * 在二叉查找树中插入结点
  3. * proot : 头结点的指针
  4. * v : 待插入变量值,会自动分配内存
  5. */
  6. void tree_insert(tree_t* proot, node_t v);
  7.  
  8. //数据打印函数,全部输出,不会打印回车,中序递归
  9. void tree_print(tree_t root);
  10.  
  11. /*
  12. * 在这个二叉查找树中查找 值为v的结点,找不见返回NULL
  13. * root : 头结点
  14. * v : 查找结点值
  15. * : 返回值为查找到的结点,找不见返回NULL
  16. */
  17. tree_t tree_search(tree_t root, node_t v);
  18.  
  19. /*
  20. * 查找这个结点的父结点
  21. * root : 头结点
  22. * v : 查找的结点
  23. * : 返回这个v值的父亲结点,找不见返回NULL,可以返回孩子结点
  24. */
  25. tree_t tree_parent(tree_t root, node_t v, tree_t* pn);
  26.  
  27. /*
  28. * 删除结点
  29. * proot : 指向头结点的结点
  30. * v : 待删除的值
  31. */
  32. void tree_delete(tree_t* proot, node_t v);
  33.  
  34. /*
  35. * 删除这个二叉查找树,并把根结点置空
  36. * proot : 指向根结点的指针
  37. */
  38. void tree_destroy(tree_t* proot);
  39.  
  40. //简单输出帮助宏
  41. #define TREE_PRINT(root) \
  42. puts("当前二叉查找树的中序数据如下:"), tree_print(root), putchar('\n')
  43.  
  44. //简单的主函数逻辑
  45. int main(int argc, char* argv[])
  46. {
  47. tree_t root = NULL;
  48. //先创建一个二叉树 试试
  49. node_t a[] = { ,,,,,,,-,,,,,, };
  50. //中间临时变量
  51. tree_t tmp;
  52. node_t n;
  53.  
  54. int i = -;
  55. //插入数据
  56. while (++i<sizeof(a) / sizeof(*a))
  57. tree_insert(&root, a[i]);
  58.  
  59. //简单输出数据
  60. TREE_PRINT(root);
  61.  
  62. //这里查找数据,删除数据打印数据
  63. n = ;
  64. tmp = tree_search(root, n);
  65. if (tmp == NULL)
  66. printf("root is no find %d!\n", n);
  67. else
  68. printf("root is find %d, is %p,%d!\n", n, tmp, tmp->v);
  69.  
  70. //查找父亲结点
  71. n = ;
  72. tmp = tree_parent(root, n, NULL);
  73. if (tmp == NULL)
  74. printf("root is no find %d!\n", n);
  75. else
  76. printf("root is find parent %d, is %p,%d!\n", n, tmp, tmp->v);
  77.  
  78. //删除测试
  79. n = ;
  80. tree_delete(&root, n);
  81. TREE_PRINT(root);
  82.  
  83. n = ;
  84. tree_delete(&root, n);
  85. TREE_PRINT(root);
  86.  
  87. //释放资源
  88. tree_destroy(&root);
  89.  
  90. system("pause");
  91. return ;
  92. }

测试代码就是把声明的接口挨个测试一遍.对于代码打桩意思就是简单的实现接口,让其能编译通过.如下

  1. /*
  2. * 在这个二叉查找树中查找 值为v的结点,找不见返回NULL
  3. * root : 头结点
  4. * v : 查找结点值
  5. * : 返回值为查找到的结点,找不见返回NULL
  6. */
  7. tree_t
  8. tree_search(tree_t root, node_t v)
  9. {
  10.  
  11. return NULL;
  12. }

就是打桩. 到这里基本都万事具备了.设计思路有了,原理也明白了,下面上一个完整案例看结果.

2.汇总代码, 看运行结果

  首先看运行结果截图

查找,删除,打印都来了一遍, 具体的实现代码如下

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <errno.h>
  4. #include <string.h>
  5.  
  6. //控制台打印错误信息, fmt必须是双引号括起来的宏
  7. #ifndef CERR
  8. #define CERR(fmt, ...) \
  9. fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\r\n",\
  10. __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__)
  11.  
  12. //检测并退出的宏
  13. #define CERR_EXIT(fmt, ...) \
  14. CERR(fmt, ##__VA_ARGS__), exit(EXIT_FAILURE)
  15.  
  16. #endif/* !CERR */
  17.  
  18. /*
  19. * 这里简单的温故一下 , 二叉查找树
  20. *一切从简单的来吧
  21. */
  22. typedef int node_t;
  23. typedef struct tree {
  24. node_t v; //这里简单测试一下吧,从简单做起
  25.  
  26. struct tree* lc;
  27. struct tree* rc;
  28. } *tree_t;
  29.  
  30. /*
  31. * 在二叉查找树中插入结点
  32. * proot : 头结点的指针
  33. * v : 待插入变量值,会自动分配内存
  34. */
  35. void tree_insert(tree_t* proot, node_t v);
  36.  
  37. //数据打印函数,全部输出,不会打印回车,中序递归
  38. void tree_print(tree_t root);
  39.  
  40. /*
  41. * 在这个二叉查找树中查找 值为v的结点,找不见返回NULL
  42. * root : 头结点
  43. * v : 查找结点值
  44. * : 返回值为查找到的结点,找不见返回NULL
  45. */
  46. tree_t tree_search(tree_t root, node_t v);
  47.  
  48. /*
  49. * 查找这个结点的父结点
  50. * root : 头结点
  51. * v : 查找的结点
  52. * : 返回这个v值的父亲结点,找不见返回NULL,可以返回孩子结点
  53. */
  54. tree_t tree_parent(tree_t root, node_t v, tree_t* pn);
  55.  
  56. /*
  57. * 删除结点
  58. * proot : 指向头结点的结点
  59. * v : 待删除的值
  60. */
  61. void tree_delete(tree_t* proot, node_t v);
  62.  
  63. /*
  64. * 删除这个二叉查找树,并把根结点置空
  65. * proot : 指向根结点的指针
  66. */
  67. void tree_destroy(tree_t* proot);
  68.  
  69. //简单输出帮助宏
  70. #define TREE_PRINT(root) \
  71. puts("当前二叉查找树的中序数据如下:"), tree_print(root), putchar('\n')
  72.  
  73. //简单的主函数逻辑
  74. int main(int argc, char* argv[])
  75. {
  76. tree_t root = NULL;
  77. //先创建一个二叉树 试试
  78. node_t a[] = { ,,,,,,,-,,,,,, };
  79. //中间临时变量
  80. tree_t tmp;
  81. node_t n;
  82.  
  83. int i = -;
  84. //插入数据
  85. while (++i<sizeof(a) / sizeof(*a))
  86. tree_insert(&root, a[i]);
  87.  
  88. //简单输出数据
  89. TREE_PRINT(root);
  90.  
  91. //这里查找数据,删除数据打印数据
  92. n = ;
  93. tmp = tree_search(root, n);
  94. if (tmp == NULL)
  95. printf("root is no find %d!\n", n);
  96. else
  97. printf("root is find %d, is %p,%d!\n", n, tmp, tmp->v);
  98.  
  99. //查找父亲结点
  100. n = ;
  101. tmp = tree_parent(root, n, NULL);
  102. if (tmp == NULL)
  103. printf("root is no find %d!\n", n);
  104. else
  105. printf("root is find parent %d, is %p,%d!\n", n, tmp, tmp->v);
  106.  
  107. //删除测试
  108. n = ;
  109. tree_delete(&root, n);
  110. TREE_PRINT(root);
  111.  
  112. n = ;
  113. tree_delete(&root, n);
  114. TREE_PRINT(root);
  115.  
  116. //释放资源
  117. tree_destroy(&root);
  118.  
  119. system("pause");
  120. return ;
  121. }
  122. /*
  123. * 在二叉查找树中插入结点
  124. * proot : 头结点的指针
  125. * v : 待插入变量值,会自动分配内存
  126. */
  127. void
  128. tree_insert(tree_t* proot, node_t v)
  129. {
  130. tree_t n, p = NULL, t = *proot;
  131.  
  132. while (t) {
  133. if (t->v == v) //不让它插入重复数据
  134. return;
  135. p = t; //记录上一个结点
  136. t = t->v > v ? t->lc : t->rc;
  137. }
  138.  
  139. //这里创建结点,创建失败直接退出C++都是这种做法
  140. n = calloc(sizeof(struct tree), );
  141. if (NULL == n)
  142. CERR_EXIT("calloc struct tree error!");
  143. n->v = v;
  144.  
  145. //这里插入了,开始第一个是头结点
  146. if (NULL == p) {
  147. *proot = n;
  148. return;
  149. }
  150. if (p->v > v)
  151. p->lc = n;
  152. else
  153. p->rc = n;
  154. }
  155.  
  156. //数据打印函数,全部输出,不会打印回车,中序递归
  157. void
  158. tree_print(tree_t root)
  159. {
  160. if (root) { //简单中序找到最左结点,打印
  161. tree_print(root->lc);
  162. printf("%d ", root->v);
  163. tree_print(root->rc);
  164. }
  165. }
  166.  
  167. /*
  168. * 在这个二叉查找树中查找 值为v的结点,找不见返回NULL
  169. * root : 头结点
  170. * v : 查找结点值
  171. * : 返回值为查找到的结点,找不见返回NULL
  172. */
  173. tree_t
  174. tree_search(tree_t root, node_t v)
  175. {
  176. while (root) {
  177. if (root->v == v)
  178. return root;
  179. if (root->v > v)
  180. root = root->lc;
  181. else
  182. root = root->rc;
  183. }
  184.  
  185. return NULL;
  186. }
  187.  
  188. /*
  189. * 查找这个结点的父结点
  190. * root : 头结点
  191. * v : 查找的结点
  192. * : 返回这个v值的父亲结点,找不见返回NULL,可以返回孩子结点
  193. */
  194. tree_t
  195. tree_parent(tree_t root, node_t v, tree_t* pn)
  196. {
  197. tree_t p = NULL;
  198. while (root) {
  199. if (root->v == v)
  200. break;
  201. p = root;
  202. if (root->v > v)
  203. root = root->lc;
  204. else
  205. root = root->rc;
  206. }
  207.  
  208. if (pn) //返回它孩子结点
  209. *pn = root;
  210.  
  211. return p;
  212. }
  213.  
  214. /*
  215. * 删除结点
  216. * proot : 指向头结点的结点
  217. * v : 待删除的值
  218. */
  219. void
  220. tree_delete(tree_t* proot, node_t v)
  221. {
  222. tree_t root, n, p, t;//n表示v结点,p表示父亲结点
  223. if ((!proot) || !(root = *proot))
  224. return;
  225. //这里就找见 v结点 n和它的父亲结点p
  226. p = tree_parent(root, v, &n);
  227. if (!n) //第零情况 没有找见这个结点直接返回
  228. return;
  229.  
  230. //第一种情况,删除叶子结点,直接删除就可以此时t=NULL; 第二情况 只有一个叶子结点
  231. if (!n->lc || !n->rc) {
  232. if (!(t = n->lc)) //找见当前结点n的唯一孩子结点
  233. t = n->rc;
  234. if (!p)
  235. *proot = t;
  236. else {
  237. if (p->lc == n) //让当前结点的父亲收养这个它唯一的孩子
  238. p->lc = t;
  239. else
  240. p->rc = t;
  241. }
  242. //删除当前结点并返回,C要是支持 return void; 语法就好了
  243. free(n);
  244. return;
  245. }
  246.  
  247. //第三种情况, 删除的结点有两个孩子
  248. //将当前结点 右子树中最小值替代为它,继承王位,它没有左儿子
  249. for (t = n->rc; t->lc; t = t->lc)
  250. ;
  251. n->v = t->v;//用nr替代n了,高效,并让n指向找到t的唯一右子树,
  252. tree_delete(&n->rc, t->v);//递归删除n右子树中最小值, 从t开始,很高效
  253. }
  254.  
  255. //采用后序删除
  256. static void __tree_destroy(tree_t root)
  257. {
  258. if (root) {
  259. __tree_destroy(root->lc);
  260. __tree_destroy(root->rc);
  261. free(root);
  262. }
  263. }
  264.  
  265. /*
  266. * 删除这个二叉查找树,并把根结点置空
  267. * proot : 指向根结点的指针
  268. */
  269. void
  270. tree_destroy(tree_t* proot)
  271. {
  272. if (proot)
  273. __tree_destroy(*proot);
  274. *proot = NULL;
  275. }

大家自己联系一下,代码不多,容易学习顺带回顾一下数据结构中二叉树结构,关于其中 tree_destroy 编码方式,是个人的编程习惯.

在C中变量声明后没有默认初始化, 所以习惯有这样的代码

  1. struct sockaddr_in sddr;
  2. memset(&sddr, , sizeof sddr);

我觉得这样麻烦,我习惯的写法是

  1. struct sockaddr_in saddr = { AF_INET };

利用了一个C声明初始化潜规则,上面和下面代码转成汇编后也许都相似.后面写法,默认编译器帮我们把它后面没初始化部分置成0.

还有一个习惯,可以允许一个烂的开始,必须要有一个perfect结束,参照老C++版本的智能指针,也叫破坏指针. 做法就是

  1. char* p = malloc();
  2. free(p);
  3. p = NULL;

防止野指针.一种粗暴的做法,所以个人习惯在结束的时候多'浪费'一点时间回顾一下以前,再将其彻底抹除,等同于亚洲飞人直接删除所有回忆的做法.

编程的实现.最后再吐槽一下,为什么C++很烂,因为看了无数的书,还是不知道它要闹哪样.它就是一本易筋经,左练右练上练下练都可以,终于练成了

恭喜你,这张一张残废证收下.

再扯一点, 为什么C++中叫模板,上层语言中叫泛型? 哈哈,可以参照全特化和偏(范)特化.这里卖一个关子,但是本文中最后会有案例解决.

3.继往开来,了解一些数据结构设计的模式 

  上面基本都扯的差不多了,这里分享C中几种的数据结构设计模式.

第一种 一切解'对象'

  1. /*
  2. * C中如何封装一个 tree '结构'(结构决定算法)
  3. */
  4.  
  5. /*
  6. * 第一种思路是 一切皆'对象'
  7. */
  8. struct otree {
  9. void* obj;
  10. struct otree* lc;
  11. struct otree* rc;
  12. };
  13.  
  14. struct onode {
  15. int id;
  16. char* name;
  17. };
  18.  
  19. // obj => &struct onde的思路,浪费了4字节,方便管理

大家看到那个 void*应该就明白了吧等同于上层语言中Object对象.

第二种 万物皆'泛型'

  1. /*
  2. * 第二种思路是 万物皆'泛型'
  3. */
  4. struct tree_node {
  5. struct tree_node *lc;
  6. struct tree_node *rc;
  7. };
  8.  
  9. #define TREE_NODE \
  10. struct tree_node *__tn
  11.  
  12. struct ttree {
  13. TREE_NODE; //必须在第一行,不在第一行需要计算偏移量 offset
  14.  
  15. //后面就是结构了
  16. int id;
  17. char* name;
  18. };

下面这种相比上面这种节约4字节.缺点调试难.还有好多种例如模板流,特定写死流. 这里扩展一下另一个技巧

关于C中宏简化结构的代码

  1. /* IPv6 address */
  2. struct in6_addr
  3. {
  4. union
  5. {
  6. uint8_t __u6_addr8[];
  7. #if defined __USE_MISC || defined __USE_GNU
  8. uint16_t __u6_addr16[];
  9. uint32_t __u6_addr32[];
  10. #endif
  11. } __in6_u;
  12. #define s6_addr __in6_u.__u6_addr8
  13. #if defined __USE_MISC || defined __USE_GNU
  14. # define s6_addr16 __in6_u.__u6_addr16
  15. # define s6_addr32 __in6_u.__u6_addr32
  16. #endif
  17. };

是不是很新奇,但是这样的代码,上层包块库都不推荐用,这些都是内核层的定义.用的越多越容易出错.

到这里基本就快结束了,上面介绍的几种结构设计思路,大家需要自己揣摩. 特别有价值.搞明白.

再扯一点,很久以前对这样的结构不明白

  1. struct mem_storage{
  2. union {
  3. int again;
  4. void* str;
  5. } mem;
  6. .....
  7. };

上面again 是干什么的,后来才明白了,主要作用是设定内存对齐的字节数.方便移植.使其结构体内存结构是一样,也方便CPU读取.

思考了很多但是还不明白, 那就对了,说明你还有追求!

这里再扩展一下, 有时候

  1. /*
  2. 常遇见下面代码
  3. */
  4. void ss_free(void* arg)
  5. {
  6. if(....){
  7. .....
  8. free(arg);
  9. return;
  10. }
  11.  
  12. ....
  13. }

真心 希望 C中提供 return void; 语法,

这样就可以写成

  1. return free(arg);
  2.  
  3. //或者
  4. return (void)check(arg);

这样代码会更精简, 更好看. 这里也可以通过宏设计处理

  1. #define return_func(f, ...) \
  2. f(##__VA_ARGS__); \
  3. return

属于伪造吧,希望C委员会提供 return void; 语法!!

后记

  错误是难免的,有问题提示马上改. 下次有机会将二叉树讲透,关于设计开发库中用的二叉树结构都来一遍,最后分享一下,实际运用的

库案例.拜~,

  有时候在想如果不以经济建设为中心,是不是人会更有意思一点? 有一款小网游叫中国, 挖了无数坑,就希望大R去充值,diao丝去陪练.哈哈

  

C 关于二叉查找树的回顾,并简述结构接口设计的更多相关文章

  1. [Effective JavaScript 笔记]第57条:使用结构类型设计灵活的接口

    想象创建wiki的库.wiki网站包含用户可以交互式地创建.删除和修改的内容.许多wiki都以简单.基于文本标记语言创建内容为特色.通常,这些标记语言只提供了HTML可用功能的一个子集,但是却有一个更 ...

  2. 项目结构的设计(iOS篇)

    项目结构的设计(iOS篇) 本文附带源码:YoungHeart-Chapter-02.zip 在设计任何一个框架之前,都应规划好项目结构. 假定Git作为我们的项目管理工具.我们要建立两个仓库,一个用 ...

  3. 关于EZDML数据库表结构制作设计工具使用踩的坑

    我使用的是一款EZDML的数据库表结构制作设计工具 最开始在数据库创建数据库名为personalmall,基字符集为默认,数据库排序规则也是默认,创建完成之后 去EZDML生成SQL 点击执行sql ...

  4. 手写SpringMVC框架(二)-------结构开发设计

    续接前文, 手写SpringMVC框架(一)项目搭建 本节我们来开始手写SpringMVC框架的第二阶段:结构开发设计. 新建一个空的springmvc.properties, 里面写我们要扫描的包名 ...

  5. Nodejs事件引擎libuv源码剖析之:句柄(handle)结构的设计剖析

    声明:本文为原创博文,转载请注明出处. 句柄(handle)代表一种对持有资源的索引,句柄的叫法在window上较多,在unix/linux等系统上大多称之为描述符,为了抽象不同平台的差异,libuv ...

  6. 无线客户端框架设计(2):项目结构的设计(iOS篇)

    本文附带源码:YoungHeart-Chapter-02.zip 在设计任何一个框架之前,都应规划好项目结构. 假定Git作为我们的项目管理工具.我们要建立两个仓库,一个用于存放我们的框架,另一个用于 ...

  7. Caffe源码理解1:Blob存储结构与设计

    博客:blog.shinelee.me | 博客园 | CSDN Blob作用 据Caffe官方描述: A Blob is a wrapper over the actual data being p ...

  8. 导出数据库表为world文档说明,以及PowerDesigner导出表结构pdm设计文档

    如何使用“mysql导出数据库结构为world工具”以及如何使用powerdesigner映射数据库模型 一.通过powerdesigner配置ojdbc 1.安装并打开powerdesigner,新 ...

  9. Android中的Preference结构的设计与实现

    本文主要通过分析源代码来分享Preference的设计和实现方式,让开发者们在今后更加顺手地使用和扩展Preference类,或者在设计其他类似的界面和功能时可以提供参考帮助. Preference概 ...

随机推荐

  1. svn老鸟转用git必须理解的概念

    不都是SCM代码管理嘛,有很大区别么?很多svn老鸟都是抱着这样的心态去学习git,然后无一幸免地陷入“查阅过很多资料,依然掌握不好”的困境,至少我们团队是这样的. 网上的资料确实已经很多了,却没有把 ...

  2. cocos2d-x 3.x丨搭建Android环境下的开发环境

    所需要的一些工具软件: 1.JDK  官网下载地址:http://www.oracle.com/ttechnetwork/java/javase/downloads/index.html 2.Andr ...

  3. 002..NET MVC实现自己的TempBag

    原文链接:http://www.dotnetbips.com/articles/bc422c95-02cc-4d05-9c5c-fa89d0e78cc0.aspx 1.前言 本来今天是想发那篇关于在W ...

  4. while((c = getchar()) != EOF)(键盘输入问题)

    问题描述: 样例输入:O S 样例输出:I A 代码实现: #include <stdio.h> char *s = "`1234567890-=qwertyuiop[]\\as ...

  5. Android SharedPreferences 见解

    今天突然遇到了SharedPreferences问题,虽然以前用过,但从没有深入的了解一下,今天就顺便深入了解一下,并总结一下,防止以后忘记. SharePreferences是Android平台上一 ...

  6. swat主流域文件(file.cio)参数详解——引自http://blog.sciencenet.cn/blog-922140-710636.html

    % file.clo,即主流域文件用于文件管理,包括与模型选项.气候输入.数据库和输出控制相关的信息. Master Watershed File: file.cio Project Descript ...

  7. EPPB also support BlackBerry device

    各位看倌不是小弟要賣弄英文,實在是外國朋友希望知道上一篇"雲取證"中所用的工具Elcomsoft Phone Password Breaker支援黑莓機否?又要求非要看到截屏才算數 ...

  8. leetcode3:不重复的最长子串长度

    package hashmap; import java.util.HashMap; import java.util.Map; public class hashmap { public stati ...

  9. js 定位到指定位置

     <script>    //滚动定位到product         function scroll() {             var scroll_offset = $(&quo ...

  10. CSS3中颜色线性渐变实战

    css3线性渐变可以设置3个参数值:方向.起始颜色.结束颜色.最简单的模式只需要定义起始颜色和结束颜色,起点.终点和方向默认自元素的顶部到底部.下面举例说明: CSS Code复制内容到剪贴板 .te ...