CPU型号:Intel(R) Core(TM) i5-2450M
系统:windows 10
IDE:Microsoft Visual C++ 6.0(下文中简称VC)
制图软件:Photoshop cs5
首先介绍下概念:
原变量:原变量即实参,引用、指针、传值都是指一个函数对应的形参
引用:即pass by reference,引用是某一变量的别名,即引用和被引用的变量的地址为同一个地址,对引用的操作与对变量的操作完全一样(要强调的一点,引用是C++中的概念,在.c中编译是编译不过的)
指针:指针的大小由当前CPU的寻址位数决定的(因此常见为32位即4个字节),指针本身是一个地址,地址里面的值为一个指向内存的存储单元,即一个地址
传值:即pass by value,传值是最简单的传递值的方式,它会在被调用函数中生成一个变量副本,作用范围为该函数体
共同点:三者可以作为传递值来使用,实际上也都是一个地址
不同点:引用和原变量为同一个地址;指针多声明了一个变量(指针本身,局部变量),但是指针的值为原变量;传值也多声明一个变量(传值本身,局部变量)
接下来用代码来说明三者作为函数参数传递时的联系和区别:
#include <iostream>
using namespace std;
void PassByValue(short value)
{
++value;
cout<<"PassByValue:Addr is 0x"<<&value<<",sizeof(value)="<<sizeof(value)<<",value is "<<value<<endl<<endl;
}
void PassByPointer(short *value)
{
++*value;
cout<<"PassByPointer:Addr is 0x"<<&value<<",sizeof(value)="<<sizeof(value)<<",value is "<<value<<",*value="<<*value<<endl<<endl;
}
void PassByReference(short &value)
{
value++;
cout<<"PassByReference:Addr is 0x"<<&value<<",sizeof(value)="<<sizeof(value)<<",value is "<<value<<endl<<endl;
}
void main(void)
{
short n = 5;
cout<<"Main:Addr is 0x"<<&n<<",value is "<<n<<endl<<endl;
PassByValue(n);
cout<<"Main(After PassByValue):Addr is 0x"<<&n<<",value is "<<n<<endl<<endl<<endl;
PassByPointer(&n);
cout<<"Main(After PassByPointer):Addr is 0x"<<&n<<",value is "<<n<<endl<<endl<<endl;
PassByReference(n);
cout<<"Main(After PassByReference):Addr is 0x"<<&n<<",value is "<<n<<endl<<endl<<endl;
}
执行结果如下:
从红色框部分可以看出,原变量n的地址为0x0019FF3C,值为5。
经过传值调用后,传值的地址为0x0019FEEC,不再是原变量的地址,对传值的值进行改变,并不会对原变量产生影响。
经过指针调用后,指针的地址为0x0019FEEC,其值为0x0019FF3C,值为原变量的地址,而为了改变原变量的值,必须通过加取值符*才能对原变量进行改变。
经过引用调用后,引用的地址为0x0018FF3C,跟原变量为同一个地址,改变引用的值当然可以改变原变量的值。
(传值和指针的地址一样的原因是因为传值和指针都是局部变量,并且是它们的内存空间是由系统中的栈自行申请及释放的,也就是超出作用域就被释放。虽然它们地址相同,但是意义不同,即所占有该地址的时间不同)
由此可以做出结论:
(1)在作为函数参数调用中,引用即省空间又省时间(声明和定义变量需耗费额外的内存和CPU计算时间)
(2)引用本质上就是原变量,只是名字不一样而已,可以直接使用;指针的值才是原变量的地址,该改变原变量的值必须通过取值符*后才能改变
(3)对形参进行sizeof()操作,就会发现传值和引用的大小就是原变量的类型大小,而指针的大小就是指针的大小,不是原变量的类型大小(代码中将原变量的类型定义为short,就是为了和指针的大小区分开来)
如要要做进一步的延伸,就可以讨论指针的特性和地址(引用本质上就是一个地址)的区别:
(4)存在多级指针,但不存在多级引用(多级引用毫无意义,因为引用本身就是个地址,对地址取地址毫无意义)
(5)指针可以指向任何地址,但是引用必须指向用户可使用的内存地址(换句话说就是指针灵活,但是破坏性也大,而引用只能引用已经定义的除了指针变量,数组外的变量,且不能直接对地址引用)
通俗点讲,指针可以指向任何地址,包括NULL,(其实NULL的定义为#define NULL 0),而一般情况下我们把指向NULL的指针理解为空指针,实际上这并不是安全的做法!指针强大但是也很危险!
#include <iostream>
using namespace std;
void main(void)
{
int *p=NULL;
int *q=(int *)0x00000011; //指向绝对地址0x00000011,需强制转换
// cout<<*p<<endl;
cout<<*q<<endl;
}
编译时不会出错,运行时就完蛋了。
而引用只能指向已定义的非指针、数组变量的变量,且不能直接对地址进行引用(NULL也算一个地址)。
#include <iostream>
using namespace std;
void main(void)
{
int a;
int *p = &a;
// int &q = a;
int &q = p; //对指针引用,报错
// int &q = 0x00000011; //对地址引用,报错
}
(6)指针声明时可以不用初始化,引用声明时必须初始化
#include <iostream>
using namespace std;
void main(void)
{
int *p;
int &q;
}
运行会报错:
error C2530: 'q' : references must be initialized
#include <iostream>
using namespace std;
void main(void)
{
int a = 1, b = 2 ;
int *p = &a;
int &q = a;
cout<<"a addr:"<<&a<<",b addr:"<<&b<<endl<<endl;
cout<<"p addr :"<<p<<",value:"<<*p<<endl;
p = &b;
cout<<"p addr :"<<p<<",value:"<<*p<<endl<<endl;
cout<<"q addr :"<<&q<<",value:"<<q<<endl;
q = b;
cout<<"q addr :"<<&q<<",value:"<<q<<endl;
}
由结果可知,指针重新指向后可以改变其地址,但是引用就无法做到,地址无法改变。这里可以大胆猜想,引用只有在声明定义的时候才能决定它的地址,之后的操作都只是赋值。