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

发布于 2024-08-27 02:33:49 字数 320 浏览 9 评论 0原文

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

您想如何以及为什么要使用参考?如果您没有将参数设为引用,而是将 & 关闭,会发生什么情况?

例如,这些函数之间有什么区别:

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

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

I am trying to understand how to use reference parameters. There are several examples in my text, however they are too complicated for me to understand why and how to use them.

How and why would you want to use a reference? What would happen if you didn't make the parameter a reference, but instead left the & off?

For example, what's the difference between these functions:

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

I understand that reference variables are used in order to change a formal->reference, which then allows a two-way exchange of parameters. However, that is the extent of my knowledge, and a more concrete example would be of much help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

真心难拥有 2024-09-03 02:33:49

将引用视为别名。当您在引用上调用某些内容时,您实际上是在引用引用的对象上调用它。

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),交换两个空值并取消引用空指针...未定义的行为!通过一些检查可以修复:

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,因为没有 null 引用。)这是有效的,因为函数内部发生的交换实际上发生在函数外部别名的变量上。

(注意,永远不要编写 swap 函数。:) 标头 中已经存在一个函数,并且它已模板化以适用于任何类型。)


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

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 正确的。这意味着如果您的函数不修改参数,请将其标记为 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.

因此,如果您看过指针版本交换函数,我们会传递要交换的变量的地址,然后进行交换,取消引用以获取和设置值。

Think of a reference as an alias. When you invoke something on a reference, you're really invoking it on the object to which the reference refers.

int i;
int& j = i; // j is an alias to i

j = 5; // same as i = 5

When it comes to functions, consider:

void foo(int i)
{
    i = 5;
}

Above, int i is a value and the argument passed is passed by value. That means if we say:

int x = 2;
foo(x);

i will be a copy of x. Thus setting i to 5 has no effect on x, because it's the copy of x being changed. However, if we make i a reference:

void foo(int& i) // i is an alias for a variable
{
    i = 5;
}

Then saying foo(x) no longer makes a copy of x; i is x. So if we say foo(x), inside the function i = 5; is exactly the same as x = 5;, and x changes.

Hopefully that clarifies a bit.


Why is this important? When you program, you never want to copy and paste code. You want to make a function that does one task and it does it well. Whenever that task needs to be performed, you use that function.

So let's say we want to swap two variables. That looks something like this:

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

Okay, great. We want to make this a function, because: swap(x, y); is much easier to read. So, let's try this:

void swap(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}

This won't work! The problem is that this is swapping copies of two variables. That is:

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

In C, where references do not exist, the solution was to pass the address of these variables; that is, use pointers*:

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

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

This works well. However, it's a bit clumsy to use, and actually a bit unsafe. swap(nullptr, nullptr), swaps two nothings and dereferences null pointers...undefined behavior! Fixable with some checks:

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

But looks how clumsy our code has gotten. C++ introduces references to solve this problem. If we can just alias a variable, we get the code we were looking for:

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

Both easy to use, and safe. (We can't accidentally pass in a null, there are no null references.) This works because the swap happening inside the function is really happening on the variables being aliased outside the function.

(Note, never write a swap function. :) One already exists in the header <algorithm>, and it's templated to work with any type.)


Another use is to remove that copy that happens when you call a function. Consider we have a data type that's very big. Copying this object takes a lot of time, and we'd like to avoid that:

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

However, all we really need is an alias to the variable, so let's indicate that. (Again, back in C we'd pass the address of our big data type, solving the copying problem but introducing clumsiness.):

void do_something(big_data& data);

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

This is why you'll hear it said you should pass things by reference all the time, unless they are primitive types. (Because internally passing an alias is probably done with a pointer, like in C. For small objects it's just faster to make the copy then worry about pointers.)

Keep in mind you should be const-correct. This means if your function doesn't modify the parameter, mark it as const. If do_something above only looked at but didn't change data, we'd mark it as const:

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

We avoid the copy and we say "hey, we won't be modifying this." This has other side effects (with things like temporary variables), but you shouldn't worry about that now.

In contrast, our swap function cannot be const, because we are indeed modifying the aliases.

Hope this clarifies some more.


*Rough pointers tutorial:

A pointer is a variable that holds the address of another variable. For example:

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.

So, if you've seen the pointer-version swap function, we pass the address of the variables we want to swap, and then we do the swap, dereferencing to get and set values.

此刻的回忆 2024-09-03 02:33:49

让我们举一个名为 increment 的函数的简单示例,该函数会递增其参数。考虑一下:

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

这将不起作用,因为更改发生在传递给实际参数上的函数的参数副本上。因此

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

将产生 1 1 作为输出。

为了使函数对传递的实际参数起作用,我们将其引用传递给函数,如下所示:

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

对函数内部input所做的更改实际上是对实际参数进行的。这将产生 1 2 的预期输出

Lets take a simple example of a function named increment which increments its argument. Consider:

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

which will not work as the change takes place on the copy of the argument passed to the function on the actual parameter. So

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

will produce 1 1 as output.

To make the function work on the actual parameter passed we pass its reference to the function as:

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

the change made to input inside the function is actually being made to the actual parameter. This will produce the expected output of 1 2

不羁少年 2024-09-03 02:33:49

GMan 的回答为您提供了参考文献的内幕。我只是想向您展示一个必须使用引用的非常基本的函数:swap,它交换两个变量。这是用于 int 的(根据您的要求):

// 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 是最简单的整体。

关于指针

