条款4:确定对象被使用前已先被初始化

  • 为内置型对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初值列(member initialization list),而不是在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,起排序次序应该和它们在class中的声明次序相同。
  • 为了免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

内置对象指的是C++自带的类似于int,double的类型。这些类型变量如果没有进行初始化,其值是未定义的,如果对其进行读取会导致不明确的行为(undefined behavior)。所以必须要保证读取内置对象前,其被初始化过。

1
2
int x; 		// bad code
int x = 0; // good code

对于非内置类型,初始化的任务则是由构造函数负责的。包括STL库里的一些类。

1
2
3
std::string str;		//it's ok
std::string str = ""; //it's also ok
Class1 obj;

类成员变量初始化 vs 赋值

类的成员变量在进入构造函数之前已经已经被初始化,所以如果在构造函数内进行”初始化“的的话其实是进行赋值,会带来额外的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Entry {
public:
Entry();
private:
std::string name;
int value;
}

Entry::Entry() {
name = "default"; //
value = 0; // not good
}

Entry::Entry()
: name("default")
, value(0)

至于作者提到的初值列中的顺序需要与class内变量声明的顺序保持一致是因为c++总是按照class内变量声明的顺序对变量,所以为了避免带来迷惑以及容易检查是否漏掉变量未初始化,最好将初值列顺序与class内变量声明顺序保持一致。

non-local static 对象的初始化

不同编译单元内定义的non-local static对象初始化次序未定义。

编译单元

编译单元是指产出单一目标文件(object file)的那些源码,基本上是它的源文件加上其所含入的头文件。

non-local static 对象

除了定义在函数内的static变量,其他的static变量均是non-local static对象,包括global对象、定义域namespace作用域内的对象、class内的、文件作用域内的static对象。

次序未定义会导致一个问题,就是如果你需要在一个编译单元内使用另一个编译单元的静态变量,则很有可能你在调用的时候其还没有初始化。这种情况你可以通过使用返回一个local static变量的reference的方法来得到,这个方法就是单例的常见使用场景。

1
2
3
4
FileSystem &tfs() {
static FileSystem fs;
return &fs
}

但这个还有一个问题就是static在多线程的情况下会出现不确定性,所以最好在程序启动时先以单线程的方式调用一遍。

总之,non-local static对象最好只在一个编译单元内使用,如果需要跨编译单元使用,则使用单的方法替代。

让自己习惯C++

条款01:视C++为一个语言联邦

  • C++ 高效编程守则是状况而变化,取决于你使用C++的哪一个部分。

C++并不是一个单一范式编程语言,它同时支持过程形式(procedural),面向对象设计(object-oriented)、函数形式(functional)、泛型形式(generic),元编程形式(meta-programming)。

所以作者认为并不是每一个规则都是适用于所有的这些范式的,那么我们可以将C++语言分为一下几种次语言:

  • C
  • Object-Oriented C++
  • Template C++
  • STL

条款02:尽量以const,enum,inline替换#define

  • 对于单纯常量,最好以const对象或enums替换#define
  • 对于形似函数的宏(macros),最好改用inline函数替换#define

#define的缺点:

  1. define 定义的值无法被编译器和调试器看到,出问题了很麻烦。
  2. 没有良好的封装新,不能控制作用域。

const

使用const我可以这样声明一个常量:

1
2
const std::string ProductName("Mango Game");
const double Pi = 3.1415;

还可以声明一个类的专属常量:

1
2
3
4
5
6
7
8
9
//Header
class Game {
private:
static const int MaxGamePlayer ;
...
};

//cpp file
const int Game::MaxGamePlayer = 5;

enum hack

如果需要编译时使用到常量就需要enum hack这个技巧,实现如下:

1
2
3
4
5
class GamePlayer {
private:
enum { NumTurns = 5}
int scores[NumTruns];
};

inline function

有一些宏长得很像函数,这种宏有类型安全问题和不可预料行为两个问题。可以使用inline function来替代。

1
2
3
4
5
6
7
8
9
//这个MAX宏很不好,会出现奇怪的一些问题且无法调试
#define MAX(a,b) ((a) > (b) ? (a) : (b))

