[C++] 编程实践之1: Google的C++代码风格7:注释

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

你写的注释是给下一个需要理解你的代码的人看的。慷慨些吧,下一个人可能就是你!

###注释风格

使用//或者/**/,统一就好。

//或者/**/都可以,但是//更常用。要在如何注释以及注释风格上确保统一。

###文件注释

在每一个文件开头加入版权公告,然后是文件内容描述。

法律公告和作者信息:

  • 每个文件都应该包含以下项,依次是:
    • 版权声明(比如,Copyright 2008 Google Inc.)
    • 许可证。为项目选择合适的许可证版本(比如Apache 2.0, BSD, LGPL, GPL)
    • 作者:标识文件的原始作者
  • 如果你对原始作者的文件做了重大修改,将你的信息添加到作者信息里。这样当其他人对该文件有疑问时可以知道该联系谁。

文件内容:

  • 紧接着版权许可和作者信息之后,每个文件都要用注释描述文件内容。
  • 通常.h文件要对声明的类的功能和用法作简单说明。.cc文件通常包含了更多的实现细节或算法技巧讨论,如果你感觉这些实现细节或者算法技巧讨论对于理解.h文件有帮助,可以将该注释挪到.h中,并且在.cc中指出文档在.h中。
  • 不要简单的在.h和.cc间复制注释。这种偏离了注释的实际意义。

###类注释

每个类的定义都要附带一份注释,描述类的功能和用法。

// Iterates over the contents of a GargantuanTable.  Sample usage:
//    GargantuanTable_Iterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTable_Iterator {
    ...
};

如果你觉得已经在文件顶部详细描述了该类,像直接简单的上来一句“完整描述见文件顶部”也不打紧,但务必确保有这类注释。

如果类有任何同步前提,文档说明之。如果该类的实例可被多线程访问,要特别注意文档说明多线程环境下相关的规则和常量使用。

###函数注释

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

函数声明:

  • 注释位于声明之前,对函数功能及用法进行描述。注释使用叙述式(“Opens the file”)而非指令式(“Open the file”);注释只是为了描述函数,而不是命令函数做什么。通常,注释不会描述函数如何工作。那是函数定义部分的事情。
  • 函数声明处注释的内容:
    • 函数的输入输出。
    • 对类成员函数而言,函数调用期间对象是否需要保持引用参数,是否会释放这些参数。
    • 如果函数分配了空间,需要由调用者释放。
    • 参数是否可以被NULL。
    • 是否存在函数使用上的性能隐患。
    • 如果函数是可重入的,其同步的前提是什么。
// 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;
  • 上面给出了一个具体示例。但是也要避免啰啰嗦嗦,或做些显而易见的说明。
  • 注释构造/析构函数时,切记读代码的人知道构造/析构函数是干啥的,所以“destroys this object”这样的注释是没有意义的。注明构造函数对参数做了什么(例如,是否取得指针所有权)以及析构函数清理了什么。如果都是些无关紧要的内容,直接省掉注释。析构函数前没有注释是很正常的。

函数定义:

  • 每个函数定义要用注释说明函数功能和实现要点。比如说说你用的编程技巧,实现的大致步骤,或者解释如此实现的理由。为什么前半部分要加锁而后半部分不需要等等之类的。
  • 不要从.h文件或其他地方的函数声明处直接复制注释。简要重述函数功能是可以的,但注释重点要放在如何实现上。

###变量注释

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

类数据成员:

  • 每个类数据成员(也叫实例变量或者成员变量)都应该用注释说明用途。如果变量可以接受NULL或者-1等警戒值,需要加以说明。
private:
    // Keeps track of the total number of entries in the table.
    // Used to ensure we do not go over the limit. -1 means
    // that we don't yet know how many entries the table has.
    int num_total_entries_;

全局变量:

  • 和数据成员一样,所有全局变量也要注释说明含义以及用途。比如:
// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

###实现注释

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

代码前注释:

  • 巧妙或者复杂的代码前要加以注释,比如:
// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
    x = (x << 8) + (*result)[i];
    (*result)[i] = x >> 1;
    x &= 1;
}

行注释:

  • 比较晦涩的地方要在行尾加入注释。在行尾空两格进行注释,比如下面的代码示例。注意这里用了两段注释分别描述这段代码的作用,和提示函数返回时错误已经被记录入日志。
// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
    return;  // Error already logged.
  • 如果你需要连续进行多行注释,可以使之对齐以获得更好的可读性:
DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // Comment here so there are 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.
}
  • 向函数传入NULL,布尔值或者整数时,要注释说明含义,或使用常量让代码望文知义。例如对比:
// First version:
bool success = CalculateSomething(interesting_value,
                                  10,
                                  false,
                                  NULL);  // What are these arguments??
// Second version:
bool success = CalculateSomething(interesting_value,
                                  10,     // Default base value.
                                  false,  // Not the first time we're calling this.
// Third version:
const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
                                  kDefaultBaseValue,
                                  kFirstTimeCalling,
                                  null_callback);
                                  

不允许:

  • 注意永远不要用自然语言翻译代码作为注释。要假设读代码的人C++水平比你高,即使他/她可能不知道你的用意:
// Bad styles:
// 现在, 检查 b 数组并确保 i 是否存在,
// 下一个元素是 i+1.
...        // 天哪. 令人崩溃的注释.

###标点,拼写和语法

注意标点,拼写和语法;写的好的注释比差的要易读的多。

注释的通常写法是包含正确的大小写和结尾句号的完整语句。短一点的注释(如代码行尾注释)可以随意点,但依然要注意风格的一致性。完整的语句可读性更好,也可以说明该注释是完整的,而不是一些不成熟的想法。

虽然被别人指出该用分号时却用了逗号多少有些尴尬,但清晰易读的代码还是很重要的。正确的标点,拼写和语法对此会有帮助。

###TODO注释

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

TODO注释要使用全大写的字符串TODO,在随后的圆括弧里面写上你的大名,邮件地址,或其它身份标识。冒号是可选的。主要目的是让添加注释的人(也是可以请求提供更多细节的人)可以根据规范的TODO格式进行查找。添加注释并不意味着你要自己来修正。

// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

如果TODO是为了在“将来某一天做某事”,可以附上一个非常明确的时间“Fix by November 2005”或者一个明确的事项(例如“Remove this code when all clients can handle XML responses.”)。

###弃用注释

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

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

在DEPRECATED一词后,留下您的名字,邮箱地址以及括弧补充。

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

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

###总结

  1. 关于注释风格,很多C++的coders更喜欢行注释,C coders或许对块注释依然情有独钟,或者在文件头大段大段的注释时使用块注释。
  2. 文件注释可以炫耀你的成就,也是为了捅了篓子了别人可以找你。
  3. 注释要言简意赅,不要拖沓冗余,复杂的东西简单化和简单的东西复杂化都是要被鄙视的。
  4. 对于Chinese coders来说,用英文注释还是用中文注释,it is a problem,但不管怎样,注释时为了让别人看懂,难道是为了炫耀编程语言之外你的母语或者外语水平吗?
  5. 注释不要太乱,适当的缩进才会让人乐意看。但也没有必要规定注释从第几列开始(我自己写代码的时候总喜欢这样)UNIX/LINUX下还可以约定是使用tab还是space,个人倾向于space。
  6. TODO很不错,有时候,注释确实是为了标记一些未完成或者完成不尽如人意的地方,这样一搜索,就知道还有哪些活要干,日志都省了。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值