函数参数传递机制

1 函数参数传递机制

函数参数传递机制问题本质上是调用函数和被调用函数在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。

在值传递过程中, 被调用的函数(简称被调函数)的形式参数(简称为形参)作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由调用其他函数的函数(简称为主调函数)放进来的实参值,从而称为了实参的一个副本。值传递的特点是被调函数对形参的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。

在引用传递过程中, 被调函数的形参对然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

2 值传递、址传递和引用传递的区别

广义上来说址传递是值传递的一种形式,即值传递包括址传递。

2.1 功能上

在值传递的过程中,实参被复制了一份,然后在函数体内使用,函数体内修改参数变量时修改的是实参的一份拷贝, 而实参本身是没有改变的,因此如果想在被调函数中修改实参的值,使用值传递是不能达到目的的,这时只能使用引用或者指针传递。

void swap1(int x,int y)
{
    int temp=x;
    x=y;
    y=temp;
}

void swap2(int *px,int *py)
{
    int temp=*px;
    *px=*py;
    *py=temp;
}
int main(int argc,char *argv[])
{
    int a=3,b=5;
    swap1(a,b);  //传值,a和b的值没有互换
    swap2(&a,&b); //传址,a和b的值互换
    return 0;
}

函数调用过程发生了什么呢? 在函数调用的过程中,系统在栈上为被调函数的局部变量(包括形参)分配地址空间,在现代操作系统中,参数的传递一般是通过寄存器直接传递,而不是栈上传递,只有当参数超过寄存器承受范围时,使用栈传递参数,之所以将参数存在寄存器是因为直接操作寄存器比操作内存效率要高,因此将寄存器中实参的值传递给形参。这里,我们知道了形参和实参是两个地址独立的变量,参数传递事实上是变量值的传递,即传值。很多人提到参数传递有传值和传址两种方式,但是事实上传址是传值的一种形式,本质上传址传的是指针变量的值,即地址值,也是一种值传递。

swap1(a,b);调用了该函数后,系统在栈上为形参xy分配了内存,并在函数内部进行值互换,函数调用完后,xy被销毁,在内部的值互换行为根本没有意义,因为ab根本没有参与到函数执行中来。

swap2(&a,&b);调用了该函数后,系统为pxpy分配空间(pxpy为指针类型),然后将ab的地址赋值给pxpy,函数内部互换的是指向的地址存放的变量,函数结束,pxpy被销毁,此时a,b的值已进行交换。

2.2 传递效率上

这里说的传递效率是 指调用被调函数的代码将实参传递到被调函数体内的过程, 正如上面代码中,这个过程就是函数 main()中的 a, b 传递到函数 swap()中的过程。

这个效率不能一概而论。对于内建的 int,char, short, long, float等4字节或以下的数据类型而言,实际上传递时也只需要传递 1-4 个字节,而使用指针传递时在32位cpu中传递的是32位的指针,4 个字节,都是一条指令,这种情况下值传递和指针传递的效率是一样的,而传递doublelong long等8字节的数据时,在 32 位 CPU 中,其传值效率比传递指针要慢,因为8个字节需要2次取完。而在 64 位的 CPU 上,传值和传址的效率是一样的。

再说引用传递 ,这个要看编译器具体实现,引用传递最显然的实现方式是使用指针,这种情况下与指针的效率是一样的,而有些情况下编译器是可以优化的,采用直接寻址的方式,这种情况下,效率比传值调用和传址调用都要快,与上面说的采用全局变量方式传递的效率相当。

再说自定义的数据类型classstruct 定义的数据类型。这些数据类型在进行传值调用时生成临时对象会执行构造函数,而且当临时对象销毁时会执行析构函数,如果构造函数和析构函数执行的任务比较多,或者传递的对象尺寸比较大,那么传值调用的消耗就比较大。这种情况下,采用传址调用和采用传引用调用的效率大多数下相当,正如上面所说,某些情况下引用传递可能被优化,总体效率稍高于传址调用。

2.3 执行效率上

这里所说的执行效率,是指在被调用的函数体内执行时的效率。因为传值调用时,当值被传到函数体内,临时对象生成以后,所有的执行任务都是通过直接寻址的方式执行的,而指针和大多数情况下的引用则是以间接寻址的方式执行的,所以实际的执行效率会比传值调用要低。如果函数体内对参数传过来的变量进行操作比较频繁,执行总次数又多的情况下,传址调用和大多数情况下的引用参数传递会造成比较明显的执行效率损失。