//Good
template<typename T>
inline T Max(const T& a, const T& b)
{
return (a > b ? a : b);
}

条款3:尽可能使用const

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”(conceptual constness)。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

const提供一种语义上的约束,它能够约束变量使用者的行为。当它作用于针、智能指针、迭代器上的时候可以指向指针本身也可以是指针指向物。作者提出了一个识别的技巧:如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针本身是常量。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

char greeting[] = "Hello"
char *p = greeting; //non const point, const data
const char * p = greeting; //non const point, const data
char const * p = greeting; //non const point, const data
char * const p = greeting; //const point, non const data
const char* const p = greeting; //const point, const data

const std::vector<int>::iterator it; // --> T * const p
*it = 10; //合法
it++; //不合法
std::vector<int>::const_iterator it; // --> const T * p
it = 10; //不合法
it++; //合法

std::shared<T> p1;
std::shared<const T> p; // --> const T *p
p = p1; //合法
*p.get() = x; //不合法
const std::shared<T> p; // --> T* const p
p = p1; //不合法
*p.get() = x; //合法

有点头晕,我有一个不是很恰当地记忆方法。可以把const看成修饰它右边所有内容的,以const char* p为例,把p看成一个指针,而*p则是数据,所以const是修饰数据的。而char * const p中,const右边只有p,所以它只是限制指针本身的。

bitwise constness vs logical constness

当限制一个成员函数为const,编译器会执行bitwise constness,即检查函数内部是否修改了类的成员变量。但是写代码的时候应该注意逻辑上的常量限制,比如说一个你返回了一个指向内部变量的指针,这个函数虽然符合编译器的const限制但却不是很好的一个写法。

[to be continued]

背景知识

grep是linux中常用的文本处理的工具,与之类似的工具有sed和awk,它们分别适用于不同的场景。同时要想很好的使用grep等文本处理工具,需要对正则表达式有一定的了解。

grep vs sed vs awk

grep主要用于对文本、文件进行搜索,查找出符合要求的字符串并输出相关的内容。

sed和awk则是更强大文本处理工具,适用于更复杂的场景。sed是一个基于字符流的处理工具(stream editor),不仅可以查找文字还可以对文本进行操作,比如说修改、删除、插入等。awk则是更加强大的工具,它本身是一个完整的编程语言,可用于对数据的提取处理然后生成一些报表,主要针对类csv格式的文件。

正则表达式

to be written

grep的种类

  • grep | grep使用简单的模式和正则表达式进行搜索。
  • egrep | egrep使用拓展版的正则表达式,包含更多的正则表达式功能。
  • fgrep | fgrep是加速版的grep,主要适用于简单的模式匹配。
  • zgrep, zegrep, zfgrep | 与grep, egrep, fgrep 效果相同,只是可以接受一个由compress或gzip压缩出来的压缩包进行搜索。在现在的操作系统中,多种的grep使用的是同一个binary,而不是不同的binary。比如说grep -E其实等效于egrep,grep -F等效于fgrep。

常用参数

-A num, --after-context=num - 打印出找到的匹配行后num行。这个跟-B-C类似功能,主要用于查看上下文。

-B num, --before-context=num - 打印出找到的匹配行前num行。

-C[num, --context=num] - 打印出找到的匹配行前后num行,相当于-A num -B num

-c, --count - 统计出匹配的总行数,同一行有两个匹配也只算一行。

--colour=[when, --color=[when]] - 将匹配的字符标记为彩色,when可以是’never’,‘auto’或’always’。

-e pattern, --regexp=pattern - 制定在搜索中使用的匹配模式,多用于指定多个匹配模式的情况。

-H - 在每一行前面打印出匹配的文件名,在多个文件的情况下这个是默认选项。

-h - 在多个文件的情况下不打印出匹配的文件名。

-n - 打印出匹配所在行的行数。

-i, --ignore-case - 查找时忽略大小写。

-R, -r, --recursive - 递归的去查找子目录下的文件。

-v, --invert-match - 打印出不匹配的行。

-w, --word-regexp - 只匹配整个单词。

案例学习

实验文件,随便找了一个莎士比亚的十四行诗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat demofile
sonnet 18

