《C++ Primer》1.3 注释简介

1.3 注释简介

注释通常用于概述算法,确定变量的用途,或者解释晦涩难懂的代码段。编译器会忽略注释,因此注释对程序的行为或性能不会有任何影响。当你修改代码时,不要忘记同时更新注释

因此,注释不是写给机器的,是写给程序员的。准确详细的注释是良好编程风格的体现。代码需要注意板式(缩进、空行等)也是同理。

C++中注释的种类

C++中有两种注释:单行注释界定符对注释

单行注释以双斜线(//)开始,以换行符结束。当前行双斜线右侧的所有内容都会被编译器忽略,这种注释可以包含任何文本,包括额外的双斜线。

实际上单行注释是C++的注释方法,传统的C语言是界定符对注释,现代的C编译器大部分会支持单行注释,某些古老的C编译器可能不支持单行注释。

另一种注释使用继承自C语言的两个界定符(/*和*/)。这种注释以/*开始,以*/结束,可以包含除*/外的任意内容,包括换行符(因而支持多行注释)。编译器将落在/*和*/之间的所有内容都当作注释。注释界定符可以跨越程序中的多行,但这并不是必须的

界定符对通常用于多行注释,而双斜线注释常用于半行和单行附注

注释界定符不能嵌套

界定符对形式的注释是以/*开始,以*/结束的。因此,一个注释不能嵌套在另一注释之内。

注释嵌套并没有什么实际意义。站在编译器设计者的角度,也不会去设计支持嵌套注释的编译器。

测试:

#include <iostream>

/*
 * 注释对/* */不能嵌套
 * "不能嵌套"几个字会被认为是源码,
 * 像剩余程序一样处理
 */

int main()
{
    return 0;
}

测试结果:

etc@ruc-etc:~/cpp/ch1/3$ g++ prog1.cpp -o prog1 -Wall -g
prog1.cpp:4:13: warning: "/*" within comment [-Wcomment]
...

其中…省略是由于非法的字符(汉字)引入的编译错误。第4行报出的warning是使用-Wcomment选项产生的警告。实际上,我们在编译时中使用-Wall选项就包含了-Wcomment,警告原因是在界定符对中出现了/*,这意味着在代码中很可能出现注释嵌套的情况。PS:在项目中,我们必须弄清楚每条warning产生的原因,并清除它们。

我们通常需要在调试期间注释掉一些代码。由于这些代码可能包含界定符对形式的注释,因此可能导致注释嵌套错误,因此最好的方式是用单行注释方式注释掉代码段的每一行

遵守编码规范可以提高编码质量、提高项目的可维护性。一般编码规范都会要求双/三目运算符与操作数之间要加空格。这不光能都提高代码的可读性,更重要的是可能会规避掉一些诡异的bug。这里举一个跟界定符注释相关的经典例子,cerror_1.cpp:

#include <iostream>

using namespace std;

int main()
{
    double dist, time, veloc, *time_ptr = &time;

    cin >> dist >> time;

    veloc = dist/*time_ptr;

    cout << "velocity = " << veloc << endl;

    return 0;
}

编译结果:

etc@ruc_etc:~/cpp/ch1/3$ g++ cerror_1.cpp -o cerror_1 -Wall -g
cerror_1.cpp:11:14: error: unterminated comment
cerror_1.cpp: In function ‘int main()’:
cerror_1.cpp:11:10: error: expected ‘;’ at end of input
cerror_1.cpp:7:29: warning: unused variable ‘time_ptr’ [-Wunused-variable]
cerror_1.cpp:11:10: error: expected ‘}’ at end of input

错误的原因是编译器将11行中的dist/*time_ptr中的除法运算符/和解引用运算*联合当做/*界定符注释了。又由于编译时错误通常具有传递性,导致一连报了好几条编译错误。将双目运算符及其左右操作数用空格隔开后,程序能够通过编译。

这里就牵扯到编译器的词法分析机制:

C/C++中,+、-、*、/和=等运算符都只有一个字符,我们称之为单字符符号。而/*和==等运算符,以及标识符(程序中的变量),关键字等,包含了多个字符,我们称之为多字符符号。当编译器读入了一个字符’/'后又跟了一个字符‘*’,那么此时编译器就必须做出判断:是将其作为两个符号分别对待,还是合起来作为一个符号对待?C/C++对这个问题的解决方案可以归结为一个很简单的规则:每一个符号应该包括尽可能多的字符。

K&R(Kernighan与Ritchie)对这个方法的表述如下:“如果编译器的输入流截止至某个字符之前都已经被分解为一个个符号,那么下一个符号将包括从该字符之后可能组成一个符号的最长字符串。”

这里所说的符号不光指运算符,而且包括标识符(即符号表所映射的符号)等。注意,除字符串与字符常量外,符号中间不能嵌有空白(空格符、制表符和换行符)。例如,==是单个符号,而= =则是两个符号。

为了加深对C/C++的词法分析机制的理解,这里举另一个经典的例子,在一些蛋疼的考试中会见到这样的写法,在项目中若非必要千万不要这么写!cerror_2.cpp:

#include <iostream>

using namespace std;

int main()
{
    int a = 3, b = 4;

    cout << a+++++b << endl;

    return 0;
}

编译结果:

etc@ruc_etc:~/cpp/ch1/3$ g++ cerror_2.cpp -o cerror_2 -Wall -g
cerror_2.cpp: In function ‘int main()’:
cerror_2.cpp:9:13: error: lvalue required as increment operand

原因是编译器将a+++++b解释成((a++)++)+b,其中a++的结果是一个值,而不是一个对象,自然不会存在内存空间的映射,因此不能作为左值。同理

3++;

也会报同样的编译错误,因为3只是一个值,一个常量,它不能作为左值。

因此,对于上述表达式,若使得其通过编译,有两种方法:1.在双目运算+与左右操作数a++和++b中间加空格;2.对左右操作数加括号,cerror_3.cpp:

#include <iostream>

using namespace std;

int main()
{
    int a = 3, b = 4;

    cout << a++ + ++b << endl;
    cout << (a++)+(++b) << endl;

    return 0;
}

测试结果:

etc@ruc_etc:~/cpp/ch1/3$ g++ cerror_3.cpp -o cerror_3 -Wall -g
etc@ruc_etc:~/cpp/ch1/3$ ./cerror_3 
8
10

对于上述两种方法,个人提倡用方法1。原因有二:第一,方法1书写规范、美观;第二,C/C++的运算符有优先级和结合性,++运算符的优先级比+运算符的优先级高,方法2把优先级高的运算用()括起来的做法与C/C++运算的设计初衷相悖。

个人习惯使用条件编译#if…#endif的方式去注释大段的代码。但是,如果不注意编码规范,还会出现如cerror_1.cpp一样的错误,cerror_4.cpp:

#include <iostream>

using namespace std;

int main()
{
    double dist, time, veloc, *time_ptr = &time;

    cin >> dist >> time;

#if 0
    veloc = dist/*time_ptr;
#else
    veloc = dist / *time_ptr;
#endif

    cout << "velocity = " << veloc << endl;

    return 0;
}

编译结果:

etc@ruc_etc:~/cpp/ch1/3$ g++ cerror_4.cpp -o cerror_4 -Wall -g
cerror_4.cpp:12:14: error: unterminated comment
cerror_4.cpp:11:0: error: unterminated #if
cerror_4.cpp: In function ‘int main()’:
cerror_4.cpp:7:21: warning: unused variable ‘veloc’ [-Wunused-variable]
cerror_4.cpp:7:29: warning: unused variable ‘time_ptr’ [-Wunused-variable]
cerror_4.cpp:9:21: error: expected ‘}’ at end of input

根据之前所说的纠正编译错误的逻辑,不难看出用#if 0并没有成功注释掉 veloc = dist/*time_ptr;。其原因是编译器先对注释进行处理,而后进行预处理(条件编译)。虽然两者都是文本处理,但是存在次序上的先后。

Tips:

  • 注释要清楚明了,不能有二义性。

  • 注释的目的是要解释为什么这么做,而不是说明正在做什么。

  • 要避免非必要的注释,要保证代码和注释应同步更新。

练习 1.7

略。

练习 1.8

测试1:

#include <iostream>

using namespace std;

int main()
{
    cout << "/*" << endl;

    return 0;
}

测试结果:

etc@ruc-etc:~/cpp/ch1/3$ g++ exercise1_8.cpp -o exercise1_8 -Wall -g
etc@ruc-etc:~/cpp/ch1/3$ ./exercise1_8 
/*

测试2:

#include <iostream>

using namespace std;

int main()
{
    cout << "*/" << endl;

    return 0;
}

