系列综述:
💞目的:本系列是个人整理为了秋招面试
的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于Google开源项目风格指南进行的,每个知识点的修正和深入主要参考各平台大佬的文章,其中也可能含有少量的个人实验自证。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
🌈【C++】秋招&实习面经汇总篇
0.扉页
译者前言
统一的编程风格
的作用- 更容易的接收其他代码贡献者的提交,从而避免给代码阅读者和其他代码提交者造成的困扰
- 更易于理解代码,方便人脑的模式匹配引擎推断各种标识符的含义
风格
的解释- 可读性:风格是可读性的体现,是指导C++编程和文件组织方式的约定
- 规则:风格是一种避免混乱的方式,规则一定要有权威性和说服力
1.头文件
- 头文件应该能够自给自足(self-contained)
- 解释:已有的头文件可以为项目提供完整的支持,不用再包含额外的头文件
- 例外:一个文件作为文本插入到代码某处。或者,文件内容实际上是其它头文件的特定平台(platform-specific)扩展部分。这些文件就要用
.inc
文件扩展名。
- 防止头文件的多重包含
- 命名格式:
<项目名>_<路径>_<文件名>_H_
- 要求:所有的头文件
// 为保证唯一性, 头文件的命名应为所在项目目录的全路径 // 例如, 项目 foo 中的头文件 foo/src/bar/baz.h 可按如下方式保护: #ifndef FOO_BAR_BAZ_H_ #define FOO_BAR_BAZ_H_ ... #endif // FOO_BAR_BAZ_H_
- 命名格式:
- 避免使用前置声明
- 正确方式:使用 #include 包含需要的头文件
- 解释:前置声明是类、函数和模板的纯粹声明,没伴随着其定义
- 内联函数的使用
- 函数体十行以内,性能关键的函数鼓励使用内联
- 内联一个大型代码。可能减弱了指令缓存优化,导致执行更慢
- 析构和构造函数有隐含的成员和操作,往往比看起来更长,不需要内联
- 虚函数和递归函数不需要内联
- #include 的路径及顺序
- #include的排序,不同
类型间
的头文件要插入空行
,类型内
的可以使用字母排序
// 本文件路径为google-awesome-project/src/foo/internal/Function.cpp // // 1.本文件所要实现的头文件 #include "foo/public/Function.h" // 优先位置 // 2.C 系统文件 #include <sys/types.h> #include <unistd.h> // 3.C++ 系统文件 #include <hash_map> #include <vector> // 4.其他库的 .h 文件 // 5.本项目内的 .h 文件 #include "base/basictypes.h" #include "base/commandlineflags.h" #include "foo/public/comment.inc"// 纯文本文件
- #include的排序,不同
2.作用域
- 鼓励在文件内使用命名空间
- 将全局作用域细分,防止全局作用域的命名冲突
- 在命名空间的最后注释出命名空间的名字
- 不要在命名空间 std 内声明任何东西
- 禁止用内联命名空间
// .h 文件 namespace mynamespace { // 所有声明都置于命名空间中 // 注意不要使用缩进 class MyClass { public: ... void Foo(); }; } // namespace mynamespace // .cpp文件 namespace mynamespace { // 函数定义都置于命名空间中 void MyClass::Foo() { ... } // 鼓励在.cpp文件中使用匿名空间 namespace { ... } // namespace } // namespace mynamespace
- 定义在同一编译单元的函数, 被其他编译单元直接调用可能会引入不必要的耦合和链接时依赖; 静态成员函数对此尤其敏感. 可以考虑提取到新类中, 或者将函数置于独立库的命名空间内
// 正确的 namespace myproject { namespace foo_bar { void Function1(); void Function2(); } // namespace foo_bar } // namespace myproject // 错误的 namespace myproject { class FooBar { public: static void Function1(); static void Function2(); }; } // namespace myproject
- 局部变量
- 尽量声明的同时初始化:局部变量应该作用域尽可能小,距离第一次使用尽可能近
- 基本变量类型可以作为局部变量:因为有寄存器优化,不会影响性能
- 在循环前声明循环中要用的局部变量:特别是类的对象,可以避免频繁的构造和析构
// 好——初始化时声明 int j = g(); // 坏——初始化和声明分离 int i; i = f(); // 好——声明时初始化 vector<int> v = {1, 2}; // 坏 vector<int> v; v.push_back(1); v.push_back(2); // 低效的实现 for (int i = 0; i < 1000000; ++i) { Foo f; // 构造函数和析构函数分别调用 1000000 次! f.DoSomething(i); } // 高效的实现 Foo f; // 构造函数和析构函数只调用 1 次 for (int i = 0; i < 1000000; ++i) { f.DoSomething(i); }
- 静态和全局变量
- 禁止使用类的静态变量:静态变量的构造函数、析构函数和初始化的顺序在 C++ 中是只有部分明确的,甚至随着构建变化而变化,导致难以发现的 bug
- 要使用尽量在同一编译单元内(self-contained)
- 多线程中的全局变量 (含静态成员变量) 不要使用 class 类型 (含 STL 容器), 避免不明确行为导致的 bug.
- 作用域的使用, 除了考虑名称污染, 可读性之外, 主要是为降低耦合, 提高编译/执行效率
- 尽量不用全局变量, 静态变量, 静态类成员变量, 以及函数静态变量,最好单独形成编译单元
3. 类
- 在构造函数中不能调用虚函数:调用是不会重定向到子类的虚函数实现
- 不要定义隐式类型转换
- 单参数构造函数尽量使用explicit 关键字就行修饰,避免隐式转换
// TODO:实力没达到,目前看不懂,待更新
7.命名约定
- 通用命名规则
- 少用缩写:尽可能使用通俗的描述性命名,别心疼空间。
- 可以使用广为人知的缩写:如 i 表示迭代变量和用 T 表示模板参数常见缩写表
int price_count_reader; // 无缩写
int num_errors; // "num" 是一个常见的写法
int num_dns_connections; // 人人都知道 "DNS" 是什么
int n; // 毫无意义.
int nerr; // 含糊不清的缩写.
int n_comp_conns; // 含糊不清的缩写.
int wgc_connections; // 只有贵团队知道是什么意思.
int pc_reader; // "pc" 有太多可能的解释了.
int cstmr_id; // 删减了若干字母.
- 文件命名
- 全部小写
- 使用
_
进行分割
// 示例 my_useful_class.cc http_server_logs.h// 比logs.h好
- 内联函数定义必须放在
.h
文件中。如果内联函数比较短,就直接将实现也放在.h
中。 - 类、结构体和函数(大驼峰法)
- 每个单词首字母都大写,但只有一个单词时不大写
- 缩写的单词, 更倾向于将它们视作一个单词进行首字母大写
// 类和结构体 class UrlTable { ... class UrlTableTester { ... struct UrlTableProperties { ... // 类型定义 typedef hash_map<UrlTableProperties *, string> PropertiesMap; // using 别名 using PropertiesMap = hash_map<UrlTableProperties *, string>; // 枚举 enum UrlTableErrors { ... // 函数 int count()// 只有一个单词时,不大写 void AddTableEntry() void DeleteUrl() void OpenFileOrDie() void StartRpc()// 不是使用StartRPC()
- 所有变量 (包括函数参数和成员变量) 和命名空间
- 一律小写, 单词之间用下划线连接
// 普通变量命名 string table_name; // 好 - 用下划线. string tablename; // 好 - 全小写. string tableName; // 差 - 混合大小写 // 类数据成员 class TableInfo { ... private: string table_name_; // 好 - 后加下划线. string tablename_; // 好. static Pool<TableInfo>* pool_; // 好. }; // 结构体内部的变量 struct UrlTableProperties { string name; int num_entries; static Pool<UrlTableProperties>* pool; }; // 命名空间 namespace web_search{ } web_search::index_util
- const声明的常量、枚举命名
- 在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合
// const声明的常量 const int kDaysInAWeek = 7;// 数学中的常量表示为k // 枚举命名,宏命名方式可能产生冲突 enum UrlTableErrors { kOK = 0, kErrorOutOfMemory, kErrorMalformedInput, };
- 函数命名
- 函数名的每个单词首字母大写
- 缩写的单词, 更倾向于将它们视作一个单词进行首字母大写
AddTableEntry() DeleteUrl() OpenFileOrDie() StartRpc()// 不是使用StartRPC()
- 宏
- 使用全大写,并使用下划线区分
#define ROUND(x) ... #define PI_ROUNDED 3.14
- 类内的私有数据成员
- 尾下划线的变量命名方式
class Test{
public:
int FunTest();
private:
int word_;// 下划线在开始位置是系统内部的变量
};
8.注释
- 注释一定要慷慨,因为需要给下一个阅读你代码的人看(下一个阅读的人大概率是你自己!6!)
- 要公布的项目文件头部应该加入版权公告
/* ** This file is part of LibreBoot progect.(项目归属) ** file_name.py - The core part of LibreBoot Progect (文件名和作用概述) ** Copyright (c) 2023-06-01 WangSen 3045672234@qq.com(项目完成时间和个人信息) ** Copyright (c) 2024 WangSen 3045672234@qq.com(在2024年你改了一点) ** All rights reserved. ** */
- 每个类的定义都要附带一份注释,描述类的功能和用法
// Function: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 { ... };
- 函数注释
声明处
的注释描述函数功能
- 函数的
输入
和输出
. - 对类成员函数而言: 函数调用期间对象是否需要保持引用参数, 是否会释放这些参数.
- 函数是否分配了必须由调用者释放的空间.
- 参数是否可以为空指针.
- 是否存在函数使用上的性能
隐患
- 如果函数是可重入的, 其同步前提是什么?
- 函数的
定义处
的注释描述函数实现
- 描述使用的特殊技巧和算法
// Returns an iterator for this table. It is the client's // responsibility to delete the iterator when it is done with it, // and it must not use the iterator once the GargantuanTable object // on which the iterator was created has been deleted. // // The iterator is initially positioned at the beginning of the table. // // This method is equivalent to: // Iterator* iter = table->NewIterator(); // iter->Seek(""); // return iter; // If you are going to immediately seek to another place in the // returned iterator, it will be faster to use NewIterator() // and avoid the extra seek. Iterator* GetIterator() const;
- 变量注释
- 通常
变量名本身
足以很好说明变量用途 类数据成员
和全局变量
通常要进行注释- 变量特殊值含义(nullptr、0、-1等)需要进行注释
private: // Used to bounds-check table accesses. -1 means // that we don't yet know how many entries the table has. int num_total_entries_;
- 通常
- 实现注释
- 对于代码中巧妙的, 晦涩的, 有趣的, 重要的地方加以注释
- 多行注释可以使用Tab进行对齐
- 不要描述显而易见的现象,最好描述是代码自身的描述
DoSomething(); // Comment here so the comments line up. DoSomethingElseThatIsLonger(); // Two spaces between the code and the comment. { // One space before comment when opening a new scope is allowed, // thus the comment lines up with the following comments and code. DoSomethingElse(); // Two spaces before line comments normally. } std::vector<string> list{ // Comments in braced lists describe the next element... "First item", // .. and should be aligned appropriately. "Second item"}; DoSomething(); /* For trailing block comments, one space is fine. */
- 不完美代码的注释
- 使用
TODO 注释
表示将来某一天做某事
// TODO(kl@gmail.com): Use a "*" here for concatenation operator. // TODO(Zeke) change this to use relations. // TODO(bug 12345): remove the "Last visitors" feature
- 使用
- 弃用注释
- 写上包含全大写的
// DEPRECATED
的注释, 以标记某接口为弃用状态. 注释可以放在接口声明前, 或者同一行.
- 写上包含全大写的
9.格式
- 每一行代码字符数不超过 80,因为80列宽是传统标准
- 尽量不使用非 ASCII 字符, 使用时必须使用 UTF-8 编码
- 制表符
- 使用两个空格缩进代替Tab
- 应该设置编辑器将制表符转为空格
- 函数声明与定义
- 函数形参用
, + 空格
进行区分 - 在
) {
直接加空格 - 形参放下,可以分行放置
- 左圆括号总是和函数名在同一行
- 函数名和左圆括号间永远没有空格
- 必须添加形参名
ReturnType LongClassName::ReallyReallyReallyLongFunctionName(// 没有空格 Type par_name1, // 4个空格 Type par_name2, Type par_name3) {// 注意空格 DoSomething(); // 函数体两个空格 ... }
- 函数形参用
- 匿名函数
int x = 0; auto add_to_x = [&x](int n) { x += n; };
- if表达式
if (condition) { // 圆括号里没有空格. ... // 2 空格缩进. } else if (...) { // else 与 if 的右括号同一行. ... } else { ... } // 只要其中一个分支用了大括号, 两个分支都要用上大括号. if (condition) { foo; } else { bar; } // 简短语句 if (x == kFoo) return new Foo();
- switch语句
switch (var) { case 0: { // 2 空格缩进 ... // 4 空格缩进 break; } case 1: { ... break; } default: { assert(false); } }
- 循环语句
while (condition) {// 圆括号前后都有空格 doing(); } for (int i = 0; i < kSomeNumber; ++i) {// 圆括号前后都有空格 doing(); }
- 指针和引用表达式
- 声明时空格前置,使用时没有空格
x = *p; p = &x; x = r.y; x = r->y; // 空格前置 char *c, *a; const string &str;
- 普通的双目运算符
- 加减乘除等两边都要加空格
- 初始化
int x = 3; int x(3); int x{3}; string name("Some Name"); string name = "Some Name"; string name{"Some Name"};
- 预处理指令位于缩进代码块中, 指令也应从行首开始
// 好 - 指令从行首开始 if (lopsided_score) { #if DISASTER_PENDING // 正确 - 从行首开始 DropEverything(); # if NOTIFY // 非必要 - # 后跟空格 NotifyClient(); # endif #endif BackToNormal(); }
- 类格式
// .h头文件 class MyClass : public OtherClass {// 注意空格 public: // 注意有一个空格的缩进 // 公有的构造函数和析构函数 MyClass(); // 标准的两空格缩进 explicit MyClass(int var);// 单参构造函数的explicit声明 ~MyClass() {} // 其他函数 void SomeFunction(); void SomeFunctionThatDoesNothing() { doing(); } void set_some_var(int var) { some_var_ = var; } int some_var() const { return some_var_; } protected: // public 放在最前面, 然后是 protected, 最后是 private private: // 函数部分 bool SomeInternalFunction(); // 数据部分:尾下划线法 int some_var_; int some_other_var_; }; // .cpp实现文件 // 如果不能放在同一行, // 必须置于冒号后, 并缩进 4 个空格 MyClass::MyClass(int var) : some_var_(var), some_other_var_(var + 1) { DoSomething(); } // 如果初始化列表需要置于多行, 将每一个成员放在单独的一行 // 并逐行对齐 MyClass::MyClass(int var) : some_var_(var), // 4 space indent some_other_var_(var + 1) { // lined up DoSomething(); } // 右大括号 } 可以和左大括号 { 放在同一行 // 如果这样做合适的话 MyClass::MyClass(int var) : some_var_(var) {}
- 命名空间不要增加额外的缩进层次
// 单命名空间 namespace { void foo() { // 正确. 命名空间内没有额外的缩进. ... } } // namespace // 嵌套命名空间 namespace foo { namespace bar {