FAQ:Operator Overloading篇

文章目录

  • 1、What’s the deal with operator overloading? (运算符重载是怎么回事?)
  • 2、What are the benefits of operator overloading?(运算符重载有什么好处?)
  • 3、What are some examples of operator overloading? (有哪些运算符重载的例子?)
  • 4、But operator overloading makes my class look ugly; isn’t it supposed to make my code clearer? (但是运算符重载让我的类看起来很丑;它不是应该让我的代码更清晰吗?)
  • 5、What operators can/cannot be overloaded?(哪些操作符可以/不能重载?)
  • 6、Why can’t I overload . (dot), ::, sizeof, etc.?(为什么我不能重载. (dot), ::, sizeof等?)
  • 7、Can I define my own operators?(我可以定义自己的操作符吗?)
  • 8、Can I overload operator== so it lets me compare two char[] using a string comparison?(我可以重载operator==,让我使用字符串比较来比较两个char[]吗?)
  • 9、Can I create a operator** for “to-the-power-of” operations?(我可以为“幂”运算创建一个operator**吗?)
  • 10、The previous FAQs tell me which operators I can override; but which operators should I override?(前面的常见问题解答告诉我 可以 覆盖哪些操作符;但是我应该 重写哪些操作符呢?)
  • 11、What are some guidelines / “rules of thumb” for overloading operators?(重载操作符有什么指导方针/经验法则?)
  • 12、How do I create a subscript operator for a Matrix class?(我如何为一个Matrix类创建下标操作符?)
  • 13、Why shouldn’t my Matrix class’s interface look like an array-of-array?(为什么我的 Matrix 类的接口不能看起来像数组的数组?)
  • 14、I still don’t get it. Why shouldn’t my Matrix class’s interface look like an array-of-array?(我还是不明白。为什么我的Matrix类的接口不能看起来像数组的数组?)
  • 15、Should I design my classes from the outside (interfaces first) or from the inside (data first)?(我应该从外部(接口优先)还是从内部(数据优先)设计我的类?)
  • 16、How can I overload the prefix and postfix forms of operators ++ and --?(如何重载运算符++和--的前缀和后缀形式?)
  • 17、Which is more efficient: i++ or ++i?(i++和++i,哪个更高效?)

1、What’s the deal with operator overloading? (运算符重载是怎么回事?)

It allows you to provide an intuitive(直观的) interface to users of your class, plus makes it possible for templates to work equally well with classes and built-in/intrinsic(内在的,固有的) types.

Operator overloading allows C/C++ operators to have user-defined meanings on user-defined types (classes). Overloaded operators are syntactic sugar(语法糖) for function calls:

class Fred {
public:// ...
};#if 0// Without operator overloading:Fred add(const Fred& x, const Fred& y);Fred mul(const Fred& x, const Fred& y);Fred f(const Fred& a, const Fred& b, const Fred& c){return add(add(mul(a,b), mul(b,c)), mul(c,a));    // Yuk...}#else// With operator overloading:Fred operator+ (const Fred& x, const Fred& y);Fred operator* (const Fred& x, const Fred& y);Fred f(const Fred& a, const Fred& b, const Fred& c){return a*b + b*c + c*a;}#endif

2、What are the benefits of operator overloading?(运算符重载有什么好处?)

By overloading standard operators on a class, you can exploit(开发,利用) the intuition(直觉) of the users of that class. This lets users program in the language of the problem domain rather than in the language of the machine.

The ultimate goal is to reduce both the learning curve(曲线) and the defect(缺陷) rate.

3、What are some examples of operator overloading? (有哪些运算符重载的例子?)

Here are a few of the many examples of operator overloading:

  • myString + yourString might concatenate(连接) two std::string objects
  • myDate++ might increment(递增) a Date object
  • a * b might multiply two Number objects
  • a[i] might access an element of an Array object
  • x = *p might dereference a “smart pointer” that “points” to a disk record(磁盘记录) — it could seek to the location on disk where p “points” and return the appropriate record into x

4、But operator overloading makes my class look ugly; isn’t it supposed to make my code clearer? (但是运算符重载让我的类看起来很丑;它不是应该让我的代码更清晰吗?)

Operator overloading makes life easier for the users of a class, not for the developer of the class!

Consider the following example.

class Array {
public:int& operator[] (unsigned i);      // Some people don't like this syntax// ...
};inline
int& Array::operator[] (unsigned i)  // Some people don't like this syntax
{// ...
}

Some people don’t like the keyword operator or the somewhat funny syntax that goes with it in the body of the class itself. But the operator overloading syntax isn’t supposed to make life easier for the developer of a class. It’s supposed to make life easier for the users of the class:

int main()
{Array a;a[3] = 4;   // User code should be obvious and easy to understand...// ...
}

Remember: in a reuse-oriented(面向重用的) world, there will usually be many people who use your class, but there is only one person who builds it (yourself); therefore you should do things that favor the many rather than the few.

5、What operators can/cannot be overloaded?(哪些操作符可以/不能重载?)

Most can be overloaded. The only C operators that can’t be are . and ?: (and sizeof, which is technically an operator). C++ adds a few of its own operators, most of which can be overloaded except :: and .*.

大部分的运算符都能重载。C中不能被重载的运算符是 .?:(以及 sizeof,它在学术上是一个操作符)。C++新增了一些自己的操作符,除了 ::.* 之外都能重载。

Here’s an example of the subscript operator (it returns a reference). First without operator overloading:

下面是下标运算符的例子(它返回一个引用)。首先是没有运算符重载:

class Array {
public:int& elem(unsigned i)        { if (i > 99) error(); return data[i]; }
private:int data[100];
};int main()
{Array a;a.elem(10) = 42;a.elem(12) += a.elem(13);// ...
}

Now the same logic is presented with operator overloading:

同样的逻辑用操作符重载:

class Array {
public:int& operator[] (unsigned i) { if (i > 99) error(); return data[i]; }
private:int data[100];
};
int main()
{Array a;a[10] = 42;a[12] += a[13];// ...
}

6、Why can’t I overload . (dot), ::, sizeof, etc.?(为什么我不能重载. (dot), ::, sizeof等?)

Most operators can be overloaded by a programmer. The exceptions are

. (dot)  ::  ?:  sizeof

There is no fundamental(基本的) reason to disallow overloading of ?:. So far the committee(委员会) just hasn’t seen the need to introduce the special case of overloading a ternary(三元) operator. Note that a function overloading expr1 ? expr2 : expr3 would not be able to guarantee that only one of expr2 and expr3 was executed.

sizeof cannot be overloaded because built-in operations, such as incrementing a pointer into an array implicitly depends on it. Consider:

sizeof不能重载,因为内置操作(例如数组中指针递增)隐式地依赖于 sizeof。考虑:

X a[10];
X* p = &a[3];
X* q = &a[3];
p++;    // p points to a[4]// thus the integer value of p must be// sizeof(X) larger than the integer value of q

Thus, sizeof(X) could not be given a new and different meaning by the programmer without violating basic language rules.

因此,程序员不能在不违反基本语言规则的情况下赋予 sizeof(X) 一个新的不同的含义。

What about ::? In N::m neither N nor m are expressions with values; N and m are names known to the compiler and :: performs a (compile time) scope resolution rather than an expression evaluation. One could imagine allowing overloading of x::y where x is an object rather than a namespace or a class, but that would – contrary to first appearances – involve introducing new syntax (to allow expr::expr). It is not obvious what benefits such a complication would bring.

::呢?在 N::m 中,Nm 都不是带值的表达式;Nm 是编译器知道的名称,而 :: 执行的是(编译时)作用域解析,而不是表达式计算。你可以想象如果允许重载 x::y,其中 x 是对象而不是命名空间或类,但这将-与首次出现相反-涉及引入新的语法(以允许 expr::expr)。目前还不清楚这种复杂化会带来什么好处。

