如何在C ++中使用引用参数?

我想了解如何使用参考参数。 在我的文章中有几个例子,但是它们太复杂了,我不明白为什么以及如何使用它们。

如何以及为什么要使用参考? 如果您没有将参数设置为参考,会发生什么情况,而是将其closures?

例如,这些function有什么区别:

 int doSomething(int& a, int& b); int doSomething(int a, int b); 

我知道引用variables是用来改变一个forms – >引用,然后允许双向交换参数。 不过,这是我所知道的程度,更具体的例子会有很大的帮助。

把一个参考看作一个别名 。 当你在一个引用上调用某个东西的时候,你真的在​​引用引用的对象上调用它。

 int i; int& j = i; // j is an alias to i j = 5; // same as i = 5 

说到function,请考虑:

 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;里面i = 5;x = 5;完全一样x = 5;x变化。

希望澄清一点。


为什么这很重要? 当你编程时,你永远不想复制和粘贴代码。 你想做一个能完成一项任务的function,而且做得很好。 无论何时需要执行该任务,都可以使用该function。

所以我们假设我们想交换两个variables。 这看起来像这样:

 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; } 

这不行! 问题是这是交换两个variables的副本 。 那是:

 int a, b; swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged 

在C中,在引用不存在的地方,解决scheme是传递这些variables的地址; 也就是使用指针*:

 void swap(int* x, int* y) { int temp = *x; *x = *y; *y = temp; } int a, b; swap(&a, &b); 

这很好。 但是,使用起来有点笨拙,实际上有点不安全。 swap(nullptr, nullptr) ,交换两个nothings和dereferences空指针…未定义的行为! 一些检查可以修复:

 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 ++引入了解决这个问题的参考。 如果我们可以只是一个variables的别名,我们得到我们正在寻找的代码:

 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 

既易于使用,又安全。 (我们不能意外地传入一个空值,没有空引用。)这是可行的,因为函数内发生的交换实际上发生在函数外部的别名variables上。

(注意,不要写swap函数:)在头文件<algorithm>已经存在一个,它的模板可以用于任何types的工作。)


另一个用途是删除在调用函数时发生的副本。 考虑我们有一个非常大的数据types。 复制这个对象需要花费很多时间,我们希望避免这种情况:

 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 :< 

但是,我们真正需要的是variables的别名,所以让我们指出。 (再次回到C中,我们将传递大数据types的地址,解决复制问题,但引入笨拙):

 void do_something(big_data& data); big_data d; do_something(d); // no copies at all! data aliases d within the function 

这就是为什么你会听到它说,你应该通过引用通过事情,除非它们是原始types。 (因为内部传递一个别名大概是用一个指针来完成的,就像在C中一样。对于小对象来说,复制只是更快,然后担心指针。)

请记住,你应该是const正确的。 这意味着如果你的函数不修改参数,将其标记为const 。 如果上面的do_something只查看但没有更改data ,我们将其标记为const

 void do_something(const big_data& data); // alias a big_data, and don't change it 

我们避免复制我们说:“嘿,我们不会修改这个。” 这有其他的副作用(像临时variables的东西),但你现在不应该担心。

相比之下,我们的swap函数不能是const ,因为我们确实在修改别名。

希望这个澄清一些。


粗糙的指针教程:

指针是保存另一个variables地址的variables。 例如:

 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. 

所以,如果你已经看到了指针版本交换函数,我们传递我们想要交换的variables的地址,然后我们做交换,取消引用来获取和设置值。

让我们举一个简单的例子来说明递增它的参数的增量。 考虑:

 void increment(int input) { input++; } 

这将无法正常工作,因为更改发生在实际参数上传递给函数的参数副本上。 所以

 int i = 1; std::cout<<i<<" "; increment(i); std::cout<<i<<" "; 

将产生1 1作为输出。

