C ++中的困惑

时间:2010-06-26 15:13:56

标签: c++ pass-by-reference parameter-passing

我是C ++的新手,我正在学习它。我有几个问题..

  1. void DoSomething(const Foo& foo) void DoSomething(Foo foo)之间有什么区别?如果我们没有指定&那么Foo的实例将通过值(不是引用)传递。它与const +&相同。在参数中,除了在编译时没有检查。那么,为什么有const +&没有&成为争论的最佳实践和const?

    在C#中,传递对象是“通过引用”,但似乎不是在C ++中。

  2. 我正在阅读的书中说成员函数通过引用传递隐式参数..

    任何人都可以给我隐式参数的样本并通过引用吗?我知道如果我们想通过引用传递对象,我们需要使用& (例如Foo(Person& p))但是为了隐式参数,C ++如何通过引用传递对象?我读到C ++中的隐式参数就像Contructor(string str):strMemberVariable(str){} ...

  3. 数组是唯一通过C ++引用传递的数组吗?

  4. 为什么我不能在Foo类中使用 Foo fInstance

  5. 示例:

    class Foo {
    
    public:    
        Foo() { }
    
        Foo(const Foo& f) : fInstance(f) {   }  
    
        Foo fInstance;      
    };
    

    提前致谢。

12 个答案:

答案 0 :(得分:10)

  

1虚空DoSomething(const Foo& foo)和void DoSomething(Foo foo)之间有什么区别?如果我们不指定&那么Foo的实例将通过值(不是引用)传递。它与const +&相同。在参数中,除了在编译时没有检查。那么,为什么有const +&没有&成为争论的最佳实践和const?

     

在C#中,传递对象是“通过引用”,但似乎不是在C ++中。

按重要性顺序存在一些差异:

  • 如果无法复制对象Foo,则需要通过引用
  • 传递它
  • 如果对象Foo是基类,则应通过引用获取它,以便用户可以使用派生类调用函数
  • 即使您持有const对它的引用
  • ,实际对象的值也可能会发生变化
  • 效率,复制用户类型可能很昂贵,但编译器可能足够聪明,因此......
  

2我正在阅读的书中说成员函数通过引用传递隐式参数..

     

任何人都可以给我隐式参数的样本并通过引用吗?我知道如果我们想通过引用传递对象,我们需要使用& (例如Foo(Person& p))但是为了隐式参数,C ++如何通过引用传递对象?我读到C ++中的隐式参数就像Contructor(string str):strMemberVariable(str){} ...

通过隐式参数,您应该理解this,即对象本身。它通过引用有效传递,因为您可以在成员函数中修改其状态。

关注Konrad的注释:请注意this本身不是通过引用传递的,this是对象的引用(指针),但是按值传递。您无法根据需要更改对象的内存地址;)

  

3数组是C ++中唯一通过引用传递的数组吗?

他们不是。您将看到数组元素的更改,但数组(结构)不会更改。

按照FredOverflow的说法,举例说明:

void fun(int* p, size_t size);

int main(int argc, char* argv[])
{
  int array[15];
  fun(array, 15);
}

我们不知道fun会做什么,它可能会更改array的某些元素,但无论其行为如何,array仍将是15个整数的数组:内容更改,结构没有。

因此,要更改array,我们需要另一个声明:

void changer(int*& array, size_t& size);

这样我们就可以改变内容和结构(并传回新的大小)。当然,我们只能使用动态分配的数组调用此函数。

  

4为什么我不能在Foo类中使用Foo fInstance?

因为那是无限递归。从编译器的角度考虑它,并尝试猜测Foo的大小。 Foo的大小是其属性大小的总和,可能还有一些填充和类型信息。此外,对象大小至少为1,以便可以解决。那么,如果Foo有一个Foo,它的大小是多少:)?

通常的解决方案是使用智能指针:

class Foo
{
public:

private:
  std::unique_ptr<Foo> mInstance;
};

因为指针的大小不依赖于指向的对象的大小,所以这里没有递归:)

答案 1 :(得分:7)

由于这里有太多误解和彻头彻尾的错误答案,这是我试图纠正这个问题:

  

void DoSomething(const Foo& foo)void DoSomething(Foo foo)?

之间有何区别?

正如其他人所说,第二个代码需要一个副本(通常调用Foo的复制构造函数)。

  

那么,为什么有const +&amp;没有&amp;成为争论的最佳实践和const?

其他人已经回答过一些特殊的问题(例如运行时多态性)。这并不能解释为什么它成为最佳实践。这样做的原因很简单,也很丑陋:因为幅度更有效率。想象一下,将矢量或字符串传递给另一个方法 - 或者基本上只是传递任何大数据结构。复制它的成本通常很大,并且可能经常在代码中调用方法 - 事实上,方法通常经常调用,否则代码设计得很糟糕。