Shall I compare thee to a summer's day?
Thou art more lovely and more temperate:
Rough winds do shake the darling buds of May,
And summer's lease hath all too short a date:

Sometime too hot the eye of heaven shines,
And often is his gold complexion dimm'd;
And every fair from fair sometime declines,
By chance or nature's changing course untrimm'd

But thy eternal summer shall not fade
Nor lose possession of that fair thou owest;
Nor shall Death brag thou wander'st in his shade,
When in eternal lines to time thou growest:

So long as men can breathe or eyes can see,
So long lives this and this gives life to thee.
$ cp demofile demofile2
  1. 在文件中搜索某个字符串。

    1
    2
    3
    4
    $ grep "and" demofile
    Thou art more lovely and more temperate:
    Nor shall Death brag thou wander'st in his shade,
    So long lives this and this gives life to thee.
  2. 匹配整个单词。

    上面的例子中搜索and这个字符串匹配了wander这个单词,如果我们只想搜and这个单词可以使用-w参数。

    1
    2
    3
    $ grep -w "and" demofile
    Thou art more lovely and more temperate:
    So long lives this and this gives life to thee.
  3. 忽略大小写

    1
    2
    3
    4
    5
    6
    $ grep -i -w "and" demofile
    Thou art more lovely and more temperate:
    And summer's lease hath all too short a date:
    And often is his gold complexion dimm'd;
    And every fair from fair sometime declines,
    So long lives this and this gives life to thee.
  4. 使用正则表达式

    只要把匹配的字符串换成需要的正则表达式即可。比如说需要搜索以And开头的行。

    1
    2
    3
    4
    $ grep "^And" demofile
    And summer's lease hath all too short a date:
    And often is his gold complexion dimm'd;
    And every fair from fair sometime declines,
  5. 显示上下文

    有时候搜出来需要看看上下文,可以利用上面提到的-B-A-C来实现。

    比如想要找出包含”nature”的句子并打印出上下两行。

    1
    2
    3
    4
    5
    6
    $ grep -C2 "nature" demofile
    And often is his gold complexion dimm'd;
    And every fair from fair sometime declines,
    By chance or nature's changing course untrimm'd

    But thy eternal summer shall not fade
  6. 统计出匹配的总行数

    比如说想要统计出现”to”的行数

    1
    2
    $ grep -c "to" demofile
    5

    如果想要统计出现的总次数的话,grep本身没有办法满足这个需求,需要使用sed或awk来实现。或者结合wc来实现。

    1
    2
    $ grep -o "to" demofile| wc -l
    5
  7. 高亮匹配文字

    高亮匹配文字可以让肉眼可以第一时间的看到匹配结果,利用–color=auto/always来实现。此处nature高亮状。

    1
    2
    $ grep --color=auto "nature" demofile
    By chance or nature's changing course untrimm'd
  8. 简单匹配多个模式

    查找多个字符串可以使用|字符来进行简单的匹配。比如说"heaven|nature"表示匹配heaven或者nature。因为这个是正则表达式的拓展部分,所以需要使用-E或者egrep。

    1
    2
    3
    $ grep -E "heaven|nature" demofile
    Sometime too hot the eye of heaven shines,
    By chance or nature's changing course untrimm'd
  9. 复杂匹配多个模式

    需要找出符合多个模式的匹配。比如说,找出以”When”开头或者包含”fade”的行。

    1
    2
    3
    $ grep -e "^When" -e "fade" demofile
    But thy eternal summer shall not fade
    When in eternal lines to time thou growest:
  10. 找出不满足条件的匹配

    在一些情况下我们需要找出不满足某个条件的行,这个时候就可以用-v来实现了。

    比如说想找出首字母不是大写字母的行。

    1
    2
    $ grep -v "^[A-Z]" demofile
    sonnet 18
  11. 打印出匹配所在行的行数

    1
    2
    3
    $ grep -e "^When" -e "fade" -n demofile
    13:But thy eternal summer shall not fade
    16:When in eternal lines to time thou growest:
  12. 打印出文件名

    进行多个文件搜索的情况下,默认会打印出文件名。

    1
    2
    3
    4
    5
    $ grep -e "^When" -e "fade" demofile demofile2
    demofile:But thy eternal summer shall not fade
    demofile:When in eternal lines to time thou growest:
    demofile2:But thy eternal summer shall not fade
    demofile2:When in eternal lines to time thou growest:

