指针传递和值传递
在C语言程序中,我们一般只关注变量的值的变化,不会去关注变量的地址。由此带来的问题是对以指针作为参数的函数与以变量作为参数的函数搞不清所以然,这几天正在看指针,思考了怎样去区分指针传递地址和值传递。
下面以三个简单的程序来说明这个问题。
一、值传递:
#include "iostream"
using namespace std;
main()
{
int swap(int ,int );
inta,b;
cin>>a>>b;
if (a<b)
swap(a,b);
cout<<a<<" "<<b<<endl;
}
int swap(intx,int y)
{
int temp;
temp = x;
x = y;
y = temp;
cout<<x<<" "<<y<<endl;
}
二、指针传递地址:
#include "iostream"
using namespace std;
main()
{
int swap(int *,int *);
int * p1,* p2,*p,a,b;
cin>>a>>b;
p1 = &a;
p2 = &b;
if (a<b)
swap(p1,p2);
cout<<a<<" "<<b<<endl;
cout<<*p1<<" "<<*p2<<endl;
}
int swap(int * p1,int * p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
我们知道,除非使用return语句,值传递是不能改变主函数中的变量的值的。而使用后面的程序用指针来传递地址才可以改变主函数中的变量的值。下边讲一下原因:
首先将以指针的地址传递,swap函数中的形式参数是两个指针,实际参数是变量a和变量b的地址。所以,
以上语句中改变的是以P1和p2为地址的变量的内容,先假定变量a的地址为2000,变量b的地址为3000,即p1=2000,p2=3000.所以以上语句中改变的就是变量a和b的内容。
再看值传递:值传递是将a值传递给x,将b的值传递给y,而x和y在函数的编译过程中是临时的变量,函数执行完毕,x和y的内存空间就被其他变量所覆盖,所以,x和y在内存中地址与a和b在内存中的地址是不同的,所以在函数调用中,改变的是以x和y的地址为内存单元的内容,假设x和y的地址为5000,6000.
int temp;
temp = x;
x = y;
y = temp;
上述语句交换的是地址单元5000和6000上的内容,当然不会改变a和b的值。
三、C++引用与指针的比较
引用是C++中的概念,初学者容易把引用和指针混淆一起。
一下程序中,n是m的一个引用(reference),m是被引用物(referent)。
int m;
int &n = m;
n相当于m的别名(绰号),对n的任何操作就是对m的操作。
所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。
l 引用的规则:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象),这里指不能再绑定其他的对象,只要不是const,就可以改变值,即可以通过引用改变绑定对象的值,因为它们其实是相同的。
以下示例程序中,k被初始化为i的引用。语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。由于k是i的引用,所以i的值也变成了6。
int i = 5;
int j = 6;
int &k = i;
k = j; // k和i的值都变成了6;
引用的主要功能是传递函数的参数和返回值。
#include <iostream>
using namespace std;
void swap(int &i, int &j){
i^=j;
j=i^j;
i=i^j;
cout<<"the call function: "<<i<<"\t"<<j<<endl;
}
void print( const int &l){//如果形参是const,实参both。如果实参const,说明实参不能被改动,则形参必须const。
cout<<"l= "<<l<<endl;
}
void main(){
int i=3,j=5;
swap(i,j);
const int k=6;//定义的同时必须初始化,const int k;k=6;报错
int &m=i;//引用定义的同时必须初始化,而且要关联到某一类型。int &m=3报错
double b=3.0;
double &a=b;
//cout<<sizeof(a)<<endl;//引用的sizeof指向其绑定的对象的大小
char *r;
//cout<<sizeof(r)<<endl;//指针都是4个字节
float array[12];
cout<<sizeof(array)<<endl;//数组的sizeof就是本身类型占用字节数*个数
//error :&m=j;引用一旦初始化后不可再绑定其他对象
double n=1.2;
const int &t=n;//const类型的引用可以绑定到其他类型,而非const类型的引用不可以。。。
// print(k);
// cout<<"the main function: "<<i<<"\t"<<j<<endl;
// cout<<"k= "<<k<<endl;
}
//总结:一般变量想被外部文件使用,直接在引用的地方加上extern就ok了。const变量不行,如果想外部引用,初始化的地方加extern,引用的地方也要加
2 引用与参数调用
首先说参数调用,参数调用做了2件事情,用对应的实参初始化函数的形参,并将控制权转移给被调用函数,主调函数的执行被挂起,被调函数开始执行。函数的运行以形参的隐式定义和初始化开始。执行到return的时候,被调函数完成,主调函数在调用处开始恢复执行,并将函数的返回值用作求解调用操作符的结果,继续处理在执行调用的语句中的剩余工作。
参数传递
第一种传递方式:值传递
值传递需要复制对应的实参实现初始化,当用实参副本初始化形参时,函数并没有访问调用函数所传递的实参本身,对实参副本的修改也是局部的,函数调用结束就结束了,因此不会修改实参的值。
引用传递:
因为复制实参并不是在所有的情况下都适合,不适合复制实参的情况包括:
当需要在函数中修改实参的值时;
当需要以大型对象作为实参传递时,对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大
当没有办法实现对象的复制时
引用形参直接关联到绑定的对象而非对象的副本,必须使用与该引用绑定的对象初始化该引用。引用形参完全以相同的模式工作。在c语言中,人们习惯用指针,在c++中使用引用。
使用引用形参返回额外的信息。这个跟上面有些类似,只不过说如果你想要多返回些东西,但是直接又没法返回,那么就借助引用好了,通过引用改变改变实参的值。
利用const引用避免复制。这个主要是大型对象的时候,复制代价比较高。比如string。。。
很多时候如果不需要改变值,就加上const。
double fl=1.2;
const double *pl=&fl;//这个是正确的,赋值初始化
*pl=4.3;//error:因为已经是const类型的了,不允许改变值,不过指针pl还是可以指向其他对象的。