tips___代码规范
Posted lllaih
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tips___代码规范相关的知识,希望对你有一定的参考价值。
函数变量尽可能置于最小作用域内,并在变量声明时进行初始化
变量声明的位置最好离第一次使用的位置越近越好;应使用初始化的方式代替声明再赋值。
int x=0;
rather than
int x;
x=0;
//
vector<>int v={1,2};
rather than
vector<int> v;
v.pushback(1);
v.pushback(2);
//尽量使用花括号赋初值
属于 if ,while, 和 for的语句的变量,应当在这些语句中正常的声明,这样子这些变量的作用域就被限制在这些语句中了。
while(const char *p = strchr(str,‘/‘)) str = p + 1;//strchr()是查找字符串str首次出现‘/‘的位置
Warning
有一个例外, 如果变量是一个对象, 每次进入作用域都要调用其构造函数, 每次退出作用域都要调用其析构函数. 这会导致效率降低.
// 低效的实现
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);
}
2.编写简短函数
总述
我们倾向于编写简短, 凝练的函数.
说明
我们承认长函数有时是合理的, 因此并不硬性限制函数的长度. 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.
即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug. 使函数尽量简短, 以便于他人阅读和修改代码.
在处理代码时, 你可能会发现复杂的长函数. 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数.
3.引用参数
总述
所有按引用传递的参数必须加上 const
.
定义
在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval)
. 在 C++ 中, 函数还可以声明为引用参数: int foo(int &val)
.
优点
定义引用参数可以防止出现 (*pval)++
这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针.
缺点
容易引起误解, 因为引用在语法上是值变量却拥有指针的语义.
结论
函数参数列表中, 所有引用参数都必须是 const
:
void Foo(const string &in, string *out);
事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 const
引用, 输出参数为指针. 输入参数可以是 const
指针, 但决不能是非 const
的引用参数, 除非特殊要求, 比如 swap()
.
有时候, 在输入形参中用 const T*
指针比 const T&
更明智. 比如:
- 可能会传递空指针.
- 函数要把指针或对地址的引用赋值给输入形参.
总而言之, 大多时候输入形参往往是 const T&
. 若用 const T*
则说明输入另有处理. 所以若要使用 const T*
, 则应给出相应的理由, 否则会使得读者感到迷惑.
4.
变长数组和 alloca()
Tip
我们不允许使用变长数组和 alloca()
.
优点:
变长数组具有浑然天成的语法. 变长数组和alloca()
也都很高效.
缺点:
变长数组和alloca()
不是标准 C++ 的组成部分. 更重要的是, 它们根据数据大小动态分配堆栈内存, 会引起难以发现的内存越界 bugs: “在我的机器上运行的好好的, 发布后却莫名其妙的挂掉了”.
结论:
改用更安全的分配器(allocator),就像std::vector
或std::unique_ptr<T[]>
.
5.
类型转换
Tip
使用 C++ 的类型转换, 如 static_cast<>()
. 不要使用 int y = (int)x
或 int y = int(x)
等转换方式;
定义:
C++ 采用了有别于 C 的类型转换机制, 对转换操作进行归类.
优点:
C 语言的类型转换问题在于模棱两可的操作; 有时是在做强制转换 (如(int)3.5
), 有时是在做类型转换 (如(int)"hello"
). 另外, C++ 的类型转换在查找时更醒目.
缺点:
恶心的语法.
结论:
不要使用 C 风格类型转换. 而应该使用 C++ 风格.
- 用
static_cast
替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.- 用
const_cast
去掉const
限定符.- 用
reinterpret_cast
指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.至于
dynamic_cast
参见 6.8. 运行时类型识别.
int
i;
float
f = 166.71;
i =
static_cast
<
int
>(f);
流
Tip
只在记录日志时使用流.
定义:
流用来替代printf()
和scanf()
.
优点:
有了流, 在打印时不需要关心对象的类型. 不用担心格式化字符串与参数列表不匹配 (虽然在 gcc 中使用printf
也不存在这个问题). 流的构造和析构函数会自动打开和关闭对应的文件.
缺点:
流使得pread()
等功能函数很难执行. 如果不使用printf
风格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串%.*s
) 用流处理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而这一点对于软件国际化很有用.
结论:
不要使用流, 除非是日志接口需要. 使用
printf
之类的代替.使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.
拓展讨论:
对这一条规则存在一些争论, 这儿给出点深层次原因. 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致. 因此, 我们不希望用户来决定是使用流还是
printf + read/write
. 相反, 我们应该决定到底用哪一种方式. 把日志作为特例是因为日志是一个非常独特的应用, 还有一些是历史原因.流的支持者们主张流是不二之选, 但观点并不是那么清晰有力. 他们指出的流的每个优势也都是其劣势. 流最大的优势是在输出时不需要关心打印对象的类型. 这是一个亮点. 同时, 也是一个不足: 你很容易用错类型, 而编译器不会报警. 使用流时容易造成的这类错误:
cout << this; // 输出地址 cout << *this; // 输出值由于
<<
被重载, 编译器不会报错. 就因为这一点我们反对使用操作符重载.有人说
printf
的格式化丑陋不堪, 易读性差, 但流也好不到哪儿去. 看看下面两段代码吧, 实现相同的功能, 哪个更清晰?cerr << "Error connecting to ‘" << foo->bar()->hostname.first << ":" << foo->bar()->hostname.second << ": " << strerror(errno); fprintf(stderr, "Error connecting to ‘%s:%u: %s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errno));你可能会说, “把流封装一下就会比较好了”, 这儿可以, 其他地方呢? 而且不要忘了, 我们的目标是使语言更紧凑, 而不是添加一些别人需要学习的新装备.
每一种方式都是各有利弊, “没有最好, 只有更适合”. 简单性原则告诫我们必须从中选择其一, 最后大多数决定采用
printf + read/write
.
7.
通用命名规则
总述
函数命名, 变量命名, 文件命名要有描述性; 少用缩写.
说明
尽可能使用描述性的命名, 别心疼空间, 毕竟相比之下让代码易于新读者理解更重要. 不要用只有项目开发者能理解的缩写, 也不要通过砍掉几个字母来缩写单词.
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; // 删减了若干字母.
注意, 一些特定的广为人知的缩写是允许的, 例如用 i
表示迭代变量和用 T
表示模板参数.
模板参数的命名应当遵循对应的分类: 类型模板参数应当遵循 类型命名 的规则, 而非类型模板应当遵循 变量命名 的规则.
8.
变量命名
总述
变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾, 但结构体的就不用, 如: a_local_variable
, a_struct_data_member
, a_class_data_member_
.
说明
普通变量命名
举例:
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;
};
常量命名
总述
声明为 constexpr
或 const
的变量, 或在程序运行期间其值始终保持不变的, 命名时以 “k” 开头, 大小写混合. 例如:
const int kDaysInAWeek = 7;
说明
所有具有静态存储类型的变量 (例如静态变量或全局变量, 参见 存储类型) 都应当以此方式命名. 对于其他存储类型的变量, 如自动变量等, 这条规则是可选的. 如果不采用这条规则, 就按照一般的变量命名规则.
函数命名
总述
常规函数使用大小写混合, 取值和设值函数则要求与变量名匹配: MyExcitingFunction()
, MyExcitingMethod()
, my_exciting_member_variable()
, set_my_exciting_member_variable()
.
说明
一般来说, 函数名的每个单词首字母大写 (即 “驼峰变量名” 或 “帕斯卡变量名”), 没有下划线. 对于首字母缩写的单词, 更倾向于将它们视作一个单词进行首字母大写 (例如, 写作 StartRpc()
而非 StartRPC()
).
AddTableEntry()
DeleteUrl()
OpenFileOrDie()
(同样的命名规则同时适用于类作用域与命名空间作用域的常量, 因为它们是作为 API 的一部分暴露对外的, 因此应当让它们看起来像是一个函数, 因为在这时, 它们实际上是一个对象而非函数的这一事实对外不过是一个无关紧要的实现细节.)
取值和设值函数的命名与变量一致. 一般来说它们的名称与实际的成员变量对应, 但并不强制要求. 例如 int count()
与 void set_count(int count)
.
以上是关于tips___代码规范的主要内容,如果未能解决你的问题,请参考以下文章
[AndroidStudio]_[初级]_[配置自动完成的代码片段]
[AndroidStudio]_[初级]_[配置自动完成的代码片段]