不积小流,无以成江海

海纳百川,有容乃大

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

我看了一下,这篇笔记最早是在六月二号创建的,可是现在 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// 定义
    1. 在类外定义非内联成员函数

内部链接和外部链接

参考自:参考链接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;

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

0%