operator. (dot) could in principle be overloaded using the same technique as used for ->. However, doing so can lead to questions about whether an operation is meant for the object overloading . or an object referred to by … For example:

operator. 原则上可以使用与 -> 相同的技术进行重载。然而,这样做可能会导致一个问题,即一个操作是对象重载 . 还是通过. 引用对象。例如:

class Y {
public:void f();// ...
};class X {   // assume that you can overload .Y* p;Y& operator.() { return *p; }void f();// ...
};void g(X& x)
{x.f();  // X::f or Y::f or error?
}

This problem can be solved in several ways. So far in standardization, it has not been obvious which way would be best. For more details, see D&E.

7、Can I define my own operators?(我可以定义自己的操作符吗?)

Sorry, no. The possibility has been considered several times, but each time it was decided that the likely problems outweighed(超过) the likely benefits.

对不起,没有。这种可能性已经考虑了很多次,但每次都被认为可能出现的问题超过可能得到的好处。

It’s not a language-technical problem. Even when Stroustrup first considered it in 1983, he knew how it could be implemented. However, the experience has been that when we go beyond the most trivial(不重要的) examples people seem to have subtly(微妙地) different opinions of “the obvious” meaning of uses of an operator. A classical example is a**b**c. Assume that ** has been made to mean exponentiation([数] 取幂,求幂;乘方). Now should a**b**c mean (a**b)**c or a**(b**c)? Experts have thought the answer was obvious and their friends agreed – and then found that they didn’t agree on which resolution was the obvious one. Such problems seem prone(有做…倾向的) to lead to subtle bugs.

这不是一个语言技术问题。甚至当Stroustrup在1983年第一次考虑它时,他就知道如何实施它。然而,经验表明,当我们超越最琐碎的例子时,人们似乎对操作符“显而易见”的用法有微妙的不同看法。一个经典的例子是 a**b**c。假设 ** 是表示指数的幂值。那么 a**b**c 的意思是 (a**b)**c 还是 a**(b**c)?专家们认为答案是显而易见的,他们的朋友们也同意,但后来发现,他们对哪种解决方案是显而易见的意见不一。这样的问题似乎容易导致微妙的bug。

8、Can I overload operator== so it lets me compare two char[] using a string comparison?(我可以重载operator==,让我使用字符串比较来比较两个char[]吗?)

No: at least one operand of any overloaded operator must be of some user-defined type (most of the time that means a class).

不可以:任何重载的运算符的操作数至少有一个必须是用户定义的类型(意味着大多数情况下是类)。

But even if C++ allowed you to do this, which it doesn’t, you wouldn’t want to do it anyway since you really should be using a std::string-like class rather than an array of char in the first place since arrays are evil.

但是,即使C++允许你这样做(实际上C++不允许),你也不会想要这样做,因为你首先应该使用类似 std::string 的类,而不是字符数组,因为数组是邪恶的。

9、Can I create a operator** for “to-the-power-of” operations?(我可以为“幂”运算创建一个operator**吗?)

Nope.

不能。

The names of, precedence(优先级) of, associativity(结合性) of, and arity(参数数量) of operators is fixed by the language. There is no operator** in C++, so you cannot create one for a class type.

运算符的名称、优先级、结合性以及参数数量都是由语言确定的。C++中没有operator** ,所有你不能为类类型创建一个。

If you’re in doubt, consider that x ** y is the same as x * (*y) (in other words, the compiler assumes y is a pointer). Besides, operator overloading is just syntactic sugar for function calls. Although this particular syntactic sugar can be very sweet, it doesn’t add anything fundamental. I suggest you overload pow(base,exponent) (a double precision version is in <cmath>).

如果你有疑问,可以考虑 x ** y,它等同于 x * (*y) (换句话说,编译器假设 y 是一个指针)。此外,运算符重载只是函数调用的语法糖。尽管这种特殊的语法糖可能非常甜,但它没有添加任何基本的东西。我建议重载 pow(base,exponent) (双精度版本在 <cmath>中)。

By the way, operator^ can work for to-the-power-of, except it has the wrong precedence and associativity.

顺便说一下,operator^ 可以用于幂次运算,只是优先级和结合性不正确。

10、The previous FAQs tell me which operators I can override; but which operators should I override?(前面的常见问题解答告诉我 可以 覆盖哪些操作符;但是我应该 重写哪些操作符呢?)

Bottom line: don’t confuse your users.

底线:不要让你的用户感到困惑。

Remember the purpose of operator overloading: to reduce the cost and defect rate in code that uses your class. If you create operators that confuse your users (because they’re cool, because they make the code faster, because you need to prove to yourself that you can do it; doesn’t really matter why), you’ve violated the whole reason for using operator overloading in the first place.

记住运算符重载的目的:减少使用你的类代码的成本和缺陷率。如果你创建了让用户感到困惑的操作符(因为它们很酷,因为它们让代码更快,因为你需要证明自己可以做到;为什么不重要),你就违背了使用运算符重载的全部理由。

11、What are some guidelines / “rules of thumb” for overloading operators?(重载操作符有什么指导方针/经验法则?)