另一方面,当您将对象作为const引用传递时,这是通过指针在内部(通常)实现的。在所有体系结构中,指针都可以始终高效复制。

  

我正在阅读的书中说成员函数通过引用传递隐式参数..

我觉得这本书错了。类的成员函数隐式地传递一个引用当前对象的this指针。但是,这是指针,C ++禁止更改它。没有理由通过引用传递它。

  

数组是C ++中唯一通过引用传递的数据吗?

在C ++中很少传递数组 - 它们通常作为指针传递:

void foo(int[] x) { … }

实际上与

相同
void foo(int* x) { … }

编译器将这两个声明视为相同。当您尝试调用这些方法中的任何一个并将其传递给数组x时,C ++将隐式地将数组转换为指向其第一个元素的指针 - 这称为“衰变”。因此,foo(x)将成为foo(&x[0])

但是,如果给出了它们的大小,那么数组可以通过引用传递:

void foo(int (&x)[4]);

但是再一次,你明确地声明数组是通过引用传递的。

答案 2 :(得分:4)

  

在C#中,传递对象是“通过引用”,但似乎不是在C ++中。

不,这是错误的,这是一种常见的误解。在C#,VB和Java等语言中,变量总是按值传递(在C#中显式传递为ref,在VB中显式传递为ByRef)。

与C ++的不同之处在于变量本身不包含类'对象,它们只包含引用。所以传递给方法的不是对象本身,而是它的引用(但 是通过值传递的)。

差异非常重要。如果C#使用通过引用传递,则以下代码将打印不同的结果:

void foo(string s) {
    s = "world";
}

string s = "hello";
foo(s);
Console.WriteLine(s); // prints "hello"

答案 3 :(得分:4)

  

为什么我不能在Foo类中使用Foo fInstance?

因为从概念上讲,Foo的一个对象需要无限量的空间。从技术上讲,类型Foo在Foo的定义中是不完整的。

你可能想要的是指向Foo作为成员的指针。

答案 4 :(得分:1)

void DoSomething(const Foo& foo)void DoSomething(Foo foo)之间的差异是第一个通过引用传递参数,第二个按值传递。实际差异是:

  1. 效率。按值传递可能需要调用复制构造函数。如果复制构造函数很昂贵,那么传递值会增加更多的开销。
  2. 适用性。按值传递需要公共复制构造函数。如果类不支持复制构造函数,则不能按值传递。
  3. 语义。通过引用传递时,您不知道可以引用该对象的对象。如果由于某些其他原因更改了基础对象,则引用的值将更改。
  4. 为了更好地解释#3,请考虑以下情况:

    std::string global_string;
    
    void foo(const std::string &str)
    {
        if (str.empty())
        {
            global_string = "whatever";
            // is str still empty??
        }
    }
    

    如果foo被称为foo(global_string),那么当您更改global_string时,这也会更改str

答案 5 :(得分:1)

一次一个:

  1. doStuff(Foo f)表示在调用方法时将在堆栈上创建新的Foo对象 - AKA by-value 。调用doStuff(const Foo &f)表示您只是传递一个新引用,对象不重复,您只保留对它的引用。这是传递参数最安全的方法,因为它不涉及复制对象的副本。这称为传递 by-reference ,是最接近Java / C#行为的。

  2. 您在谈论哪个隐含参数?

  3. 同样,数组(假设它们是std::array s)可以通过值,指针或引用传递 - 没有单一的行为。正如Konard所提到的,C风格的数组(只不过是内存块)不能按值传递。

答案 6 :(得分:0)

  

我正在阅读的书中说成员函数通过引用传递隐式参数..

本书讨论的是隐式指针 this ,它被传递给类中定义的每个非静态成员函数。这是因为C ++在类中保存了每个成员函数的副本而不是每个对象,因此该方法应该知道该类应该处理哪个对象。

class FOO{
 int x;
 void doSomthing(int x);
}

void FOO::doSomething(int x){
  x = x;
}

会被编译成类似的东西

void FOO::doSomething(FOO* this, int x){
  this->x = x;
}

由于静态函数是类函数而不是对象函数,因此它们不需要创建对象以便被调用,因此它们不应该访问类的非静态字段,因此不能需要一个指向对象的指针。

答案 7 :(得分:0)

通过const引用而不是通过值传递并不是完全接受的“好习惯”。

This blog post解释了原因。

