In this entry, I show you 4 tips that address frequently asked questions from C++ programmers of all levels of expertise.
在这篇文章里,我将和大家谈谈各种层次的C++程序员经常问及的四个问题.
It's surprising to discover how many experienced programmers are still unaware of the deprecation of the .h notation of standard header files,
我很惊讶地发现还有很多程序员没有意识到标准头文件扩展名.h的争议
the proper usage of namespaces,
命名空间的恰当用法
and the rules regarding binding of references to temporary objects, for example.
以及引用临时对象的规则。
These issues and others will be discussed here.
这些问题及其它将在这里进行讨论。
First, we start by explaining the difference between the deprecated “xxx.h” header names and the modern,
首先我们从解释受非议的“XXX.h”头文件名与现代、
standard-compliant “xxx” header-naming notation.
符合标准的“<XXX>”头文件名记号之间的区别开始。
Next, we explore a few dark corners of C++ which due to compilers' limitations and the somewhat recondite nature of the associated language rules tend to confuse many programmers,
接下来我们探索C++不为人知的角落,由于编译器的局限性和关联语言规则某些隐蔽的自然特性迷惑了许多程序员,
e.g., the notion of comma-separated expressions and the rules of binding references to rvalues(左右值).
比如逗号分隔表达式的意义与引用型变量的规则。
Finally, we will learn how to invoke a function prior to a program's startup.
最后我们将学习如何在程序启动前启动一个函数。
Tip 1: “iostream.h” or “iostream”?
语题1:“iostream.h” or “iostream”?
Many C++ programmers still use “iostream.h” instead of the newer, standard compliant “iostream” library.
很多C++程序员还在使用“iostream.h”代替新的符合标准的“iostream”库。
What are the differences between the two?
两者有什么区别呢?
First, the .h notation of standard header files was deprecated more than five years ago.
首先,标准头文件“.h”扩展名在五年前就倍受争议。
Using deprecated features in new code is never a good idea.
在新代码中使用有争议的(过时的)特性永远都不是一个好主意。
In terms of functionality, “iostream” contains a set of templatized I/O classes which support both narrow and wide characters, as opposed to “iostream.h” which only supports char-oriented streams.
从本质上看,“iostream”包括一系列支持窄字符与宽字符的模板化(templatized) I/O输入输出类, 而相反地,“iostream.h”只支持字符流。
Third, the C++ standard specification of iostream's interface was changed in many subtle aspects.
第三,iostream接口的标准C++规范在许多细节方面进行了变动。
Consequently, the interfaces and implementation of “iostream” differ from those of “iostream.h”.
因此,“iostream”的接口与实现同那些“iostream.h”是有区别的。
Finally, “iostream” components are declared in namespace std whereas “iostream.h” components are global.
最后,“iostream”是在std命名空间中定义的而“iostream.h”则是全局的。
Because of these substantial differences, you cannot mix the two libraries in one program.
由于这些本质方面的不同,不能在同一程序中混合使用这两种库。
As a rule, use “iostream” unless you're dealing with legacy code that is only compatible with “iostream.h”.
结论为除非在处理与“iostream”保持兼容的历史遗留代码时否则最好使用“iostream.h”。
Tip 2: Binding a Reference to an R-Value
话题2:将引用与右值绑定
(R-Value:右值,与“左值”相对。例如x=3中,“x”是一个“左值”,“3”是一个右值。从本质上讲,左值是一个内存的地址,右值是一个实际的二进制值。)
R-Values and L-Values are a fundamental concept of C++ programming.
右值和左值是C++编程的一个基本概念。
In essence, an R-Value is an expression that cannot appear on the left-hand side of an assignment expression.
本质上来讲右值是一个不可能出现在等号左边的表达式。
By contrast, an L-Value refers to an object (in its wider sense), or a chunk of memory, to which you can write a value.
相反,左值引用一个对象(广义范围上的),或者一块可读写的内存。
References can be bound to both R-Values and L-Values.
引用既可以指向右值也可以指向左值。
However, due to the language's restrictions regarding R-Values,
然而,由于语言在处理右值上的限制,
you have to be aware of the restrictions on binding references to R-Values, too.
你也得在将引用指向右值是慎重考虑。
Binding a reference to an R-Value is allowed as long as the reference is bound to a const type.
将引用与右值绑定像引用静态类型一样也是被允许的。
The rationale behind this rule is straightforward:
这条原则背后的原理是很显而易见的:
you can't change an R-Value, and only a reference to const ensures that the program doesn't modify an R-Value through its reference.
你无法改变右值,因为对静态类型的引用确保程序不会通过这个接口改变右值。
In the following example, the function f() takes a reference to const int:
下面的例子,f()函数包含一个对静态整型变量的引用。
void f(const int & i);
int main()
{
f(2); /* OK */
}
The program passes the R-Value 2 as an argument to f().
这段代码将右值“2”做为函数f()的一个参数。
At runtime, C++ creates a temporary object of type int with the value 2 and binds it to the reference i.
代码运行时,C++将创建一个值为2的临时整型变量并将其与引用类型i绑定。
The temporary and its reference exist from the moment f() is invoked until it returns;
这个临时对象与它的接口将在f()运行期间一直存到直到函数f返回。
they are destroyed immediately afterwards.
函数f返回后它们立即被释放。
Note that had we declared the reference i without the const qualifier,
注意我们没有将i声明为静态类型,
the function f() could have modified its argument,
是函数f仍有可能修改它的这个参数,
thereby causing undefined behavior.
但这将引起异常。
For this reason, you may only bind references to const objects.
因此最好是将引用与静态类型绑定。
The same rule applies to user-defined objects.
同样的规则适用于自定义类型。
You may bind a reference to a temporary object only if it's const:
只有一个临时对象为静态时才可以与引用类型绑定。
struct A{};
void f(const A& a);
Int main()
{
f(A()); /* OK, binding a temporary A to a const reference*/
}
Tip 3: Comma-Separated Expressions
话题3:逗号表达式
Comma-separated expressions were inherited from C.
逗号表达式是从C语言沿袭下来的。
It's likely that you use such expressions in for- and while-loops rather often.
它就像你经常使用的for循环与while-loop循环一样。
Yet, the language rules in this regard are far from being intuitive.
但是这里面的语法规则远不像看起来的那样。
First, let's see what a comma separated expression is.
首先,让我们看看什么是逗号表达式。
An expression may consist of one or more sub-expressions separated by commas. For example:
一个表达式可以被逗号分隔为一个或若干个子表达式。例如:
if(++x, --y, cin.good()) /*three expressions*/
The if condition contains three expressions separated by commas.
这条if语句被逗号分成为三个表达式。
C++ ensures that each of the expressions is evaluated and its side effects take place.
从C++的角度每个表达式都是合法的但是副作用产生了。
However, the value of an entire comma-separated expression is only the result of the rightmost expression.
整个逗号表达式的值是由最右边的表达式决定的。
Therefore, the if condition above evaluates as true only if cin.good() returns true. Here's another example of a comma expression:
于是只有con.good()的返回值是true时整个表达式的值才是true。这里有另一个关于逗号表达式的例子。
int j=10;
int i=0;
while( ++i, --j)
{
/*..repeat as long as j is not 0*/
}
Tip 4: Calling a Function Before Program's Startup
在程序启动前调用函数
Certain applications need to invoke startup functions that run before the main program starts.
有些应用需要在主程序启运前运行启动函数。
For example, polling, billing, and logger functions must be invoked before the actual program begins.
例如投票、支付和登录函数必须在实际种程序启动前运行。
The easiest way to achieve this is by calling these functions from a constructor of a global object.
一个最简单的实现方法就是在一个全局对象的构造函数里调用这些函数。
Because global objects are conceptually constructed before the program's outset, these functions will run before main() starts.
因为全局对象在程序的最开头被隐式的创建,这些函数就可以在main()函数之前得到运行。
For example:
例如:
class Logger
{
public:
Logger()
{
activate_log();
}
};
Logger log; /*global instance*/
int main()
{
record * prec=read_log();
//.. application code
}
The global object log is constructed before main() starts.
全局对象log在main()函数启动之前被创建。
During its construction, log invokes the function activate_log().
在它的构造函数里,log调用了active_log()函数。
Thus, when main() starts, it can read data from the log file.
于是,当main()函数启动时,它可以从日志文件中读取数据。