如何将对象传递给C ++中的函数?

时间:2010-01-26 12:08:08

标签: c++ pointers pass-by-reference pass-by-value c++-faq

我是C ++编程的新手,但我有Java经验。我需要有关如何将对象传递给C ++函数的指导。

我是否需要传递指针,引用或非指针和非引用值?我记得在Java中没有这样的问题,因为我们只传递了保存对象引用的变量。

如果您还可以解释在哪里使用这些选项,那将会很棒。

8 个答案:

答案 0 :(得分:265)

C ++ 11的经验法则:

按值 传递 ,除非

  1. 您不需要对象的所有权,只需要一个简单的别名,在这种情况下, 通过const引用
  2. 您必须改变对象,在这种情况下,使用 传递非const左值参考
  3. 您将派生类的对象作为基类传递,在这种情况下,您需要 通过引用传递 。 (使用先前的规则来确定是否通过const引用。)
  4. 几乎从不建议通过指针传递。可选参数最好表示为std::optional(对于较旧的std libs boost::optional),并且通过引用完成别名。

    C ++ 11的移动语义使得传递和返回值更具吸引力,即使对于复杂的对象也是如此。


    C ++ 03:

    的经验法则

    通过const引用 传递参数 ,除非

    1. 它们将在函数内部进行更改,并且此类更改应反映在外部,在这种情况下, 通过非const引用
    2. 该函数应该可以在没有任何参数的情况下调用,在这种情况下,您可以通过指针传递,以便用户可以通过NULL / 0 / nullptr来代替;应用之前的规则来确定是否应 通过指向const参数的指针
    3. 它们是内置类型,可以 通过副本传递
    4. 要在功能内部进行更改,并且 的更改应在外部反映出来,在这种情况下,您可以 通过副本传递 (另一种方法是根据之前的规则传递并在函数内部复制)
    5. (此处,“按值传递”称为“按副本传递”,因为传递值始终在C ++中创建副本03)


      还有更多内容,但这些初学者的规则会让你走得更远。

答案 1 :(得分:104)

C ++和Java中的调用约定存在一些差异。在C ++中,从技术上讲,只有两种约定:按值传递和按引用传递,一些文献包括第三次传递指针约定(实际上是指针类型的值传递)。最重要的是,您可以将const-ness添加到参数的类型中,从而增强语义。

通过引用传递

通过引用传递意味着该函数将在概念上接收您的对象实例而不是它的副本。该引用在概念上是调用上下文中使用的对象的别名,并且不能为null。在函数内执行的所有操作都适用于函数外部的对象。此约定在Java或C中不可用。

按值传递(并按指针传递)

编译器将在调用上下文中生成对象的副本,并在函数内使用该副本。在函数内执行的所有操作都是对副本完成的,而不是外部元素。这是Java中基本类型的约定。

它的一个特殊版本是将指针(对象的地址)传递给函数。函数接收指针,并且应用于指针本身的任何和所有操作都应用于副本(指针),另一方面,应用于解除引用指针的操作将应用于该内存位置的对象实例,因此该函数可能有副作用。使用指向对象的指针传递值的效果将允许内部函数修改外部值,如使用pass-by-reference,并且还允许使用可选值(传递空指针)。

当函数需要修改外部变量时,这是C中使用的约定,而Java中使用的约定是引用类型:引用被复制,但引用的对象是相同的:对引用/指针的更改是在函数外部不可见,但指向内存的更改是。

将const添加到等式中

在C ++中,您可以在定义不同级别的变量,指针和引用时为对象分配常量。您可以将变量声明为常量,可以声明对常量实例的引用,并且可以定义所有指向常量对象的指针,指向可变对象的常量指针和指向常量元素的常量指针。相反,在Java中,您只能定义一个级别的常量ness(final关键字):变量的类型(基本类型的实例,引用类型的引用),但是您不能定义对不可变元素的引用(除非类本身是不可变的)。

这在C ++调用约定中广泛使用。当对象很小时,您可以按值传递对象。编译器将生成一个副本,但该副本不是一个昂贵的操作。对于任何其他类型,如果函数不会更改对象,则可以将引用传递给该类型的常量实例(通常称为常量引用)。这不会复制对象,而是将其传递给函数。但同时编译器将保证在函数内部不更改对象。

经验法则

这是一些基本规则:

  • 首选基本类型的值传递
  • 首选引用传递,引用其他类型的常量
  • 如果函数需要修改参数,请使用pass-by-reference
  • 如果参数是可选的,请使用pass-by-pointer(如果不应修改可选值,则为常量)

这些规则还有其他小的偏差,第一个是处理对象的所有权。当使用new动态分配对象时,必须使用delete(或其[]版本)对其进行解除分配。负责销毁对象的对象或函数被视为资源的所有者。当在一段代码中创建动态分配的对象,但是所有权被转移到另一个元素时,通常使用pass-by-pointer语义,或者如果可能的话使用智能指针。

旁注

重要的是要坚持C ++和Java引用之间差异的重要性。在C ++中,引用在概念上是对象的实例,而不是对象的访问者。最简单的例子是实现交换功能:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

上方的交换函数通过使用引用来更改两个参数。 Java中最接近的代码:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

