0%

个人知识管理(PKM)和时间管理(GTD),是很重要的。日常生活学习中肯定有零散的涉及到,但并无系统性、条理性的认识。有关的概念也是近两年才接触到,网上能搜到的东西很多,但太知识性的、深度的“规范”针对普通人又不具有实用价值,即,适当的掌握概念,按照“规则”整理知识,规划时间做事是有价值的,提升会很明显,但对于普通人来说并无掌握其原理的必要,简单的拿来用就可以了。过犹不及。

最重要的是坚持

无论用什么方式,纸笔也好,还是 APP 也好,重要的是坚持,养成习惯。不能三天打鱼两天晒网。

我自己使用过,或正在使用的工具:

  1. 思维导图工具:mindjet

    最初半年的使用频率非常高,慢慢地到现在一两个月甚至用不上一次。工作、生活过程中记录事情,现在更习惯用列表,是思维固化了?还是这半年涉及的场景不需要发散性的思维?

  2. GTD 工具:doit.im,到 any.do,再到接触 Fabulous 之后简单的使用纸笔记录

    挺好用。坚持得不够,买了一年的会员,高频率使用了半年,闲置了半年之后销户了。直到今天,在 Fabulous 中见到一段话,直戳痛点:

    别掉入无止境清单症候群的圈套。换句话说,当你加入太多待办事项,你只会完成最容易的几项。

    (而)迅速完成容易的事项会让你有种虚假的成就感。你会发现忙碌了一整天以后,其实并没有在你的终极目标上取得多少进展。

    必须养成专注的性格。选择最重要的几件事项,然后除掉其他(相对来说不重要的)。

    doit 我没能坚持下来就是因为错误的使用方式。每天罗列好多的内容,带来虚假的成就感;每天完成一些(大多是最容易的),看着任务打上对勾,被划掉,带来虚假的成就感。甚至为了“打对勾,划掉”写上已经完成的事项,写上不重要的、做不做并无关键区别、随手就能解决、非关键任务等等类似的内容。然后把上午、下午大块大块的完整时间用来处理这些个“自讨的”琐碎任务,以“划掉多少”等价“成就多少”。好脆弱的内心,好虚假的幻想!随着时间的积累(也就是半年),doit 列表中堆积了大量的待办事项,而且都是筛选之后需要花费一定时间、精力并且有意义的事情,积累的越多越是提醒着自己的失败。潜意识里回避真相,现实世界里就做出“不再使用这款工具”的决定。关键问题在于:

    • 罗列待办事项清单之后,安排事项处理顺序、处理日期不合理,对自身解决问题的能力没有清晰的认识,“拍脑袋”定日期;

      • 也就是:不抓重点,或者抓不住重点。精力有限,肯定要做取舍。
    • (因此)计划制定完之后,并不能严格的执行,做不到日清月清,更谈不上总结;

      这也是放弃 doit 使用 any.do 之后没多长时间,又放弃 any.do 而使用 Fabulous/纸笔的原因,如果纠正不了根本问题,依旧还会在工具之间跳来跳去,在一个工具上遗留大量的未完事务之后迁移到另一个工具,如此循环,一直得不到解脱。类似的应用层出不穷,上文提到的 3 个是非常棒的了,用好一个就能给生活带来质变,不要因为错误的习惯埋没、错过了。

      收集任务是前提;安排任务是关键;完成任务是目的。不能把时间浪费在“前提”上,在对自身解决问题的能力有清晰的认识的基础上,以量力而行、轻重缓急为原则制定当日计划是重中之重。

      制定计划,包含完成时间,还隐含完成的能力,而不单单是罗列待办事项,想到的就罗列出来,不管今天能不能完成,今天想到的就列到今天的待办事项里,没有这么无脑操作。

整理为知笔记中和 make makefile automake 有关内容,成此篇。

gcc 说起

在介绍 make 之前,首先要保证你会使用 gcc/g++ 编译。了解 gcc 执行的四个阶段,知道预编译阶段、链接阶段需要什么,做什么操作;知道预编译时找不到头文件,链接时缺少库文件怎么处理;知道动态库、静态库的区别,怎么创建并使用它们。以上是必需的!只有掌握了上面的内容,才能保证面对一个简单的项目(可能包含头文件、源文件和链接库)知道怎么组织,最终得到可执行文件。

维基百科的描述:

GNU编译器套装(英语:GNU Compiler Collection,缩写为 GCC),一套编程语言编译器,以GPL及LGPL许可证所发行的自由软件,也是GNU项目的关键部分,也是GNU工具链的主要组成部分之一。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准。

