繁体   English   中英

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

[英]How to pass objects to functions in C++?

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

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

如果您还可以解释在何处使用这些选项,那就太好了。

C++11的经验法则

按值传递,除非

  1. 你不需要对象的所有权,一个简单的别名就可以了,在这种情况下,你通过const引用传递
  2. 您必须改变对象,在这种情况下,使用通过非const左值引用传递
  3. 您将派生类的对象作为基类传递,在这种情况下,您需要通过引用传递 (使用前面的规则来确定是否通过const引用传递。)

实际上从不建议通过指针传递。 可选参数最好表示为std::optionalboost::optional对于较旧的 std 库),并且通过引用可以很好地完成别名。

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


C++03的经验法则

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

  1. 它们的功能内被改变并且这些变化应被反射的外部,在这种情况下,你通过非传递const参考
  2. 该函数应该可以在没有任何参数的情况下调用,在这种情况下,您可以通过指针传递,以便用户可以传递NULL / 0 / nullptr 应用前面的规则来确定是否应该传递指向const参数的指针
  3. 它们是内置类型,可以通过复制传递
  4. 它们的功能内被改变而且这种改变应该被反射的外部,在这种情况下,可以通过复制通过(一个替代方法是,根据前述规则,以通过和使函数的副本内)

(这里,“传值”被称为“传值”,因为传值总是在C++03中创建一个副本)


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

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

通过引用传递

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

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

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

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

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

将 const 添加到等式中

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

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

经验法则

这是要遵循的一些基本规则:

  • 首选按值传递原始类型
  • 更喜欢通过引用传递引用其他类型的常量
  • 如果函数需要修改参数,请使用 pass-by-reference
  • 如果参数是可选的,则使用传递指针(如果不应该修改可选值,则为常量)

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

边注

坚持 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 ); 
}

上面的 swap 函数通过使用引用来改变它的两个参数。 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 指针,它们按值传递给函数。

有几种情况需要考虑。

参数修改(“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 传递。 但是,如果您无论如何都需要在函数中进行复制,则 应该通过 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 纯粹是一个实现细节。 试试看:

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

按值传递:

void func (vector v)

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

缺点是 CPU 周期和用于复制对象的额外内存。

通过常量引用:

void func (const vector& v);

这种形式在消除复制开销的同时模拟传值行为。 该函数可以读取原始对象,但不能修改其值。

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

通过非常量引用:

void func (vector& v)

当函数必须将一些值写回变量时使用它,这最终将被调用者使用。

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

通过 const 指针传递:

void func (const vector* vp);

除了语法不同之外,在功能上与通过常量引用传递相同,加上调用函数可以传递 NULL 指针以指示它没有要传递的有效数据的事实。

不是线程安全的。

通过非常量指针传递:

void func (vector* vp);

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

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

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

由于没有人提到我正在添加它,当您将对象传递给 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 确实按值传递,值是引用,因此它不需要复制构造函数。

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

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

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

有关选择参数类型的指南,请参阅C++ 核心指南的函数部分,从F.15开始。 作为一般规则,尽量避免使用原始指针*

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

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

通过以下示例:

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

输出:

说我在 someFunc
指针的值为-17891602
变量的值为 4

以下是在 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();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM