算法第一章作业

谷歌代码规范

头文件

通常每一个 .cc 文件都有一个对应的 .h 文件. 也有一些常见例外, 如单元测试代码和只包含 main() 函数的 .cc 文件.

1.1. Self-contained 头文件

头文件应该能够自给自足(self-contained,也就是可以作为第一个头文件被引入),以 .h 结尾。至于用来插入文本的文件,说到底它们并不是头文件,所以应以 .inc 结尾。不允许分离出 -inl.h 头文件的做法.

1.2. #define 保护

所有头文件都应该使用 #define 来防止头文件被多重包含, 命名格式当是: <PROJECT>_<PATH>_<FILE>_H_ .

1.3. 前置声明

尽可能地避免使用前置声明。使用 #include 包含需要的头文件即可。

1.4. 内联函数

只有当函数只有 10 行甚至更少时才将其定义为内联函数.

1.5. #include 的路径及顺序

使用标准的头文件包含顺序可增强可读性, 避免隐藏依赖: 相关头文件, C , C++ , 其他库的 .h, 本项目内的 .h.

 

2. 作用域

2.1. 命名空间

鼓励在 .cc 文件内使用匿名命名空间或 static 声明. 使用具名的命名空间时, 其名称可基于项目名或相对路径. 禁止使用 using 指示(using-directive)。禁止使用内联命名空间(inline namespace

2.2. 匿名命名空间和静态变量

.cc 文件中定义一个不需要被外部引用的变量时,可以将它们放在匿名命名空间或声明为 static 。但是不要在 .h 文件中这么做。

2.3. 非成员函数、静态成员函数和全局函数

使用静态成员函数或命名空间内的非成员函数, 尽量不要用裸的全局函数. 将一系列函数直接置于命名空间中,不要用类的静态方法模拟出命名空间的效果,类的静态方法应当和类的实例或静态数据紧密相关.

2.4. 局部变量

将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化.

2.5. 静态和全局变量

禁止定义静态储存周期非POD变量,禁止使用含有副作用的函数初始化POD全局变量,因为多编译单元中的静态变量执行时的构造和析构顺序是未明确的,这将导致代码的不可移植。

 

3. 

3.1. 构造函数的职责

不要在构造函数中调用虚函数, 也不要在无法报出错误时进行可能失败的初始化.

3.2. 隐式类型转换

不要定义隐式类型转换. 对于转换运算符和单参数构造函数, 请使用 explicit 关键字.

3.3. 可拷贝类型和可移动类型

如果你的类型需要, 就让它们支持拷贝 / 移动. 否则, 就把隐式产生的拷贝和移动函数禁用.

3.4. 结构体 VS.

仅当只有数据成员时使用 struct, 其它一概使用 class.

3.5. 继承

使用组合 (YuleFox : 这一点也是 GoF <<Design Patterns>> 里反复强调的) 常常比使用继承更合理. 如果使用继承的话, 定义为 public 继承.

3.6. 多重继承

真正需要用到多重实现继承的情况少之又少. 只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类; 其它基类都是以 Interface 为后缀的 纯接口类.

3.7. 接口

接口是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制).

3.8. 运算符重载

除少数特定环境外, 不要重载运算符. 也不要创建用户定义字面量.

3.9. 存取控制

所有 数据成员声明为 private, 除非是 static const 类型成员 (遵循 常量命名规则). 出于技术上的原因, 在使用 Google Test 时我们允许测试固件类中的数据成员为 protected.

3.10. 声明顺序

将相似的声明放在一起, public 部分放在最前.

 

4. 函数

4.1. 参数顺序

函数的参数顺序为: 输入参数在先, 后跟输出参数.

4.2. 编写简短函数

我们倾向于编写简短, 凝练的函数.

4.3. 引用参数

所有按引用传递的参数必须加上 const.
4.4. 函数重载

若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底

哪一种. 这一规则也适用于构造函数.

4.5. 缺省参数

只允许在非虚函数中使用缺省参数, 且必须保证缺省参数的值始终一致. 缺省参数与 函数重载 遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在缺省函数带来的可读性提升不能弥补下文中所提到的缺点的情况下
  4.6. 函数返回类型后置语法
