值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
引用传递:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
看一个例子:
#include <iostream>
using namespace std;
//传值
void change1(int n)
{
cout<<"传值-函数操作地址:"<<&n<<endl;
n++;
}
//传引用
void change2(int &n)
{
cout<<"传引用-函数操作地址:"<<&n<<endl;
n++;
}
//传指针
void change3(int *n)
{
cout<<"传指针-函数操作地址:"<<n<<endl;
*n=*n+1;
}
int main()
{
int n=10;
cout<<"实参地址"<<&n<<endl;
change1(n);
cout<<"after change1 n="<<n<<endl;
change2(n);
cout<<"after change2 n="<<n<<endl;
change3(&n);
cout<<"after change3 n="<<n<<endl;
return 0;
}
输出:
实参地址0x7ffc78c7649c
传值-函数操作地址:0x7ffc78c7647c
after change1 n=10
传引用-函数操作地址:0x7ffc78c7649c
after change2 n=11
传指针-函数操作地址:0x7ffc78c7649c
after change3 n=12
1、三种方式传参:
1)引用的规则:
- 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
- 不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
- 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
2)指针传递的实质:
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。但是对于指针,形参、实参都操作一个地址,所以对形参的改变会影响到实参。
3)指针传递和引用传递一般适用于:
- 函数内部修改参数并且希望改动影响调用者。对比指针/引用传递可以将改变由形参“传给”实参(实际上就是直接在实参的内存上修改,不像值传递将实参的值拷贝到另外的内存地址中才修改)。
- 当一个函数实际需要返回多个值,而只能显式返回一个值时,可以将另外需要返回的变量以指针/引用传递给函数,这样在函数内部修改并且返回后,调用者可以拿到被修改过后的变量,也相当于一个隐式的返回值传递吧。
2、C/C++ 按值传递对象和按址传递对象区别:
1)按值传递对象:
在传递时候,会建立一个该对象的拷贝(调用复制构造函数);而从函数返回一个对象是,也要建立这个被返回的对象的一个拷贝。这样带来的内存开销非常大。
- 在传递的过程中会默认调用复制构造函数,创建一个临时某个对象的临时副本;
- 在函数返回时,传递该对象时创建的副本会被删除,这时候又会自动调用该对象的析构函数来释放内存;
- 将一个对象按值传递给一个函数,会调用两次复制构造函数和两次析构函数(一个给参数传值,一次返回值);
- 按址传递可以防止调用复制构造函数和析构函数,这样可以减小开销;
2)按址传递对象:
传递对象的实际地址,不用再创建对象,不会调用复制构造函数和析构函数,减小内存开销。
- 按址传递破坏了按值传递的保护机制,解决方式就是使用const指针;
- 使用const指针可以防止任何试图对该对象所进行的操作行为,并且保证返回一个不被修改的对象。
举一个例子:
三个例子均可以编译、运行成功。第一个需要调用Point两次构造、析构函数。