综合传递效率和执行效率两种情况,具体的执行效率要结合实际情况,通过比较传递过程的资源消耗和执行函数体消耗之和来选择哪种情况比较合适。而就引用传递和指针传递的效率上比,引用传递的效率始终不低于指针传递,所以从这种意义上讲,在 C++ 中进行参数传递时优先使用引用传递而不是指针

2.4 类型安全上

值传递与引用传递在参数传递过程中都执行强类型检查,而指针传递的类型检查较弱,特别地,如果参数被声明为void ,那么它基本上没有类型检查,只要是指针,编译器就认为是合法的,所以这给bug的产生制造了机会,使程序的健壮性稍差,如果没有必要,就使用值传递和引用传递,最好不用指针传递,更好地利用编译器的类型检查,使得我们有更少的出错机会,以增加代码的健壮性。

这里有个特殊情况,就是对于多态的情况,如果形参是父类,而实参是子类,在进行值传递的时候,临时对象构造时只会构造父类的部分,是一个纯粹的父类对象,而不会构造子类的任何特有的部分,因为只有虚的析构函数,而没有虚的构造函数,这一点是要注意的。如果想在被调函数中通过调用虚函数获得一些子类特有的行为,这是不能实现的。

2.5 参数检查上

一个健壮的函数,总会对传递来的参数进行参数检查,保证输入数据的合法性,以防止对数据的破坏并且更好地控制程序按期望的方向运行,在这种情况下使用值传递比使用指针传递要安全得多,因为你不可能传一个不存在的值给值参数或引用参数,而使用指针就可能,很可能传来的是一个非法的地址(没有初始化的指针,或指向已经delete掉的对象的指针等)。所以使用值传递和引用传递会使你的代码更健壮,具体是使用引用还是使用值,最简单的一个原则就是看传递的是不是内建的数据类型,对内建的数据类型优先使用值传递,而对于自定义的数据类型,特别是传递较大的对象,那么请使用引用传递。

2.6 灵活性上

无疑,指针是最灵活的,因为指针除了可以像值传递和引用传递那样传递一个特定类型的对象外,还可以传递空指针,不传递任何对象。指针的这种优点使它大有用武之地,比如标准库里的 time() 函数,你可以传递一个指针给它,把时间值填到指定的地址,你也可以传递一个空指针而只要返回值。

3 函数传递该用指针还是引用

函数传递该用指针还是引用,遵守一个大的方向:在引用和指针都可以用的情况下,尽量使用引用避免使用指针。一是引用传递的执行效率不低于指针传递;二是使用引用比指针更加安全。 以下根据不同情形需要具体使用:

  1. 如果传参需要比较灵活的话建议选择指针传递。

  2. 如果你的参数需要传递数组的话,你只能使用指针。

  3. 函数不是构造函数,且参数是只读,推荐使用 const引用。一方面如果用指针,调用者可能会传 nullptr作为参数,使用起来不安全;另一方面使用 const引用可以避免参数拷贝的开销。

  4. 函数不是构造函数,且参数作为输出参数,用指针。因为指针参数在调用的时候会比引用要明显一些,如下

int fun(val);
int fuc(&val);

前者比较难以看出是传递引用还是直接传递值,而第二个很明显是传递指针。

  1. 函数不是构造函数,且不是只读,但也不是输出参数,用引用。这种情况用引用比用指针好是引用可以防止不小心传nullptr这种情况。

  2. 函数是构造器,但是参数并不参与组成构造类的一部分,那就参考以上几点。因为如果参数不参与构造的话其实跟不是构造函数的情况一样。

  3. 函数是构造器,且参数参与构造,且参数是可移动的类型,传值。比如:

class person {
  public:
    person(std::string name) : name_{std::move(name)} {}
  private:
    std::string name_;
};

传值比传const引用好处在于,取决于用户是否需要保留原参,可以省略复制。比如,

Person p{"Jack"}; // 创建一个临时变量,如果一个move;多半情况下编译器会直接创建在Person里
std::string name{"John"};
Person p1{name}; // 这种情况跟用const引用一样,都是一次copy
Person p2{std::move(name)}; // 因为被转化成了右值,name会被直接移动到p2里,省掉一次copy
  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值