条款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 - 粉碎性删除。删除文件前重新复写整个文件,往文件里不停写入0xff和0x00。这样删除的文件无法恢复。
-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