传值和传引用的区别

[size=large][b][align=center]传值和传引用的区别[/align][/b][/size]

在C语言中,大都是通过值传递,C++也是继承了这一传统,C++里默认都是值传递,除非明确指出。

[b]一、引用可以减少巨大的开销[/b]

但是在C++中值传递即”实参的拷贝“有时会带来很大的开销,看下面的例子:

#include <iostream>
using namespace std;

class person {
public:
person(){cout << "Call the Base Constructor!\n";} // 为简化,省略参数
~person(){cout << "Call the Base Destructor!\n";}
person(const person &temp) //Copy Constructor
{
cout << "Call The Base Copy Constructor !\n";
}
private:
string name, address;
};

class student: public person {
public:
student(){cout << "Call the Inherit Constructor!\n";} // 为简化,省略参数
~student(){cout << "Call the Inherit Destructor!\n";}
student(const student &temp) //Copy Constructor
{
cout << "Call The Inherit Copy Constructor !\n";
}
private:
string schoolname, schooladdress;
};

student returnstudent(student s)
{
cout << "Call returnstudent function !\n";
return s;
}

int main()
{
student plato; // plato(柏拉图)在
// socrates(苏格拉底)门下学习
returnstudent(plato); // 调用returnstudent
}


输出结果为:
Call the Base Constructor!
Call the Inherit Constructor!
Call the Base Constructor!
Call The Inherit Copy Constructor !
Call returnstudent function !
Call the Base Constructor!
Call The Inherit Copy Constructor !
Call the Inherit Destructor!
Call the Base Destructor!
Call the Inherit Destructor!
Call the Base Destructor!
Call the Inherit Destructor!
Call the Base Destructor!

一个简简单单的函数,开销竟然这么大,那么这些函数是何时如何被调用的?

1.student plato; 这句需要调用默认构造函数初始化对象,由于student继承于person(:class student: public person),首先调用基类的构造函数,在调用子类构造函数:Call the Base Constructor!Call the Inherit Constructor!

2.returnstudent(plato); 这句首先要调用基类和子类的构造函数初始化类s,然后再调用子类的拷贝构造函数初始化s(s = plato);Call the Base Constructor!Call The Inherit Copy Constructor !Call returnstudent function !

3.return s;这句首先要调用基类的构造函数初始化返回值类的变量,然后再调用子类的赋值构造函数,将函数返回值对象初始化为s;Call the Base Constructor!Call The Inherit Copy Constructor !

4.returnstudent(student s)函数结束后首先调用子类的析构函数,然后再调用基类的析构函数,其顺序为:s的析构函数被调用,returnstudent返回值对象的析构函数被调用,plato的析构函数被调用。

总共6个构造函数,6个析构函数,开销巨大。

[b]如何避免和巨大的开销:
为避免这种潜在的昂贵的开销,就不要通过值来传递对象,而要通过引用:[/b]

const student& returnstudent(const student& s)
{ return s; }

其输出结果为:
Call the Base Constructor!
Call the Inherit Constructor!
Call returnstudent function !
Call the Inherit Destructor!
Call the Base Destructor!

这里的构造函数和析构函数只是对象plato调用的。

这会非常高效:没有构造函数或析构函数被调用,因为没有新的对象被创建。


[b]二、通过引用来传递参数还有另外一个优点:它避免了所谓的“切割问题(slicing problem)”。[/b]

当一个派生类的对象作为基类对象被传递时,它(派生类对象)的作为派生类所具有的行为特性会被“切割”掉,从而变成了一个简单的基类对象。这往往不是你所想要的。例如,假设设计这么一套实现图形窗口系统的类:


class window {
public:
string name() const; // 返回窗口名
virtual void display() const // 绘制窗口内容
{
cout << "Call Base diaplay() ! \n";
}
};

class windowwithscrollbars: public window {
public:
virtual void display() const
{
cout << "Call Inherit diaplay() ! \n";
}
};

void printnameanddisplay(window w)
{
w.display();
}

int main()
{
windowwithscrollbars wwsb;

printnameanddisplay(wwsb);

}


其输出为:Call Base diaplay() !

