我试图了解如何使用参考参数。我的文本中有几个例子,但是它们太复杂了我无法理解为什么以及如何使用它们。
您希望如何以及为何使用参考?如果您没有将参数作为参考,而是将&
关闭,会发生什么?
例如,这些功能之间的区别是什么:
int doSomething(int& a, int& b);
int doSomething(int a, int b);
我知道参考变量用于更改形式的>引用,然后允许参数的双向交换。然而,这是我的知识范围,更具体的例子会有很大帮助。
答案 0 :(得分:113)
将引用视为an alias。当您在引用上调用某些内容时,您实际上是在引用引用的对象上调用它。
int i;
int& j = i; // j is an alias to i
j = 5; // same as i = 5
说到功能,请考虑:
void foo(int i)
{
i = 5;
}
上面,int i
是一个值,传递的参数按值传递 。这意味着如果我们说:
int x = 2;
foo(x);
i
将是x
的副本。因此,将i
设置为5对x
没有影响,因为它是x
被更改的副本。但是,如果我们将i
作为参考:
void foo(int& i) // i is an alias for a variable
{
i = 5;
}
然后说foo(x)
不再复制x
; i
是 x
。因此,如果我们说foo(x)
,则函数i = 5;
内部与x = 5;
完全相同,x
更改。
希望澄清一点。
为什么这很重要?编程时,从不想要复制和粘贴代码。你想创建一个完成一项任务的功能,并且它做得很好。只要需要执行该任务,就可以使用该功能。
所以我们假设我们要交换两个变量。看起来像这样:
int x, y;
// swap:
int temp = x; // store the value of x
x = y; // make x equal to y
y = temp; // make y equal to the old value of x
好的,太好了。我们希望将其作为一个函数,因为:swap(x, y);
更容易阅读。所以,让我们试试这个:
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
这不起作用!问题是这是交换两个变量的副本。那就是:
int a, b;
swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged
在C中,不存在引用,解决方案是传递这些变量的地址;也就是说,使用指针*:
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int a, b;
swap(&a, &b);
这很有效。但是,它使用起来有点笨拙,实际上有点不安全。 swap(nullptr, nullptr)
,交换两个nothings并取消引用空指针...未定义的行为!可通过一些检查进行修复:
void swap(int* x, int* y)
{
if (x == nullptr || y == nullptr)
return; // one is null; this is a meaningless operation
int temp = *x;
*x = *y;
*y = temp;
}
但看起来我们的代码有多笨拙。 C ++引入了引用来解决这个问题。如果我们可以为变量添加别名,我们就会得到我们正在寻找的代码:
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int a, b;
swap(a, b); // inside, x and y are really a and b
既易于使用又安全。 (我们不能意外地传入null,没有空引用。)这是有效的,因为函数内部发生的交换实际上发生在函数外面的别名变量上。
(注意,永远不要编写swap
函数。:)标题<algorithm>
中已存在一个,并且模板适用于任何类型。)
另一个用途是删除调用函数时发生的副本。考虑一下我们的数据类型非常大。复制此对象需要花费大量时间,我们希望避免这种情况:
struct big_data
{ char data[9999999]; }; // big!
void do_something(big_data data);
big_data d;
do_something(d); // ouch, making a copy of all that data :<
但是,我们真正需要的只是变量的别名,所以让我们指出。 (同样,回到C,我们会传递我们的大数据类型的地址,解决复制问题但引入笨拙。):
void do_something(big_data& data);
big_data d;
do_something(d); // no copies at all! data aliases d within the function
这就是为什么你会听到它说你应该一直通过引用传递东西,除非它们是原始类型。 (因为内部传递别名可能是用指针完成的,就像在C中一样。对于小对象,制作副本的速度更快,然后担心指针。)
请记住,你应该是const-correct。这意味着如果您的函数未修改参数,请将其标记为const
。如果以上do_something
仅查看但未更改data
,我们会将其标记为const
:
void do_something(const big_data& data); // alias a big_data, and don't change it
我们避免复制和我们说“嘿,我们不会修改它。”这有其他副作用(像临时变量这样的东西),但你现在不应该担心。
相反,我们的swap
函数不能是const
,因为我们确实修改了别名。
希望这能澄清一些。
*粗略指针教程:
指针是一个保存另一个变量地址的变量。例如:
int i; // normal int
int* p; // points to an integer (is not an integer!)
p = &i; // &i means "address of i". p is pointing to i
*p = 2; // *p means "dereference p". that is, this goes to the int
// pointed to by p (i), and sets it to 2.
所以,如果你看过指针版本交换函数,我们传递我们想要交换的变量的地址,然后我们进行交换,取消引用以获取和设置值。
答案 1 :(得分:4)
让我们举一个名为increment
的函数的简单例子,它增加了它的参数。考虑:
void increment(int input) {
input++;
}
这将不起作用,因为更改发生在传递给实际参数的函数的参数的副本上。所以
int i = 1;
std::cout<<i<<" ";
increment(i);
std::cout<<i<<" ";
将生成1 1
作为输出。
为了使函数适用于传递的实际参数,我们将其reference
传递给函数:
void increment(int &input) { // note the &
input++;
}
对函数内部input
所做的更改实际上是对实际参数进行的。这将产生1 2
答案 2 :(得分:4)
GMan的答案为您提供了参考资料的缩减。我只想向您展示一个必须使用引用的基本函数:swap
,它交换两个变量。这是int
s(按照您的要求):
// changes to a & b hold when the function exits
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
// changes to a & b are local to swap_noref and will go away when the function exits
void swap_noref(int a, int b) {
int tmp = a;
a = b;
b = tmp;
}
// changes swap_ptr makes to the variables pointed to by pa & pb
// are visible outside swap_ptr, but changes to pa and pb won't be visible
void swap_ptr(int *pa, int *pb) {
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main() {
int x = 17;
int y = 42;
// next line will print "x: 17; y: 42"
std::cout << "x: " << x << "; y: " << y << std::endl
// swap can alter x & y
swap(x,y);
// next line will print "x: 42; y: 17"
std::cout << "x: " << x << "; y: " << y << std::endl
// swap_noref can't alter x or y
swap_noref(x,y);
// next line will print "x: 42; y: 17"
std::cout << "x: " << x << "; y: " << y << std::endl
// swap_ptr can alter x & y
swap_ptr(&x,&y);
// next line will print "x: 17; y: 42"
std::cout << "x: " << x << "; y: " << y << std::endl
}
int
有一个更聪明的交换实现,不需要临时的。但是,在这里我更关心明确而不是聪明。
没有引用(或指针),swap_noref
不能改变传递给它的变量,这意味着它根本无法工作。 swap_ptr
可以改变变量,但它使用指针,这些指针很乱(当引用不会完全削减它时,指针可以完成这项工作)。 swap
是最简单的整体。
指针可以让你做一些与引用相同的东西。但是,指针给程序员更多的责任来管理它们以及它们指向的内存(一个名为“memory management”的主题 - 但现在不用担心它)。因此,引用应该是您现在的首选工具。
将变量视为绑定到存储值的框的名称。常量是直接绑定到值的名称。两者都将值映射到值,但不能更改常量的值。虽然框中保存的值可以更改,但名称与框的绑定不能,这就是无法更改引用以引用其他变量的原因。
对变量的两个基本操作是获取当前值(仅使用变量的名称完成)并分配新值(赋值运算符'=')。值存储在内存中(保存值的框只是一个连续的内存区域)。例如,
int a = 17;
会产生类似的结果(注意:在下面,“foo @ 0xDEADBEEF”代表一个名为“foo”的变量,存储在地址“0xDEADBEEF”。内存地址已经组成):
____
a @ 0x1000: | 17 |
----
存储在内存中的所有内容都有一个起始地址,因此还有一个操作:获取值的地址(“&amp;”是操作符的地址)。指针是存储地址的变量。
int *pa = &a;
结果:
______ ____
pa @ 0x10A0: |0x1000| ------> @ 0x1000: | 17 |
------ ----
请注意,指针只是存储一个内存地址,因此它无法访问它指向的名称。事实上,指针可以指向没有名字的东西,但这是另一天的主题。
指针上有一些操作。您可以取消引用指针(“*”运算符),该指针为您提供指针指向的数据。解除引用与获取地址相反:*&a
与a
相同,&*pa
与pa
的值相同,*pa
相同框为a
。特别是,示例中的pa
保持0x1000; * pa
表示“位置pa的内存中的int”或“位于0x1000的内存中的int”。 “a”也是“内存位置0x1000的int”。对指针的其他操作是加法和减法,但这也是另一天的主题。
答案 3 :(得分:1)
// Passes in mutable references of a and b.
int doSomething(int& a, int& b) {
a = 5;
cout << "1: " << a << b; // prints 1: 5,6
}
a = 0;
b = 6;
doSomething(a, b);
cout << "2: " << a << ", " << b; // prints 2: 5,6
可替换地,
// Passes in copied values of a and b.
int doSomething(int a, int b) {
a = 5;
cout << "1: " << a << b; // prints 1: 5,6
}
a = 0;
b = 6;
doSomething(a, b);
cout << "2: " << a << ", " << b; // prints 2: 0,6
或const版本:
// Passes in const references a and b.
int doSomething(const int &a, const int &b) {
a = 5; // COMPILE ERROR, cannot assign to const reference.
cout << "1: " << b; // prints 1: 6
}
a = 0;
b = 6;
doSomething(a, b);
引用用于传递变量的位置,因此不需要将它们复制到堆栈中的新函数。
答案 4 :(得分:1)
您可以在线运行的一对简单示例。
第一个使用普通函数,第二个使用引用:
编辑 - 这里是您不喜欢链接的源代码:
示例1
using namespace std;
void foo(int y){
y=2;
}
int main(){
int x=1;
foo(x);
cout<<x;//outputs 1
}
示例2
using namespace std;
void foo(int & y){
y=2;
}
int main(){
int x=1;
foo(x);
cout<<x;//outputs 2
}
答案 5 :(得分:0)
我不知道这是否是最基本的,但这里有......
typedef int Element;
typedef std::list<Element> ElementList;
// Defined elsewhere.
bool CanReadElement(void);
Element ReadSingleElement(void);
int ReadElementsIntoList(int count, ElementList& elems)
{
int elemsRead = 0;
while(elemsRead < count && CanReadElement())
elems.push_back(ReadSingleElement());
return count;
}
这里我们使用引用将我们的元素列表传递给ReadElementsIntoList()
。这样,该函数将元素直接加载到列表中。如果我们没有使用引用,那么elems
将是传入列表的副本,它会添加元素,但是elems
会在函数返回时被丢弃。
这两种方式都有效。在count
的情况下,我们不使其成为引用,因为我们不想修改传入的计数,而是返回读取的元素数。这允许调用代码将实际读取的元素数量与请求的数量进行比较;如果它们不匹配,则CanReadElement()
必须返回false
,并且立即尝试阅读更多内容可能会失败。如果它们匹配,则可能count
小于可用元素的数量,并且进一步读取将是合适的。最后,如果ReadElementsIntoList()
需要在内部修改count
,则可以在不破坏调用者的情况下执行此操作。
答案 6 :(得分:0)
用隐喻怎么样:假设你的函数在jar中计算bean。它需要一罐豆子,你需要知道结果不能是返回值(由于许多原因)。您可以将jar和变量值发送给它,但您永远不会知道它是否将值更改为。相反,您需要通过返回地址包络发送该变量,因此它可以将值放入其中并知道它将结果写入所述地址的值。
答案 7 :(得分:0)
如果我错了,请纠正我,但是引用只是一个解除引用的指针,或者?
与指针的区别在于,您无法轻松提交NULL。