Here are a few guidelines / rules of thumb (but be sure to read the previous FAQ before reading this list):

  1. Use common sense. If your overloaded operator makes life easier and safer for your users, do it; otherwise don’t. This is the most important guideline. In fact it is, in a very real sense, the only guideline; the rest are just special cases. 【最重要的准则就是利用常识。】
  2. If you define arithmetic operators, maintain the usual arithmetic identities. For example, if your class defines x + y and x - y, then x + y - y ought to return an object that is behaviorally equivalent to x. The term behaviorally equivalent is defined in the bullet on x == y below, but simply put, it means the two objects should ideally act like they have the same state. This should be true even if you decide not to define an == operator for objects of your class.【如果定义了算术运算符,请维护通常的算术恒等式。例如,如果你的类定义了x + yx - y,那么 x + y - y 应该返回一个在行为上与 x 等价的对象。行为等价的术语定义在下面的 x == y 中,但简单地说,它意味着两个对象在理想情况下应该具有相同的状态。即使你决定不为类中的对象定义 == 运算符,也应该如此。】
  3. You should provide arithmetic operators only when they make logical sense to users. Subtracting two dates makes sense, logically returning the duration between those dates, so you might want to allow date1 - date2 for objects of your Date class (provided you have a reasonable class/type to represent the duration between two Date objects). However adding two dates makes no sense: what does it mean to add July 4, 1776 to June 5, 1959? Similarly it makes no sense to multiply or divide dates, so you should not define any of those operators. 【你应该只提供对用户有逻辑意义的算术运算符。两个日期相减是有意义的,逻辑上返回这两个日期之间的持续时间,因此你可能希望允许 Date 类的对象使用 date1 - date2(前提是你有一个合理的类/类型来表示两个日期对象之间的持续时间)。然而,两个日期相加是没有意义的:把1776年7月4日加到1959年6月5日是什么意思?类似地,日期的乘除运算也没有意义,因此不应该定义这些运算符。】
  4. You should provide mixed-mode arithmetic operators only when they make logical sense to users. For example, it makes sense to add a duration (say 35 days) to a date (say July 4, 1776), so you might define date + duration to return a Date. Similarly date - duration could also return a Date. But duration - date does not make sense at the conceptual level (what does it mean to subtract July 4, 1776 from 35 days?) so you should not define that operator. 【你应该只提供对用户有逻辑意义的混合模式算术运算符。例如,在日期(例如1776年7月4日)上加一个持续时间(例如35天)是有意义的,所以你可以定义 date + duration 来返回一个日期。类似地,date - duration 也可以返回日期。但是duration - date 在概念层面上没有意义(从35天中减去1776年7月4日是什么意思?)所以你不应该定义这个运算符。】
  5. If you provide constructive operators, they should return their result by value. For example, x + y should return its result by value. If it returns by reference, you will probably run into lots of problems figuring out who owns the referent and when the referent will get destructed. Doesn’t matter if returning by reference is more efficient; it is probably wrong. See the next bullet for more on this point.【如果提供了构造运算符,它们应该以值的形式返回结果。例如,x + y 应该通过值返回结果。如果它通过引用返回,你可能会遇到很多问题,要弄清楚谁拥有引用以及引用何时会被析构。通过引用返回是否更高效并不重要;它可能是错的。关于这一点,请参阅下一个要点。】
  6. If you provide constructive operators, they should not change their operands. For example, x + y should not change x. For some crazy reason, programmers often define x + y to be logically the same as x += y because the latter is faster. But remember, your users expect x + y to make a copy. In fact they selected the + operator (over, say, the += operator) precisely because they wanted a copy. If they wanted to modify x, they would have used whatever is equivalent to x += y instead. Don’t make semantic decisions for your users; it’s their decision, not yours, whether they want the semantics of x + y vs. x += y. Tell them that one is faster if you want, but then step back and let them make the final decision — they know what they’re trying to achieve and you do not. 【如果提供了构造操作符,它们不应该改变操作数。例如,x + y不应该改变 x。出于某种疯狂的原因,程序员通常将 x + y 定义为与 x += y 在逻辑上相同,因为后者更快。但请记住,你的用户希望 x + y 创建一个副本。事实上,他们选择 + 操作符(而不是 += 操作符)正是因为他们想要一个副本。如果要修改 x,则会使用与 x += y 相等的值。不要为你的用户做语义决策;他们想要 x + y 还是x += y 的语义,这是他们的决定,而不是你的。告诉他们如果你想要,其中一个更快,然后退后一步,让他们做出最终决定——他们知道他们想要实现什么,而你不知道。】
  7. If you provide constructive operators, they should allow promotion of the left-hand operand (at least in the case where the class has a single-parameter ctor that is not marked with the explicit keyword). For example, if your class Fraction supports promotion from int to Fraction (via the non-explicit ctor Fraction::Fraction(int)), and if you allow x - y for two Fraction objects, you should also allow 42 - y. In practice that simply means that your operator-() should not be a member function of Fraction. Typically you will make it a friend, if for no other reason than to force it into the public: part of the class, but even if it is not a friend, it should not be a member. 【如果提供了构造操作符,它们应该允许对左操作数进行提升(至少在类有一个没有 explicit 关键字标记的单参数构造函数的情况下是这样)。例如,如果你的类 Fraction 支持从 int 提升到 Fraction(通过非 explicit 的构造函数 Fraction::Fraction(int) ),并且如果你允许两个 Fraction 对象使用 x - y,那么你也应该允许 42 - y。在实践中,这仅仅意味着你的 operator-() 不应该是 Fraction 的成员函数。】
  8. In general, your operator should change its operand(s) if and only if the operands get changed when you apply the same operator to intrinsic types. x == y and x << y should not change either operand; x *= y and x <<= y should (but only the left-hand operand). 【一般来说,当且仅当将相同的操作符应用于内在类型时,操作数发生变化时,操作符应该更改其操作数。x == yx << y 不应该改变它们的操作数;x *= yx <<= y 应该(但只能是左边的操作数)改变。】
  9. If you define x++ and ++x, maintain the usual identities. For example, x++ and ++x should have the same observable effect on x, and should differ only in what they return. ++x should return x by reference; x++ should either return a copy (by value) of the original state of x or should have a void return-type. You’re usually better off returning a copy of the original state of x by value, especially if your class will be used in generic algorithms. The easy way to do that is to implement x++ using three lines: make a local copy of *this, call ++x (i.e., this->operator++()), then return the local copy. Similar comments for x-- and --x. 【如果您定义了 x++++x,请保持通常的标识。例如,x++++x 应该对 x 有相同的效果,只在返回值上有所不同。++x 应该通过引用返回 xx++ 要么返回 x 原始状态的副本(按值),要么返回 void 类型。通常最好通过值返回 x 的原始状态的副本,特别是当你的类将用于泛型算法时。要做到这一点,简单的方法是使用三行代码实现 x++:创建 *this 的本地副本,调用 ++x(即 this->operator++()),然后返回本地副本。x----x 类似】
  10. If you define ++x and x += 1, maintain the usual identities. For example, these expressions should have the same observable behavior, including the same result. Among other things, that means your += operator should return x by reference. Similar comments for --x and x -= 1. 【如果您定义了 ++xx += 1,请保持通常的标识。例如,这些表达式应该具有相同的行为,包括相同的结果。这意味着 += 运算符应该通过引用返回 x--xx -= 1 类似。】
  11. If you define *p and p[0] for pointer-like objects, maintain the usual identities. For example, these two expressions should have the same result and neither should change p. 【如果为类指针对象定义 *pp[0],则保持通常的标识。例如,这两个表达式应该有相同的结果,它们都不应该改变p。】
  12. If you define p[i] and *(p+i) for pointer-like objects, maintain the usual identities. For example, these two expressions should have the same result and neither should change p. Similar comments for p[-i] and *(p-i). 【如果为类指针对象定义 p[i]*(p+i),则保持通常的标识。例如,这两个表达式应该有相同的结果,并且都不应该改变 pp[-i]*(p-i) 同理。】
  13. Subscript operators generally come in pairs; see on const-overloading. 【下标操作符通常成对出现;请参阅const-overloading。】
  14. If you define x == y, then x == y should be true if and only if the two objects are behaviorally equivalent. In this bullet, the term “behaviorally equivalent” means the observable behavior of any operation or sequence of operations applied to x will be the same as when applied to y. The term “operation” means methods, friends, operators, or just about anything else you can do with these objects (except, of course, the address-of operator). You won’t always be able to achieve that goal, but you ought to get close, and you ought to document any variances (other than the address-of operator). 【如果定义x == y,那么仅当两个对象的行为相等时,x == y的值为true。在这里,术语 “行为相等” 意味着应用于 x 的任何操作或操作序列的可观察行为将与应用于y的相同。术语“操作”意味着方法、友元、操作符或其他任何你可以对这些对象进行的操作(当然,除了操作符的地址)。你或许不能总能实现这个目标,但应该很接近,而且应该记录所有的差异(除了操作符的地址)。】
  15. If you define x == y and x = y, maintain the usual identities. For example, after an assignment, the two objects should be equal. Even if you don’t define x == y, the two objects should be behaviorally equivalent (see above for the meaning of that phrase) after an assignment. 【如果你定义 x == yx = y,保持通常的恒等式。例如,在赋值之后,两个对象应该相等。即使你没有定义 x == y,在赋值之后,这两个对象在行为上应该是相等的(请参阅上面的短语的含义)。】
  16. If you define x == y and x != y, you should maintain the usual identities. For example, these expressions should return something convertible to bool, neither should change its operands, and x == y should have the same result as !(x != y), and vice versa. 【如果定义 x == yx != y ,应该保持通常的恒等式。例如,这些表达式应该返回可转换为 bool 的值,它们都不应该改变其操作数,x == y 的结果应该与 !(x != y) 相同,反之亦然。】
  17. If you define inequality operators like x <= y and x < y, you should maintain the usual identities. For example, if x < y and y < z are both true, then x < z should also be true, etc. Similar comments for x >= y and x > y. 【如果定义像 x <= yx < y 这样的不等式运算符,应该保持通常的恒等式。例如,如果 x < yy < z 都为真,那么 x < z 也应该为真,以此类推。对于 x >= yx > y也类似。】
  18. If you define inequality operators like x < y and x >= y, you should maintain the usual identities. For example, x < y should have the result as !(x >= y). You can’t always do that, but you should get close and you should document any variances. Similar comments for x > y and !(x <= y), etc. 【如果你定义不等式运算符,如 x < yx >= y,你应该保持通常的恒等式。例如,x < y 的结果应该是 !(x >= y)。你不能总是这样做,但你应该接近它,并记录所有差异。类似的还有 x > y!(x <= y)等。】
  19. Avoid overloading short-circuiting operators: x || y or x && y. The overloaded versions of these do not short-circuit — they evaluate both operands even if the left-hand operand “determines” the outcome, so that confuses users. 【避免重载短路运算符:x || yx && y。这些运算符的重载版本不会短路——即使左边的操作数“决定”了结果,它们也会计算两个操作数,这会让用户感到困惑。】
  20. Avoid overloading the comma operator: x, y. The overloaded comma operator does not have the same ordering properties that it has when it is not overloaded, and that confuses users. 【避免重载逗号操作符:x, y。重载的逗号操作符与未重载时没有相同的排序属性,这会让用户感到困惑。】
  21. Don’t overload an operator that is non-intuitive to your users. This is called the Doctrine of Least Surprise. For example, although C++ uses std::cout << x for printing, and although printing is technically called inserting, and although inserting sort of sounds like what happens when you push an element onto a stack, don’t overload myStack << x to push an element onto a stack. It might make sense when you’re really tired or otherwise mentally impaired, and a few of your friends might think it’s “kewl,” but just say No. 【不要重载对用户来说不直观的操作符。这就是所谓的“最少意外原则”。例如,尽管C++使用了 std::cout <<x 表示打印,尽管打印在技术上叫做插入,尽管插入听起来有点像你把元素压入栈时发生的事情,但不要重载 myStack << x 将一个元素压入栈。】
  22. Use common sense. If you don’t see “your” operator listed here, you can figure it out. Just remember the ultimate goals of operator overloading: to make life easier for your users, in particular to make their code cheaper to write and more obvious. 【利用常识。请记住:运算符重载的目的是让用户使用起来更方便,特别是让他们的代码编写成本更低,更清楚。】

