注意:答案是按特定顺序给出的,但是由于许多用户根据投票而不是给出的时间对答案进行排序,所以这里是一个按最有意义的顺序排列的答案索引:
C++中运算符重载的一般语法
C++中运算符重载的三个基本规则
会员与非会员之间的决定
重载的常用运算符 赋值运算符 输入和输出运算符 函数调用运算符 比较运算符 算术运算符 数组下标运算符,用于指针类类型
赋值运算符
输入和输出运算符
函数调用运算符
比较运算符
算术运算符
数组下标
类指针类型的运算符
转换运算符
重载new和delete
(注意:这是 Stack Overflow's C++ FAQ 的一个条目。如果您想批评以这种形式提供常见问题解答的想法,那么 the posting on meta that started all this 将是这样做的地方。回答该问题在 C++ chatroom 中进行监控,FAQ 想法首先从此处开始,因此您的回答很可能会被提出该想法的人阅读。)
operator&
。
operator&()
然后期望结果类型与标准库一起工作的人应该被迫实现 std执行这个奇迹的lib。 IOW,如果您认为您有一个重载此运算符的应用程序,我很想听听。 (不过,不要屏住呼吸等待掌声。)
常用运算符重载
重载运算符的大部分工作是样板代码。这并不奇怪,因为运算符只是语法糖,它们的实际工作可以通过(并且通常被转发到)普通函数来完成。但重要的是你要正确地获得这个样板代码。如果你失败了,要么你的操作员的代码不能编译,要么你的用户代码不能编译,或者你的用户代码的行为会出人意料。
赋值运算符
关于任务有很多话要说。不过大部分已经在GMan's famous Copy-And-Swap FAQ中讲过了,这里就跳过大部分,只列出完美的赋值运算符供参考:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
位移运算符(用于流 I/O)
移位运算符 <<
和 >>
尽管仍然用于硬件接口以实现它们从 C 继承的位操作功能,但在大多数应用程序中作为重载流输入和输出运算符已变得更加普遍。有关作为位操作运算符的重载指南,请参阅下面有关二元算术运算符的部分。当您的对象与 iostreams 一起使用时,为了实现您自己的自定义格式和解析逻辑,请继续。
在最常见的重载运算符中,流运算符是二元中缀运算符,其语法对它们应该是成员还是非成员没有任何限制。由于它们改变了左参数(它们改变了流的状态),根据经验法则,它们应该被实现为左操作数类型的成员。但是,它们的左操作数是来自标准库的流,虽然标准库定义的大多数流输出和输入操作符确实定义为流类的成员,但是当您为自己的类型实现输出和输入操作时,您不能更改标准库的流类型。这就是为什么您需要为您自己的类型实现这些运算符作为非成员函数。两者的规范形式如下:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
实现 operator>>
时,仅当读取本身成功时才需要手动设置流的状态,但结果不是预期的。
函数调用运算符
用于创建函数对象的函数调用运算符,也称为函子,必须定义为 member 函数,因此它始终具有 member 的隐式 this
参数功能。除此之外,它可以被重载以获取任意数量的附加参数,包括零。
下面是一个语法示例:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
用法:
foo f;
int a = f("hello");
在整个 C++ 标准库中,函数对象总是被复制的。因此,您自己的函数对象复制起来应该很便宜。如果函数对象绝对需要使用复制成本高的数据,最好将该数据存储在其他地方并让函数对象引用它。
比较运算符
根据经验法则,二进制中缀比较运算符应实现为非成员函数1。一元前缀否定 !
应该(根据相同的规则)作为成员函数实现。 (但过载通常不是一个好主意。)
标准库的算法(例如 std::sort()
)和类型(例如 std::map
)总是只期望 operator<
存在。但是,您的类型的用户也会期望所有其他运算符都存在,因此如果您定义 operator<
,请务必遵循运算符重载的第三个基本规则,并定义所有其他布尔比较运算符。实现它们的规范方法是:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
这里要注意的重要一点是,这些运算符中只有两个实际上做任何事情,其他的只是将他们的论点转发给这两个中的任何一个来做实际的工作。
重载其余二进制布尔运算符(||
、&&
)的语法遵循比较运算符的规则。但是,非常您不太可能找到这些2 的合理用例。
1 正如所有经验法则一样,有时也可能有理由打破这一规则。如果是这样,不要忘记二进制比较运算符的左侧操作数(对于成员函数将是 *this
)也需要是 const
。因此,作为成员函数实现的比较运算符必须具有以下签名:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(注意末尾的 const
。)
2 需要注意的是,||
和&&
的内置版本使用的是快捷语义。而用户定义的(因为它们是方法调用的语法糖)不使用快捷语义。用户会期望这些运算符具有快捷语义,并且它们的代码可能依赖于它,因此强烈建议不要定义它们。
算术运算符
一元算术运算符
一元递增和递减运算符有前缀和后缀风格。为了区分另一个,后缀变体采用了一个额外的虚拟 int 参数。如果重载增量或减量,请确保始终实现前缀和后缀版本。这是增量的规范实现,减量遵循相同的规则:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
请注意,后缀变体是根据前缀实现的。还要注意 postfix 做了一个额外的 copy.2
重载一元减号和加号并不是很常见,最好避免。如果需要,它们可能应该作为成员函数重载。
2 还要注意,后缀变体的工作量更大,因此使用效率低于前缀变体。这是一个很好的理由,通常更喜欢前缀增量而不是后缀增量。虽然编译器通常可以优化内置类型的后缀增量的额外工作,但它们可能无法为用户定义的类型做同样的事情(这可能看起来像列表迭代器一样无辜)。一旦习惯了 i++
,当 i
不是内置类型时,就很难记住用 ++i
代替(而且在更改类型时必须更改代码),所以除非明确需要后缀,否则最好养成始终使用前缀增量的习惯。
二元算术运算符
对于二元算术运算符,不要忘记遵守运算符重载的第三条基本规则:如果提供 +
,也提供 +=
,如果提供 -
,则不要省略 -=
,等等。Andrew Koenig 是据说是第一个观察到复合赋值运算符可以用作非复合赋值运算符的基础。也就是说,运算符+
是根据+=
实现的,-
是根据-=
实现的,等等。
根据我们的经验法则,+
及其同伴应该是非成员,而它们的复合赋值对应物(+=
等)改变了他们的左参数,应该是成员。这是 +=
和 +
的示例代码;其他二元算术运算符应以相同的方式实现:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=
按引用返回其结果,而 operator+
返回其结果的副本。当然,返回引用通常比返回副本更有效,但在 operator+
的情况下,没有办法绕过复制。当您编写 a + b
时,您希望结果是一个新值,这就是 operator+
必须返回一个新值的原因。3 另请注意,operator+
采用其左操作数 通过复制而不是通过 const 引用。这样做的原因与 operator=
在每个副本中获取其参数的原因相同。
位操作运算符 ~
&
|
^
<<
>>
应该以与算术运算符相同的方式实现。然而,(除了重载 <<
和 >>
用于输出和输入)重载这些的合理用例很少。
3 同样,从中吸取的教训是 a += b
通常比 a + b
更有效,如果可能的话应该首选。
数组下标
数组下标运算符是二元运算符,必须作为类成员实现。它用于允许通过键访问其数据元素的类似容器的类型。提供这些的规范形式是这样的:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
除非您不希望您的类的用户能够更改 operator[]
返回的数据元素(在这种情况下,您可以省略非常量变体),否则您应该始终提供运算符的两种变体。
如果已知 value_type 引用内置类型,则运算符的 const 变体应该更好地返回副本而不是 const 引用:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
类指针类型的运算符
为了定义您自己的迭代器或智能指针,您必须重载一元前缀取消引用运算符 *
和二元中缀指针成员访问运算符 ->
:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
请注意,这些也几乎总是需要 const 和 non-const 版本。对于 ->
运算符,如果 value_type
是 class
(或 struct
或 union
)类型,则递归调用另一个 operator->()
,直到 operator->()
返回非类类型的值。
一元地址运算符永远不应该被重载。
对于 operator->*()
,请参阅 this question。它很少使用,因此很少超载。事实上,即使是迭代器也不会重载它。
C++中运算符重载的三个基本规则
当谈到 C++ 中的运算符重载时,您应该遵循三个基本规则。与所有此类规则一样,确实有例外。有时人们已经偏离了它们,结果不是糟糕的代码,但这种积极的偏差很少而且相差甚远。至少,我见过的 100 个这样的偏差中有 99 个是不合理的。但是,它也可能是 1000 中的 999。因此,您最好遵守以下规则。
每当一个运算符的含义不是很清楚和无可争议的时候,它就不应该被重载。相反,提供一个名称选择得当的函数。基本上,重载运算符的首要规则,其核心是:不要这样做。这可能看起来很奇怪,因为有很多关于运算符重载的知识,所以很多文章、书籍章节和其他文本都涉及到这一切。但是尽管有这些看似明显的证据,但只有极少数情况下运算符重载是合适的。原因是实际上很难理解运算符应用背后的语义,除非运算符在应用领域的使用是众所周知且无可争议的。与普遍的看法相反,这种情况几乎从未发生过。始终坚持运营商众所周知的语义。 C++ 对重载运算符的语义没有任何限制。您的编译器将愉快地接受实现二进制 + 运算符的代码以从其右操作数中减去。但是,这种运算符的用户永远不会怀疑表达式 a + b 会从 b 中减去 a。当然,这是假设算子在应用领域的语义是无可争议的。始终提供一组相关操作中的所有操作。运算符相互关联并与其他操作相关。如果您的类型支持 a + b,那么用户也希望能够调用 a += b。如果它支持前缀增量 ++a,他们会期望 a++ 也能正常工作。如果他们可以检查是否 a < b,他们肯定会期望也能够检查是否 a > b。如果他们可以复制构造您的类型,他们希望分配也能工作。
继续The Decision between Member and Non-member。
boost::spirit
大声笑。
+
进行字符串连接是一种违规行为,但它现在已经成为公认的实践,因此看起来很自然。虽然我确实记得我在 90 年代看到的一个自制字符串类,它为此目的使用二进制 &
(参考 BASIC 以获得已建立的实践)。但是,是的,将它放入标准库中基本上是一成不变的。对于 IO 滥用 <<
和 >>
也是如此,顺便说一句。为什么左移是明显的输出操作?因为当我们看到我们的第一个“Hello, world!”时,我们都知道了它。应用。而且没有其他原因。
operator==
的唯一绝对明显和无可争辩的事实是它应该是一个等价关系(IOW,你不应该使用非信号 NaN)。容器上有许多有用的等价关系。平等是什么意思? “a
等于 b
”表示 a
和 b
具有相同的数学值。 (非 NaN)float
的数学值的概念很清楚,但容器的数学值可以有许多不同的(类型递归)有用的定义。平等的最强定义是“它们是相同的对象”,它是无用的。
会员与非会员之间的决定
二元运算符 =
(赋值)、[]
(数组订阅)、->
(成员访问)以及 n 元 ()
(函数调用)运算符必须始终实现为 成员函数,因为语言的语法要求它们。
其他操作员可以作为成员或非成员来实现。然而,其中一些通常必须作为非成员函数来实现,因为您不能修改它们的左操作数。其中最突出的是输入和输出运算符 <<
和 >>
,它们的左操作数是标准库中的流类,您无法更改。
对于必须选择将它们实现为成员函数或非成员函数的所有运算符,请使用以下经验法则来决定:
如果是一元运算符,则将其实现为成员函数。如果二元运算符同等对待两个操作数(保持不变),则将此运算符实现为非成员函数。如果一个二元运算符没有平等地对待它的两个操作数(通常它会改变它的左操作数),如果它必须访问操作数的私有部分,让它成为它的左操作数类型的成员函数可能会很有用。
当然,正如所有经验法则一样,也有例外。如果你有一个类型
enum Month {Jan, Feb, ..., Nov, Dec}
并且您想为其重载递增和递减运算符,您不能将其作为成员函数执行,因为在 C++ 中,枚举类型不能具有成员函数。所以你必须将它作为一个自由函数重载。嵌套在类模板中的类模板的 operator<()
在类定义中作为内联成员函数完成时更容易编写和阅读。但这些确实是罕见的例外。
(但是,如果你做了一个例外,不要忘记操作数的 const
-ness 问题,对于成员函数,它成为隐式 this
参数。如果运算符作为非-member 函数将其最左边的参数作为 const
引用,与成员函数相同的运算符需要在末尾有一个 const
以使 *this
成为 const
引用。)
继续Common operators to overload。
operator+=()
不成为会员的想法。它必须改变它的左操作数,所以根据定义它必须深入挖掘它的内部。如果不成为会员,您将获得什么?
operator +=
和 append
方法编写一个 String 类。 append
方法更完整,因为您可以将参数的子字符串从索引 i 附加到索引 n -1: append(string, start, end)
让 +=
调用附加 start = 0
和 end = string.size
似乎是合乎逻辑的。此时,append 可以是成员方法,但 operator +=
不需要是成员,将其设为非成员会减少使用 String 内部的代码量,所以这是一件好事。 ... ^_^ ...
C++中运算符重载的一般语法
您不能更改 C++ 中内置类型的运算符的含义,运算符只能为用户定义的类型重载1。也就是说,至少有一个操作数必须是用户定义的类型。与其他重载函数一样,运算符只能为一组特定参数重载一次。
并非所有运算符都可以在 C++ 中重载。不能重载的运算符有:.
::
sizeof
typeid
.*
和 C++ 中唯一的三元运算符 ?:
在 C++ 中可以重载的运算符包括:
算术运算符:+ - * / % 和 += -= *= /= %=(所有二进制中缀); + - (一元前缀); ++ -- (一元前缀和后缀)
位操作: & | ^ << >> 和 &= |= ^= <<= >>= (所有二进制中缀); ~(一元前缀)
布尔代数:== != < > <= >= || && (所有二进制中缀); ! (一元前缀)
内存管理:new new[] delete delete[]
隐式转换运算符
miscellany: = [] -> ->* , (所有二进制中缀); * & (所有一元前缀) () (函数调用, n-ary 中缀)
然而,你可以超载所有这些并不意味着你应该这样做。请参阅运算符重载的基本规则。
在 C++ 中,运算符以 具有特殊名称的函数 的形式重载。与其他函数一样,重载运算符通常可以实现为其左操作数类型的成员函数或非成员函数。您是否可以自由选择或必须使用其中任何一个取决于几个条件。2 应用于对象 x 的一元运算符 @
3 被调用为operator@(x)
或 x.operator@()
。应用于对象 x
和 y
的二元中缀运算符 @
称为 operator@(x,y)
或 x.operator@(y)
。4
作为非成员函数实现的运算符有时是其操作数类型的朋友。
1 “用户定义”一词可能有点误导。 C++ 区分了内置类型和用户定义类型。前者属于例如 int、char 和 double;后者属于所有 struct、class、union 和 enum 类型,包括标准库中的那些,即使它们不是由用户定义的。
2 这在本常见问题解答的 a later part 中有所介绍。
3 @
在 C++ 中不是一个有效的运算符,这就是我使用它作为占位符的原因。
4 C++ 中唯一的三元运算符不能重载,唯一的 n 元运算符必须始终作为成员函数实现。
继续The Three Basic Rules of Operator Overloading in C++。
~
是一元前缀,而不是二元中缀。
.*
。
:)
operator+()
作为成员函数,但给它一个自由函数的签名。请参阅here。
转换运算符(也称为用户定义的转换)
在 C++ 中,您可以创建转换运算符,这些运算符允许编译器在您的类型和其他定义的类型之间进行转换。有两种类型的转换运算符,隐式和显式。
隐式转换运算符(C++98/C++03 和 C++11)
隐式转换运算符允许编译器将用户定义类型的值隐式转换(如 int
和 long
之间的转换)到某个其他类型。
下面是一个带有隐式转换运算符的简单类:
class my_string {
public:
operator const char*() const {return data_;} // This is the conversion operator
private:
const char* data_;
};
隐式转换运算符,如单参数构造函数,是用户定义的转换。当尝试匹配对重载函数的调用时,编译器将授予一个用户定义的转换。
void f(const char*);
my_string str;
f(str); // same as f( str.operator const char*() )
起初这似乎很有帮助,但这样做的问题是隐式转换甚至在不期望的时候启动。在以下代码中,将调用 void f(const char*)
,因为 my_string()
不是 lvalue,因此第一个不匹配:
void f(my_string&);
void f(const char*);
f(my_string());
初学者很容易出错,甚至有经验的 C++ 程序员有时也会感到惊讶,因为编译器选择了他们没有怀疑的重载。这些问题可以通过显式转换运算符来缓解。
显式转换运算符 (C++11)
与隐式转换运算符不同,显式转换运算符永远不会在您不希望它们出现时起作用。下面是一个带有显式转换运算符的简单类:
class my_string {
public:
explicit operator const char*() const {return data_;}
private:
const char* data_;
};
注意 explicit
。现在,当您尝试从隐式转换运算符执行意外代码时,您会收到编译器错误:
prog.cpp: In function ‘int main()’: prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’ prog.cpp:15:18: note: candidates are: prog.cpp:11:10: note: void f(my_string&) prog.cpp:11:10: note: no known conversion for argument 1 from ‘my_string’ to ‘my_string&’ prog.cpp:12:10: note: void f(const char*) prog.cpp:12:10: note: no known conversion for argument 1 from ‘my_string’ to ‘const char*’
要调用显式转换运算符,您必须使用 static_cast
、C 样式转换或构造函数样式转换(即 T(value)
)。
但是,有一个例外:允许编译器隐式转换为 bool
。另外,编译器在转换为 bool
后不允许再进行一次隐式转换(允许编译器一次进行 2 次隐式转换,但最多只能进行 1 次用户定义的转换)。
因为编译器不会强制转换“过去的”bool
,显式转换运算符现在不再需要 Safe Bool idiom。例如,C++11 之前的智能指针使用 Safe Bool 习惯用法来防止转换为整数类型。在 C++11 中,智能指针使用显式运算符,因为编译器在将类型显式转换为 bool 后不允许隐式转换为整数类型。
重载 new 和 delete 运算符
注意:这里只处理重载 new
和 delete
的语法,不处理与此类重载运算符的实现。我认为重载的语义 new
and delete
deserve their own FAQ,在运算符重载的主题中,我永远无法做到公正。
基本
在 C++ 中,当您编写像 new T(arg)
这样的 新表达式 时,会在计算此表达式时发生两件事:首先 operator new
被调用以获取原始内存,然后调用 T
的适当构造函数将这个原始内存转换为有效对象。同样,当您删除一个对象时,首先调用其析构函数,然后将内存返回给 operator delete
。
C++ 允许您调整这两个操作:内存管理和对象的构造/销毁分配的内存。后者是通过为类编写构造函数和析构函数来完成的。通过编写您自己的 operator new
和 operator delete
来微调内存管理。
运算符重载的第一条基本规则——不要这样做——尤其适用于重载 new
和 delete
。重载这些运算符的几乎唯一原因是性能问题和内存限制,在许多情况下,还有其他操作,就像更改所使用的算法,将提供比尝试调整内存管理更高的成本/收益比。
C++ 标准库带有一组预定义的 new
和 delete
运算符。最重要的是这些:
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void*) throw();
void* operator new[](std::size_t) throw(std::bad_alloc);
void operator delete[](void*) throw();
前两个为一个对象分配/释放内存,后两个为一个对象数组。如果您提供自己的版本,它们将不会重载,而是替换标准库中的版本。
如果您重载 operator new
,您应该始终即使您从未打算调用它,也会重载匹配的 operator delete
。原因是,如果构造函数在计算新表达式期间抛出异常,运行时系统会将内存返回到与 operator new
匹配的 operator delete
,该 operator delete
被调用以分配内存以在其中创建对象。如果您没有提供匹配的 operator delete
,调用默认的 operator delete
,这几乎总是错误的。
如果您重载 new
和 delete
,您也应该考虑重载数组变体。
新的展示位置
C++ 允许 new 和 delete 运算符采用额外的参数。所谓的placement new允许您在某个地址创建一个对象,该地址传递给:
class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{
X* p = new(buffer) X(/*...*/);
// ...
p->~X(); // call destructor
}
标准库为此提供了适当的 new 和 delete 运算符重载:
void* operator new(std::size_t,void* p) throw(std::bad_alloc);
void operator delete(void* p,void*) throw();
void* operator new[](std::size_t,void* p) throw(std::bad_alloc);
void operator delete[](void* p,void*) throw();
请注意,在上面给出的放置 new 示例代码中,永远不会调用 operator delete
,除非 X 的构造函数抛出异常。
您还可以使用其他参数重载 new
和 delete
。与放置新的附加参数一样,这些参数也列在关键字 new
之后的括号内。仅出于历史原因,此类变体通常也称为放置新,即使它们的论点不是将对象放置在特定地址。
特定于类的新建和删除
最常见的情况是,您需要微调内存管理,因为测量表明,特定类或一组相关类的实例经常被创建和销毁,并且运行时系统的默认内存管理已针对一般性能,在这种特定情况下处理效率低下。为了改善这一点,您可以为特定类重载 new 和 delete:
class my_class {
public:
// ...
void* operator new(std::size_t);
void operator delete(void*);
void* operator new[](std::size_t);
void operator delete[](void*);
// ...
};
因此重载后,new 和 delete 的行为类似于静态成员函数。对于 my_class
的对象,std::size_t
参数将始终为 sizeof(my_class)
。但是,对于派生类的动态分配对象也调用这些运算符,在这种情况下,它可能会更大。
全局新建和删除
要重载全局 new 和 delete,只需将标准库的预定义运算符替换为我们自己的。然而,这很少需要做。
nothrow
new 的人来说,这只是一种好的做法。
Why can't operator<<
function for streaming objects to std::cout
or to a file be a member function?
假设您有:
struct Foo
{
int a;
double b;
std::ostream& operator<<(std::ostream& out) const
{
return out << a << " " << b;
}
};
鉴于此,您不能使用:
Foo f = {10, 20.0};
std::cout << f;
由于 operator<<
作为 Foo
的成员函数被重载,因此运算符的 LHS 必须是 Foo
对象。这意味着,您将需要使用:
Foo f = {10, 20.0};
f << std::cout
这是非常不直观的。
如果将其定义为非成员函数,
struct Foo
{
int a;
double b;
};
std::ostream& operator<<(std::ostream& out, Foo const& f)
{
return out << f.a << " " << f.b;
}
您将能够使用:
Foo f = {10, 20.0};
std::cout << f;
这是非常直观的。
<<
以与 std::cout
一起使用的内容,但是重载 <<
以与同一类一起使用呢?在那种情况下,它可以是成员函数吗?
简而言之,我将参考一些要点,这些要点是我在过去一周学习 Python 和 C++、oops 和其他东西时遇到的,所以它如下所示:
运算符的 Arity 不能被进一步修改!重载的运算符只能有一个默认参数,而函数调用运算符则不能。只有内置运算符可以重载,rest 不能!
有关更多信息,您可以参考以下链接,该链接会将您重定向到 GeekforGeeks 提供的文档。
https://www.geeksforgeeks.org/g-fact-39/
operator->()
实际上非常很奇怪。不需要返回value_type*
- 事实上,它可以返回另一个类类型,前提是该类类型有一个operator->()
,随后会调用它。这种对operator->()
的递归调用会继续进行,直到出现value_type*
返回类型。疯狂! :)*=
定义*
,但这会很尴尬,因为*=
的第一个操作会创建一个新对象,这是计算的结果。然后,在 for-ijk 循环之后,我们将用*this
交换这个临时对象。 IE。 1.copy, 2.operator*, 3.swapT* const
在取消引用时返回const T&
,但事实并非如此。或者换句话说:一个 const 指针并不意味着一个 const 指针。事实上,模仿T const *
并非易事——这就是标准库中整个const_iterator
东西的原因。结论:签名应该是reference_type operator*() const; pointer_type operator->() const
operator<=>()
当这是最终的 (C++20)。