C++ 和 Java 中的调用约定存在一些差异。在 C++ 中,从技术上讲只有两种约定:按值传递和按引用传递,一些文献包括第三种按指针传递约定(实际上是指针类型的按值传递)。最重要的是,您可以将 const-ness 添加到参数的类型中,从而增强语义。
通过引用传递
通过引用传递意味着该函数将在概念上接收您的对象实例,而不是它的副本。引用在概念上是调用上下文中使用的对象的别名,不能为空。函数内部执行的所有操作都适用于函数外部的对象。此约定在 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 );
}
上面的交换函数通过使用引用改变它的两个参数。 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 指针,它按值传递给函数。