在c++中指针和引用的使用至关重要的,这两种在传值、函数使用过程中经常可以替换使用,最近在编写前辈留下的代码,发现对指针和引用的概念与使用又有点含糊,于是重新翻看了c++ primer plus,浏览网上的一些博客,下面是一些个人总结:
1.指针
(1)指针概念:这里引用c++primer plus里的解释:计算机存储数据的三个属性:信息存储在何处、存储的值是多少、存储的信息是什么类型。其中以变量名来表示是其中一种策略。另外一种则是使用指针。指针是一种变量,其存储的是值的地址,而不是值本身。指针通过地址操作符&和取值操作符*来获取地址和其指向的值。
(2)指针的声明与初始化:我们知道int地址与double的地址是一样的都只有4个字节(32位机器),但是int与double的字节数是不同的,导致指针指向的内部格式也不同。所以指针在声明时必须指定指针指向的数据类型。对于初始化则必须将指针指向一个确定的、适当的地址。
(3)指针的运算:我们知道将整型变量加1,其值将增加1;但是指针变量加1后,增加的量等于它指向的类型的字节数:如将double的指针加1,如果系统使用8个字节存储double变量,那么指针的值将增加8。int类型指针加1,则指针的值将增加4。
(4)指针的使用:一般指针表示某一个变量的地址,是为可以通过变量名直接访问的内存提供了一个别名。然而,指针的真正用武之地则是在运行阶段分配未命名的内存以存储值,这种情况下只能通过指针来访问这块内存。可以通过new、malloc等给存储值分配内存并赋值给指针,这样指针就占用了一块内存,在不使用的时候需要释放(delete、free)。但是如果使用指针时,没有及时释放或者两个指针指向一块内存时就会产生内存泄露或者野指针现象。因此指针的使用需要谨慎小心,具有危险性。
下面是一个例子表示了指针的基本用法:
int main()
{
/*指针初始化测试*/
int* p ;
*p =10; //初始化错误,10没有确定地址
int i = 10;
int* p = &i;
double k = 3.1415;
double* pd = &k;
/*指针长度测试*/
int pl= sizeof(p);
int vl = sizeof(*p);
cout<<"指向int指针的字节长度:"<<pl<<endl;
cout<<"int型数据的字节长度:"<<vl<<endl;
int pdl= sizeof(pd);
int vdl = sizeof(*pd);
cout<<"指向double的指针的字节长度:"<<pdl<<endl;
cout<<"double型数据的字节长度:"<<vdl<<endl;
/*指针加法移动长度*/
int* p1= p+1;
cout<<"指向int型数据的指针地址:"<<p<<endl;
cout<<"int型指针加1后的地址:"<<p1<<endl;
int paddr = (int)p;
int plue = (int)p1;
int len = plue - paddr;
cout<<"int型指针移动的字节长度"<<len<<endl;
double* pd1 = pd+1;
cout<<"指向double型数据的指针地址:"<<pd<<endl;
cout<<"指向double指针加1后的地址:"<<pd1<<endl;
paddr = (int)pd;
plue = (int)pd1;
len = plue - paddr;
cout<<"指向double指针移动的字节长度"<<len<<endl;
/*指针的使用*/
// 1.内存泄露测试
/*特此说明:这里以三次是否分配一个地址判断是否该地址在被占用(仅以此说明内存泄露。原理尚不清楚为什么)。*/
for (int i=0;i<3;i++)
{
int* ptest = new int;
*ptest = i;
cout<<"new分配分配之后释放,指针的值是:"<<ptest<<"\n";
delete ptest;
ptest = NULL;
}
for (int i=0;i<3;i++)
{
int* ptest = new int;
*ptest = i;
cout<<"new分配之后不释放,指针的值是:"<<ptest<<"\n";
}
//2.野指针测试
int* pTest = new int;
*pTest = 5;
int* pTest2 = pTest; //将pTest指针赋值给pTest2
cout<<"野指针测试-释放pTest指针之前ptest2的值是:"<<*pTest2<<"\n";
delete pTest;
pTest = NULL;
cout<<"野指针测试-释放pTest指针之后ptest2的值是:"<<*pTest2<<"\n";
system("pause");
return 0;
}
运行结果是:
从上图可以看出不释放会引起内存泄露。两个指针指向一个内存块容易引起野指针。
2.引用
(1)引用的概念:是c++新增的一种复合类型,同样引用c++primerplus的解释:引用是已定义变量的别名(另一个名称);&不是地址操作符,而是类型标识符的一部分,就像声明指针一样,char*表示指向char的指针,同样int&表示指向int的引用。引用与原变量名指向相同的值和内存单元。
(2)引用的创建与初始化:引用是已定义变量的别名,所以引用在创建时必须指定一个确定的变量,并始终关联。c++规范引用在声明时必须初始化,而且指向一个确定的变量。
(3)引用的作用:引用的主要用途则是用作函数的形参或返回值,通过将引用变量用作参数,函数将使用原始数据,而不是其拷贝。也就是引用传递。
下面程序表示了引用的基本用法:
#include <iostream>
void changeCount(int& count);
void ordinaryChange(int count);
using namespace std;
int main()
{
//引用声明时必须初始化
int count = 10;
int& numberinitial; //error C2530: 'numberinitial' : references must be initialized
numberinitial = count;
/*引用一旦初始化,则始终与初始化变量关联
即使存在出现赋值,也是两个变量一起变化,地址始终不变*/
int& number = count;
cout<<"count地址="<<&count<<";count的值="<<count<<"\n";
cout<<"number地址="<<&number<<";number的值="<<number<<"\n";
int account = 100;
number = account;
cout<<"account地址="<<&account<<";account的值="<<account<<"\n";
cout<<"\n将account赋值给number引用之后:"<<"\n\n";
cout<<"赋值之后:count地址="<<&count<<";count的值="<<count<<"\n";
cout<<"赋值之后:number地址="<<&number<<";number的值="<<number<<"\n";
cout<<"赋值之后:account地址="<<&account<<";account的值="<<account<<"\n\n";
//使用普通值传递赋值
ordinaryChange(count);
cout<<"count使用ordinaryChange函数(值传递)之后:"<<count<<"\n\n";
//使用引用传递赋值
changeCount(count);
cout<<"count使用changeCount函数(引用传递)之后:"<<count<<"\n";
system("pause");
return 0;
};
void changeCount(int& count)
{
cout<<"引用传递:函数内部count的地址="<<&count<<";count的值="<<count<<"\n";
count =20;
}
void ordinaryChange(int count)
{
cout<<"值传递:函数内部count的地址="<<&count<<";count的值="<<count<<"\n";
count =30;
}
程序运行结果:
①根据程序看出如果引用在声明时没有初始化,编译的时候会报:error C2530: 'numberinitial' : references must be initialized。
②根据运行结果,引用number初始化之后与count一样,表示同一个地址。实际上上述的初始化可以模拟为:int* const ptr = &count;,即ptr是常量指针,本身的值为常量。
③number引用初始化之后,可以再赋值,如程序将account赋值给number,并且复制成功number的值由10变成100。但是count的也变成了100,同时number与count的地址相同且一直没有变化,并不是account的地址。此次赋值使用指针模拟即为:*ptr = account;即此次赋值只改变了地址中的值。
④根据运行结果可以看出:**使用值传递实际上是传递的是count的一个副本,传入函数内部的变量count的地址已经变化,值没有变化。说明重新分配了内存空间。值传递可以模拟为:int counttest =count; counttest即为传入函数内部的变量。**使用引用传递则是传入的只是count的一个别名,根据运行结果可以看出传入函数内部的count的地址与值都没有变化。说明并没有重新分配内存,只是再次声明一个别名而已。引用传递可以模拟为:int& counttest = count; counttest即为传入函数内部的别名。
3.指针与引用的区别
通过上述介绍总结指针与引用的区别:
(1)初始化与赋值:指针声明与初始化可以分开操作;引用声明时必须初始化。
非常量指针可以通过赋值更改指向;引用通过赋值只能改变地址存储的值,不能改变引用的地址
(2)const结合使用:指针与const结合三种情况:常量指针、指针指向常量、指针与指向的值均为常量。具体请见上一遍博客:http://blog.csdn.net/chen956/article/details/50596109;引用与const的结合则是:const int& number = count; 表示该引用存储的值是个常量。不可以执行 number++等操作。实际上此时的number可以模拟为:const int* const ptr。
(3)sizeof使用:指针使用sizeof结果是指针本身的字节数量(32位系统一直是4);引用使用sizeof的结果则是引用存储的值的字节数量,如程序所示:
int main()
{
double salary = 10.5;
double* testsize =&salary;
double& testsize2 = salary;
cout<<"指针testsize的字节数量="<<sizeof(testsize)<<"\n";
cout<<"引用testsize2的字节数量="<<sizeof(testsize2)<<"\n";
system("pause");
return 0;
};
运行结果是:
(4)参数传递:在参数传递方面指针可以归结为值传递(地址值的传递),引用则为引用传递。具体区别见程序:
#include <iostream>
void SwapByPointer(int *a,int *b);
void SwapByReference(int &a,int &b);
void SwapByPointer2(int *a,int *b);
using namespace std;
int main()
{
int count = 10;
int account = 20;
int* pcount = &count;
int* paccount = &account;
cout<<"count的地址="<<&count<<";count的值="<<count<<endl;;
cout<<"account的地址="<<&account<<";account的值="<<account<<"\n\n";
SwapByReference(count,account);
cout<<"转换之后的count的地址="<<&count<<";count的值="<<count<<endl;;
cout<<"转换之后的account的地址="<<&account<<";account的值="<<account<<"\n\n";
SwapByPointer(&count,&account);
cout<<"转换之后的count的地址="<<&count<<";count的值="<<count<<endl;;
cout<<"转换之后的account的地址="<<&account<<";account的值="<<account<<"\n\n";
SwapByPointer2(&count,&account);
cout<<"转换之后的count的地址="<<&count<<";count的值="<<count<<endl;;
cout<<"转换之后的account的地址="<<&account<<";account的值="<<account<<"\n\n";
system("pause");
return 0;
};
void changeCount(int& count)
{
cout<<"引用传递:函数内部count的地址="<<&count<<";count的值="<<count<<"\n";
count =20;
}
void ordinaryChange(int count)
{
cout<<"值传递:函数内部count的地址="<<&count<<";count的值="<<count<<"\n";
count =30;
}
void SwapByReference(int &a,int &b){
cout<<"使用SwapByReference:"<<"\n";
cout<<"函数中的a的地址="<<&a<<endl;
cout<<"函数中的b的地址="<<&b<<endl;
int temp=a;
a=b;
b=temp;
}
void SwapByPointer(int *a,int *b){
cout<<"使用SwapByPointer:"<<"\n";
cout<<"函数中的a的地址="<<&a<<endl;
cout<<"函数中的b的地址="<<&b<<endl;
int temp=*a;
*a=*b;
*b=temp;
}
void SwapByPointer2(int *a,int *b){
cout<<"使用SwapByPointer2:"<<"\n";
cout<<"函数中的a的地址="<<&a<<endl;
cout<<"函数中的b的地址="<<&b<<endl;
int* temp=a;
a=b;
b=temp;
}
运行结果如下:
根据运行结果可以看出:引用传递不会创建新变量,只是创建一个别名;指针传递则本质上是值传递,传递的是一个地址值,该地址值赋给了一个临时指针变量a,就像 int* a = &count;最后一个交换函数实际上验证了值传递不会改变实参的值(函数中操作的指针本身的值,而不是指针指向的值,所以交换不成功)。
4. 使用环境
1.在普通参数传递过程中即可以使用指针也可以使用引用。
2.在拷贝构造函数与赋值函数中则是使用常量引用,因为使用拷贝构造函数和赋值函数的时候是传递的实参是一个对象而不是指针。也可以使用指针但是这时已经不能称他为拷贝构造函数或赋值函数了。
3.在一些单例模式下,为了保护唯一的一个实例,最好使用引用传递,避免使用指针传递,而被释放了。