只有在常规写法 (返回类型前置) 不便于书写或不便于阅读时使用返回类型后置语法.

5. 来自 Google 的奇技

5.1. 所有权与智能指针

动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.

5.2. Cpplint

使用 cpplint.py 检查风格错误

 

6. 其他 C++ 特性

6.1. 引用参数

C 语言中, 如果函数需要修改变量的值, 参数必须为指针, int foo(int *pval). C++ , 函数还可以声明引用参数: int foo(int &val).

6.2. 右值引用

只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward.

6.3. 函数重载

若要用好函数重载,最好能让读者一看调用点(call site)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。

6.4. 缺省参数

我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。

6.5. 变长数组和 alloca()

我们不允许使用变长数组和 alloca().

6.6. 友元

我们允许合理的使用友元类及友元函数.

6.7. 异常

我们不使用 C++ 异常.

6.8. 运行时类型识别

我们禁止使用 RTTI.

6.9. 类型转换

使用 C++ 的类型转换, static_cast<>(). 不要使用 int y = (int)x int y = int(x) 等转换方式;

6.10.

只在记录日志时使用流.

6.11. 前置自增和自减

对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.

6.12. const 用法

我们强烈建议你在任何可能的情况下都要使用 const. 此外有时改用 C++11 推出的 constexpr 更好。

6.13. constexpr 用法

C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。

6.14. 整型

C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, int16_t.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t. 此外要留意,哪怕您的值并不会超出 int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。

6.15. 64 位下的可移植性

代码应该对 64 位和 32 位系统友好. 处理打印, 比较, 结构体对齐时应切记:

6.16. 预处理宏

使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.

6.17. 0, nullptr NULL

整数用 0, 实数用 0.0, 指针用 nullptr NULL, 字符 () '\0'.

整数用 0, 实数用 0.0, 这一点是毫无争议的.

对于指针 (地址值), 到底是用 0, NULL 还是 nullptr. C++11 项目用 nullptr; C++03 项目则用 NULL, 毕竟它看起来像指针。实际上,一些 C++ 编译器对 NULL 的定义比较特殊,可以输出有用的警告,特别是 sizeof(NULL) 就和 sizeof(0) 不一样。

字符 () '\0', 不仅类型正确而且可读性好.

6.18. sizeof

尽可能用 sizeof(varname) 代替 sizeof(type).

使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新. 您或许会用 sizeof(type) 处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式,这时用变量就不合适了。

6.19. auto

auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。

6.20. 列表初始化

你可以用列表初始化。

早在 C++03 里,聚合类型(aggregate types)就已经可以被列表初始化了,比如数组和不自带构造函数的结构体:

struct Point { int x; int y; };

Point p = {1, 2};

C++11 中,该特性得到进一步的推广,任何对象类型都可以被列表初始化。示范如下:

// Vector 接收了一个初始化列表。

vector<string> v{"foo", "bar"};

// 不考虑细节上的微妙差别,大致上相同。

// 您可以任选其一。

vector<string> v = {"foo", "bar"};

// 可以配合 new 一起用。

auto p = new vector<string>{"foo", "bar"};

// map 接收了一些 pair, 列表初始化大显神威。

map<int, string> m = {{1, "one"}, {2, "2"}};

// 初始化列表也可以用在返回类型上的隐式转换。

vector<int> test_function() { return {1, 2, 3}; }

// 初始化列表可迭代。

for (int i : {-1, -2, -3}) {}

// 在函数调用里用列表初始化。

void TestFunction2(vector<int> v) {}

TestFunction2({1, 2, 3});

用户自定义类型也可以定义接收 std::initializer_list<T> 的构造函数和赋值运算符,以自动列表初始化:

class MyType {

 public:

  // std::initializer_list 专门接收 init 列表。

  // 得以值传递。

  MyType(std::initializer_list<int> init_list) {

    for (int i : init_list) append(i);

  }

  MyType& operator=(std::initializer_list<int> init_list) {

    clear();

    for (int i : init_list) append(i);

  }

};

MyType m{2, 3, 5, 7};

最后,列表初始化也适用于常规数据类型的构造,哪怕没有接收 std::initializer_list<T> 的构造函数。