gcc 编译过程

预处理、编译、汇编和链接。学习 gcc,比较重要的是预处理和链接:

参考 gcc编译4个阶段

  1. 预处理阶段

    预处理器(cpp)根据以字符#开头的命令(directives),修改原始的C程序。

    也就是说预处理阶段,会查找并加载头文件生成一个新的C程序。

  2. 编译阶段

    编译阶段会将代码翻译成汇编语言。

    汇编语言是非常有用的,它为不同高级语言不同编译器提供了 通用的语言。如:C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。

  3. 汇编阶段

    汇编阶段将汇编语言程序转换成为目标文件,也就是二进制机器码。

  4. 链接阶段

    该阶段将用到的一个或多个目标文件(库文件)链接生成可执行文件。

    在预编译阶段包含进来的“stdio.h”中只有“printf”函数的声明,而没有函数的实现,那么,是在哪里实现的“printf”函数呢?答案是:系统把这些函数实现都做到名为libc.so.6的库文件中去了,链接阶段gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” 了,而这也就是链接的作用。

    从上面可以看出,链接时需要目标文件的名称及其所在目录。

常用参数

知道了预处理器(cpp)要读取头文件,那么报错“xxx.h:No such file or directory”就需要确认我们是不是指定了查找目录,或者核对我们指定的路径是否包含该头文件;同样的道理,链接器(ld)需要链接目标文件,和前者稍有不用的地方在于:头文件一般都写在源代码中,我们只需要指定头文件路径;而链接目标文件时,既需要我们指出目标文件所在的目录,同时还需要我们指定使用目标路径下的哪个文件。

以上算是原理。知其所以然之后,我们来看工具具体的使用方法:

  1. 头文件相关的: -I 后跟绝对路径或相对路径,例如 -I ./include。预处理器会优先在 @搜索路径
  2. 链接过程: -L 后跟绝对路径或相对路径,例如 -L ./lib, -l 后跟库名,例如 -lmath,对应库文件 libmath.a 或 libmath.so
  3. -c:使用源文件生成对应的目标文件,而不进行链接,使用 ,例如 g++ -c main.cpp -o main.o
  4. -Wall:使 gcc 产生尽可能多的警告信息,并非全部。
  5. -Werror:把警告当做错误处理,即产生 warning 时就停止编译操作。

特殊的参数:

  1. -nostdinc:使编译器不在系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。

  2. -Wl,-rpath:在编译过程指定程序在运行时动态库的搜索路径,示例:

    1
    2
    # 当指定多个动态库搜索路径时,路径之间用冒号":"分隔
    gcc -Wl,-rpath,libPath -L libPath -ltest hello.c

    将搜索路径信息写入可执行文件(rpath代表runtime path)。这样就不需要设置环境变量。坏处是,如果库文件移动位置,我们需要重新编译test。

更多的参数,更详细的使用说明可以查看 官方手册,或者使用 man info –help 等方式获取。

搜索顺序

测试环境:

1
2
3
4
5
6
7
8
9
vimer@debian8light:~/code/test_search$ uname -a
Linux debian8light 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u4 (2016-02-29) x86_64 GNU/Linux
vimer@debian8light:~/code/test_search$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.3 (jessie)
Release: 8.3
Codename: jessie
vimer@debian8light:~/code/test_search$

在 gcc version 4.9.2 (Debian 4.9.2-10) 环境下实际测试,linux 编译时头文件的搜索路径:

  1. 搜索会从 -I 开始
  2. 再找 /usr/lib/gcc/x86_64-linux-gnu/4.9/include
  3. 再找 /usr/local/include/
  4. 查找 /usr/include/

此测试只是确定以上 4 者相对的先后。测试时未包含更多的潜在的搜索路径,比如 gcc 的环境变量 C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、OBJC_INCLUDE_PATH

两种不类型的头文件 #include<>#include"" 搜索规则:

  1. 使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项,注意是大写),之后就是标准的系统头文件路径。
  2. 而用””号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。

事实上,知道上述提及的搜索路径即可。不必学究式地死记硬背之间的搜索顺序,能有多少意义呢?。

更直接、简单的方式,确认查找头文件时的路径搜索顺序。参考 使用gcc时头文件路径和动态链接库路径, 通过使用 -v 参数看到:

1
2
3
4
5
6
7
8
9
10
11
12
#include "..." search starts here:
#include <...> search starts here:
./include
/usr/include/c++/4.9
/usr/include/x86_64-linux-gnu/c++/4.9
/usr/include/c++/4.9/backward
/usr/lib/gcc/x86_64-linux-gnu/4.9/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.

