传递参数的方式 - 值传递 vs 引用传递 vs 指针传递?

5
与其他编程语言相比,C++支持三种不同的参数传递方式:
  • 按值传递
  • 按引用传递
  • 按地址传递
作为一名C#开发者,我已经有很长时间在使用C#进行编程。从我的角度来看,C++让我感到困惑。在C#中,这非常容易。只需定义您想要的内容并返回结果即可,而C++则更为复杂。是否有类似指南的东西可以告诉我们何时使用by value、by reference或者by address呢?

1
在我看来,C# 也可能会令人感到困惑。结构体按值传递,而类按引用传递,除非另有规定。 - parapura rajkumar
这方面已经有几个指南了。理解每个指南的作用是关键。 - Pubby
5
"按地址传递" 其实是 "按值传递"。可参考维基百科上的 Evaluation Strategy 文章。 - user166390
“按地址传递”实际上只是“按值传递”的一个子集。您传递的值恰好是一个地址(即指针值)。 - Keith Thompson
5
我不理解为什么人们认为这是“没有建设性的”问题;这是一个关于基本编程技巧的问题,应该可以以相当客观的方式回答。以不同方式传递参数的能力存在有其原因,当新手程序员试图弄清这些原因时,即使在必须进行权衡的情况下,对他们进行排斥似乎也很奇怪。 - Karl Knechtel
显示剩余4条评论
3个回答

13

通过引用传递和通过值传递的区别非常重要。当您通过值传递时,实际上是传递了一个问题值的副本。无论函数中发生什么情况,在调用者中的值都将保持不变。因此,假设您有一个将一个int加一并返回一个int的函数:

int addOne(int theNumber)
{
    theNumber += 1;
    return theNumber;
}

在这里,你是按值传递的。你需要像这样调用它:

int a = 10;
int b = addOne(a);    // b gets 11, but a remains the same

如果您想使用引用传递,函数将如下所示:
int addOne(int &theNumber)
{
    theNumber += 1;
    return theNumber;
}

请注意,函数体保持不变。同样,您可以这样调用它:

int a = 10;
int b = addOne(a);    // b gets 11, but this time a is also changed to 11.

这里的大区别在于,您传递了对“a”的引用。实际上这是一种对a的隐式指针,但您可以将其视为传递a本身。由于您传递的是a而不是a的值的副本,因此函数实际上会更改a本身。
第三种方法,通过传递地址来完成:
int addOne(int *theNumber)
{
    *theNumber = *theNumber + 1;
    return *theNumber;
}

这与参考版本执行的相同操作,但指针在此处是显式的。您可以像以下方式使用它:

int a = 10;
int b = addOne(&a);    // b gets 11, but this time a is also changed to 11.

所以,在这种情况下,您明确传递了a的地址,也就是指向a的指针。如果您只习惯按值传递,则应该很熟悉。这就是在C和一些其他类C语言中通过引用传递a的方式。它可以正常工作,但您必须自己处理所有指针内容。C++将“按引用传递”的概念添加到语言中,以使所有这些更加容易。

最后一种可能性是传递一个const引用,它避免了复制值,但禁止在被调用函数中更改它。如果一个函数采用const引用,则可以将其解释为不更改参数的承诺(并且编译器会强制执行该承诺)。如果值大于几个字节,则特别有用,因此如果可以避免复制该值,则通常将对象按const引用传递。

因此,作为指导方针,当您不希望调用者传递的内容发生更改时,请按值或const引用传递。当您想要更改时,请按引用传递。除非涉及需要它的C库或其他代码,否则不要传递指针。


1

按值传递参数会复制该参数,该副本将在函数内部使用。参数的修改将在函数结束后丢失:

例如:

void f(int a);
[
    a = 25;
}

int main()
{
    int i = 10;
    f(i);
    cout<<i;   //the printed   value will be 10;
}

但如果您使用地址或引用,那就不同了:

void g(int &a)
{
    a = 25;
}

int main()
{
    int i = 10;
    g(i);
    cout<<i;  // the printed value is 25 

}

通过地址或引用传递参数的优点是,参数不会完全复制到堆栈中。

0

当你传递自己定义的对象或其他库中的对象时,情况会变得更加混乱/复杂。一个限制依赖关系的问题是你可以前向声明一个类而不实际包含头文件,例如:

 // #include "myclass.h"   // we **Don't** include it! Less code-bloat
 class MyClass;  // foward-declare it

 void MyFunction(  MyClass*  obj  ); 
 // good to go, define this in .cpp file. 
 // someone including **This** header won't have to include myclass.h

 void MyFunction( MyClass& obj ); // Not Legal! 
 void MyFunction( MyClass obj ); // Not Legal! 
 void MyFunction( const MyClass& obj ); // Not Legal! 

我尝试使用“const MyClass& obj”约定,但有时由于库阻止了这种模式而不可能。如果你刚开始学习,请尝试通过值传递像'int','double'这样的东西,并且不要担心它。将不应更改的标准模板对象作为“const std::Class& var”传递,并让函数返回东西而不是通过引用修改。

当你的代码实现所需功能并担心性能问题,或者你使用的库需要它时,请重新考虑方法。


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