double d{1.23};

// MyOtherType 没有 std::initializer_list 构造函数,

 // 直接上接收常规类型的构造函数。

class MyOtherType {

 public:

  explicit MyOtherType(string);

  MyOtherType(int, string);

};

MyOtherType m = {1, "b"};

// 不过如果构造函数是显式的(explict),您就不能用 `= {}` 了。

MyOtherType m{"b"};

千万别直接列表初始化 auto 变量,看下一句,估计没人看得懂:

6.21. Lambda 表达式

适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。

6.22. 模板编程

不要使用复杂的模板编程

6.23. Boost

只使用 Boost 中被认可的库.

6.24. C++11

适当用 C++11(前身是 C++0x)的库和语言扩展,在贵项目用 C++11 特性前三思可移植性。

 

7. 命名约定

最重要的一致性规则是命名管理. 命名的风格能让我们在不需要去查找类型声明的条件下快速地了解某个名字代表的含义: 类型, 变量, 函数, 常量, , 等等, 甚至. 我们大脑中的模式匹配引擎非常依赖这些命名规则.

 

命名规则具有一定随意性, 但相比按个人喜好命名, 一致性更重要, 所以无论你认为它们是否重要, 规则总归是规则.

7.1. 通用命名规则

函数命名, 变量命名, 文件命名要有描述性; 少用缩写.

7.2. 文件命名

文件名要全部小写, 可以包含下划线 (_) 或连字符 (-), 依照项目的约定. 如果没有约定, 那么 “_” 更好.

7.3. 类型命名

类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.

7.4. 变量命名

变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, : a_local_variable, a_struct_data_member, a_class_data_member_.

7.5. 常量命名

声明为 constexpr const 的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:

const int kDaysInAWeek = 7;

7.6. 函数命名

常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable().

 

7.7. 命名空间命名

命名空间以小写字母命名. 最高级命名空间的名字取决于项目名称. 要注意避免嵌套命名空间的名字之间和常见的顶级命名空间的名字之间发生冲突.

 

顶级命名空间的名称应当是项目名或者是该命名空间中的代码所属的团队的名字. 命名空间中的代码, 应当存放于和命名空间的名字匹配的文件夹或其子文件夹中.

 

注意 不使用缩写作为名称 的规则同样适用于命名空间. 命名空间中的代码极少需要涉及命名空间的名称, 因此没有必要在命名空间中使用缩写.

 

要避免嵌套的命名空间与常见的顶级命名空间发生名称冲突. 由于名称查找规则的存在, 命名空间之间的冲突完全有可能导致编译失败. 尤其是, 不要创建嵌套的 std 命名空间. 建议使用更独特的项目标识符 (websearch::index, websearch::index_util) 而非常见的极易发生冲突的名称 (比如 websearch::util).

 

对于 internal 命名空间, 要当心加入到同一 internal 命名空间的代码之间发生冲突 (由于内部维护人员通常来自同一团队, 因此常有可能导致冲突). 在这种情况下, 请使用文件名以使得内部名称独一无二 (例如对于 frobber.h, 使用 websearch::index::frobber_internal).

7.8. 枚举命名

枚举的命名应当和 常量 一致: kEnumName 或是 ENUM_NAME.

7.9. 宏命名

你并不打算使用宏, 对吧? 如果你一定要用, 像这样命名: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.

7.10. 命名规则的特例

如果你命名的实体与已有 C/C++ 实体相似, 可参考现有命名策略.

bigopen(): 函数名, 参照 open() 的形式

uint: typedef

bigpos: struct class, 参照 pos 的形式

sparse_hash_map: STL 型实体; 参照 STL 命名约定

LONGLONG_MAX: 常量, 如同 INT_MAX

 

8. 注释

注释虽然写起来很痛苦, 但对保证代码可读性至关重要. 下面的规则描述了如何注释以及在哪儿注释. 当然也要记住: 注释固然很重要, 但最好的代码应当本身就是文档. 有意义的类型名和变量名, 要远胜过要用注释解释的含糊不清的名字.

8.1. 注释风格

使用 // /* */, 统一就好.

8.2. 文件注释

在每一个文件开头加入版权公告.

