C++中的按引用传递

10

我的C++老师告诉我,如果函数内不对数组进行任何更改,那么应该仅使用引用传递。

我有一些非常大的向量在程序中传递。所有这些向量都将在函数内部被修改。我的矩阵大小约为[256*256][256][50]...

在这种情况下,有没有什么特别的原因不使用引用传递?

据我所知,引用传递应该更快,并且消耗的内存更少吧?


3
也许你的老师的意思是,“通过 const 引用传递只应在函数内部不会更改任何数据时使用。” - Dan
不,他没有这么说。他告诉我们,如果你不打算改变任何东西,那么只有在引用调用时才使用经验法则。从下面的答案中,我现在看到即使老师们也不总是知道自己在说什么。 - user119653
你说得对。老师并不总是知道自己在说什么。有时候他们确实知道,但是口误了。或者有时候,他们说了正确的话,但学生听错了。别排除最后一种可能性。 - jalf
@jalf 嘿,实际上这一次,我可以排除最后一个选项,因为我可以在线阅读他的幻灯片,并且上述语句在其中找到。然而,这个论坛是关于编程的,所以让我们把教师讨论留到另一个论坛吧... - user119653
11个回答

8
除了通常讨论非基本类型可能需要使用const引用传递的情况,数组在这里是相当特殊的。
由于与C的向后兼容性以及由于您的特定问题:数组可能很大,在C或C++中数组实际上从来没有通过值传递。数组将会“衰变”为指向第一个元素的指针,因此当您编写以下代码时:
void foo( type array[100] );

编译器实际上正在处理以下内容:
void foo( type *array );

无论数组大小是多少(两个常见的错误:相信foo内的array是一个数组,并且相信它保证有100个元素)。

现在,在C++中,您实际上可以通过引用传递数组,但引用必须是数组的具体类型,包括大小:

void foo_array( type (&array)[100] );

这里的有趣语法告诉编译器函数将接受一个类型为type的恰好100个元素的数组。其中的优点是编译器可以为您执行大小检查:

// assuming 'type' is defined
int main() {
   type array0[99];
   type array1[100];

   foo( array0 );     // compiles, but if size=100 is assumed it will probably break
                      // equivalent to: foo( &array0[0] )
   // foo2( array0 ); // will not compile, size is not 100
   foo2( array1 );    // compiles, size is guaranteed to be 100
}

现在的问题是,你的函数只能用于大小为100的数组,而有时你可能需要对不同大小的数组执行相同的操作。有两种解决方案:将函数模板化为数组大小,这将为每个使用的大小提供大小安全的实现--编译时间和二进制大小较大,该模板将为每个不同的大小进行编译--或使用传值语法,这将使数组退化--大小不安全,必须作为额外参数传递,编译时间和二进制大小较小。第三个选项是将两者结合起来:

void foo( type *array, int size );
template <size_t N>
void foo( type (&array)[N] ) {
   foo( array, N );
}

在这种情况下,对于每个大小,都会有一个模板化的foo,但编译器很可能会内联调用,并且生成的代码将等同于调用者提供数组和大小。不需要额外计算并且实现了真正数组的类型安全。
现在,按引用传递在数组中非常少使用。

1
非常非常有用的答案,至少对我来说是这样。我一直想知道如何创建像那样的大小安全数组。太酷了!(+1,显然) - Gui Prá

5
我的c++老师告诉我,如果函数内不需要更改数组内容,则应仅使用按引用调用。
当您不更改函数内的内容或更改内容并希望将更改反映到原始数组中或者不关心更改是否反映在原始数组中时,应使用它。
如果您不希望函数更改原始数组(需要在调用后保留原始值)且被调用方函数更改传递参数的值,则不应使用它。请注意保留HTML标记。

1
即使您不想更改它,也应该在较大的数组中使用它。此时,您可以通过const引用传递它,编译器会让您保持诚实。 - Xorlev
@Xorlev:是的。我还说了别的吗?我在读我的回答,但我不太确定哪一部分可能会被误解。它表达的意思是一样的。 - Mehrdad Afshari

4