为了让函数对实际parameter passing起作用,我们把它的reference传递给函数:

 void increment(int &input) { // note the & input++; } 

实际参数是对函数内部的input的修改。 这将产生1 2的预期输出

GMan的回答给了你参考文献的低调。 我只想告诉你一个非常基本的function,必须使用引用: swap ,交换两个variables。 这里是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 s不需要临时。 但是,在这里,我更关心的是聪明而不是聪明。

没有引用(或指针), swap_noref不能改变传递给它的variables,这意味着它根本无法工作。 swap_ptr可以改变variables,但它使用的指针是凌乱的(当引用不会完全切断它,但是指针可以完成这个工作)。 swap是最简单的整体。

在指针

指针让你做一些与引用相同的东西。 然而,指针会让程序员承担更多的责任来pipe理他们和他们指向的内存(一个名为“ 内存pipe理 ”的主题 – 但现在不用担心)。 因此,参考文献应该是您现在的首选工具。

将variables看作是绑定到存储值的框的名称。 常量是直接绑定到值的名称。 都将名称映射到值,但常量的值不能更改。 虽然框中的值可以更改,但是名称与框的绑定不能,因此不能将引用更改为引用不同的variables。

variables的两个基本操作是获取当前值(仅通过使用variables名称完成)并分配一个新值(赋值运算符'=')。 值存储在内存中(保存值的框只是内存的一个连续区域)。 例如,

 int a = 17; 

(注意:在下文中,“foo @ 0xDEADBEEF”代表名称为“foo”的variables存储在地址“0xDEADBEEF”中,内存地址已经组成):

  ____ a @ 0x1000: | 17 | ---- 

存储在内存中的所有东西都有一个起始地址,所以还有一个操作:获取值的地址(“&”是操作符的地址)。 指针是一个存储地址的variables。

 int *pa = &a; 

结果是:

  ______ ____ pa @ 0x10A0: |0x1000| ------> @ 0x1000: | 17 | ------ ---- 

请注意,指针只是存储一个内存地址,所以它不能访问它指向的名称。 事实上,指针可以指向没有名字的事物,但这是另一个话题。

指针有几个操作。 您可以取消引用指针(“*”运算符),该指针为您提供指针指向的数据。 取消引用与获取地址相反: *&a相同, &*pa相同, *paa相同。 特别是,在例子中的pa保存0x1000; * pa表示“位置pa上的内存中的int”或“位置0x1000上的内存中的int”。 “a”也是“内存位置0x1000处的int”。 指针上的其他操作是加法和减法,但这也是另一个话题。

 // 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); 

引用用于传递variables的位置,所以它们不需要在堆栈上复制到新函数中。

一个简单的例子,你可以在网上运行。

第一个使用正常的函数,第二个使用引用:

  • 例1(没有参考)
  • 例2(参考)

编辑 – 这里的源代码incase你不喜欢的链接:

例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 } 

我不知道这是最基本的,但是这里呢…

 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() 。 这样,该function将元素加载到列表中。 如果我们没有使用引用,那么elems就是传入列表的副本 ,它将添加元素,但是当函数返回时elems将被丢弃。

这两种方式。 在count的情况下,我们把它作为参考,因为我们不想修改传入的计数,而是返回读取的元素的数量。 这允许调用代码比较实际读取的元素的数量和请求的数量; 如果它们不匹配,那么CanReadElement()必须返回false ,并立即尝试读取更多可能会失败。 如果它们匹配,那么count可能less于可用的元素的数量,并且进一步阅读是合适的。 最后,如果ReadElementsIntoList()需要在内部修改count ,它可以这样做,而不会打扰调用者。

如何用隐喻:说你的function在一个jar子里计算豆。 它需要jar的jar,你需要知道结果不能是返回值(由于许多原因)。 你可以发送它的jar和variables值,但你永远不会知道是否或者它改变了什么值。 相反,您需要通过返回地址信封发送该variables,以便将值写入该地址,并知道将结果写入该地址的值。

纠正我,如果我错了,但引用只是一个解除引用的指针,或?

与指针的区别在于,您不能轻松提交NULL。