[EOF]

mv (move)

mv是用于移动文件的命令,mac下没有重命名的命令,所以很多时候mv也用来重命名文件。

常用参数

-f - 强制覆盖不提醒
-i - 覆盖目标文件前提醒
-n - 不覆盖目标文件

将n个文件移动到某个目录下

mv file1 file2 file3 dir

如果目录不存在,会报错

重命名/移动文件

mv file1 file2

如果file2不存在则重命名为file2,如果存在则覆盖

移动目录

mv dir1 dir2

将dir1移动到dir2目录下,如果dir2不存在则是一个重命名的效果

rm(remove)

此命令用于删除文件和目录,与通配符结合使用时破坏很大,稍有不慎将酿成大错后悔莫及。

普通的删除只是将文件从文件系统中移除,磁盘上的那块空间并不会去动它。所以如果误删除的话,通过某一些还原工具是有可能找回来的。

Linux并没有撤销删除命令,一旦你用rm命令将某个文件删除,你将很难恢复(mac可以通过特殊的数据还原工具)。如果错误的使用rm,特别是跟通配符结合使用的时候,将有可能造成极大的危害。
rm中使用通配符之前,有一个有用的小诀窍:先将你的通配符用ls命令试试看,看一下所影响的文件。当你用ls测试过没问题以后,你可以直接把ls特换成rm[1]

基本用法

rm [-dfiPRrvW] file ...

-f - 强制删除,没有提示
-i - 删除某个文件前请求确认
-P - 粉碎性删除。删除文件前重新复写整个文件,往文件里不停写入0xff0x00。这样删除的文件无法恢复。
-R - 递归删除。如果指定的文件为目录,则删除目录下所有的文件及其子目录。

rmdir(remove directory)

不要被这货的名字迷惑了,这个命令唯一的用途就是删除一个空白的文件夹。如果文件夹非空还是要用rm -r来删除,不知道它存在的意义是什么。

mkdir(make directory)

顾名思义,创建文件夹使用。

基本用法

-p 创建中间目录,比如mkdir /path/with/many/layer会帮你吧layer之前的所有层级的目录都创建出来。
-m 制定文件夹的权限,详见chmod。

参考资料

  1. Manipulating Files

阅读更多

Mac/Linux 命令学习

背景知识

ls命令中有出现过一个-O的选项,即列出文件的file flag。这个File flag所为何物?

File flag是在BSD Unix中的概念,跟Linux系统中的attr是差不多的一个概念,是文件的一些标志位来存放文件的某些属性。chflags就是来修改这个file flag的。这个文件属性是跟文件系统相关的,所以这个命令在不同的文件系统上的支持程度不一样,体现在某一些flag在一些特定的文件系统上没有。[1]

所有的属性都可以被超级管理员修改,有一些可以被文件的所有者修改。以下列出比较常用的几个属性[1]

属性 ls中显示 chflags中使用 文件所有者能否修改? 详述
隐藏 hidden hidden 设置以后在GUI上看不到,ls依然可以看到d
系统级只能添加 sappnd sappnd, sappend 设置以后此文件不能够截断或者复写(overwrite),只能通过append模式添加内容
用户级只能添加 uappnd uappnd, uappend 设置以后此文件不能够截断或者复写(overwrite),只能通过append模式添加内容
系统级只读 schg schg, schange, simmutable 不能够重命名、移动、删除、更改内容
用户级只读 uchg uchg, uchange, uimmutable 不能够更改内容

基本用法

chflags [-fhv] [-R [-H | -L | -P]] flags file

常用参数

-R 递归修改参数中文件夹下面的所有文件和文件夹的属性

具体案例

为一个文件添加一个属性

chflags uchg file

为一个文件删除一个属性

chflags nouchg file

在属性名字前面添加no就可以将属性删除,如果这个属性本身已no开头(比如nodump)则去掉no。

将文件夹及其文件夹下所有文件属性进行修改

chflags -R uchg directory

参考资料

  1. chattr - Wikipedia