因此,当按值传递参数时,值会被复制到函数的参数中,但它是如何工作的?参数是刚刚声明为常规变量并分配了作为参数传递的值吗?像这样:
int getx(int z, int x)
{
int a = z+x;
return a;
}
int main()
{
int q = 2;
int w = 55;
int xx = getx(w, 2);
return 0;
}
如果是这样,为什么要调用它来复制值?参数变量不是刚刚赋值变量x的值吗?什么被复制呢?
答案 0 :(得分:1)
简短有趣的回答: 您应该将变量视为装有玩具的盒子。 如果一个函数通过vaule获取参数并且你调用了该函数,那么你只是告诉函数你的盒子里有什么玩具。它拥有自己的玩具,就像你拥有的玩具(但不是你的玩具),玩它。所以你不关心它对功能内部玩具的作用,因为它不是你真正的玩具。当你通过引用传递时,你实际上是将玩具从你自己的盒子中提供给它,并且它与你的玩具一起玩而不是自己玩具。
更深入的回答:
这意味着当您在主要功能中调用int xx = getx(w, 2);
时
在getx函数内部,您将使用一块数据,这些数据与您传递的数据块具有相同的位。但它们不是同一块数据。这意味着z
只是您调用函数时w
中信息的副本。
假设您这样写getx
(其中z
按值传入)
int getx(int z, int x) {
int a = z + x;
z = z + 1;
return a;
}
在这种情况下,在main(getx(w, 2)
)内调用此函数后
w
的副本输入为55,w
输出为55
相反,如果你有这个:
int getx(int& z, int x) {
int a = z + x;
z = z + 1;
return a;
}
然后用你在主要的
中调用它int main()
{
int q = 2;
int w = 55;
int xx = getx(w, 2);
return 0;
}
在这种情况下,z
通过引用传入(请注意使用int&
代替int
)。这意味着,您没有使用w
数据的副本,实际上您将使用w
内的真实getx
数据。
所以在这种情况下,在你的主要功能中,在你致电getx(w, 2)
之后
w
(不是副本)的格式为55,因此w
显示为56
<强> P.S 即可。在我看来,通过引用传递通常是不好的做法。你只能通过阅读getx(w, 2)
告诉我w
的出现方式不同于它。
答案 1 :(得分:0)
为了理解函数如何使用它们的参数列表或者通过值或通过引用传递,您应该尝试做的事情之一是创建或模拟一个表,该表将代表执行的每行代码的堆栈帧对于每个新的代码块或范围,您将需要一个新的堆栈帧表。
查看这个简短的节目&amp;图:
<强>的main.cpp 强>
#include <iostream>
int someCalculationByValue( int a, int b ) {
a *= 2 + b;
return a;
}
int someCalculationByReference( int& a, int& b ) {
a *= 2 + b;
return a;
}
int main() {
int x = 3;
int y = 4;
std::cout << "Starting with x = " << x << " and y = " << y << std::endl;
int ansValue = someCalculationByValue( x, y );
std::cout << "After Calculation" << std::endl;
std::cout << "x = " << x << " y = " << y << std::endl;
std::cout << "ansValue = " << ansValue << std::endl << std::endl;
std::cout << "Starting with x = " << x << " and y = " << y << std::endl;
int ansReference = someCalculationByReference( x, y );
std::cout << "After Calculation" << std::endl;
std::cout << "x = " << x << " y = " << y << std::endl;
std::cout << "ansReference = " << ansReference << std::endl << std::endl;
return 0;
}
表 - 为了简单起见,我将跳过仅显示局部变量和用户定义函数的std::cout
调用。
// Stack Frame Staring With First line of execution in the main function
// Stack Frame Start - Visible Scope Resolution of Main
{x} - Type int : 4bytes on 32bit - Scope Visibility - main()
+--+--+--+--+
| | | | | // On 32bit system 4bytes of memory - each block is 1 byte
+--+--+--+--+
{y} - Type int : 4bytes on 32bit - Scope Visibility - main()
+--+--+--+--+
| | | | |
+--+--+--+--+
{ansValue} - Type int : 4bytes on 32bit - Scope Visibility - main()
+--+--+--+--+
| | | | |
+--+--+--+--+
{=} assignment or evaluation
{someCalculationByValue} - Function Call - New Scope - New Stack Frame
{return value} - Type int 4bytes on 32bit - Returns back to calling function
In this case it returns back to main and flows into assignment which then
gets stored into {ansValue}
+--+--+--+--+
| | | | | // Normally Has no name look up but is expected to be
+--+--+--+--+ // returned by this function when it goes out of scope
{a} - Type int - 4bytes on 32bit system - Parameter List - Visibility
+--+--+--+--+ is local to this function only - it copies the value
| | | | | that is already stored in the variable that was passed
+--+--+--+--+ to it or by direct value
{b} - Type int - 4bytes on 32bit system - Parameter List - Visibility
+--+--+--+--+ is local to this function only - it copies the value
| | | | | that is already stored in the variable that was passed
+--+--+--+--+ to it or by direct value
{a} an L-Value followed by compound assignment
{*=} a compound assignment followed by arithmetic operation or expression
R-Value {a} = {a} * (2 + {b})
{return} Return statement - return back to caller in this case main() which
flows into the previous assignment in main that stores this return
value in {ansValue}
// Scope Resolution is now back in main()
{ansReference} - Type int : 4bytes on 32bit - Scope Visilbity - main()
+--+--+--+--+
| | | | |
+--+--+--+--+
{=} assignment or evaluation
{someCalculationByReference} - Function Call - New Scope - New Stack Frame
{return value} - Type int 4bytes on 32bit - Returns back to calling function
In this case it returns back to main and flows into assignment which then
gets stored into {ansReference}
+--+--+--+--+
| | | | | // Normally Has no name look up but is expected to be
+--+--+--+--+ // returned by this function when it goes out of scope
// No Local Variables Declared - Uses the actual variables that are passed
// in by the caller as this does substitution from its declarative variables
{a} - the actual variable passed in followed by compound assignment
{*=} followed by arithmetic operation or expression {a} = {a} * (2 + {b})
However since this is by reference the direct use of main's variables
are used so this then becomes: {x} = {x} * (2 + {y})
{return} - back to caller in this case main() which flows into the previous
assignment in main that stores this return value in {ansReference}
// Scope Resolution is now back in main()
现在让我们执行实际的函数调用,了解编译器在每个函数调用的引擎下做了什么。
<强> someCalculationByValue()强>
x & y are passed by value from main's local scope variables
x has value of 3
y has value of 4
// Since passing by value
a is assigned a value of what x has which is 3
b is assigned a value of what y has which is 4
The arithmetic compound assignment and expression with substitution
{a(3)} *= 2 + {b(4)}
{a(3)} = {a(3)} * (2 + {b(4)})
{a} = (18)
return {a(18)} -> 18 is returned back and saved into main's {ansValue}
在计算后的主函数中我们打印x&amp;你到控制台 x的值仍为3,y的值为4;没有任何改变与主要的 x&amp; y值。
<强> someCalculationByReference()强>
x & y are passed by reference from main's local scope variables
x has value of 3
y has value of 4
// Since passing by reference
a is replaced with x that has a value of 3
b is replaced with y that has a value of 4
The arithmetic compound assignment and expression with substitution
Since by reference this function has no local variables of (a & b) it
uses direct substitution of the variables that are passed in by its caller:
in this case; the main function.
{x(3)} *= 2 + {y(4)}
{x(3)} = {x(3)} * (2 + {y(4)})
{x} = (18)
return {x(18)} -> 18 is returned back and saved into main's {ansReference}
这次我们打印主要的本地堆栈变量x&amp;但这一次 x不再是3,它现在18与返回值相同,因为它在这个函数中被修改为引用,同样会发生在y上,因为它也是一个引用但我们没有在第二个函数中修改它所以它的价值保持不变。你有它;传递值(复制)或通过引用传递(直接替换)的工作差异。
答案 2 :(得分:0)
有点难以弄清楚你的样本&#34;代码应该显示正在发生。部分原因是因为参数传递的底层语义并没有真正映射到可以显示的代码中。这是所有幕后细节,并且无法以任何比通常更明确的方式用正确的语言表达。
我也不能确切地从你的简短解释中确切地知道你的心理模型是什么,因此我不知道从哪里开始澄清它。所以,让我们从一开始就开始。
当函数接受参数&#34; by value&#34;时,该函数的调用者生成传递对象的 new 副本,并将其传递给函数。然后该函数使用该副本,执行它想要的任何操作。当该函数结束时,该副本将被有效地丢弃。这会给调用者留下原始对象。
当函数接受参数&#34;通过引用&#34;时,该函数的调用者实际上将其自己的对象副本传递给函数。然后该函数使用该副本,执行它想要的任何操作。当该函数结束时,它对该对象所做的任何更改都是永久性的,因为它是同一个对象,并且这些更改会反映在调用者站点上。换句话说,没有复制品。
按值传递实际上是所有在C中的工作方式。当你这样做时:
void Function(int foo);
参数foo
按值传递。同样,当你这样做时:
void Function(int * foo);
参数foo
仍按传递值;它只是通过值传递的参数实际上是一个指针,所以这个模拟传递&#34;通过引用&#34;,因为你&#39 ;通过指针间接传递对内存中原始值的引用。
在C ++中,您实际上具有真正的传递引用语义,因为该语言具有一流的引用类型。所以当你这样做时:
void Function(int & foo);
参数foo
实际上是通过引用传递的 - Function
获取对调用者具有的原始对象的引用。现在,在幕后,C ++将通过指针实现引用,所以真的没有任何新的事情发生。您只需从语言中获得保证,即永远不会出现&#34; null&#34;引用创建,它可以帮助您避免整个类别的错误。
我相信通过查看编译器如何实际实现这些细节,可以增强对这些细节的理解。实现细节因实现和体系结构而异,但通常,参数可以通过两种基本方式传递给函数:堆栈或处理器的内部寄存器。
如果在堆栈上传递参数,则调用者&#34;推送&#34;堆栈上的值。然后调用该函数,它读取/使用堆栈中的数据。功能完成后,参数弹出&#34;弹出&#34;堆栈。用伪汇编语言:
PROCEDURE getx // int getx(int one, int two)
LOAD reg1, [stack_slot1] // load parameter from stack slot #1 into reg1
LOAD reg2, [stack_slot2] // load parameter from stack slot #2 into reg2
ADD reg1, reg2 // add parameters (reg1 += reg2)
RETURN reg1 // return result, in reg1
END
PROCEDURE main // int main()
PUSH 2 // push parameter 1 onto stack
PUSH 55 // push parameter 2 onto stack
CALL getx // call function 'getx'
// The function has returned its result in reg1, so we can use it
// if we want, or ignore it.
POP stack_slot1 // pop parameters from stack to clean up stack
POP stack_slot2
RETURN 0
END
在这里,我们已经推动了#34;常量值到堆栈上。但是,我们可以很容易地将一个值的副本推送到寄存器中。
请注意&#34;推送&#34;复制该值,因此通过堆栈传递始终将是值传递,但正如我们所说,可以传递指针的副本为了给出pass-by-reference语义。通过指针对对象所做的任何更改都将反映在被调用者中。
如果在寄存器中传递参数,则调用者必须确保将该值加载到适当的寄存器中。然后调用该函数,它读取/使用该寄存器中的数据。函数完成后,对寄存器中的值所做的任何更改仍然可见。例如:
PROCEDURE getx // int getx(int one, int two)
ADD reg1, reg2 // add parameters (reg1 += reg2)
RETURN // result is left in reg1
END
PROCEDURE main // int main()
MOVE reg1, 2 // put '2' in reg1
MOVE reg2, 55 // put '55' in reg2
CALL getx // call function 'getx'
// The function has modified one or both registers, so we can use
// those values here, or ignore them.
RETURN 0
END
如果main
正在对函数调用之前或之后的值执行其他操作,那么它可以在getx
用于其参数的完全相同的寄存器中执行此操作。这基本上是传递引用语义。或者,它可以首先通过将值>复制到新寄存器中,调用getx
,然后将结果复制回来来获取值传递语义。