在此不再具体测试编译过程中链接库文件时的搜索顺序、运行时动态库的搜索顺序,真正需要用之间的搜索顺序时再写代码测试。

动态链接和静态链接

在“链接阶段”一节中出现了库文件“libc.so.6”。@Linxu 中动态库

@引出静态库

@动态链接和静态链接

make 和 Makefile

make 命令用来解释、执行 Makefile文件。针对开发 C++ 程序来说:Makefile 文件是对项目文件(以源文件、链接库为主,也包括头文件)依赖关系的描述,是对 gcc 命令的有效组织。抛开依赖关系,如果项目不复杂,你也可以用 shell 脚本来组织 gcc 命令,前提是根据依赖关系调整好编译命令的排序。虽然能够得到同样的结果,但是执行的效率不如前者,容错的能力不如前者,耗费的精力更是项目越大越难以承受。事实上:

在make诞生之前,编译工作主要依赖于操作系统里面的类似于“make”、“install”功能的shell脚本。

来看看维基百科中的描述:

在软件开发中,make 是一个工具程序(Utility software),经由读取叫做“makefile”的文件,自动化建构软件。

它是一种转化文件形式的工具,转换的目标称为“target”;与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。

它使用叫做“makefile”的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。

需要注意的是我们在 Linux 上用的一般是 make 的重写/改写版本 GNU Make,除此之外还有 BSD Make 等。

我们来看怎么写 Makefile 文件。

automake

人工写 Makefile 文件已经满足不了我们了。事实是人越来越懒,项目也越来越大,我们希望电脑帮我们做更多的工作。引用 Wikipedia 中的描述:

GNU Automake 是一种编程工具,可以产生供 make 程式使用的 Makefile,用来编译程式。它是自由软件基金会发起的 GNU 计划的其中一项,作为 GNU 构建系统的一部分。automake 所产生的Makefile 符合 GNU 编程标准。

automake 是由 Perl 语言所写的,必须和 GNU autoconf 一并使用。

延伸

以下概念当做扩展知识保留下来,如果有兴趣可以展开学习:

由内部链接、外部链接引出怎么写头文件。

我看了一下,这篇笔记最早是在六月二号创建的,可是现在 2016/8/13 16:12:05 ,呵呵。一方面是自己懒,另一方面,对于“内部链接、外部链接”的概念,不多了解一些,不敢随便整理啊。

我们先看看 维基百科- 头文件 中说了什么:

当一个子程序在定义的位置以外的地方被使用时,就需要进行前置声明,声明函数原型等。

假设一个程序员不用“头文件”,那么他至少需要在两个地方维护函数的声明:一个是包含函数实现的的文件,以及使用该函数的文件。如果使用该函数的文件有很多个,那么对函数的定义进行更改时就是灾难。

从某个方面来说,头文件降低了这种场景中程序员手工操作的复杂度(解放双手,繁琐的工作交给机器/编译器)。更重要的是保证了编写大型项目的易用性,难以想象如果没有头文件,几十万行的代码全都在一个源文件中。

将函数原型移到 XXX 头文件中之后,我们可以在需要的地方通过 #include <XXX> 预处理器指令 将其包含进来,这样每次编译时预处理阶段就会将 XXX 文件中的内容替换掉 #include <XXX> ,我们的函数原型也就被“前置声明”了。

阅读 wikipedia - include derective,我们可以从更高的层次考虑 Include 行为,想想 Makefile 中的 include,shell 脚本中的 include,表示类似甚至同样意义的关键字还有 import、copy。

在 C 语言中,什么内容需要放在头文件中,什么内容可以放在头文件中相对来说是比较容易区分的。接下来我们看看在 C++ 中,什么东西可以放在 .h 文件中,什么不能,什么东西又可以放在 .cpp 文件中。

声明和定义

首先需要区分开这两个概念,只是理解所有关键问题的前提。在笔记中暂时不展开说了,如果分不清楚,自行 Google。这里只备注几个容易混淆的条目:

  • 声明

    1. 仅仅提供函数原型。类外面的,类里面的

    2. class A;

    3. typedef声明

    4. 在类中定义的静态数据成员的声明

      1
      2
      3
      4
      5
      class A
      {
      public:
      static int a; // 声明
      };
  • 定义

    1. 在类定义之外,定义并初始化一个静态数据成员。如 A::a=0;

      1
      2
      3
      4
      5
      6
      class A
      {
      public:
      static int a; // 声明
      };
      A::a=0// 定义
    2. 在类外定义非内联成员函数