Caveat: the list is not exhaustive. That means there are other entries that you might consider “missing.” I know.

警告:这个列表并不详尽。这意味着还有其他可能被认为是“缺失”的条目。我知道。

Caveat: the list contains guidelines, not hard and fast rules. That means almost all of the entries have exceptions, and most of those exceptions are not explicitly stated. I know.

警告:这个列表包含的是指导方针,而不是严格的规则。这意味着几乎所有的条目都有例外,而且大多数例外都没有明确声明。我知道。

Caveat: please don’t email me about the additions or exceptions. I’ve already spent way too much time on this particular answer.

警告:请不要通过电子邮件告诉我增加或例外。我已经在这个问题上花了太多时间了。

12、How do I create a subscript operator for a Matrix class?(我如何为一个Matrix类创建下标操作符?)

Use operator() rather than operator[].

使用 operator() 而不是 operator[]

When you have multiple subscripts, the cleanest way to do it is with operator() rather than with operator[]. The reason is that operator[] always takes exactly one parameter, but operator() can take any number of parameters (in the case of a rectangular matrix, two parameters are needed).

当有多个下标时,最简洁的方法是使用 operator() 而不是 operator[]。原因是 operator[] 总是只接受一个参数,而 operator() 可以接受任意多个参数(对于矩形矩阵,需要两个参数)。

例如:

class Matrix {
public:Matrix(unsigned rows, unsigned cols);double& operator() (unsigned row, unsigned col);        // Subscript operators often come in pairsdouble  operator() (unsigned row, unsigned col) const;  // Subscript operators often come in pairs// ...~Matrix();                              // DestructorMatrix(const Matrix& m);               // Copy constructorMatrix& operator= (const Matrix& m);   // Assignment operator// ...
private:unsigned rows_, cols_;double* data_;
};inline
Matrix::Matrix(unsigned rows, unsigned cols): rows_ (rows), cols_ (cols)//, data_ ← initialized below after the if...throw statement
{if (rows == 0 || cols == 0)throw BadIndex("Matrix constructor has 0 size");data_ = new double[rows * cols];
}inline
Matrix::~Matrix()
{delete[] data_;
}inline
double& Matrix::operator() (unsigned row, unsigned col)
{if (row >= rows_ || col >= cols_)throw BadIndex("Matrix subscript out of bounds");return data_[cols_*row + col];
}inline
double Matrix::operator() (unsigned row, unsigned col) const
{if (row >= rows_ || col >= cols_)throw BadIndex("const Matrix subscript out of bounds");return data_[cols_*row + col];
}

Then you can access an element of Matrix m using m(i,j) rather than m[i][j]:

然后通过 m(i,j) 而不是 m[i][j] 访问 Matrix m

int main()
{Matrix m(10,10);m(5,8) = 106.15;std::cout << m(5,8);// ...
}

更多关于使用 m(i,j)m[i][j] 的原因,请参阅下一个 FAQ。

13、Why shouldn’t my Matrix class’s interface look like an array-of-array?(为什么我的 Matrix 类的接口不能看起来像数组的数组?)

Here’s what this FAQ is really all about: Some people build a Matrix class that has an operator[] that returns a reference to an Array object (or perhaps to a raw array, shudder), and that Array object has an operator[] that returns an element of the Matrix (e.g., a reference to a double). Thus they access elements of the matrix using syntax like m[i][j] rather than syntax like m(i,j).

本FAQ解答的内容:有些人构建了一个 Matrix 类,其中有一个operator[] 函数,该函数返回 Array 对象引用(或可能是对原始数组的引用),而 Array 对象有一个返回 Matrix 元素(如一个对 double 的引用)的 operator[] 函数。因此,它们使用像 m[i][j] 这样的语法来访问 Matrix 中的元素,而不是m(i,j) 这样的语法。

The array-of-array solution obviously works, but it is less flexible than the operator() approach. Specifically, there are easy performance tuning tricks that can be done with the operator() approach that are more difficult in the [][] approach, and therefore the [][] approach is more likely to lead to bad performance, at least in some cases.

数组的数组的解决方案显然是可行的,但它不如 operator() 方法灵活。具体来说,使用 operator() 方法可以完成一些简单的性能调优技巧,这些技巧在 [][] 方法中比较困难,因此 [][] 方法更有可能导致糟糕的性能,至少在某些情况下是这样。

For example, the easiest way to implement the [][] approach is to use a physical layout of the matrix as a dense matrix that is stored in row-major form (or is it column-major; I can’t ever remember). In contrast, the operator() approach totally hides the physical layout of the matrix, and that can lead to better performance in some cases.

例如,实现 [][] 方法的最简单方法是使用矩阵的物理布局作为一个稠密矩阵,以行为主(或者列为主;我不记得了) 的形式存储。相比之下,operator() 方法完全隐藏了矩阵的物理布局,这在某些情况下可以带来更好的性能。

Put it this way: the operator() approach is never worse than, and sometimes better than, the [][] approach.

这么说吧,operator() 方法永远不会比 [][] 方法差,有时甚至会比 [][] 方法好。

  • The operator() approach is never worse because it is easy to implement the dense, row-major physical layout using the operator() approach, so when that configuration happens to be the optimal layout from a performance standpoint, the operator() approach is just as easy as the [][] approach (perhaps the operator() approach is a tiny bit easier, but I won’t quibble over minor nits).
    operator() 方法永远不会更差,因为它很容易实现稠密的、以行为主的物理布局,所以当配置恰巧是性能角度的最佳布局时,operator() 方法就像 [][] 方法一样简单(也许 operator() 方法更容易一些,但我不会吹毛求疵)。
  • The operator() approach is sometimes better because whenever the optimal layout for a given application happens to be something other than dense, row-major, the implementation is often significantly easier using the operator() approach compared to the [][] approach.
    operator() 方法有时更好,因为当给定应用程序的最佳布局碰巧不是密集的、以行为主的布局时,使用operator() 方法通常比使用 [][] 方法更容易实现。

