C++中参数传递

参数传递

每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。

和其他变量一样,形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。

当形参是引用类型时,我们说它对应的实参被引用传递(passed by reference)或者函数被传引用调用(called by reference)。和其他引用一样,引用形参也是它绑定的对象的别名;也就是说,引用形参是它对应的实参的别名。

当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。我们说这样的实参被值传递(passed by value)或者函数被传值调用(called by value)。

传值参数

当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值:

int n = 0;        // int类型的初始变量
int i = n;        // i是n的值的副本
i = 42;           // i的值改变;n的值不变

传值参数的机理完全一样,函数对形参做的所有操作都不会影响实参。例如,在fact函数内对变量val执行递减操作:

int fact(int val)
{
    int ret = 1;        // 局部变量,用于保存计算结果
    while (val > 1)    
        ret *= val--;   // 把ret和val的乘积赋给ret,然后将val减一
    return ret;         // 返回结果
}

尽管fact函数改变了val的值,但是这个改动不会影响传入fact的实参。调用fact(i)不会改变i的值。

指针形参

指针的行为和其他非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值:

int n = 0, i = 42;
in *p = &n, *q = &i;    // p指向n;q指向i
*p = 42;                // n的值改变;p不变
p = q;                  // p现在指向了i;但i和n的值都不变

指针形参的行为与之类似:

// 该函数接受一个指针,然后将指针所指的值置为0
void reset(int *ip)
{
    *ip = 0;    // 改变了指针ip所指对象的值
    ip = 0;     // 只改变了ip的局部拷贝,实参未被改变
}

调用reset函数之后,实参所指的对象被置为0,但是实参本身并没有改变:

int i = 42;
reset(&i);                    // 改变i的值而非i的地址
cout << "i = " << i << endl;  // 输出i = 0

传引用参数

我们知道,对于引用的操作实际上是作用在引用所引的对象上:

int n = 0, i = 42;
int &r = n;    // r绑定了n
r = 42;        // 现在n的值是42
r = i;         // 现在n的值和i相同
i = r;         // i的值和n相同

引用形参的行为与之类似。通过使用引用形参,允许函数改变一个或多个实参的值。

我们可以改写之前的reset程序,使其接受的参数是引用类型而非指针:

void reset(int &i)    // i是传给reset函数的对象的另一个名字
{    
    i = 0;            // 改变了i所引对象的值
}

和其他引用一样,引用形参绑定初始化它的对象。当调用这一版本的reset函数时,i绑定我们传给函数的int对象,此时改变i也就是改变i所引对象的值。此例中,被改变的对象是传入reset的实参。

调用这一版本的reset函数时,我们直接传入对象而无须传递对象的地址:

int j = 42;
reset(j);                        // j采用传引用的方式,它的值被改变
cout << "j = " << j << endl;     // 输出j = 0

在上述调用过程中,形参i仅仅是j的又一个名字。在reset内部对i的使用即是对i的使用。

  • 使用引用避免拷贝

拷贝大的类型对象或者容器对象比较低效,甚至有的类类型(包括IO类型在内)根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

举个例子,我们准备编写一个函数比较两个string对象的长度。因为string对象可能会非常长,所以应该尽量避免值即拷贝它们,这时使用引用形参是比较明智的选择。又因为比较长度无序改变string对象的内容,所以把形参定义成对常量的引用:

// 比较两个string对象的长度
bool isShorter(const string &s1, const string $s2)
{
    return s1.size() < s2.size();
}

如果函数无需改变引用形参的值,最好将其声明为常量引用。

形参与实参的区别

  • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
  • 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传递给形参。因此应预先用赋值,输入等办法使实参获得确定值,会产生一个临时变量。
  • 实参和形参在数量上,类型上,顺序上应严格一致,否则会发生“类型不匹配”的错误。
  • 函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向的传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
  • 在该函数运行时,形参和实参是不同的变量,它们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。
  1. 值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象或者大的结构体对象,将耗费一定的时间和空间。(传值)
  2. 指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)
  3. 引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)
  4. 效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰。

猜你喜欢

转载自blog.csdn.net/weixin_42570248/article/details/88924106