不积小流,无以成江海

海纳百川,有容乃大

2016/9/8 17:07:08

最近对于在类中使用的 static 有几个疑问,逐一列举并给出解惑。

问题一

问题一:我们都知道 C++ 类的静态成员变量在使用前必须要初始化。可是为什么一定要初始化呢?如果不初始化,为什么报 ld 链接错误?

先强调一点,其中的使用包括在类的成员函数中对静态成员变量的访问。

如果对于类的内存模型稍微理解那么一点点,这个问题其实很简单。

  1. 类的定义是在 POD 结构体的基础上进行了的升级,定义类本质上是对其普通成员变量(不包括静态成员变量)的封装,就是 C 中普通的结构体。

  2. 对于其静态成员变量,可以理解为在此结构体外声明的 C 语言静态变量,并且在结构体与静态此变量之间建立了某种映射关系(绑定)。

阅读全文 »

在项目开发中,非主体逻辑的部分在整体代码量中占得比重往往更大。比如我们做一个加减乘除计算器,需要处理用户输入非数字怎么办,数字太大溢出怎么办,零作为除数了怎么办等等;比如我们要读取配置文件中的用户名、密码,我们得首先处理配置文件不存在,内容格式不正确,用户名过长等等。

机器(具体说就是一门语言,比如 C++)是一板一眼的,你告诉它了它就能做,你没告诉的它就不知道。对机器来说不存在“常识”这个词。

用 C++ 写代码的时候总是避免不了处理错误,一般来说有两种方式:通过函数的返回值 return code;抛出异常 exceptions。

使用返回值的缺点

从 C 语言过渡过来的开发者可能更习惯使用返回值。就我自己的开发经历(给某个风场使用 Labwindows/CVI 开发自动化采集软件)来说,使用返回值有四点很是不爽的地方:

阅读全文 »

微软、Visual Studio 和 Visual C++ 那些事……

.NET 框架

.NET 框架(.NET Framework)的 维基百科 中有很详细的阐述,其中的 组件堆栈图 一目了然,对于 .NET vs. Java EE.NET vs. COM 的介绍也言简意赅。以下摘取几句,做笔记。