As an example of when a physical layout makes a significant difference, a recent project happened to access the matrix elements in columns (that is, the algorithm accesses all the elements in one column, then the elements in another, etc.), and if the physical layout is row-major, the accesses can “stride the cache”. For example, if the rows happen to be almost as big as the processor’s cache size, the machine can end up with a “cache miss” for almost every element access. In this particular project, we got a 20% improvement in performance by changing the mapping from the logical layout (row,column) to the physical layout (column,row).

作为物理布局产生显著差异的一个例子,最近的一个项目碰巧访问了矩阵列中的元素(也就是说,算法访问了一列中的所有元素,然后是另一列中的元素,等等),如果物理布局是行为主的,访问可以“跨越缓存”。例如,如果数据行几乎和处理器的缓存大小一样大,那么计算机可能会以几乎每个元素访问都“缓存未命中”而告终。在这个特定的项目中,通过将逻辑布局(行,列)映射到物理布局(列,行),我们获得了20%的性能提升。

Of course there are many examples of this sort of thing from numerical methods, and sparse matrices are a whole other dimension on this issue. Since it is, in general, easier to implement a sparse matrix or swap row/column ordering using the operator() approach, the operator() approach loses nothing and may gain something — it has no down-side and a potential up-side.

当然,在数值方法中也有很多这样的例子,稀疏矩阵则是另一种情况。由于通常使用 operator() 方法更容易实现稀疏矩阵或交换行/列排序,因此 operator() 方法没有任何损失,可能会得到一些东西——它没有缺点,也有潜在的优点。

Use the operator() approach.

使用 operator() 方法!!

14、I still don’t get it. Why shouldn’t my Matrix class’s interface look like an array-of-array?(我还是不明白。为什么我的Matrix类的接口不能看起来像数组的数组?)

The same reasons you encapsulate your data structures, and the same reason you check parameters to make sure they are valid.

这和你封装数据结构的原因一样,也和你检查参数以确保它们有效的原因一样。

A few people use [][] despite its limitations, arguing that [][] is better because it is faster or because it uses C-syntax. The problem with the “it’s faster” argument is that it’s not — at least not on the latest version of two of the world’s best known C++ compilers. The problem with the “uses C-syntax” argument is that C++ is not C. Plus, oh yea, the C-syntax makes it harder to change the data structure and harder to check parameter values.

还有一些人使用 [][],尽管它有局限性,他们认为 [][] 更好,因为它更快,或者因为它使用了C语法。“它更快”的论点的问题是,它不是——至少在世界上最著名的两个C++编译器的最新版本上不是。“使用C语法”参数的问题在于C++不是C。哦,是的,C语法使更改数据结构和检查参数值变得更加困难。

The point of the previous two FAQs is that m(i,j) gives you a clean, simple way to check all the parameters and to hide (and therefore, if you want to, change) the internal data structure. The world already has way too many exposed data structures and way too many out-of-bounds parameters, and those cost way too much money and cause way too many delays and way too many defects.

前面两个常见问题的要点是,m(i,j) 为您提供了一种干净、简单的方法来检查所有参数并隐藏(因此,如果您想更改)内部数据结构。世界上已经有太多暴露的数据结构和太多越界的参数,这些花费了太多的钱,造成了太多的延迟和太多的缺陷。

Now everybody knows that you are different. You are clairvoyant with perfect knowledge of the future, and you know that no one will ever find any benefit from changing your matrix’s internal data structure. Plus you are a good programmer, unlike those slobs out there that occasionally pass wrong parameters, so you don’t need to worry about pesky little things like parameter checking. But even though you don’t need to worry about maintenance costs (no one ever needs to change your code), there might be one or two other programmers who aren’t quite perfect yet. For them, maintenance costs are high, defects are real, and requirements change. Believe it or not, every once in a while they need to (better sit down) change their code.

现在大家都知道你与众不同了。你拥有对未来完全了解的千里眼,你知道没有人会从改变你矩阵的内部数据结构中得到任何好处。另外,你是一个优秀的程序员,不像那些偶尔传递错误参数的笨蛋,所以你不需要担心像参数检查这样烦人的事情。但是,即使你不需要担心维护成本(没有人需要修改你的代码),也可能有一两个程序员还不够完美。对于他们来说,维护成本很高,缺陷是真实存在的,而且需求会发生变化。信不信由你,每隔一段时间他们就需要(最好坐下来)修改他们的代码。

Admittedly my tongue wath in my theek. But there was a point. The point was that encapsulation and parameter-checking are not crutches for the weak. It’s smart to use techniques that make encapsulation and/or parameter checking easy. The m(i,j) syntax is one of those techniques.

我承认我的舌头在我的脑袋里。但这是有意义的。关键是封装和参数检查不是弱者的拐杖。使用使封装和/或参数检查更容易的技术是明智的。m(i,j) 语法就是其中一种技术。

Having said all that, if you find yourself maintaining a billion-line app where the original team used m[i][j], or even if you are writing a brand new app and you just plain want to use m[i][j], you can still encapsulate the data structure and/or check all your parameters. It’s not even that hard. However it does require a level of sophistication that, like it or not, average C++ programmers fear. Fortunately you are not average, so read on.

话虽如此,如果你发现自己在维护一个原来团队使用 m[i][j] 的十亿级应用程序,或者即使你正在编写一个全新的应用程序,你只是想使用 m[i][j],你仍然可以封装数据结构和/或检查所有参数。这并不难。然而,它需要一定程度的复杂性,不管喜欢与否,一般c++程序员都害怕这种复杂性。幸运的是,你不是一般人,所以请继续阅读。

If you merely want to check parameters, just make sure the outer operator[] returns an object rather than a raw array, then that object’s operator[] can check its parameter in the usual way. Beware that this can slow down your program. In particular, if these inner array-like objects end up allocating their own block of memory for their row of the matrix, the performance overhead for creating / destroying your matrix objects can grow dramatically. The theoretical cost is still O(rows × cols), but in practice, the overhead of the memory allocator (new or malloc) can be much larger than anything else, and that overhead can swamp the other costs. For instance, on two of the world’s best known C++ compilers, the separate-allocation-per-row technique was 10x slower than the one-allocation-for-the-entire-matrix technique. 10% is one thing, 10x is another.

如果你只是想检查参数,只需要确保外部的 operator[] 返回一个对象而不是一个原始数组,那么该对象的 operator[] 就可以以通常的方式检查它的参数。注意,这可能会降低程序的速度。特别是,如果这些内部的类似数组的对象最终为它们的一行矩阵分配了自己的内存块,创建/销毁矩阵对象的性能开销会急剧增加。理论上的开销仍然是O(行数×列数),但在实践中,内存分配器(new或malloc)的开销可能比其他开销大得多,而且这种开销可能会超过其他开销。例如,在世界上最著名的两个c++编译器上,逐行单独分配内存的技术比为整个矩阵单独分配内存的技术慢10倍。10%是一回事,10x是另一回事。

If you want to check the parameters without the above overhead and/or if you want to encapsulate (and possibly change) the matrix’s internal data structure, follow these steps:

如果你想在没有上述开销的情况下检查参数,或者想封装(或者改变)矩阵的内部数据结构,请按照以下步骤操作:

  1. Add operator()(unsigned row, unsigned col) to the Matrix class.

  2. Create nested class Matrix::Row. It should have a ctor with parameters (Matrix& matrix, unsigned row), and it should store those two values in its this object.

  3. Change Matrix::operator[](unsigned row) so it returns an object of class Matrix::Row, e.g., { return Row(*this,row); }.

  4. Class Matrix::Row then defines its own operator[](unsigned col) which turns around and calls, you guessed it, Matrix::operator()(unsigned row, unsigned col). If the Matrix::Row data members are called Matrix& matrix_ and unsigned row_, the code for Matrix::Row::operator[](unsigned col) will be { return matrix_(row_, col); }

  5. Matrix 类添加 operator()(unsigned row, unsigned col)

  6. 创建嵌套类 Matrix::Row,它应该有一个参数为 (Matrix& matrix, unsigned row) 的构造函数,并且应该将这两个值存储在它的 this 对象中。

  7. 修改 Matrix::operator[](unsigned row),使其返回一个 Matrix::Row 的对象,例如 { return Row(*this,row); }

  8. 然后类 Matrix::Row 定义它自己的 operator[](unsigned col),它反过来调用 Matrix::operator()(unsigned row, unsigned col)。如果Matrix::Row 的数据成员是 Matrix& matrix_ unsigned row_,那么Matrix::Row::operator[](unsigned col) 的代码为 { return matrix_(row_, col); }