代码的Java版本将在内部修改引用的副本,但不会在外部修改实际对象。 Java引用是没有指针算术的C指针,它们通过值传递给函数。

答案 2 :(得分:22)

有几种情况需要考虑。

参数修改(“out”和“in / out”参数)

void modifies(T &param);
// vs
void modifies(T *param);

这种情况主要是关于样式的:您希望代码看起来像 call(obj) call(& obj)吗?但是,差异有两个重点:下面的可选案例,并且您希望在重载运算符时使用引用。

...和可选

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

参数未修改

void uses(T const &param);
// vs
void uses(T param);

这是一个有趣的案例。经验法则是“便宜复制”类型是通过值传递的 - 这些通常是小类型(但并非总是) - 而其他类型则由const ref传递。但是,如果您需要在函数中制作副本,则should pass by value。 (是的,这暴露了一些实现细节。 C'est le C ++。

...和可选

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

所有情况之间的差异最小,因此请选择最适合您生活的方式。

Const by value是一个实现细节

void f(T);
void f(T const);

这些声明实际上是完全相同的函数!当通过值传递时,const纯粹是一个实现细节。 Try it out:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

答案 3 :(得分:19)

按值传递:

void func (vector v)

当函数需要与环境完全隔离时,按值传递变量,即阻止函数修改原始变量,以及防止其他线程在执行函数时修改其值。

缺点是CPU周期和复制对象所花费的额外内存。

通过const引用:

void func (const vector& v);

此表单模拟了按值传递行为,同时消除了复制开销。该函数获取对原始对象的读访问权限,但无法修改其值。

缺点是线程安全:另一个线程对原始对象所做的任何更改都将在函数内部显示,但仍然在执行。

通过非const引用:

void func (vector& v)

当函数必须将某些值写回变量时使用此函数,最终将由调用者使用。

就像const引用案例一样,这不是线程安全的。

通过const指针:

void func (const vector* vp);

在功能上与const-reference相同,除了不同的语法,以及调用函数可以传递NULL指针以指示它没有有效数据传递的事实。

不是线程安全的。

通过非常量指针:

void func (vector* vp);

与非const引用类似。当函数不应该写回值时,调用者通常将变量设置为NULL。在许多glibc API中都可以看到这种约定。例如:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

就像所有通过引用/指针传递一样,而不是线程安全。

答案 4 :(得分:0)

由于没有人提到我正在添加它,当你将一个对象传递给c ++中的函数时,如果没有一个创建对象的克隆然后将它传递给对象,则调用该对象的默认复制构造函数。方法,所以当您更改将反映在对象副本而不是原始对象上的对象值时,这就是c ++中的问题,因此如果您将所有类属性都指向为指针,则复制构造函数将复制指针属性的地址,所以当对操作存储在指针属性地址中的值的对象上的方法调用时,更改也会反映在作为参数传递的原始对象中,因此这可以表现为与Java相同但不要忘记你的所有类属性都必须是指针,你也应该改变指针的值,用代码解释会很清楚。

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

但这不是一个好主意,因为你最终会编写大量涉及指针的代码,这些代码容易出现内存泄漏并且不会忘记调用析构函数。并且为了避免这个c ++具有复制构造函数,当包含指针的对象传递给将停止操作其他对象数据的函数参数时,您将创建新内存,Java确实通过值传递,值是引用,因此它不需要复制构造函数。

答案 5 :(得分:0)

<块引用>

我是否需要传递指针、引用或非指针和非引用值?

这是一个在编写函数和选择它需要的参数类型时很重要的问题。这个选择会影响函数的调用方式,这取决于一些事情。

最简单的选择是按值传递对象。这基本上在函数中创建了对象的副本,这有很多优点。但有时复制成本很高,在这种情况下,常量引用 const& 通常是最好的。有时你需要你的对象被函数改变。然后需要一个非常量引用 &

有关选择参数类型的指南,请参阅以 the Functions section of the C++ Core Guidelines 开头的 F.15。作为一般规则,尽量避免使用原始指针,*

答案 6 :(得分:-1)

将对象作为参数传递给函数有三种方法:

  1. 通过引用传递
  2. 按值传递
  3. 在参数
  4. 中添加常量

    通过以下示例:

    class Sample
    {
    public:
        int *ptr;
        int mVar;
    
        Sample(int i)
        {
            mVar = 4;
            ptr = new int(i);
        }
    
        ~Sample()
        {
            delete ptr;
        }
    
        void PrintVal()
        {
            cout << "The value of the pointer is " << *ptr << endl
                 << "The value of the variable is " << mVar;
       }
    };
    
    void SomeFunc(Sample x)
    {
    cout << "Say i am in someFunc " << endl;
    }
    
    
    int main()
    {
    
      Sample s1= 10;
      SomeFunc(s1);
      s1.PrintVal();
      char ch;
      cin >> ch;
    }
    

    输出:

      

    说我在某些功能中   指针的值是-17891602
      变量的值是4

答案 7 :(得分:-1)

以下是在C ++中将参数/参数传递给函数的方法。

<强> 1。按值计算。

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

<强> 2。参考。

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

第3。按对象。

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}