.NET框架是以一种采用系统虚拟机运行的编程平台,以通用语言运行库(Common Language Runtime)为基础,支持多种语言(C#、F#、VB.NET、C++、Python等)的开发。

通用中间语言被设计来即时编译(JIT),而Java的字节码在最初的时候则是设计成用于解释运行,而非即时编译。

前一版软件组件技术由Microsoft所提出的COM,该技术被用来创建大型(large-scale)的软件系统上,使用COM+ 或MTS对于传统分布式组件有强化的作用。很明显的,Microsoft最终将以.NET全面替换COM成为软件组件的架构。

.NET 的语言

CLI是一套运作环境规范,包括一般系统、基础类库和与机器无关的中间代码,全称为Common Language Infrastructure。如果一种语言实现生成了CLI,它也可以通过使用CLR被调用,这样它就可以与任何其他.NET语言生成的数据相交互。CLR也被设计为操作系统无关性。

CLI被设计成支持任何面向对象的编程语言,分享共同对象模型与大型共同类库。

大部分的语言都做了重大改变以搭配.NET框架。厂商通常利用这个机会来同时改变语言的其他特性。

  1. C#,一个以C++和Java语法为基础开发的一个全新的面向对象语言,是.NET开发的首选语言。

    我们圈外人以为做 C# 的就是 .NET,其实前者只是后者的官方开发语言。

  2. Visual Basic .NET,一个加强了面向对象支持的,支持多线程的Visual Basic版本。

    VB .NET 和 VB 不同。

  3. C++/CLI,一个C++的.NET平台版本变种。

    接下来我们就说说这个。

托管 C++ 和 C++/CLI

托管C++并非独立存在的编程语言,而仅仅是微软对C++的一个语法扩展,允许C++程序员在.NET框架和CLR的基础上进行托管编程。

C++ 托管扩展(Managed Extensions for C++,也经常被称为“托管 C++”)自Visual C++ 2005起被正在标准化的C++/CLI所替换。

目前只有托管C++及其后继者C++/CLI可以做到无缝集成托管和非托管代码,在托管代码中调用COM的速度又相当慢(所以用非托管代码调用咯),所以经常被用于其他语言和非托管代码之间的桥梁。

C++/CLI(CLI: Common Language Infrastructure)在计算机语言中是一门由微软设计,用来代替C++托管扩展(Managed C++,下文使用MC++指代)的语言。这门语言在兼容原有的C++标准的同时,重新简化了托管代码扩展的语法,提供了更好的代码可读性。

托管代码和本地代码

在维基百科 托管代码 的词条中提及托管代码的运行:

运行代码时,运行库编译器(runtime-aware compiler)在受控运行环境下,将中间语言(Intermediate Language)编译成本机的机器码。受控运行环境可为代码插入垃圾回收、异常处理、类型安全、数组边界和索引检查等,以保证代码安全的运行。

另有 博客 介绍的很好。

Visual Basic .NET和C#只能产生托管代码。如果你用这类语言写程序,那么所产生的代码就是托管代码。如果你愿意,Visual C++ .NET可以生成托管代码。当你创建一个项目的时候,选择名字是以.Managed开头的项目类型。例如.Managed C++ application。

跟Visual Studio平台的其他编程语言不一样,Visual C++可以创建非托管程序。当你创建一个项目,并且选择名字以M FC,ATL或者Win32开头的项目类型,那么这个项目所产生的就是非托管程序。

托管代码和非托管代码性能的比较:理解这个问题的关键在于对“即时编译器”和“解释器”是否有正确的认识。

.Net程序被加载入内存以后,当某段IL代码被第一次运行的时候,JIT编译器就会将这段IL代码,全部编译成本地代码,然后再执行。这也就是为什么.NET程序第一次运行都启动很慢的原因! 随.NET库,微软还附带了一个工具,可以事先将.NET程序所有的IL代码都编译成本地代码并保存在缓存区中,这样一来,这个程序就跟c++编译的一模一样了,没有任何区别,运行时也可以脱离JIT了(这里不要混淆了,这里不是说可以脱离.NET库,而是说不需要在进行即时编译这个过程了)。

所以,请不要将.NET和Java混为一谈,两个的运行效率根本不是一个等级的!

打包工具

我们在 VS2010 中更习惯使用 Visual Studio Installer 打包项目,进行安装和部署。但不知什么原因(目前我还不知道,也没有在网上认真找过 资料),微软放弃了自家的这款工具,在 VS2012 中不再有,而只能用 InstallShield Limited Edition,同时更新过 VS2010 的朋友也会发现在“安装和部署”下除了原有的 Visual Studio Installer,也增加了 InstallShield LE 项目,看来微软是铁了心放弃 VS Installer 了,那我们也没有坚守的必要了。除非公司跟不上时代,否则还是早放弃的好。

在 stackoverflow 上 Create MSI or setup project with Visual Studio 2012 说了同一件事情。好在 InstallShield LE 太难用,最关键的是在对 ActiveX 控件、Windows services的支持上有 硬伤,事情出现 转机,Visual Studio Installer 通过扩展的形式重新回到 VS 中,不过貌似只有 VS2013 和 2015 版本,但也足够了,你说呢?你要是死磕 VS2012,何必呢

预编译头

进阶操作不包含基础,进阶分为两部分:使用分支和修改提交。主要整理自 猴子都能懂的 GIT 入门。关于工作流,罗列了几个链接,值得反复阅读学习。

其实我们在 Git 官网 就能找到最好的学习材料:《Pro Git》。知识点讲得很全,也讲得很好。无论是进行系统的学习,还是作为参考手册都是不二之选!但如果你真的只有 5 分钟时间,还要快速上手使用,拿这么一本书显然是不合适的,所以就推荐了上面的入门贴。实际工作中,碰到什么问题,我都是来翻书的。

使用分支

先确定运用规则才可以有效地利用分支。

Merge 分支

Merge 分支是为了可以随时发布 release 而创建的分支,它还能作为 Topic 分支的源分支使用。保持分支稳定的状态是很重要的。如果要进行更改,通常先创建 Topic 分支,而针对该分支,可以使用Jenkins 之类的 CI 工具进行自动化编译以及测试

通常,大家会将 master 分支当作 Merge 分支使用。

Topic 分支

Topic 分支是为了开发新功能或修复 Bug 等任务而建立的分支。若要同时进行多个的任务,请创建多个的 Topic 分支。

Topic 分支是从稳定的 Merge 分支创建的。完成作业后,要把 Topic 分支合并回 Merge 分支。

分支的合并

完成作业后的 topic 分支,最后要合并回 merge 分支。合并分支有2种方法:使用 merge 或 rebase。使用这2种方法,合并后分支的历史记录会有很大的差别。

merge

进行 merge 的话,分为两种情况:

  1. fast-forward(快进)合并

    合并 bugfix 分支到 master 分支时,如果 master 分支的状态没有被更改过,那么这个合并是非常简单的。 bugfix 分支的历史记录包含 master 分支所有的历史记录,所以只要把 bugfix 移动到 master 分支就可以导入 bugfix 分支的内容了(即将 master 的 HEAD 移动到 bugfix 的 HEAD 这里)。这样的合并被称为 fast-forward(快进)合并。

    这种场景不会产生新的提交。

  2. non fast-forward

    但是,master 分支的历史记录有可能在 bugfix 分支分叉出去后有新的更新。这种情况下,要把 master 分支的修改内容和 bugfix 分支的修改内容汇合起来。

    因此,合并两个修改会生成一个提交。这时,master 分支的 HEAD 会移动到该提交上。

Note

执行合并时,如果设定了 non fast-forward (--no-ff)选项,即使在能够 fast-forward 合并的情况下也会生成新的提交并合并。

执行 non fast-forward 后,分支会维持原状。那么要查明在这个分支里的操作就很容易了。

阮一峰老师写到:

使用 --no-ff 参数后,会执行正常合并,在 Master 分支上生成一个新节点。为了保证版本演进的清晰,我们希望采用这种做法。

rebase

这个没有亲自实践过,要认真学学。

  1. 首先,rebase bugfix 分支到 master 分支, bugfix 分支的历史记录会添加在 master 分支的后面。

    这时移动提交 X 和 Y 有可能会发生冲突,所以需要修改各自的提交时发生冲突的部分。

  2. rebase 之后,master 的 HEAD 位置不变(此时就是典型的 fast-forward merge 场景了)。因此,要合并 master 分支和 bugfix 分支,即是将 master 的 HEAD 移动到 bugfix 的 HEAD 这里。

rebase 最大的优点是:历史记录成一条线,相当整洁。在《Pro Git》书中提到:

变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是先后串行的一样,提交历史是一条直线没有分叉。

一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个别人维护的项目贡献代码时。在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到 origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。

同样,在书中也提到了变基的风险,作为新手我们并不需要做过多的学习。

具体练习见 用 rebase 合并

改写提交

参考自 改写提交。改写提交非常容易出现冲突,需要人工合并。

“改写提交”,我觉得《Pro Git》的表达更好一些——“重写历史”。因为有关的操作大多比较敏感,都有后遗症,操纵不当更会变成灾难,所以建议 认真读书、反复练习之后再在实际项目中操作。

在进入正文之前,我们必须清楚 ^~ 的区别:What’s the difference between HEAD^ and HEAD~ in Git?

强调~ 是对 ^ 的复合运算,而不是 ^n 的复合运算。

修改最近的提交

git commit –amend

取消过去的提交(内容)

git revert 可以取消指定的提交内容。

虽然使用后面要提到的 rebase -i 或 reset 可以直接删除提交,但是有时候并不合适(甚至不能)直接删除提交,比如不能随便删除已经发布的提交。这时就可以通过 revert 创建(否定某一次提交的)提交。

遗弃提交

git reset 除了默认的 mixed 模式,还有 soft 和 hard 模式。

模式名称 HEAD 的位置 索引 工作树
soft 修改 不修改 不修改
mixed 修改 修改 不修改
hard 修改 修改 修改

主要使用的场合:

  • 复原修改过的索引的状态(mixed)
  • 彻底取消最近的提交(hard)
  • 只取消提交(soft)

提取提交

cherry 樱桃

git cherry-pick 可以从其他分支复制指定的提交,然后导入到现在的分支。

例如,假设我们有个稳定版本的分支,叫 v2.0,另外还有个开发版本的分支 v3.0,我们不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个 v3.0 中的功能到 v2.0 中,这里就可以使用 cherry-pick 了。

改写提交的历史记录

git rebase -i

这个命令很强大,在 rebase 指定 -i(interactive,交互式的) 选项,可以改写、替换、删除或合并提交。但也有一些副作用……

请翻书,重写历史

副作用?

场景描述:今天进行 rebase -i 操作后发现 merge –no-ff 的分支合并记录都没了

![merge –no-ff](https://raw.githubusercontent.com/nielong0610/MarkdownPhotos/master/merge –no-ff.png)

ps:借着这张图,复习一下 ^~ 的知识。

  • git reset head~,head 变更为 7c8d8a;
  • git reset head~2,head 变更为 6af94;
  • git reset head~^2,head 变更为 78663f;
  • git reset head4,报错!因为 head4 不存在
  • 强调~ 是对 ^ 的复合运算,而不是 ^n 的复合运算。(7c8d8a)^2~ = 6af94d = (7c8d8a)~

返回来继续说,执行 git rebase -i head~3 后结果如下:

![merge –no-ff](https://raw.githubusercontent.com/nielong0610/MarkdownPhotos/master/merge –no-ff 2.png)

在执行 git rebase -i head~3 时对话框如下:

1
2
3
4
5
6
pick 6af94d1 modify name.txt
pick 78663ff new age.txt (笔者注:7c8d8a2 透明的)
pick 915b8b1 re modify name.txt

# Rebase d0cd09d..915b8b1 onto d0cd09d (3 command(s))
#

可以看到,是不存在任何合并节点的。这也是 rebase 的目的!

疑问:如果我只是想改写 7c8d8a 之前某个 commit 的备注信息,且不想丢失分支演进,改怎么做呢?

结论:其实这算不得 rebase -i 的副作用,git rebase 默认 ignore merges。我自己以期上述目的,是用错了命令,虽然我至今不知道怎么实现上述目的 查看 --preserve-merges 参数。再往深的追究,其实我至今都不知道该怎么使用 git 去实现一个完善的工作流程,我了解一些命令的用法,但去不知道该怎么组织一个工程,怎么对一个项目进行细致的、完整的版本管理?

参见 git rebase --preserve-merges 手册。此参数的某些使用情景很乱,见 What exactly does git’s “rebase –preserve-merges” do (and why?)

汇合分支上的提交,然后一同合并到分支

git merge –squash

TortoiseGit 的 Show RefLog

TODO:感觉很有用哎,今天进行 rebase -i 操作后发现 merge –no-ff 的分支合并记录都没了,而且进行了两次 reabse -i,所以也没办法通过 git reset ORIG_HEAD 恢复。幸运的是发现了 RefLog 的存在,不知道这是 Git 本身的功能,还是 TortoiseGit 的功能扩展。抽空查探一下。

是 Git 自身的机制,很强大!官方手册,真心没有探索的欲望,怎么破?

其他

Tips

不同类别的修改 (如:Bug修复和功能添加) 要尽量分开提交,以方便以后从历史记录里查找特定的修改内容。

Tips

查看其他人提交的修改内容或自己的历史记录的时候,提交信息是需要用到的重要资料。所以请用心填写修改内容的提交信息,以方便别人理解。

以下是Git的标准注解:

1
2
3
第1行:提交修改内容的摘要
第2行:空行
第3行以后:修改的理由

请以这种格式填写提交信息。

Get

切换分支使用 checkout,检出文件也使用 checkout……不是因为作者懒得起名字,而是因为两者本质是同一种操作,都是从版本库(或索引)检出文件到工作区。分支本质上是某一个提交(的引用),切换分支就是检出对应的提交。

Get

如果在log命令添加 --decorate 选项执行,可以显示包含标签资料的历史记录。

在 windows 下搭建 git 服务器

2016/12/15 12:49:18 前两天将写的代码误删了,没有用版本管理工具。代码在一台连不上互联网的服务器上,因为系统老旧的原因,使用上有好多限制,负责人担心在新系统上写的代码拿到旧系统(最终必须部署在旧系统上)上用还得再次修改兼容性之类的问题,所以一般都是在旧系统上直接一个字母一个字母的敲代码。误删上周的工作内容后,后悔之余,也得考虑以后怎么避免这种情况。

在旧系统上凑活能装上 git,很原始的版本了。但它只能访问到我的笔记本(局域网),而不能访问互联网,所以就需要在 E431 windows7 的电脑上部署 Git 仓库。

依据上一篇搭建完成之后,操作中报错:(看着好像是不能是 --bare Repo 啊)

工作流

工作流 flow,对于大团队和个人肯定是不一样的,不能一概而论。对于个人开发,其重点最终要落在对分支 branch 的利用上,只要合理安排分支基本就可以了。对于人员很多,强调合作开发的团队,在上述基础上还要找准各节点(每个开发人员)的定位,会变得复杂很多。

一个完整的工作流包括对分支的利用、对节点的安排。好在大多时候我们只需关注前面一部分就能达到目的。

分支的安排

3.4 Git 分支 - 分支开发工作流 中描述了“长期分支”和“特性分支”两个概念。

Git分支管理策略 - 阮一峰 中使用了两个常设分支(长期分支)和三个临时分支(特性分支)。master 用于正式发布,develop 用于日常开发;feature 功能分支,Release 预发布分支,fixbug 修补分支。此策略即 Git flow

合并分支使用 --no-ff 选项。

Git flow 并不完美,Git 工作流程 - 阮一峰 中又讲解了另外两种分支策略:Github flow、Gitlab flow。推荐 Gitlab flow。在摸索中学习,在学习后实践。

ps:2017/2/23 15:23:53 我现在认为基于“版本发布”的,还是使用 git flow 更好一些,更符合直觉,更何况有那么多的前辈们(非 web 开发)都是如此呢。

gitlab flow

如何使用gitlab的flow以及代码review

GitLab Flow的使用

分布式节点的安排

5.1 分布式 Git - 分布式工作流程 中提到三种

集中式工作流、集成管理者工作流、司令官与副官工作流。

个人开发或者小团队开发,使用前者足够了。这种单点协作模型完全符合人们的直觉,好多人虽然不知道“集中式工作流”这个名词,但已经在这么用了。

其他

个人学习笔记,学习的总结,肯定是采用文字的形式记录。是以纸笔墨砚的形式,还是以更现代的码字的形式?上学的时候还固执于前者,写写画画了好几个本子,每个上面都有些文字,却也都不成系统,而且基本上全是个人日记,记录心情,发泄情绪。工作后渐渐放弃了纸笔间的习惯,喜欢在键盘上码字,整理的内容也由日记全部过渡成了技术帖。一开始在印象笔记中零零散散保存,后来同时使用为知笔记并逐渐过渡了日记使用前者,技术帖使用后者。因为基本不再写日记,印象笔记也就变成了存储记忆的空间,还是不翻书的那种。

后来迷上 markdown,有半个月打算放弃为知笔记,后来发现 markdown 只能替代为知笔记默认编辑器的位置,而个人知识管理的体系、框架的建立与维护还是得靠为知笔记。但两者的结合并不完美,核心问题在于技术人员(指我自己)使用 GFM,而为知笔记的 markdown 解释器并不能完全支持 GFM,尤其是语法高亮方面(主要使用 shell、vimL、console 的支持),GFM 不支持 [TOC] 标签。

使用 MarkdownPad 2 编辑文字很爽,但保存的 .md 文档在查阅时并不直接。将其拷贝到为知笔记中在首部添加 [TOC] 标签后发布,阅读时直观,查找时无论是根据多层级文件级树形查找还是直接使用内建的搜索框直接搜索都是很方便的,不足之处在于:

  • 一方面即便其支持 markdown 编辑,但并不友好,尤其是在使用 MarkdownPad 2 之后;
  • 另一方面,使用 markdown 语法的前提下,无法实现在笔记中插入对另一篇笔记的引用(链接),这在整理系列笔记时尤其不能容忍。

想通过搭建个人的博客网站来解决上述矛盾:保留使用 MarkdownPad 2 编辑文字的习惯,放弃为知笔记,将文字直接发布到外网上就可以解决无法链接、笔记之间相互跳转的问题。此种情况下带来一点点不便,如果帖子中涉及个人隐私、公司商业机密(我笑),需要仔细筛选。

在搭建个人博客的摸索阶段,从为知笔记到博客网站的过渡阶段,依然会使用为知笔记,并不在时间轴上一刀切,一下子将为知笔记中的内容全部拷贝到网站中,然后清空、销户。

使用 GitHub Pages 和 Hexo 搭建个人博客,静态网页会在 Github 中保留有版本记录,但源 .md 文件的存储(乃至版本控制)尚需考虑:

  • 对编辑的过程(版本控制)要求高,考虑~\hexo\source 创建版本库;2020/12/25,折腾一遭还是选择此方案

  • 只要求结果(对版本控制要求低),有 .md 文件的备份即可,考虑整体迁移到 Dropbox 中;

    因为 hexo\blog\source\_posts 目录(.md 源)只能在 hexo 树形目录的固定节点,所以想将其备份到 dropbox 中,需要 trick / workaround,Windows下硬链接、软链接和快捷方式的区别

  • 图片库的问题,考虑使用图床;2020/12/25,PicGo+GitHub,谁用谁知道

  • 对目录有需求,使用哪种实现呢?[TOC] 不解析,使用支持目录的主题。

搭建博客

阮一峰老师说喜欢写 Blog 的人,会经历三个阶段:

第一阶段,刚接触 Blog,觉得很新鲜,试着选择一个免费空间来写。
第二阶段,发现免费空间限制太多,就自己购买域名和空间,搭建独立博客。
第三阶段,觉得独立博客的管理太麻烦,最好在保留控制权的前提下,让别人来管,自己只负责写文章。

在网上查找两篇教程,按部就班的操作就能得到结果。目前对于博客没有任何复杂的要求,对评论、对布局、主题都没有挑选的欲望。博客只是用来将自己的整理的笔记、学习总结放到互联网上存储,查阅时不用再打开为知笔记,不用打开 MarkdownPad2 实时生成,带来查阅时的便利即可。

对于评论,教程中多提及中文用户推荐“多说”,起步阶段没有需求,加上暂时不想再多花时间在这些“边缘”上,所以未开启评论功能。总的来说,博客还是处于自我查阅阶段。

参考 hexo 官方站点 的介绍,在 下载安装 Node.js 之后,执行以下命令即可完成搭建。

  • 下载 Node.js 时,不使用代理时下载飞快;
  • 安装 Node.js 的过程会自动配置 Path,安装完毕使用 powershell 输入 node 验证
1
2
3
4
5
6
7
8
9
10
$ npm install hexo-cli -g
$ hexo init blog #解释:初始化了一个文件夹叫做blog,如果不想叫blog可以改为别的名字
$ cd blog
$ npm install
$ hexo server

# 通过 github 发布需要
npm install hexo-deployer-git -S
# 使用本地搜索需要
npm install hexo-generator-searchdb --save

拉取笔记仓库,并替换 source 目录:

  1. 进入 blog\ 目录,
  2. 执行 git clone git@github.com:tnie/notes.git
  3. 然后 rm source -rf && mv notes source
  4. 安装 Next 主题 npm install hexo-theme-next
  5. 使用仓库中的配置文件,更新 blog 的配置项目。

注意:绑定本地 hexo 和个人的 Github Pages 后(修改全局配置文件),执行 hexo deploy 命令部署时需要先 npm install hexo-deployer-git -S

发布博客

整理完笔记之后,怎么发布到 github.io 上?在这里针对具体的步骤做一份备忘录,防止因为懒惰或其他原因较长时间不发博客,“回归”时还要再次花费时间查阅 Hexo + GitHub Pages 技术帖子。而且,技术相通,但每个人都会培养出属于自己的习惯,不想“回归”时再摸索。

  1. 在 Git Shell 中打开至 hexo 目录。 cd hexo

  2. 敲入 hexo new "filename" 命令。此命令会在 source/_posts 目录下创建 filename.md 文件

    • filename 建议使用英文,且不要存在空白字符。可以使用下划线 _ 连接两个单词。
    • 用翻译工具查一查,起个好名字。
  3. 打开 source/_posts 目录,使用 MarkdownPad2 编辑文字,修改 title tags categories 属性,保存。

    • 一般来说,都是将写好的笔记全文粘贴过来。记得删除源文件,在过一段时间之后维护多个相似文件是很痛苦的。
    • title 使用中文;tags categories 原则上一律小写。
    • tags 属性存在多个时,可以这样写 tags: [make,makefile],但注意每一个 tag 中不能有空格,比如 tags: design pattern 这是错的
  4. 敲入 hexo generate 生成静态网页。

  5. 敲入 hexo server 启动本地服务器,查看效果。

  6. 敲入 hexo deploy 发布到互联网。

  7. 迁移为知笔记中的内容。“迁移”顾名思义,要从为知笔记中删除。

参考链接

有讲述整体流程的,有着眼于模块的——描述主题的、强调分类和标签的、添加站内搜索、添加评论的……

  1. 其中,在 Debian8 上安装 Node.js 使用 其官方 的 Linux Binaries (.tar.xz) 版本。解压之后直接配置系统 PATH 路径即可。tar xJvf node-v4.4.7-linux-x64.tar.xz
  2. 如果有更换域名的需求,可以参考 简明Github Pages与Hexo教程,而且作者是在 win7 环境下搭建的,同时文末给出了大量的参考资料。

主题

官方是有 辣么多的主题 让我们选,挑花了眼。

2017/4/13,使用新的主题 NexT。原因有二:

  • Jacman 的搜索不能用。网上有很多其搭配 swiftype,但其他的第三方(比如谷歌)却怎么都调试不成功。
  • Jacman 作者自己貌似都放弃了,作者自己的博客 使用 NexT 主题是其一,不再维护 Jacman 是关键。

使用 NexT 主题,碰到的问题 —— 其 404 页面的配置在使用 https 协议时有问题:

Hexo NexT 主题给出的腾讯 404 公益页面的教程仅适用于非 HTTPS 站点,对于严格限制混合内容的 HTTPS 站点来说,腾讯 404 公益页面使用的 JS 文件是无法引入的。

解决方案:关于 Hexo 上传 404 页面的问题

目录和标签

Hexo使用攻略:(四)Hexo的分类和标签设置

目录和标签有什么区别?关键词:一个目录,多个标签;树状结构,网状结构;

  1. 怎么给文章添加合适的标签呢?
  2. 在目录上尽可能的粗放,再用标签进行管理
  3. 使用树状目录尽量不要超过3级。
  4. 树状目录应该是和标签系统互补的;
  5. 标签起一种聚合作用,将相关性很强的东西联系到一起;分类目录则是相当于存放同类文章的容器,里面的文章虽属同类,相关性却可能不太强。
  6. 多参考大牛们的网站

不建议你“未雨绸缪”的打上标签。而是在有一定数量的笔记有大量相同性时,再打上标签,目录也是如此。

遵循这么一个原则:想一下读者看了这篇文章后,还会对哪些其他的文章感兴趣,然后这些文章就都添加上同一个标签。这样标签就可以实现目录交叉

其他

利用swiftype为hexo添加站内搜索v2.0

swiftype 试用结束后,只能付费。免费试用期到期后,官网登录之后无论点击哪里都是“Your Swiftype free trial has come to an end” 页面,猜测 swiftype 不再提供免费版本,佐证。需要寻找备案了……

使用本地搜索,安装插件(需要相应的配置项)。

1
npm install hexo-generator-searchdb --save

过程也是结果

在整理笔记的时候肯定会从网上查找资料,每个知识点肯定都会碰到一篇帖子让自己感慨“哇,总结得好好,娓娓道来,该讲的都讲到了,却也不多一句废话”,每每到此刻都觉得自己整理的笔记就是一坨垃圾,用词不当,表述不清晰,上下文转接不流畅,有的重点落下没讲,废话说太多……可这就是成长必经的过程,如果不是整理这个知识点,就不会一板一眼、较真地去查阅好多资料,线上的博客、线下的书,随手 google 来的终究只是编码过程中的 code demo,而非知识,只有当你读了很多篇笔记,看过了很多风景,才能有足够的理解,才能通过已经掌握的,通过对比,认识到某一篇是够精彩的。

如果因为总结的笔记太烂而气馁,放弃整理,那么就不会查阅足够的资料,就不会见到那篇“哇,精彩”的帖子!整理出来的笔记是结果,整理过程中见到的风景也是结果!

ps:在 2017/4/28 10:13:43 之前的文章大多是从 为知笔记 中手动迁移出来的。

  • 源于为知笔记不支持账号销户,所以也就没有清空其中的内容,暂时保留一段时间还可以校正迁移过程中的失误;
  • 但在文章全部迁移完毕之后也就不再使用为知笔记了,因为它 变更服务策略 太激进了。

我们从 GoF 说起,书中写到单例模式(Singleton)的意图是:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

在什么时候会用到单例模式呢?开发过程中常见的应用举例:

  • 日志类,一个应用往往只对应一个日志实例:丑陋的写法是在每个场景都实例化一个日志对象使用。
  • 配置类,应用的配置集中管理,并提供全局访问:丑陋的写法是在每次使用配置时都实例化一个配置对象,然后访问配置文件读取其中的配置项。
  • 管理器,比如我们写了一个 windows 服务,然后我们要封装一个启停此服务的管理器。
  • 共享资源类,加载资源需要较长时间,使用单例可以避免重复加载资源,并被多个地方共享访问。比如文件连接,数据库连接等
阅读全文 »

原来的笔记写于 2015年11月23日,当时使用的是为知笔记默认编辑器。十个月过去了,对于 make & Makefile 也有了更多的认识。今天重新整理一下,不过仍然定位在入门的帖子,所以不会添加新的内容,只是修改表述不当的地方,对原有内容作出删减,并用 markdown 格式重写。

这篇本来是打算写成阅读笔记的。但是所知不多,刚开始学习应该博览,求入门,求上手使用。再加上看的东西越来愈多,单纯的记录一篇帖子中的重点、难点,较真于一处在实践中基本不会碰到的细节,格局太小,意义也不大。

阅读全文 »

个人知识管理(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 一并使用。

延伸

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

0%