Next you will enable const overloading by repeating the above steps. You will create the const version of the various methods, and you will create a new nested class, probably called Matrix::ConstRow. Don’t forget to use const Matrix& instead of Matrix&.

接下来,重复上述步骤启用 const 重载。我们将创建这些方法的 const 版本,并创建一个新的嵌套类,可能名为 Matrix::ConstRow。不要忘记使用 const Matrix& 代替 Matrix&

If you have a decent compiler and if you judiciously use inlining, the compiler should optimize away the temporary objects. In other words, the operator[]-approach above will hopefully not be slower than what it would have been if you had directly called Matrix::operator()(unsigned row, unsigned col) in the first place. Of course you could have made your life simpler and avoided most of the above work by directly calling Matrix::operator()(unsigned row, unsigned col) in the first place. So you might as well directly call Matrix::operator()(unsigned row, unsigned col) in the first place.

如果你有一个不错的编译器,并且明智地使用内联,编译应该会优化掉临时对象。换言之,上面的 operator[] 方法应该不会比直接调用 Matrix::operator()(unsigned row, unsigned col) 慢。当然,你可以通过直接调用 Matrix::operator()(unsigned row, unsigned col) 来简化工作,避免上面的大部分工作。因此,你可以直接调用 Matrix::operator()(unsigned row, unsigned col)

15、Should I design my classes from the outside (interfaces first) or from the inside (data first)?(我应该从外部(接口优先)还是从内部(数据优先)设计我的类?)

From the outside!
从外部!

A good interface provides a simplified view that is expressed in the vocabulary of a user. In the case of OO software, the interface is normally the set of public methods of either a single class or a tight group of classes.

一个好的接口提供了用户词汇表中表达的简化视图。在面向对象软件的情况下,接口通常是单个类或一组紧密的类的公共方法。

First think about what the object logically represents, not how you intend to physically build it. For example, suppose you have a Stack class that will be built by containing a LinkedList:

首先考虑对象在逻辑上代表什么,而不是你打算如何在物理上构建它。例如,假设你有一个 Stack类,它包含一个 LinkedList

class Stack {
public:// ...
private:LinkedList list_;
};

Should the Stack have a get() method that returns the LinkedList? Or a set() method that takes a LinkedList? Or a constructor that takes a LinkedList? Obviously the answer is No, since you should design your interfaces from the outside-in. I.e., users of Stack objects don’t care about LinkedLists; they care about pushing and popping.

Stack类 应该有个返回 LinkedListget() 方法吗?或者有一个接收 LinkedList 参数的 set() 方法吗?或者接收LinkedList 的构造函数吗?明显答案是不需要。因为你应该从外向内设计你的接口。也就是说,使用Stack对象的人并不关心 LinkedList,他们只关心入栈和出栈。

Now for another example that is a bit more subtle. Suppose class LinkedList is built using a linked list of Node objects, where each Node object has a pointer to the next Node:

另一个更微妙的例子。假设类 LinkedList 是用一个 Node 类对象的链表创建的,其中每个 Node 对象都有一个指向下一个节点的指针:

class Node { /*...*/ };class LinkedList {
public:// ...
private:Node* first_;
};

Should the LinkedList class have a get() method that will let users access the first Node? Should the Node object have a get() method that will let users follow that Node to the next Node in the chain? In other words, what should a LinkedList look like from the outside? Is a LinkedList really a chain of Node objects? Or is that just an implementation detail? And if it is just an implementation detail, how will the LinkedList let users access each of the elements in the LinkedList one at a time?

LinkedList 类是否应该有一个 get() 方法,让用户能够访问第一个 NodeNode 对象是否应该有一个 get() 方法,让用户沿着节点找到链中的下一个节点?换言之,从外部看 LinkedList 应该是什么样子?LinkedList 真的是 Node 对象链吗?或者,这只是一个实现细节?如果是实现细节,LinkedList 如果让用户访问其中的每个元素呢?

The key insight is the realization that a LinkedList is not a chain of Nodes. That may be how it is built, but that is not what it is. What it is is a sequence of elements. Therefore the LinkedList abstraction should provide a LinkedListIterator class as well, and that LinkedListIterator might have an operator++ to go to the next element, and it might have a get()/set() pair to access its value stored in the Node (the value in the Node element is solely the responsibility of the LinkedList user, which is why there is a get()/set() pair that allows the user to freely manipulate that value).

关键要意识到 LinkedList 不是 Node 链。这可能是它的构造方式,但不是它的本质。它的本质是一个元素序列。因此,LinkedList 抽象类还应该提供一个 LinkedListIterator 类,而且 LinkedListIterator 类可以有一个operator++来访问下一个元素,还可以有一对 get()/set() 方法对来访问存储在节点中的值(Node元素中的值完全由 LinkedList 用户负责,所以有了 get()/set()方法对,用户可以自由操作这个值)。

Starting from the user’s perspective, we might want our LinkedList class to support operations that look similar to accessing an array using pointer arithmetic:

从用户角度出发,我们可能希望 LinkedList 类支持类似于使用指针访问数组的操作:

void userCode(LinkedList& a)
{for (LinkedListIterator p = a.begin(); p != a.end(); ++p)std::cout << *p << '\n';
}

To implement this interface, LinkedList will need a begin() method and an end() method. These return a LinkedListIterator object. The LinkedListIterator will need a method to go forward, ++p; a method to access the current element, *p; and a comparison operator, p != a.end().

要实现这个接口,LinkedList 需要一个 begin() 方法和一个 end() 方法。它们返回一个 LinkedListIterator 对象。 LinkedListIterator 需要一个前进的方法,++p;一个访问当前元素的方法,*p;以及一个比较运算符,p != a.end()

The code follows. The important thing to notice is that LinkedList does not have any methods that let users access Nodes. Nodes are an implementation technique that is completely buried. This makes the LinkedList class safer (no chance a user will mess up the invariants and linkages between the various nodes), easier to use (users don’t need to expend extra effort keeping the node-count equal to the actual number of nodes, or any other infrastructure stuff), and more flexible (by changing a single typedef, users could change their code from using LinkedList to some other list-like class and the bulk of their code would compile cleanly and hopefully with improved performance characteristics).

代码如下。需要注意的是,LinkedList 中没有任何方法允许用户访问NodeNode是一种完全隐藏的实现技术。这使得 LinkedList 类更安全(用户不会弄乱各种节点之间的不变量和链接)、更易于使用(用户不需要花费额外的精力来保持节点数等于实际节点数,或任何其他基础设施的东西)、更灵活(通过更改一个 typedef,用户可以将他们的代码从使用 LinkedList 类改为使用其他类似 list 的类,这样他们的大部分代码就可以编译干净,并且有望提高性能)。

#include <cassert>    // Poor man's exception handlingclass LinkedListIterator;
class LinkedList;class Node {// No public members; this is a "private class"friend class LinkedListIterator;   // A friend classfriend class LinkedList;Node* next_;int elem_;
};class LinkedListIterator {
public:bool operator== (LinkedListIterator i) const;bool operator!= (LinkedListIterator i) const;void operator++ ();   // Go to the next elementint& operator*  ();   // Access the current element
private:LinkedListIterator(Node* p);Node* p_;friend class LinkedList;  // so LinkedList can construct a LinkedListIterator
};class LinkedList {
public:void append(int elem);    // Adds elem after the endvoid prepend(int elem);   // Adds elem before the beginning// ...LinkedListIterator begin();LinkedListIterator end();// ...
private:Node* first_;
};