内部链接和外部链接

参考自:参考链接1解析C++中的内部连接与外部连接

链接把不同编译单元产生的符号联系起来。有两种链接方式:内部链接和外部链接。

内部链接

如果一个符号名对于它的编译单元来说是局部的,并且在链接时不可能与其他编译单元中的同样的名称相冲突,那个这个符号就是内部链接。内部链接意味着对此符号的访问仅限于当前的编译单元中,对其他编译单元都是不可见的。

具有内部链接的符号无法作用于当前文件外部,要让其影响程序的其他部分,可以将其放在.h文件中。此时在所有包含此.h文件的源文件都有自己的定义且互不影响。

  1. 所有的声明,包括类的声明,比如:class A;;(有时也将声明看作是无连接的,这里我们统一看成是内部连接的)。

    由于声明只对当前编译单元有用,因此声明并不将任何东西写入.o文件。

    这些声明本身不会影响到.o文件的内容。在编译阶段(狭义的,从高级语言到汇编语言到二进制,从 main.i 到 main.o,预处理已过,尚未链接),编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会报错。链接阶段,声明已经没有用了。

    而函数调用会导致一个未定义的符号被写入到.o文件,此后此.o文件与定义此符号的.o文件被连接在一起,前面未定义的符号被解析。

  2. 局部变量肯定是内部链接性质的,更应该被看作无连接的;

  3. 全局变量,如果使用了 static、const 关键词修饰,其作用域仅仅在当前文件作用域内,其他文件中即使使用extern声明也是无法使用的。因此,带有 static、const 关键字的全局变量也是内部链接性质的;

    static 和 const 还是有区别的。static 和 extern不能对同一个变量同时声明;但 const 和 extern 不是同一存储类别,可以同时用在同一个变量的声明。所以,我们可以用使用extern关键字修改const的连接属性。更多详情特别说明

  4. 枚举 enum、联合 union 类型是内部链接性质的;

  5. 类的定义是内部链接性质的:

    • 定义,意味着在同一编译单元中不能重复出现;

    • 内部链接性质,意味着如果需要在其他编译单元使用,类必须被定义在头文件且被其他文件包含。

      仅仅在其他文件中使用class a;声明是不行的,原因就是类的定义是内部链接,不会在目标文件导出符号。也就不会被其他单元解析它们的未定义符号。

  6. 内联函数定义(包括自由函数和非自由函数)。

    内联函数之所有具有内部链接,因为编译器在可能的时候,会将所有 对函数的调用替换为函数体,不将任何符号写入.o文件。

  7. 名字空间(包括全局名字空间)中的静态自由函数、静态友元函数、静态变量的定义。补充条目来源

外部链接

在一个多文件的程序中,如果一个符号在链接时可以和其他编译单元交互,那么这个名称就有外部链接。外部链接意味着该定义不仅仅局限在单个编译单元中。它可以在.o文件中产生外部符号。可以被其他编译单元访问用来解析它们未定义的符号。因此它们在整个程序中必须是唯一的,否则将会导致重复定义。

区分:判断一个符号是内部链接还是外部链接的一个很好的方法就是看该符号是否被写入.o文件。

  1. 类非内联成员函数,包括类成员函数和类静态成员函数

  2. 类的静态数据成员的定义具有外部链接性质

    1
    2
    3
    4
    5
    6
    7
    class A
    {
    public:
    static int a; // 内部链接
    };

    A::a = 0; // 外部链接
  3. 非内联函数

  4. 名字空间(包括全局名字空间)中非静态自由函数、非静态友元函数及非静态变量。补充条目来源

    前提是认为,类之外一般不存在使用 inline 修饰的函数。

学习

摘抄来源

Linkage

To understand the behavior of C and C++ programs, you need to know about linkage. In an executing program, an identifier is represented by storage in memory that holds a variable or a compiled function body. Linkage describes this storage as it is seen by the linker. There are two types of linkage: internal linkage and external linkage.

Internal linkage means that storage is created to represent the identifier only for the file being compiled. Other files may use the same identifier name with internal linkage, or for a global variable, and no conflicts will be found by the linker – separate storage is created for each identifier. Internal linkage is specified by the keyword static in C and C++.