你的老师是错的。如果需要修改数组,传递引用是正确的方法。如果不想被修改,就使用常量引用。


2
其实,“错误”可能太严厉了。也许“误解”更好一些。 - Richard Pennington

1

等一下..我有点害怕人们如何回答这个问题。就我所记得的,数组总是按引用传递。

void function(int array[])
{
    std::cout << array[0] << '\n';
}

// somewhere else..
int array[2] = { 1, 2 };
function(array); // No copy happens here; it is passed by reference

此外,你不能明确地说数组参数是一个引用,因为那将是创建引用数组的语法(这是不允许的)。
void function(int &array[]) // error here
{ /* ... */ }

那你的意思是什么?

此外,许多人认为只有在函数内修改数组内容时才应该这样做。那么,对于常量引用呢?

void function(const int arr[])
{
    std::cout << arr[0] << '\n';
}

-- 编辑

请问有人能告诉我如何在C++中不通过引用传递数组吗?

-- 编辑

哦,你是在谈论向量。好的,那么经验法则如下:

  • 仅当您想修改向量内容时才通过引用传递。
  • 尽可能通过引用传递const。
  • 仅当所涉及的对象非常小(例如包含整数的结构体)或在有意义的情况下(我无法想到一个例子)时,才通过值传递。

我漏掉了什么吗?

-- 编辑

  • 对于普通的C数组,当您想确保数组具有给定的确定大小时,最好通过引用传递它们(例如void function(int (&array)[100]))。

谢谢,dribeas。


这种混淆可能来自于我的问题,因为我谈论的是数组,但实际上我指的是向量... - user119653
它们不是按引用传递的,而是降解为指向第一个元素的指针。编译器将更改签名为 void foo(int *array) 并调用 foo(&array[0])。但你可以通过引用传递数组:void foo(int (&array)[100])。语法有些晦涩,大小必须明确声明(因为它是类型的一部分),但你可以这样做。 - David Rodríguez - dribeas

1
通常来说,对象应该总是通过引用传递。否则,将生成对象的副本,如果对象非常大,这将影响性能。
现在,如果您调用的方法或函数不修改对象,则最好将函数声明为以下形式:
void some_function(const some_object& o);

如果您尝试在函数体内修改对象的状态,这将生成编译错误。

此外,请注意数组始终按引用传递。


1
为了防止意外更改,请使用传递常量引用;这样,默认情况下,被调用函数不能更改传入的数组。
*可以使用const_cast覆盖该行为。

1

如果符合以下条件,您可以通过引用传递:

  1. 您不会修改传递的对象
  2. 您想要修改对象并且不想保留旧对象

当您通过引用传递时,只有指针被传递到函数。如果您传递整个对象,则需要复制它,因此会消耗更多的 CPU 和内存。


0

将对象通过引用传递始终是一个不错的选择,但我们需要小心,并首先决定我们的目的/函数的目的是什么?

在这里,您必须做出选择,无论我们只读取对象的数据还是修改它。

假设您有一个接口如下:

void increament_value(int& a);

所以在这里,您可以修改传递的对象的值,但是当您传递敏感数据时,这可能是一场灾难,您可能会丢失原始数据并无法恢复,对吧?

因此,C++为您提供了一个功能,即不更改传递给函数的对象的值,并且始终将对象的const引用传递给函数是一个明智的选择,例如:

double get_discounted_amount(const double &amount,double discount){
    return (amount*discount/100);
}

这可以保证你的对象的实际值不会改变,但是这取决于你接口的目的,你是想改变它还是只是使用(读取)它。


0
通常在入门课程中,他们会告诉你这样做是为了防止你意外更改不想更改的内容。
例如,如果你通过引用传递了userName,并意外将其更改为mrsbuxley,那么可能会导致错误,或者至少会在以后变得混乱。

0

我不认为有任何理由不能通过引用传递。或者你可以传递指针,但是有时候通过引用传递更好,因为它避免了空指针异常。

如果你的老师建议这种方式作为某种约定,那么如果有意义,可以自由打破它。你总可以在函数上方的注释中记录这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接