`
weihe6666
  • 浏览: 440167 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

传值和传引用的区别

    博客分类:
  • C++
C++ 
阅读更多
传值和传引用的区别


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

一、引用可以减少巨大的开销

但是在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个析构函数,开销巨大。

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


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调用的。

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


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

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

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——传值实际上会比传引用更高效。


三、值传递和引用的传递效率测试

下面的例子是对值传递和引用传递在时间上的测试:编译环境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;
}


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

可以看出每一次的循环,值传递要比引用多调用一次默认构造函数和一次拷贝构造函数和一次析构函数,可见用引用可以减少不必要的调用,提高其效率。
分享到:
评论

相关推荐

    C++传值调用与引用调用区别实例代码

    当一个函数被调用时,可以通过不同的方式传递参数,其中最常见的是传值(call by value)和传引用(call by reference)两种方法。这两种方式在内存管理、性能影响以及数据修改能力上有着显著的不同。 #### 1. 传值调用...

    php传值和传引用的区别点总结

    在PHP编程中,理解变量的传值和传引用是非常重要的概念,它们决定了函数内部操作对原始变量的影响。下面我们将详细探讨这两个概念的区别、工作原理以及优缺点。 **1. PHP传值** 当一个变量作为参数传递给函数时,...

    php 传值赋值与引用赋值的区别

    传值赋值:当将一个表达式的值赋予一个变量时,整个原始表达式的值被赋予到目标变量。这意味着,例如,当一个变量的值赋予另一个变量时,改变其中一个变量的值,将不会影响到另一个变量。 复制代码 代码如下: &lt;?...

    通过5个php实例细致说明传值与传引用的区别

    在PHP编程语言中,理解变量传值和传引用的区别对于写出高效和正确的代码是非常关键的。传值和传引用在程序执行过程中的行为有所不同,这主要体现在变量之间传递数据时,对原变量的影响程度上。 首先,我们来看传值...

    java的传值与传值后的改变

    3. **传值与传引用的区别**: - 原始类型传值时,函数内部的操作不会影响到外部变量。 - 引用类型传值时,函数可以修改对象的内容,但不能改变对象的引用。 4. **局部变量与成员变量**: 函数内部的变量是局部...

    c#几个区别传值和传址的好例子

    在C#编程中,了解和区分传值与传址的概念至关重要,因为这直接影响到函数调用时参数的处理方式,从而影响程序的行为。下面通过两个示例来详细讲解这两个概念。 **传值(Pass by Value)** 在C#中,基本类型(如int...

    传值 修改json 相似

    3. 传值和传引用的区别,以及在函数调用中如何传递JSON对象。 4. 操作JSON对象的方法,如`JSON.parse()`和`JSON.stringify()`。 5. 如何比较两个JSON对象的相似性。 通过以上的讨论,你可以更好地理解和应用“传值...

    C语言中传值与传指针的介绍与区别

    在C语言中,函数参数的传递主要有两种方式:传值(Pass by Value)和传指针(Pass by Pointer)。这两种方式在处理数据时有显著的差异,了解它们的区别对于编写高效且可控的C语言程序至关重要。 **传值**是C语言中...

    引用参数和传值参数的区别深入解析

    1. 我们都知道,普通的传值参数是由行参传给实参; 编译器在函数内部为每一个参数产生一个临时变量,将每一个参数压入栈(stack)中,将实参的数值保存到临时变量中。 所以才有在低端8位机中,对参数的数量有比较...

    php传值赋值和传地址赋值用法实例分析

    PHP传值赋值和传地址赋值是PHP编程中的两种基本赋值方式,它们在内存管理和变量使用上有明显的区别。理解这两种赋值方式对于编写高效且错误少的PHP代码是至关重要的。 首先,让我们明确什么是传值赋值和传地址赋值...

    java及C++中传值传递、引用传递和指针方式的理解

    C++中,指针和引用都可以用来实现对原始对象的修改,但它们之间存在细微差别。指针需要显式解引用,而引用则是隐式的。此外,指针可以为`NULL`,而引用必须始终引用一个有效的对象。指针还可以被重新赋值,指向其他...

    java中参数传递的演示

    java中参数传递的演示,分析传值与传引用的区别

    经典Java基础面试题.docx

    本文将对经典Java基础面试题进行总结,涵盖从main方法、私有变量、传值和传引用、equals方法、hashCode、Java的平台独立性、public static void main(String args[])声明、==和equals的区别、finalize方法、Java API...

    java类

    本文将通过一个具体的例子来深入探讨Java中的传值与传引用的区别,并解释如何利用这一特性来更好地管理程序的状态。 #### 1. 传值与传引用的概念 在Java中,基本数据类型(如int、char等)的参数传递遵循“传值”...

    php程序员面试题(附答案).pdf,这是一份不错的文件

    1. 传值与传引用的区别 在 PHP 中,传值和传引用是两种不同的参数传递方式。传值是将某一个变量的值传给另一个变量,而传引用则是将两者指向同一个地方。在实际开发中,需要根据具体情况选择合适的参数传递方式。 ...

    C语言函数调用

    理解C语言中的函数调用机制,尤其是传值、传址和传引用的区别,对于编写高效、可维护的代码至关重要。传值适用于不需要修改外部状态的情况;传址和传引用则在需要直接操作外部数据时更为合适。掌握这些概念将帮助...

    Java-Java面向对象中引用传递教程

    1. **传值与传引用的区别** - **传值**:基本数据类型(如int、char等)的参数传递是值传递,也就是说,方法内部对参数的修改不会影响到方法外部的变量。 - **传引用**:对于对象,Java总是采用引用传递。这意味着...

Global site tag (gtag.js) - Google Analytics