也就是子类的diaplay()函数的功能被”切割“了,wwsb以值得方式传进去会变为基类的对象而不是子类的对象,当然子类的功能也将被”切割“。

但是若利用引用:

void printnameanddisplay(const window &w)
{
w.display();
}


其输出结果为:Call Inherit diaplay() !

参数w将会作为一个windows对象而被创建(它是通过值来传递的,记得吗?),所有wwsb所具有的作为windowwithscrollbars对象的行为特性都被“切割”掉了。printnameanddisplay内部,w的行为就象是一个类window的对象(因为它本身就是一个window的对象),而不管当初传到函数的对象类型是什么。尤其是,printnameanddisplay内部对display的调用总是window::display,而不是windowwithscrollbars::display。

解决切割问题的方法是通过引用来传递w:

// 一个不受“切割问题”困扰的函数
void printnameanddisplay(const window& w)
{
cout << w.name();
w.display();
}

现在w的行为就和传到函数的真实类型一致了。为了强调w虽然通过引用传递但在函数内部不能修改,就要将它声明为const。

传递引用是个很好的做法,但它会导致自身的复杂性,最大的一个问题就是别名问题。另外,更重要的是,有时不能用引用来传递对象。最后要说的是,引用几乎都是通过指针来实现的,所以通过引用传递对象实际上是传递指针。因此,如果是一个很小的对象——例如int——传值实际上会比传引用更高效。


[b]三、值传递和引用的传递效率测试[/b]

下面的例子是对值传递和引用传递在时间上的测试:编译环境VS2008

#include <iostream>
using namespace std;

#include <stdio.h>
#include <time.h>

class test {
friend test foo( double );
friend void Temp_Foo(test &,double);
public:
test()
{ memset( array, 0, 100*sizeof( double )); }
test( const test &t );

private:
double array[ 100 ];
};

inline test::test( const test &t )
{
memcpy( this, &t, sizeof( test ));
}

test foo( double val )
{
test local;

local.array[ 0 ] = val;
local.array[ 99 ] = val;

return local;
}

void Temp_Foo(test & temp,double val)
{
temp.array[0] = val;
temp.array[99] = val;
}

void PrintLocalTime()
{
struct tm *timeptr;
time_t secsnow;

time(&secsnow);
timeptr = localtime(&secsnow);

printf("Local time is %d-%d-%d\n",timeptr->tm_hour,timeptr->tm_min,timeptr->tm_sec);

}
int main()
{
PrintLocalTime();

for ( int cnt = 0; cnt < 1000000000; cnt++ )
{
test t = foo( double( cnt ));
}

PrintLocalTime();

for ( int cnt = 0; cnt < 1000000000; cnt++ )
{
test t;// = foo( double( cnt ));
Temp_Foo(t,double(cnt));
}

PrintLocalTime();
return 0;
}


输出为:
Local time is 16-15-45
Local time is 16-22-8
Local time is 16-25-6

有输出结果可以看出:
1.利用值传递的时间是:15-45到22-8,约为443秒
2.利用引用的时间是:22-8到25-6,约为178秒

可见效率有很大的提高。

那么现在分析一下值传递和引用的不同:

1.值传递的代码,编辑器会在背后做哪些动作呢?

test t = foo( double( cnt ));
test foo( double val )
{
test local;

local.array[ 0 ] = val;
local.array[ 99 ] = val;

return local;
}


上面的代码编辑器会这样扩充:

test t;
foo(test &t, double( cnt ));

test foo( test & _result, double val )
{
test local; //调用默认构造函数

local.array[ 0 ] = val;
local.array[ 99 ] = val;

_result.test::test(const test & local);//调用test的Copy Constructor
local.test::~test();//调用test的析构函数
return ;
}


2.引用的代码,编辑器会在背后做哪些动作呢?

test t;
Temp_Foo(t,double(cnt));
void Temp_Foo(test & temp,double val)
{
temp.array[0] = val;
temp.array[99] = val;
}


上面的代码编辑器没有对其进行扩充。

[b]可以看出每一次的循环,值传递要比引用多调用一次默认构造函数和一次拷贝构造函数和一次析构函数,可见用引用可以减少不必要的调用,提高其效率。[/b]
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值