Here are the methods that are obviously inlinable (probably in the same header file):

以下是明显可内联的方法(可能在同一个头文件中):

inline bool LinkedListIterator::operator== (LinkedListIterator i) const
{return p_ == i.p_;
}inline bool LinkedListIterator::operator!= (LinkedListIterator i) const
{return p_ != i.p_;
}inline void LinkedListIterator::operator++()
{assert(p_ != NULL);  // or if (p_==NULL) throw ...p_ = p_->next_;
}inline int& LinkedListIterator::operator*()
{assert(p_ != NULL);  // or if (p_==NULL) throw ...return p_->elem_;
}inline LinkedListIterator::LinkedListIterator(Node* p): p_(p)
{ }inline LinkedListIterator LinkedList::begin()
{return first_;
}inline LinkedListIterator LinkedList::end()
{return NULL;
}

Conclusion: The linked list had two different kinds of data. The values of the elements stored in the linked list are the responsibility of the user of the linked list (and only the user; the linked list itself makes no attempt to prohibit users from changing the third element to 5), and the linked list’s infrastructure data (next pointers, etc.), whose values are the responsibility of the linked list (and only the linked list; e.g., the linked list does not let users change (or even look at!) the various next pointers).

结论:链表有两种不同类型的数据。存储在链表中的元素的值,由链表的用户负责(而且仅由用户负责;链表本身没有试图禁止用户将第3个元素改为5),以及链表的基础结构数据(next指针,等等),其值由链表负责(且仅链表本身负责;例如,链表不允许用户更改(甚至不允许查看)各种 next 指针。

Thus the only get()/set() methods were to get and set the elements of the linked list, but not the infrastructure of the linked list. Since the linked list hides the infrastructure pointers/etc., it is able to make very strong promises regarding that infrastructure (e.g., if it were a doubly linked list, it might guarantee that every forward pointer was matched by a backwards pointer from the next Node).

因此,唯一的get()/set()方法是获取和设置链表的元素,而不是链表的基础结构。因为链表隐藏了基础结构指针等,它能够对该基础结构做出非常强的承诺(例如,如果它是一个双向链表,它可能会保证每个向前指针都与下一个节点的向后指针匹配)。

So, we see here an example of where the values of some of a class’s data is the responsibility of users (in which case the class needs to have get()/set() methods for that data) but the data that the class wants to control does not necessarily have get()/set() methods.

因此,我们在这里看到一个示例,其中一些类的数据的值是用户的责任(在这种情况下,类需要对该数据具有get() / set() 方法),但类想控制的数据不一定具有 get() / set() 方法。

Note: the purpose of this example is not to show you how to write a linked-list class. In fact you should not “roll your own” linked-list class since you should use one of the “container classes” provided with your compiler. Ideally you’ll use one of the standard container classes such as the std::list<T> template.

注意:这个例子的目的不是向你展示如何编写链表类。事实上,你不应该“创建自己的”链表类,因为你应该使用编译器提供的“容器类”之一。理想情况下,您将使用 std::list<T> 模板等标准容器类之一。

16、How can I overload the prefix and postfix forms of operators ++ and ?(如何重载运算符++和–的前缀和后缀形式?)

Via a dummy parameter.

通过一个虚拟参数。

Since the prefix and postfix ++ operators can have two definitions, the C++ language gives us two different signatures. Both are called operator++(), but the prefix version takes no parameters and the postfix version takes a dummy int. (Although this discussion revolves around the ++ operator, the -- operator is completely symmetric, and all the rules and guidelines that apply to one also apply to the other.)

由于前缀 ++ 和后缀++运算符可以有两种定义,因此C++语言提供了两种不同的签名。它们都被称为operator++(),但前缀版本不接受形参,后缀版本接受一个虚拟int。(虽然这里的讨论围绕着++操作符展开,但--操作符是完全对称的,适用于一个操作符的所有规则和指导方针也适用于另一个操作符。)

class Number {
public:Number& operator++ ();    // prefix ++Number  operator++ (int); // postfix ++
};

Note the different return types: the prefix version returns by reference, the postfix version by value. If that’s not immediately obvious to you, it should be after you see the definitions (and after you remember that y = x++ and y = ++x set y to different things).

注意不同的返回类型:前缀版本通过引用返回,后缀版本通过值返回。如果这对你来说还不是很明显,那么在你看到定义之后(并且记住 y = x++y = ++xy 设置为不同的值之后),你就应该明白了。

Number& Number::operator++ ()
{// ...return *this;
}Number Number::operator++ (int)
{Number ans = *this;++(*this);  // or just call operator++()return ans;
}

The other option for the postfix version is to return nothing:

后缀版本的另一种写法是不返回任何东西:

class Number {
public:Number& operator++ ();void    operator++ (int);
};Number& Number::operator++ ()
{// ...return *this;
}void Number::operator++ (int)
{++(*this);  // or just call operator++()
}

However you must not make the postfix version return the this object by reference; you have been warned.

但是,不能让后缀版本通过引用返回 this 对象;我已经警告过你了。

Here’s how you use these operators:

使用这些运算符的方法:

Number x = /* ... */;
++x;  // calls Number::operator++(), i.e., calls x.operator++()
x++;  // calls Number::operator++(int), i.e., calls x.operator++(0)

Assuming the return types are not ‘void’, you can use them in larger expressions:

假设返回类型不是 ‘void’,可以在更大的表达式中使用它们:

Number x = /* ... */;
Number y = ++x;  // y will be the new value of x
Number z = x++;  // z will be the old value of x

17、Which is more efficient: i++ or ++i?(i++和++i,哪个更高效?)

++i is sometimes faster than, and is never slower than, i++.

++i 有时比 i++ 快,且绝不会比 i++ 慢。

For intrinsic types like int, it doesn’t matter: ++i and i++ are the same speed. For class types like iterators or the previous FAQ’s Number class, ++i very well might be faster than i++ since the latter might make a copy of the this object.

对于像 int 这样的内置类型来说,这并不重要:++ii++ 具有相同的速度。对于像迭代器或前面FAQ中的 Number 类这样的类型,++i 很可能比 i++ 更快,因为后者可能会复制 this 对象。

The overhead of i++, if it is there at all, won’t probably make any practical difference unless your app is CPU bound. For example, if your app spends most of its time waiting for someone to click a mouse, doing disk I/O, network I/O, or database queries, then it won’t hurt your performance to waste a few CPU cycles. However it’s just as easy to type ++i as i++, so why not use the former unless you actually need the old value of i.

i++ 的开销,如果存在的话,可能不会有任何实际影响,除非你的应用程序受CPU限制。例如,如果你的应用程序大部分时间都在等待用户点击鼠标,执行磁盘I/O、网络I/O或数据库查询,那么浪费一些CPU周期并不会影响性能。然而,输入 ++ii++ 一样容易,所以为什么不使用前者,除非你确实需要 i 的旧值。

So if you’re writing i++ as a statement rather than as part of a larger expression, why not just write ++i instead? You never lose anything, and you sometimes gain something. Old line C programmers are used to writing i++ instead of ++i. E.g., they’ll say, for (i = 0; i < 10; i++) … Since this uses i++ as a statement, not as a part of a larger expression, then you might want to use ++i instead. For symmetry, I personally advocate that style even when it doesn’t improve speed, e.g., for intrinsic types and for class types with postfix operators that return void.

所以,如果你将 i++ 写成一个语句,而不是一个更大表达式的一部分,为什么不直接写++i 呢?你不会失去任何东西,且会收获一些东西。老的C程序员习惯写 i++ 而不是 ++i,例如,他们会写 for (i = 0; i < 10; i++) … 由于这里使用 i++ 作为一个语句,而不是更大表达式的一部分,因此你可能希望使用 ++i。 出于对称性考虑,我个人支持这种风格,即使它不能提高速度,例如对内置类型和带有后缀操作符返回 void 的类类型。

Obviously when i++ appears as a part of a larger expression, that’s different: it’s being used because it’s the only logically correct solution, not because it’s an old habit you picked up while programming in C.

显然,当 i++ 作为更大的表达式的一部分出现时,情况就不同了:使用它是因为它是唯一逻辑上正确的解决方案,而不是因为它是你使用C编程时养成的旧习惯。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/237863.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github2023-12-22开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-22统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目2非开发语言项目2C项目1C项目1HTML项目1Dart项目1 Tailwind CSS&#xff1a;快速U…

main函数获取传入的参数

文章目录 代码获取到参数转整型 代码 #include <stdio.h> #include <stdlib.h> #include <getopt.h> #include <stdint.h>static uint8_t optstr[] "?:i:o:v:h:"; static struct option long_options[] {{"input", required_…

c# opencv 提取图片文字,如读取身份证号

在C#中使用OpenCV读取身份证号码并不是一个直接的任务&#xff0c;因为OpenCV主要是一个用于图像处理和计算机视觉的库&#xff0c;它并不直接支持文本识别功能。然而&#xff0c;你可以结合其他OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xf…

spark-thrift-server 报错 Wrong FS

文章目录 [toc]具体报错实际原因查看 hive 元数据修改 spark-thrift-server 配置修改 hive 元数据 具体报错 spark-thrift-server 执行删表语句&#xff0c;出现如下报错 Error: org.apache.hive.service.cli.HiveSQLException: Error running query: org.apache.spark.sql.Ana…

kibana-7.15.2 一分钟下载、安装、部署 linux

文章目录 一、下载安装部署 1. 下载2. 解压3. 修改配置 二、kibana 启动 2.1. 创建kibana 用户2.2. 赋予权限2.3. 切换用户2.4. kibana启动2.5. 监控服务2.6. 监控服务2.7. kibana停止2.8. 效果图 三、kibana 启动2 3.1. 浏览器访问3.2. 效果图 一、下载安装部署 https:…

HTML美化网页

使用CSS3美化的原因 用css美化页面文本,使页面漂亮、美观、吸引用户 可以更好的突出页面的主题内容,使用户第一眼可以看到页面主要内容 具有良好的用户体验 <span>标签 作用 能让某几个文字或者某个词语凸显出来 有效的传递页面信息用css美化页面文本&#xff0c;使页面漂…

面试题:JVM 对锁都进行了哪些优化?

文章目录 锁优化自旋锁和自适应自旋锁消除锁粗化逃逸分析方法逃逸线程逃逸通过逃逸分析&#xff0c;编译器对代码的优化 锁优化 jvm 在加锁的过程中&#xff0c;会采用自旋、自适应、锁消除、锁粗化等优化手段来提升代码执行效率。 自旋锁和自适应自旋 现在大多的处理器都是…

易点易动设备管理系统:解决企业设备管理难题的利器

在现代企业中&#xff0c;设备管理是一个至关重要的环节。无论是制造业、物流业还是服务业&#xff0c;设备的高效管理对于企业的运营和竞争力都至关重要。然而&#xff0c;许多企业在设备管理方面面临着各种挑战。为了解决这些难题&#xff0c;易点易动设备管理系统应运而生。…

C语言数组与指针的关系,使用指针访问数组元素方法

数组与指针 如果您阅读过上一章节“C语言数组返回值”中的内容&#xff0c;那么您是否会产生一个疑问&#xff0c;C语言的函数要返回一个数组&#xff0c;为什么要将函数的返回值类型指定为指针的类型&#xff1f;换句话说&#xff0c;C语言中数组和指针到底是什么关系呢&…

[vue]Echart使用手册

[vue]Echart使用手册 使用环境Echart的使用Echart所有组件和图表类型Echart 使用方法 使用环境 之前是在JQuery阶段使用Echart&#xff0c;直接引入Echart的js文件即可&#xff0c;现在是在vue中使用,不仅仅时echarts包&#xff0c;还需要安装vue-echarts&#xff1a; "…

Pycharm解释器的配置: System Intgerpreter 、Pipenv Environment、Virtualenv Environment

文章目录 前提1. 环境准备2. 了解虚拟环境 一、进入Interpreter设置页二、添加Interpreter1. 方式一2. 方式二 三、 System Interpreter四、 Pipenv Environment前提条件&#xff1a;详细步骤1&#xff09; 选择pipenv2&#xff09; 设置Base Interpreter3&#xff09; 设置Pip…

程序员福利:好用的第三方api接口

空号检测&#xff1a;通过手机号码查询其在网活跃度&#xff0c;返回包括空号、停机等状态。手机在网状态&#xff1a;支持传入三大运营商的号码&#xff0c;查询手机号在网状态&#xff0c;返回在网等多种状态。反欺诈&#xff08;羊毛盾&#xff09;&#xff1a;反机器欺诈&a…

计算机网络个人小结

不同层的数据报的名称 应用层: data TCP层: segment IP 层: packet MAC层: frame MTU vs MSS: MTU&#xff1a;一个网络包的最大长度&#xff0c;以太网中一般为 1500 字节。 https://www.xiaolincoding.com/network/1_base/how_os_deal_network_package.html#linux-%E7%BD%91…

Centos9(Stream)配置Let‘s Encrypt (免费https证书)

1. 安装snap&#xff0c;用来安装certbot&#xff1a; sudo dnf install epel-release sudo dnf upgrade sudo yum install snapd sudo systemctl enable --now snapd.socket sudo ln -s /var/lib/snapd/snap /snap snap install core snap refresh core 2. 安装 certbot命令…

Ubuntu搭建Nodejs服务器

转自&#xff1a;https://www.8kiz.cn/archives/3228.html 在Ubuntu上搭建Node.js服务器&#xff0c;按照以下步骤进行&#xff1a; 打开终端。 使用包管理器安装Node.js。可以使用以下命令安装Node.js&#xff1a; sudo apt update sudo apt install nodejs安装Node.js后&a…

基于Java SSM框架实现人事员工考勤签到请假管理系统项目【项目源码+论文说明】

基于java的SSM框架实现人事员工考勤签到请假管理系统演示 摘要 在高速发展的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;人们对人事管理系统越来越重视&#xff0…

css图片属性,图片自适应

CSS 图片属性指南&#xff1a;background-size 和 object-fit 在前端开发中&#xff0c;使用图片是非常常见的。为了让图片在网页中显示得更好&#xff0c;CSS 提供了多种属性来调整和控制图片的大小和布局。其中&#xff0c;background-size 和 object-fit 是两个常用的属性&a…

制作系统安装盘教程——烧录Windows原版镜像

前言 本次教程不经过WinPE工具进行安装Windows原版镜像&#xff0c;而是直接把系统镜像文件直接烧录进U盘&#xff0c;这样做的好处是不经过WinPE安装Win系统的过程&#xff0c;避免有些带木马病毒的WinPE在安装系统的过程把木马病毒带进系统&#xff0c;从而导致文件泄漏。 开…

【JavaWeb学习笔记】13 - JSP浏览器渲染技术

JSP 一、JSP引入 1.JSP现状 1.目前主流的技术是前后端分离(比如: Spring Boot Vue/React),我们会讲的.[看一下] 2. JSP技术使用在逐渐减少&#xff0c;但使用少和没有使用是两个意思&#xff0c;一些老项目和中小公司还在使用JSP&#xff0c;工作期间,你很有可能遇到JSP …

【设计模式-2.5】创建型——建造者模式

说明&#xff1a;本文介绍设计模式中&#xff0c;创建型设计模式中的最后一个&#xff0c;建造者模式&#xff1b; 入学报道 创建型模式&#xff0c;关注于对象的创建&#xff0c;建造者模式也不例外。假设现在有一个场景&#xff0c;高校开学&#xff0c;学生、教师、职工都…