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


249

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

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

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


6
您从哪本书学习C ++?

17
强烈建议该书。前往Stan Lippman的C ++ Primer。
Prasoon Saurav,2010年

23
好吧,那是你的问题。Schildt基本上是cr * p-通过Koenig&Moo获得Accelerated C ++。

9
我想知道没人会提到Bjarne Stroustrup的C ++编程语言。Bjarne Stroustrup是C ++的创建者。一本非常好的学习C ++的书。
乔治,2010年

15
@乔治:TC ++ PL不适合初学者,但它被认为是C ++。xD 的圣经
Prasoon Saurav,2010年

Answers:


277

经验法则 C ++ 11:

传递价值,除非

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

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

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


经验法则 C ++ 03:

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

  1. 它们将在函数内部进行更改,并且此类更改应反映在外部,在这种情况下,您会通过非const引用
  2. 该函数应该是可调用不带任何参数,在这种情况下,你通过指针传递,这样用户就可以通过NULL/ 0/ nullptr代替; 应用前面的规则来确定您是否应该通过指向const参数
  3. 它们是内置类型,可以是 通过副本传递
  4. 他们是在函数内部改变和这种变化应该不会被外部的反射,在这种情况下,你可以通过复制传递(一个替代方法是,根据前述规则,以通过和使函数的副本内)

(此处,“按值传递”称为“按副本传递”,因为按值传递始终在C ++ 03中创建一个副本)


还有更多的东西,但是这几个初学者的规则将使您走得更远。


17
+1-我还要指出,有些人(例如Google)认为应该在函数内更改的对象应该通过指针而不是非常量引用来传递。理由是当将对象的地址传递给函数时,很明显,所述函数可以更改它。示例:使用引用,调用为foo(bar); 引用是否为const(带指针)为foo(&bar); 并且更明显的是foo被传递了一个可变对象。
RC。

19
@RC仍然不会告诉您指针是否为const。在各种C ++在线社区中,Google的指导方针大张旗鼓-恕我直言,恕我直言。

14
虽然在其他情况下google可能处于领先地位,但在C ++中,其样式指南并不是那么好。
DavidRodríguez-dribeas 2010年

4
@ArunSaha:作为纯样式指南,Stroustrup具有为航空航天公司开发的指南。我浏览了Google指南,但出于几个原因不喜欢它。Sutter和Alexandrescu C ++编码标准是一本不错的书,您可以获得很多不错的建议,但是它并不是真正的样式指南。除了人类和常识之外,我不知道是否有任何样式自动检查器。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2010年

3
@anon但是,您可以确保如果没有通过指针传递参数,则不会更改它。这是非常有价值的恕我直言,否则,当尝试跟踪函数中的变量发生什么时,您必须检查传递给它的所有函数的头文件,以确定其是否已更改。这样,您只需要查看它是通过指针传递的。
smehmood 2011年

107

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

通过参考

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

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

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

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

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

将const添加到方程式中

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

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

经验法则

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

  • 首选原始类型的值传递
  • 对于其他类型,首选通过引用引用引用常量
  • 如果函数需要修改参数,请使用传递引用
  • 如果参数是可选的,则使用传递指针(如果不应修改可选值,则为常量)

这些规则还有其他一些小的偏差,第一个是处理对象的所有权。当使用新对象动态分配对象时,必须使用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指针,它通过值传递给函数。


4
@ david-rodriguez-dribeas我喜欢经验法则,特别是“首选传递原始类型的值”的规则
yadab 2010年

据我说,这是对这个问题的更好回答。
unrealsoul007

22

有几种情况需要考虑。

修改参数(“输出”和“输入/输出”参数)

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)

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

按值构造是实现细节

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

3
+1 const当按值传递时,我不知道是否要实现。
balki 2012年

20

按值传递:

void func (vector v)

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

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

通过const参考传递:

void func (const vector& v);

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

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

通过非常量引用传递:

void func (vector& v)

当函数必须将一些值写回变量时,请使用此方法,该值最终将被调用方使用。

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

通过const指针传递:

void func (const vector* vp);

除了语法不同外,其他功能与通过const引用传递的功能相同,此外,调用函数可以传递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
    }
}

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


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确实按值传递并且value是引用,因此不需要复制构造函数。


-1

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

  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


只有2种方法(您提到的前2种)。不知道您所说的“在参数中传递常量”是什么意思
MM

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

1
“通过对象”不是问题。只有按值传递,并按引用传递。您的“情况3”实际上显示了一种按值传递情况和一种按引用传递情况。
MM
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.