External linkage means that a single piece of storage is created to represent the identifier for all files being compiled. The storage is created once, and the linker must resolve all other references to that storage. Global variables and function names have external linkage. These are accessed from other files by declaring them with the keyword extern. Variables defined outside all functions (with the exception of const in C++) and function definitions default to external linkage. You can specifically force them to have internal linkage using the static keyword. You can explicitly state that an identifier has external linkage by defining it with the extern keyword. Defining a variable or function with extern is not necessary in C, but it is sometimes necessary for const in C++.

Automatic (local) variables exist only temporarily, on the stack, while a function is being called. The linker doesn’t know about automatic variables, and so these have no linkage.

结论

综上,我们可以知道:将具有外部链接的定义放在头文件中几乎都是编程错误。因为如果该头文件中被多个源文件包含,那么就会存在多个定义,链接时就会出错。

在头文件中放置内部链接的定义却是合法的,但不推荐使用的。

链接,本质上都是具有外部链接性质的符号们的事情!

惯例

头文件为相关声明提供了一个集中存放的位置。头文件一般包含类的定义、枚举的定义、extern变量的声明、函数的声明、const int的定义、inline函数的定义。使用或者定义这些实体的文件要包含适当的头文件。

跌跌撞撞,兜来绕去,终究还是在 C++ 这个圈子里。毕业时,因为身外事去选择工作单位,而工作单位决定了工作性质,决定了自己处在做软件的边缘,甚至一度成为网管。两年之后跳出陷阱时,背负着“过去的选择”走进了 C++ 的世界,因为一无所长,在诸多技术更迭交替的 IT 圈我像个刚入门的新人似的,只了解一点点 VC++。我没有从头来过的魄力,我也并不清楚 Java 是否更有前途,互联网是否更多辛苦。

在毕业刚好三年的边上,想着“就是 C++ 了”其实很愚蠢。通过做决定安慰自己“明智”,其实只是没得选择的妥协。此时此刻应该想的是“如何学好 C++”,更现实一点“掌握 C++ 的哪些内容,能找到更好的工作”。暂时确定的目标:

  1. 会写代码。强调编码规范。
  2. 会编写类,使用类。强调抽象思维。
  3. 数据库操作,封装
  4. 并发(多线程),POSIX、boost、C++11原生支持
  5. 通信,socket 编程

曾经完美主义,现在坚持实用主义。站在公司的角度考虑,现实中生产使用,强调实用性。如果新特性带不来生产力上的提升,是不会有公司买账的。就是说是否掌握 C++11 甚至更新的特性,对找工作没有最直接的影响,如果带不了更高的生产力,都是空谈。不应该只为了关注新特性,学究式地死板地投入时间精力,要明白花时间做一件事的初衷、目的、意义和价值。举例来说,做并发时是否使用 C++11 的原生支持,在投产使用时没有什么影响的,重要的是能否解决问题,解决问题的能力,关键是把并发做出来,用什么技术往往并不重要。

那就好好学 C++11。工欲善其事,必先利其器。看看 C++ compiler support ,编译器有了,编辑代码呢?主要关注代码提示,开发者体验。

  • 在 Windows 上考虑使用 VS2015。个人观点,除了太大并不排斥 VS。
  • Linux 上呢?很难找到完善支持 C++11 代码提示的 IDE…Vim代码补全
  • Vim 和 IDE 之争 - 知乎,个人观点,两者会越来越像,追求生产力。
阅读全文 »

Flume 学习笔记

Flume(NG)架构设计要点及配置实践,在此基础上的扩展:Flume-ng的原理和使用

  1. Agent 的串联、并联
  2. Source 一般都是单一 Source;Source 一对多 Channel,分为复制(replication)和分流(multiplexing)
  3. Channel 一对多 Sink,实现负载均衡(load balance)和故障转移(failover)

在上述中多提到 Avro,在 Flume 的配置文件中也用到此项。Avro 介绍,但目前不需要了解。

Avro 是 Hadoop 中的一个子项目,也是 Apache 中一个独立的项目,Avro 是一个基于二进制数据传输高性能的中间件。

Redis 学习笔记

Redis Quick Start

  1. 在安装过程中,如果缺少依赖项,需要先编译 deps 目录。
  2. 将 redis 加入系统启动项,手册中使用 sudo update-rc.d redis_6379 defaults。但此命令只在 Debian 系下使用。

70.13x 系列服务器搭建指南

  1. 先安装 jdk,配置好 JAVA_HOME 环境变量

    1
    2
    3
    4
    5
    export JAVA_HOME=/home/cts/tool/jdk1.6.0_45/
    export JAVA_BIN=$JAVA_HOME/bin/
    export PATH=$JAVA_HOME/bin:$PATH
    export CLASSPATH=.:$JAVA_HOME/jreb:$JAVA_HOMEb/tools.jar
    export JRE_HOME=$JAVA_HOME/jre
  2. 安装 redis启动 redis

    • 如果需要,可以配置主从 redis
  3. 使用 cts2Cache-v0.2 程序初始化 redis

    • 执行 run-Init.sh 脚本执行初始化,需要修改 cts2Cache-0.1.0.jar 中配置文件 redis IP;
    • 使用 crontab 命令设置定时任务,每日执行 run-update-recv.sh 和 run-update-send.sh 脚本生成次日的节目表;
  4. 使用 cts2LogGate 程序开启日志网关

    • 参考 visio 图
    • 依照 send、recv、mu、lb 顺序开启,使用 kill 命令逆序关闭
    • 开启时可以使用 nohup 命令
    • 怎么关闭呢?
  5. 向日志网关发送日志

分为语法和工具两个部分,备忘录。

语法

写在前面

在使用 JotterPad(CommonMark)阅读 markdown 格式的文本时,发现的细节:

  1. 标题 # 后需要有空格,才能解析成标题,不然就是文本;【重要】

    • 进一步修正:任意级别的标题行前后添加空行,至少标题行之前要有空行,# 之后添加空格;
  2. 使用参考式链接时,链接网址只能放在文件最末尾,不然识别不出来;【重要】

  3. 引用内使用编号问题:

    引用文字

    1. item1
    2. item

    编号结束,结尾的引用文字

  4. 怎么写代码块?——是解析的,只不过呈现方式只体现在字体上,排版效果并不明显。

  5. 尽量不要嵌套使用。在 GitHub 上项目的 README.md 都不复杂。占一屏幕,不需要滚屏。 建议很值得采纳,但实在是不实用。

  6. 可以做图片链接的哦,惊喜

  7. 嵌套时无需有空行(分段需求除外,比如引用时),当两个模块是平行关系时需要有空行(列表除外,列表项之间添加空行会引入 <p>)。参考 1.1 节

  8. 中英文混合排版加不加空格问题,持保留态度,无论加或者不加,都保持原状。

    1. 强调语义,不加;强调呈现,添加。

    2. 书写笔记不考虑纯手工补充空格,为了表现效果美观吹毛求疵,时间成本太高,没有意义。【重要】

      扩展阅读:

  9. 测试 item

块元素

模块之间的嵌套使用,怎么书写是规范的一直是一件比较困惑的事情。但在我写下这篇文字的时候,我已经感觉到胜利在朝我招手了。