人们倾向于认为const引用更快,但事实是允许编译器在传递值时优化副本,因此传递值是一个很好的默认值(事实上,标准库通常会这样做例如,std::for_each按值获取两个迭代器,按值获取一个仿函数

使用const引用的主要原因是对象无法在逻辑上被复制。假设该对象代表一个窗口。您不希望第二个窗口出现在屏幕上,因为您将窗口对象传递给另一个函数,隐式创建副本。

许多对象代表不能或不应该被复制的东西。这些通常具有私有拷贝构造函数,并且必须通过引用或const引用传递给函数。

通过引用传递的另一个原因(const或其他)可能是使用多态对象。假设您有基类B和派生类D。您可以安全地将类型D的对象作为const B&传递,但是将值作为B类型的对象传递可能会导致切片(仅复制B子对象,而不是整个D对象)。

所以一个好的做法是默认传递值 ,但是传递const引用肯定也有它的位置。两者都有语言是有原因的。

答案 8 :(得分:0)

  What is the differences between void DoSomething(const Foo& foo) and
     

虚空DoSomething(Foo foo)?

实际上没有区别,const会阻止你改变'foo'的内容,而传递值也不会影响参数的内容,但是在效果方面,const Foo&amp; foo更有效,因为当对象传递给方法时它不会创建副本。

答案 9 :(得分:0)

  

虚空之间有什么区别   DoSomething(const Foo&amp; foo)和无效   DoSomething(Foo foo)?

一般来说,后者将深度复制传递的参数(换句话说,它会复制原始的Foo对象)。前者将传递参数的浅拷贝(将其地址复制到不可变的const引用而不是复制实际的Foo对象)。

这两个版本都可以访问传递的Foo对象的成员。它们都不会修改调用者中的Foo对象。如果函数不需要深层复制,那么基本的区别在于前者更有效,因为它避免了深层复制的需要。

任何人都可以给我隐含参数的样本并通过引用吗?我知道如果我们想通过引用传递对象,我们需要使用&amp; (例如Foo(Person&amp; p))但是为了隐式参数,C ++如何通过引用传递对象?我读到C ++中的隐式参数就像Contructor(string str):strMemberVariable(str){} ...

在参数化的一元构造函数(构造函数采用一个参数)的上下文中,它们可以是隐式的(默认的)或显式的。

class Foo
{
    Foo(int x) {...}
};

这是隐含的。它允许我们编写如下代码:

Foo foo = 123;

void f(const Foo& x);
f(123);

虽然这是明确的:

class Foo
{
    explicit Foo(int x) {...}
};

...并且不会是之前的代码。必须相应地修改先前的代码:

Foo foo(123);

void f(const Foo& x);
f(Foo(123) );

将这些构造函数显式化通常是一个好习惯,除了复制构造函数之外我不会进入这里,因为它更容易参与。

  

数组是唯一经过的数组   用C ++引用?

我不确定这里有什么问题,但如果这就是你的意思,那么数组不能通过值传递。我们只能将引用/指针传递给数组:

// takes an array of 10 integers
void fn(int(&some_array)[10]);

// takes a pointer to an int array
void fn(int* some_array);

// takes a pointer to an int array (the 10 
// literal constant is ignored) and this function
// can likewise take any pointer to int
void fn(int some_array[10]);
  

为什么我不能在Foo中使用Foo fInstance   类?

这是无限递归的。 Foo存储fInstance,fInstance存储另一个fInstance,依此类推。没有什么可以阻止递归,所以你只需要存储存储对象的对象的对象,等等,直到你的内存不足为止。因此编译器检测到这种情况并且不允许,因为它不会产生合法的运行时行为。也没有办法确定Foo的大小 - 这将是一个无限的值。

答案 10 :(得分:-1)

void DoSomething(Foo foo)

实际上传递了foo和

的副本
void DoSomething(Foo& foo)

传递对foo的引用,所以如果你在函数中修改foo,你将修改原来的foo。我希望这是有道理的。

对于数组,数组实际上是指向数组开头的指针,并且该指针被传递(不复制整个数组)。

array[5] = 0;//is the same as :
*(array+5) = 0; //this

答案 11 :(得分:-5)

  

void DoSomething(const Foo&amp; foo)和void DoSomething(Foo foo)之间有什么区别

如果Foo是原始数据类型,则DoSomething(Foo foo)按值传递对象foo,但如果Foo是用户定义的数据类型,则通过引用传递。但在第二种情况下,如果你改变foo,它会被反射回原始对象,这通常是不可取的。这由DoSomething(const Foo&amp; foo)处理,它通过引用传递foo(因此节省了传递值的额外内存成本),并且仍然没有对foo的DoSomething函数进行写访问。因此,这是一种最佳实践。

  

有人能给我样品吗?   隐含参数和引用?

成员函数中隐式参数的一个示例是对父对象的引用,即。 this,在函数的定义中从未提及,但始终可供使用。

  

数组是唯一经过的数组   用C ++引用?

不,所有用户定义的对象都通过引用传递。