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

Rak*_*h K 245 c++ pointers c++-faq pass-by-reference pass-by-value

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

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

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

sbi*_*sbi 269

C++ 11的经验法则:

通过按值时,除

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

几乎从不建议通过指针传递.可选参数最好用a表示std::optional,并且通过引用完成别名.

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


C++ 03的经验法则:

通过boost::optional引用传递参数,除非

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

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


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

  • @RC仍然没有告诉你指针是否为const.恕我直言,谷歌的指导方针在各种C++在线社区中引起了很多抨击 - 理所当然. (19认同)
  • +1 - 我还会注意到有些人(即Google)认为在函数中将要更改的对象应该通过指针而不是非const引用传递.理由是当对象的地址传递给函数时,更明显的是所述函数可以改变它.示例:使用引用,调用是foo(bar); 引用是否为const,指针是foo(&bar); 并且更明显的是foo正在传递一个可变对象. (17认同)
  • 虽然在其他情况下谷歌可能会引领潮流,但在C++中他们的风格指南并不是那么好. (14认同)
  • @ArunSaha:作为一个纯粹的风格指南,Stroustrup有一个为航空航天公司开发的[指南](http://www2.research.att.com/~bs/JSF-AV-rules.pdf).我浏览了谷歌指南并且因为几个原因而不喜欢它.Sutter&Alexandrescu [C++编码标准](http://www.amazon.com/Coding-Standards-Rules-Guidelines-Practices/dp/0321113586)是一本很好的书,你可以得到很多好的建议,但它并不是真正的*风格指南*.除了人类和常识之外,我不知道*style*的任何自动检查器. (4认同)
  • @anon然而,你确实得到了一个保证,如果一个参数没有通过指针传递,那么它就不会改变.这是非常有价值的恕我直言,否则当试图追踪函数中的变量发生了什么时,你必须检查传递给它的所有函数的头文件,以确定它是否被更改.这样,您只需要查看通过指针传递的那些. (3认同)

Dav*_*eas 106

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 ); 
}
Run Code Online (Sandbox Code Playgroud)

上面的交换函数通过使用引用来更改其参数.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
   }
}
Run Code Online (Sandbox Code Playgroud)

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

  • @ david-rodriguez-dribeas我喜欢经验法则部分,特别是"更喜欢原始类型的值传递" (4认同)

小智 22

有几种情况需要考虑.

参数修改("out"和"in/out"参数)

void modifies(T &param);
// vs
void modifies(T *param);
Run Code Online (Sandbox Code Playgroud)

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

......和可选的

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);
Run Code Online (Sandbox Code Playgroud)

参数未修改

void uses(T const &param);
// vs
void uses(T param);
Run Code Online (Sandbox Code Playgroud)

这是一个有趣的案例.经验法则是"便宜复制"类型是通过值传递的 - 这些通常是小类型(但并非总是) - 而其他类型则由const ref传递.但是,如果您需要在函数内复制,则应按值传递.(是的,这暴露了一些实现细节.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)
Run Code Online (Sandbox Code Playgroud)

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

Const by value是一个实现细节

void f(T);
void f(T const);
Run Code Online (Sandbox Code Playgroud)

这些声明实际上是完全相同的功能! 当传递值时,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
Run Code Online (Sandbox Code Playgroud)

  • +1我不知道`const`在传递值时是一个实现. (3认同)

nav*_*nav 19

通过价值:

void func (vector v)
Run Code Online (Sandbox Code Playgroud)

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

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

通过const引用:

void func (const vector& v);
Run Code Online (Sandbox Code Playgroud)

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

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

通过非const引用:

void func (vector& v)
Run Code Online (Sandbox Code Playgroud)

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

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

传递const指针:

void func (const vector* vp);
Run Code Online (Sandbox Code Playgroud)

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

不是线程安全的.

通过非const指针:

void func (vector* vp);
Run Code Online (Sandbox Code Playgroud)

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

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

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