首先一定要有意识区分“块元素(Block Element)”和“Span Element”,对于熟悉 html 的程序员来说可能是很 easy 的一件事情,但是虽然我用 Markdown 两三个月,相关的操作手册、语法说明也看了很多,但我是自动过滤这两个单词的,讲解标题(用不同数目的 # 区分标题级别)和强调(用 * 斜体,** 加粗)时肯定是分别放在 Block Element 和 Span Element 中介绍,但我脑子里是没有这两个概念的,我并未意识到它们意味着什么,甚至并未意识到它们的存在。我真的是个前端白痴,原谅我。

  1. 块元素(Block Element)包括:

    • 标题
    • 引用
    • 列表,注意是整个列表,而不是列表的 item
    • 代码块
    • 分割线
  2. Span Element 包括:

    • 链接
    • 强调
    • 代码
    • 图片

块元素是涉及嵌套的主体!Span Element 不是。所以嵌套问题是块元素的嵌套问题,额,如果我了解 html 多好。

结论(针对 GFM)

秘籍:(在严格坚持第一点的基础上,除却以下情况不会用到第二点:如果问题出现在章节末尾(见本文末尾)、列表末尾,使用第二点)

  1. 每个独立的块元素(包括分段)之后留有空行,标题、分割线可以除外;

  2. 哪个块元素解析有问题,就在哪个块元素之前加空行;

    1. 为了保证一致性(特殊情形,即当前嵌套情形(列表主体除末尾空行无其他空行,且在末尾嵌套有子列表)),要求嵌套子列表时,子模块前添加空行(即本行之前的空行,否则解释时不完美);
  3. 综合上述两点,在每个块元素(包括分段)的前后添加空行可以保证所有情形下解释正确。唯一的不足在于阅读源文本时可能稍显松散。

  4. 其实,在每个块元素(包括分段、标题、分割线)之前留有空行是不是就万事大吉了呢?【重要】

详细描述:

  1. 分段用空行,这句是废话;

  2. 标题之前加空行,保证所有情况下语法正常解释;【重要】

    • 如果前置标题、分割线或者普通段落,则没有空行语法也能正常解释;
    • 如果前置列表、引用等,没有空行,语法解释后呈现有问题;
  3. 空行意味着引用、列表的结束;标题、分割线是单行的;GFM 中代码块是 ``` 结束的。多个空行合并成一个。

    • 分段时,空行意味着段落的结束;
    • 非分段情况下,段落之后不用跟空行。接标题、引用、列表、代码块都意味着段落结束。
    • 非分段情况下,段落之后无空行直接跟有序列表,是有问题的;直接跟无序列表没问题;
    • 综合上述三条,建议每个独立的块元素之后留有空行,标题、分割线除外【重要】
  4. 列表内容拥有缩进概念;

    • 列表 item 中缩进内容(针对引用、代码块)前(或者后)加空行;否则语法无法正常解释
    • 缩进内容为列表(即嵌套子列表)时,按照规则建议添加空行。即使无空行,语法也能正常解释,看着还顺眼一点
    • 综合上述两条,建议在嵌套子模块的结尾留有空行【重要】

补充说明:

  1. 列表的 item 之间一般不需要空行(分段除外),如果

    如果列表项之间有空行,markdown会给每一个生成的li元素创建一个p:

工具

前两年一直使用的 MarkdownPad2,后来随着系统更新(而软件并未跟进)出现了几项功能性问题,虽然也能通过某些手段结局,但无疑是影响体验的。

MarkdownPad 2 使用问题

在此基础上,由于对 Visual Studio Code 好奇心(喜新厌旧嘛),放弃了使用 MarkdownPad2,但回头再看(2020/12/25 17:38:28 )前者也只那些 vimer 的狂欢,经过各种调教之后可能好用,但调教本身也是成本。

快捷键支持并不完全。还是算了。来源

深有感触,不胜其烦。所以又回归了 MarkdownPad2

这两天工作上用到了 grep、sed、awk 文本处理命令。有心在业余时间花心思多掌握一些,在此前提下,发现熟悉正则表达式是很重要的。

正则表达式

查阅维基百科,发现其中文词条 正则表达式 没有价值,个别的知识点即便有效也充斥在大量的垃圾信息里,此词条好似百度百科的众多词条,灌水横拉硬拽拼凑而成,或源于翻译英文词条时偷懒挑肥拣瘦,只翻译了一部分内容,却篡改章节目录造成“不成文”的感受。

其英文词条 Regular expression 相对“丰满”一些,看目录至少提到了不同标准、不同流派,比中文词条要好。

参考 正则表达式“派别”简述正则表达式 流派(flavor)及差异简介,都明确指出了三种:

  • BRE: Basic Regular Expression
  • ERE: Extended Regular Express
  • PCRE: Perl Compatible Regular Expression

其中,前两者都是 POSIX: Portable Operating System Interface 的规范。

常见的编程语言中使用正则表达式的记法,其实都源于 Perl。

现在的编程语言中的正则表达式,大部分都属于 PCRE 这个分支。

常见的正则表达式记法,其实都源于 Perl

但在 POSIX 的系统上,有关的工具使用正则表达式,其记法与上述不同,大都要落于 BRE、ERE 这两者之中:

- 使用BRE语法的命令有:grep、ed、sed、vim

- 使用ERE语法的命令有:egrep、awk、emacs

需要补充的一点是,在 linux 上有关工具对 BRE、ERE 语法是进行过扩充的,好比对 C 语言标准、C++ 语言标准进行的诸多 GNU 扩展。在 Linux/Unix 工具与正则表达式的 POSIX 规范 中有提到 GNU 扩展的相关内容,在 正则表达式“派别”简述 讲述 POSIX 标准时也提到了 GNU 扩展。

GNU 在实现了 POXIS 标准的同时,做了一定的扩展

PCRE

PCRE(Perl Compatible Regular Expression):可以说是正则表达式的老前辈(niel 注:需考证),它是从 Perl 衍生出来的一个显赫流派,\d \w \s 等表示法就是它的特征;

BRE

BRE(Basic Regular Expression):POSIX 规范的正则表达式之一,grep、vi、sed 都属于这一派,它显著的特征就是 ( ) { } 这几个括号元字符必须经过转义才具有特殊含义,不支持 + ? | 等元字符,随着时间发展,后来又出现了 GNU BRE,GNU BRE 支持上边这些字符,但是也必须都经过转义才能有特殊含义;

ERE

ERE(Extended Regular Express):也是 POSIX 规范的正则表达式之一,egrep awk 都属于这一派,( ) { } + ? | 等元字符可以直接使用不需要转义,这个流派后来也出现了 GNU ERE,在之前的基础上添加了支持 \1 \2 等。

参考

不明觉厉的 各种语言或工具软件的不同风格的正则表达式文法规定

如果更关注使用有关工具时的细节,可以参考 Linux 中常用文本工具与正则表达式的关系 。更多的语法细节还是要在用到的时候仔细查阅。

引擎 正则表达式引擎及其分类

55分钟学会正则表达式 学习笔记

建议看原文(英文)比较好,译者也给出了原文的链接。翻译时译者不够细心,和原文相比有好几处错误,有的是很明显的上下文冲突,所以觉得译者根本就不用心。

前言部分,简单描述正则表达式是什么;之后讲解正则表达式的基础语法。

  • 正则表达式有可能出现语法错误——不是所有的字符串都是正则表达式

字符

理解“元字符”概念。

普通字符只能匹配它们本身;元字符可以匹配一些特殊规则。使用反斜杠 \ 可以忽略元字符,使得元字符的功能与普通字符一样。

点“.”

匹配任意一个字符

字符类

字符类是一组在方括号内的字符,表示可以匹配其中的任何一个字符。

重要提示:字符类中和字符类外的规则有时不同,一些字符在字符类中是元字符,在字符类外是普通字符。一些字符正好相反。还有一些字符在字符类中和字符类外都是元字符,这要视情况而定!

字符类的范围

在字符类中,你可以通过使用短横线来表示匹配字母或数字的范围。

字符类的反义

你可以在字符类的起始位放一个反义符 ^

Freebie character classes

译者翻译为“转义字符类”,个人觉得不能够表达原作的味道。参考上下文及“freebie”单词原意,应该是要表达,和某些字符类表示相同意义的转义字符。

下文中Freebie multipliers 译为“关于重复的转义字符”。类比,此处译为“关于字符类的转义字符”就会好很多。

  • \d:[0-9]
  • \w:[0-9A-Za-z_]
  • \s:匹配一个空字符(空格,制表符,回车或者换行)
  • \D:[^0-9]
  • \W:[^0-9A-Za-z]
  • \S:匹配一个非空字符

重复

在字符或字符集之后,你可以使用 { } 大括号来表示重复

指定重复次数范围

Freebie multipliers

  • ?:{0,1},重复0次或1次
  • *:{0,},重复任意次(0次、1次或多次)
  • +:{1,},重复1次以上(包括1次)

当一条声明语句中包含多个指针,或者混用数组的时候,每每傻傻分不清。

变量定义

在《C++ Primer》P38 页,2.2 节中描述到

变量定义的基本形式是:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表,其中变量名以逗号分隔,最后以分号结束。

列表中每个变量名的类型都由类型说明符指定,定义时还可以为一个或多个变量赋初值:int a=0, b, c=3;

ps:稍后再对变量的定义和声明做区分,对于初始化和赋值做区分。

复合类型

P45 页,2.3 节描述如下

一个简单的声明语句由……。更通用的描述是,一条声明语句由一个基本数据类型(base-type)和紧随其后的一个声明符(declarator)列表组成。每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型。

P51 页

在同一条定义语句中个,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说,一条定义语句可能定义出不同类型的变量。

1
2
// i是一个int型的数,p是一个int型指针,r是一个int型引用。
int i = 1024, *p = &i, &r =i;

P53 页

面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。

1
int *p, *&r = p;  // r是一个对指针p的引用

数组是一种复合类型 (P101页)

数组中元素的个数也属于数组类型的一部分。

P102 页

默认情况下,类型修饰符从右向左依次绑定。理解复杂的数组声明,由内向外,从右向左。

1
2
3
int *ptrs[10];  		// ptrs是含有10个元素的数组,数组元素是int型指针
int *(&array)[10] = ptr; // array是数组的引用,该数组含有10个指针
int (*Parray)[10]; // Parray是一个指针,指向一个含有10个整数的数组

const 限定符

reference to const,对常量的引用;不存在 const reference,因为引用不是对象。

1
2
const int ci = 1.24;
const int &r1 = ci;

pointer to const,指向常量的指针;const pointer,指针本身是常量。

* 放在 const 关键字之前用以说明指针是一个常量。

1
2
const double pi = 3.1415;
const double *const pip = &pi;

要想弄清楚这些生命的含义最行之有效的方法是从右向左阅读。