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

时间:2010-04-02 03:38:33

标签: c++ reference-parameters

我试图了解如何使用参考参数。我的文本中有几个例子,但是它们太复杂了我无法理解为什么以及如何使用它们。

您希望如何以及为何使用参考?如果您没有将参数作为参考,而是将&关闭,会发生什么?

例如,这些功能之间的区别是什么:

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

我知道参考变量用于更改形式的>引用,然后允许参数的双向交换。然而,这是我的知识范围,更具体的例子会有很大帮助。

8 个答案:

答案 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 |
              ------                     ----

请注意,指针只是存储一个内存地址,因此它无法访问它指向的名称。事实上,指针可以指向没有名字的东西,但这是另一天的主题。

指针上有一些操作。您可以取消引用指针(“*”运算符),该指针为您提供指针指向的数据。解除引用与获取地址相反:*&aa相同,&*papa的值相同,*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。