文件注释描述了该文件的内容. 如果一个文件只声明, 或实现, 或测试了一个对象, 并且这个对象已经在它的声明处进行了详细的注释, 那么就没必要再加上文件注释. 除此之外的其他文件都需要文件注释.

8.3. 类注释

每个类的定义都要附带一份注释, 描述类的功能和用法, 除非它的功能相当明显.

// Iterates over the contents of a GargantuanTable.

// Example:

//    GargantuanTableIterator* iter = table->NewIterator();

//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {

//      process(iter->key(), iter->value());

//    }

//    delete iter;

class GargantuanTableIterator {

  ...

};

8.4. 函数注释

函数声明处的注释描述函数功能; 定义处的注释描述函数实现.

8.5. 变量注释

通常变量名本身足以很好说明变量用途. 某些情况下, 也需要额外的注释说明.

8.6. 实现注释

对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释.

8.8. TODO 注释

对那些临时的, 短期的解决方案, 或已经够好但仍不完美的代码使用 TODO 注释.

 

TODO 注释要使用全大写的字符串 TODO, 在随后的圆括号里写上你的名字, 邮件地址, bug ID, 或其它身份标识和与这一 TODO 相关的 issue. 主要目的是让添加注释的人 (也是可以请求提供更多细节的人) 可根据规范的 TODO 格式进行查找. 添加 TODO 注释并不意味着你要自己来修正, 因此当你加上带有姓名的 TODO , 一般都是写上自己的名字.

8.9. 弃用注释

通过弃用注释(DEPRECATED comments)以标记某接口点已弃用.

 

您可以写上包含全大写的 DEPRECATED 的注释, 以标记某接口为弃用状态. 注释可以放在接口声明前, 或者同一行.

 

DEPRECATED 一词后, 在括号中留下您的名字, 邮箱地址以及其他身份标识.

 

弃用注释应当包涵简短而清晰的指引, 以帮助其他人修复其调用点. C++ , 你可以将一个弃用函数改造成一个内联函数, 这一函数将调用新的接口.

 

仅仅标记接口为 DEPRECATED 并不会让大家不约而同地弃用, 您还得亲自主动修正调用点(callsites, 或是找个帮手.

 

修正好的代码应该不会再涉及弃用接口点了, 着实改用新接口点. 如果您不知从何下手, 可以找标记弃用注释的当事人一起商量.

 

 

数学之美第二章读后感

读完数学之美的第二章,我感受到了很多。

第二章的主要内容讲的时数学对人类在自然语言处理上的作用,让人类对自然语言的处理从规则到统计。人类对机器理解自然语言的认识走了一条大弯路。早期的研究集中采用基于规则的方法,虽然解决了一些简单的问题,但是无法从根本上将自然语言理解实用化。直 20 多年后,人们开始尝试用基于统计的方法进行自然语言处理,才有了突破性进展和实用的产品。这与我们计算机专业来说是息息相关的。

《数学之美》这本书告诉我们,数学是以什么方式定义了我们的世界,数学思维的魅力究竟在哪里。你可以不懂那么多复杂的公式,但如果你理解了数学背后的思维方式,有了感知和理解这个世界的能力,就会进入一个新境界。比如让计算机处理自然语言时,人们一开始想让计算机学会人类的语法,但后来发现,要写出来的语法不仅数量特别多,而且实际的识别率特别低。后来科学家们在数学统计学工具的帮助下,让计算机计算出句子的出现概率,通过概率的大小来判断正确与否,从而解决了这个难题。在让计算机进行新闻分类时,科学家们也是另辟蹊径,利用新闻中的特征词来构建向量,再使用余弦定理完成了对新闻的分类。数学让我们抓住做事的规律也就是“道”。

那数学为什么这么有用,归根到底是因为数学能帮助我们发现仅凭经验无法发现的规律,找到仅凭经验无法总结出来的办法。比如我们是在数学的帮助下,才发现了行星围绕恒星运转的真正规律。而我们每天使用的全拼输入法,也都是靠着数学的帮助,才让输入效率不断提高。

posted @ 2021-09-19 20:25  tZJQ  阅读(81)  评论(0编辑  收藏  举报