测试结果:

etc@ruc-etc:~/cpp/ch1/3$ !g
g++ exercise1_8.cpp -o exercise1_8 -Wall -g
etc@ruc-etc:~/cpp/ch1/3$ !./
./exercise1_8 
*/

测试3:

#include <iostream>

using namespace std;

int main()
{
    cout << /* "*/" */ << endl;

    return 0;
}

测试结果:

etc@ruc-etc:~/cpp/ch1/3$ !g
g++ exercise1_8.cpp -o exercise1_8 -Wall -g
exercise1_8.cpp:7:16: warning: missing terminating " character [enabled by default]
exercise1_8.cpp:7:2: error: missing terminating " character
exercise1_8.cpp: In function ‘int main()’:
exercise1_8.cpp:9:2: error: expected primary-expression before ‘return’
exercise1_8.cpp:9:2: error: expected ‘;’ before ‘return’

修正为:

#include <iostream>

using namespace std;

int main()
{
    cout << /* "*/" */" << endl;

    return 0;
}

修正后测试结果:

etc@ruc-etc:~/cpp/ch1/3$ !g
g++ exercise1_8.cpp -o exercise1_8 -Wall -g
etc@ruc-etc:~/cpp/ch1/3$ !./
./exercise1_8 
 */

测试4:

#include <iostream>

using namespace std;

int main()
{
    cout << /* "*/" /* "/*" */ << endl;

    return 0;
}

测试结果:

etc@ruc-etc:~/cpp/ch1/3$ !g
g++ exercise1_8.cpp -o exercise1_8 -Wall -g
etc@ruc-etc:~/cpp/ch1/3$ !./
./exercise1_8 
 /* 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值