指针可以让你做一些与引用相同的事情。然而,指针给程序员带来了更多的责任来管理它们和它们指向的内存(一个名为“内存管理"——但现在不用担心)。因此,参考文献目前应该是您的首选工具。

将变量视为绑定到存储值的框的名称。常量是直接绑定到值的名称。两者都将名称映射到值,但常量的值无法更改。虽然框中保存的值可以更改,但名称与框的绑定不能更改,这就是不能更改引用以引用不同变量的原因。

对变量的两个基本操作是获取当前值(只需使用变量名称即可完成)和分配新值(赋值运算符“=”)。值存储在内存中(保存值的框只是内存的连续区域)。例如,

int a = 17;

结果如下(注意:在下文中,“foo @ 0xDEADBEEF”代表名称为“foo”的变量,存储在地址“0xDEADBEEF”。内存地址已组成):

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

存储在内存中的所有内容都有一个起始位置地址,所以还有一个操作:获取值的地址(“&”是取址运算符)。指针是存储地址的变量。

int *pa = &a;

结果:

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

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

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

GMan's answer gives you the lowdown on references. I just wanted to show you a very basic function that must use references: swap, which swaps two variables. Here it is for ints (as you requested):

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

There is a cleverer swap implementation for ints that doesn't need a temporary. However, here I care more about clear than clever.

Without references (or pointers), swap_noref cannot alter the variables passed to it, which means it simply cannot work. swap_ptr can alter variables, but it uses pointers, which are messy (when references won't quite cut it, however, pointers can do the job). swap is the simplest overall.

On Pointers

Pointers let you do some of the same things as references. However, pointers put more responsibility on the programmer to manage them and the memory they point to (a topic called "memory management"–but don't worry about it for now). As a consequence, references should be your preferred tool for now.

Think of variables as names bound to boxes that store a value. Constants are names bound directly to values. Both map names to values, but the value of constants can't be changed. While the value held in a box can change, the binding of name to box can't, which is why a reference cannot be changed to refer to a different variable.

Two basic operations on variables are getting the current value (done simply by using the variable's name) and assigning a new value (the assignment operator, '='). Values are stored in memory (the box holding a value is simply a contiguous region of memory). For example,

int a = 17;

results in something like (note: in the following, "foo @ 0xDEADBEEF" stands for a variable with name "foo" stored at address "0xDEADBEEF". Memory addresses have been made up):

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

Everything stored in memory has a starting address, so there's one more operation: get the address of the value ("&" is the address-of operator). A pointer is a variable that stores an address.

int *pa = &a;

results in:

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

Note that a pointer simply stores a memory address, so it doesn't have access to the name of what it points to. In fact, pointers can point to things without names, but that's a topic for another day.

There are a few operations on pointers. You can dereference a pointer (the "*" operator), which gives you the data the pointer points to. Dereferencing is the opposite of getting the address: *&a is the same box as a, &*pa is the same value as pa, and *pa is the same box as a. In particular, pa in the example holds 0x1000; * pa means "the int in memory at location pa", or "the int in memory at location 0x1000". "a" is also "the int at memory location 0x1000". Other operation on pointers are addition and subtraction, but that's also a topic for another day.

夏末 2024-09-03 02:33:49
// 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);

引用用于传递变量的位置,因此不需要将它们复制到堆栈上到新函数。

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

Alternatively,

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

Or the const version:

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

References are used to pass locations of variables, so they don't need to be copied on the stack to the new function.

寄意 2024-09-03 02:33:49

您可以在线运行一对简单的示例。

第一个使用普通函数,第二个使用引用:


编辑 - 这是源代码,以防您不喜欢链接:

示例 1

using namespace std;

void foo(int y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 1
}

< br>
示例2

using namespace std;

void foo(int & y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 2
}

A simple pair of examples which you can run online.

The first uses a normal function, and the second uses references:


Edit - here's the source code incase you don't like links:

Example 1

using namespace std;

void foo(int y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 1
}

Example 2

using namespace std;

void foo(int & y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 2
}
怪我入戏太深 2024-09-03 02:33:49

我不知道这是否是最基本的,但这里是...

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,它可以在不影响调用者的情况下完成此操作。

I don't know if this is the most basic, but here goes...

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

Here we use a reference to pass our list of elements into ReadElementsIntoList(). This way, the function loads the elements right into the list. If we didn't use a reference, then elems would be a copy of the passed-in list, which would have the elements added to it, but then elems would be discarded when the function returns.

This works both ways. In the case of count, we don't make it a reference, because we don't want to modify the count passed in, instead returning the number of elements read. This allows the calling code to compare the number of elements actually read to the requested number; if they don't match, then CanReadElement() must have returned false, and immediately trying to read some more would likely fail. If they match, then maybe count was less than the number of elements available, and a further read would be appropriate. Finally, if ReadElementsIntoList() needed to modify count internally, it could do so without mucking up the caller.

谁人与我共长歌 2024-09-03 02:33:49

打个比方:假设你的函数计算罐子里的豆子数量。它需要一罐豆子,并且您需要知道结果,而该结果不能是返回值(出于多种原因)。您可以向它发送 jar 和变量值,但您永远不会知道它是否或将值更改为什么。相反,您需要通过回邮信封向其发送该变量,以便它可以将值放入其中,并知道它已将结果写入所述地址处的值。

How about by metaphor: Say your function counts beans in a jar. It needs the jar of beans and you need to know the result which can't be the return value (for any number of reasons). You could send it the jar and the variable value, but you'll never know if or what it changes the value to. Instead, you need to send it that variable via a return addressed envelope, so it can put the value in that and know it's written the result to the value at said address.

奢望 2024-09-03 02:33:49

如果我错了,请纠正我,但引用只是一个取消引用的指针,或者?

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

Correct me if I'm wrong, but a reference is only a dereferenced pointer, or?

The difference to a pointer is, that you can't easily commit a NULL.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文