C++ 编译器如何扩展前缀和后缀运算符++()?
How does a C++ compiler expand prefix and postfix operator++()?
考虑:
class Example
{
private:
int m_i;
public:
Example(int i) : m_i{i} {}
//Post-fix
Example operator++(int) {m_i++; return *this;}
//Pre-fix
Example& operator++() {m_i++; return *this;}
void print() const {std::cout << m_i << '\n'}
};
我正在对此进行试验以确定编译器如何扩展对前缀和 post修复运算符的调用。
例如,当我这样写的时候:
Example e = 1;
e++;
我预计它会扩展到 "e.operator++(int)",或者更进一步,我预计
e++(2);
扩展为 "e.operator++(2)." 之类的东西,但我得到的是编译器抱怨一些 "no match for call to '(Example) (int)'"。
接下来我很好奇“++e”是如何神秘地扩展成 "e.operator++()",即 returns 引用的那个。
再玩一些,我得到了:
Example e = 1;
++e++;
e.print();
其中打印了2,然后:
Example e = 1;
(++e)++;
e.print();
其中打印了 3.
我明白 (++e) returns 对对象的引用,然后 post 递增 1,所以这是有道理的。我还怀疑“++e++”在这里给予 postfix 运算符优先级(正如我在另一个 post 中读到的),所以这是递增 postfix 返回的临时变量操作员。这也是有道理的。这让我想知道
这样的表达式如何
++++e
++e++++
++++e++
++++e++++
已扩展(它们全部编译并且 运行 具有预期结果)。
真的,内部到底发生了什么?编译器如何知道要调用哪个 operator++(),以及如何扩展这些表达式(尤其是在前缀的情况下)? "operator++(int)"中占位符变量的用途是什么?
What is the purpose of the placeholder variable in "operator++(int)"?
因为++
运算符有两个不同的功能:postfix-++和prefix-++。因此,重载它时,必须有两个不同的函数签名。
how does the compiler know which operator++() to call,
当您的代码使用前缀-++
(例如:++e;
)时,将调用具有签名operator++()
的函数。当您的代码使用 postfix-++
(例如:e++
;)时,将调用签名为 operator++(int)
的函数,并且编译器将提供一个未指定的虚拟参数值。
从技术上讲,operator++(int)
的实现可以使用虚拟参数值。您可以通过编写 e.operator++(5);
而不是 e++;
来传递您自己的值。但这会被认为是糟糕的编码风格——当重载运算符时,建议保留内置运算符的语义以避免混淆阅读代码的人。
请注意,您当前的 postfix-++
实现不遵守此规则:正常语义是应返回先前的值;但是您的代码 returns 更新后的值。
++e++++;
要解析此语句,您需要了解这些解析规则:
- 令牌由 "maximal munch" 解析,即这意味着
++ e ++ ++;
(而不是一些一元-+
运算符)。
- 语言语法根据这些标记确定哪些表达式是哪些运算符的操作数。这个过程可以概括为precedence table.
查询该表达式的 table 会告诉您:++(((e++)++))
。使用我之前提到的扩展,这可以写成函数调用符号:
((e.operator++(0)).operator++(0)).operator++();
在这种情况下,需要从左到右调用这些函数,因为在对其调用的表达式求值之前不能输入成员函数。
因此,假设我们在此语句之前有 Example e(1);
,则以下函数调用将按此顺序发生:
e.operator++(int)
- 将 e.m_i
设置为 2
和 returns 一个临时的(我将其称为 temp1
作为伪代码)与 temp1.m_i
作为 2
.
temp1.operator++(int)
- 将 temp1.m_i
设置为 3
,returns temp2
的 m.i
为 3
temp2.operator++()
- 将 temp2.m_i
设置为 4
和 returns 对 temp2
的引用。
注意。我的回答只谈到重载运算符是一个成员函数。也可以作为非成员重载 ++
(两种形式)。在那种情况下,行为将与我的描述相同,但 "written in function call notation" 表达式将采用不同的语法。
考虑:
class Example
{
private:
int m_i;
public:
Example(int i) : m_i{i} {}
//Post-fix
Example operator++(int) {m_i++; return *this;}
//Pre-fix
Example& operator++() {m_i++; return *this;}
void print() const {std::cout << m_i << '\n'}
};
我正在对此进行试验以确定编译器如何扩展对前缀和 post修复运算符的调用。
例如,当我这样写的时候:
Example e = 1;
e++;
我预计它会扩展到 "e.operator++(int)",或者更进一步,我预计
e++(2);
扩展为 "e.operator++(2)." 之类的东西,但我得到的是编译器抱怨一些 "no match for call to '(Example) (int)'"。
接下来我很好奇“++e”是如何神秘地扩展成 "e.operator++()",即 returns 引用的那个。
再玩一些,我得到了:
Example e = 1;
++e++;
e.print();
其中打印了2,然后:
Example e = 1;
(++e)++;
e.print();
其中打印了 3.
我明白 (++e) returns 对对象的引用,然后 post 递增 1,所以这是有道理的。我还怀疑“++e++”在这里给予 postfix 运算符优先级(正如我在另一个 post 中读到的),所以这是递增 postfix 返回的临时变量操作员。这也是有道理的。这让我想知道
这样的表达式如何++++e
++e++++
++++e++
++++e++++
已扩展(它们全部编译并且 运行 具有预期结果)。
真的,内部到底发生了什么?编译器如何知道要调用哪个 operator++(),以及如何扩展这些表达式(尤其是在前缀的情况下)? "operator++(int)"中占位符变量的用途是什么?
What is the purpose of the placeholder variable in "operator++(int)"?
因为++
运算符有两个不同的功能:postfix-++和prefix-++。因此,重载它时,必须有两个不同的函数签名。
how does the compiler know which operator++() to call,
当您的代码使用前缀-++
(例如:++e;
)时,将调用具有签名operator++()
的函数。当您的代码使用 postfix-++
(例如:e++
;)时,将调用签名为 operator++(int)
的函数,并且编译器将提供一个未指定的虚拟参数值。
从技术上讲,operator++(int)
的实现可以使用虚拟参数值。您可以通过编写 e.operator++(5);
而不是 e++;
来传递您自己的值。但这会被认为是糟糕的编码风格——当重载运算符时,建议保留内置运算符的语义以避免混淆阅读代码的人。
请注意,您当前的 postfix-++
实现不遵守此规则:正常语义是应返回先前的值;但是您的代码 returns 更新后的值。
++e++++;
要解析此语句,您需要了解这些解析规则:
- 令牌由 "maximal munch" 解析,即这意味着
++ e ++ ++;
(而不是一些一元-+
运算符)。 - 语言语法根据这些标记确定哪些表达式是哪些运算符的操作数。这个过程可以概括为precedence table.
查询该表达式的 table 会告诉您:++(((e++)++))
。使用我之前提到的扩展,这可以写成函数调用符号:
((e.operator++(0)).operator++(0)).operator++();
在这种情况下,需要从左到右调用这些函数,因为在对其调用的表达式求值之前不能输入成员函数。
因此,假设我们在此语句之前有 Example e(1);
,则以下函数调用将按此顺序发生:
e.operator++(int)
- 将e.m_i
设置为2
和 returns 一个临时的(我将其称为temp1
作为伪代码)与temp1.m_i
作为2
.temp1.operator++(int)
- 将temp1.m_i
设置为3
,returnstemp2
的m.i
为3
temp2.operator++()
- 将temp2.m_i
设置为4
和 returns 对temp2
的引用。
注意。我的回答只谈到重载运算符是一个成员函数。也可以作为非成员重载 ++
(两种形式)。在那种情况下,行为将与我的描述相同,但 "written in function call notation" 表达式将采用不同的语法。