最近在用VC6做一个项目,有的弟兄反映不太理解指针和引用的区别。从目前现况来看,现在的开发人员用惯了Java,C#等语言,距离操作系统底层越来越远了,以至于大部分人对C/C++中的一些东西早已淡忘。
说到指针,凡是学过c语言的人都不陌生,这是是c语言的精华所在。指针操作不仅高效,而且非常接近系统底层,容易掌控。所以凡是使用c/c++编码的技术人员,个人建议是首先花精力把指针搞清楚。当然,事情总是具有两面性,有利必有弊,指针操作最为诟病的就是它的不安全性。除非你对它理解很透彻,否则难免会出现访问越界、内存泄露以及不可预知的结果。
“引用”是C++中引入的概念,我们经常会看到“引用”应用在函数调用过程中。在C++语言中,调用一个函数时,参数传递可以采用3种方式:传值、传址和传引用。
比如下面一段VC代码:
//主程序代码
CString strUserId = "001";
CString strUserName = "无名";
BOOL bFind = FindUserName(strUserId, strUserName);
if( bFind )
{
MessageBox("User Name:" + strUserName);
}
else
{
MessageBox("No found!");
}
//查找用户函数
BOOL FindUserName(CString& strUserIdParam, CString& strUserNameParam)
{
if( strUserIdParam == "001" )
{
strUserNameParam = "王泽宾";
return TRUE;
}
return FALSE;
}
FindUserName函数中使用了两个引用型参数。主程序代码最终的运行结果是:弹出一个对话框,内容是“User Name:王泽宾”。函数中的“CString &”,表示参数使用“传引用方式”。FindUserName中的strUserNameParam与主程序代码中的strUserName是同一个对象,所以修改他们中的任何一个,都会反映到另外一个。strUserNameParam只不过是strUserName的别名而已,这非常类似于windows系统中文件的快捷方式,或者linux系统中文件的符号链接。
如果把查找用户函数修改为:
//查找用户函数
BOOL FindUserName(CString strUserIdParam, CString strUserNameParam)
{
if( strUserIdParam == "001" )
{
strUserNameParam = "王泽宾";
return TRUE;
}
return FALSE;
}
FindUserName函数中使用了传值参数。主程序代码最终的运行结果是:弹出一个对话框,内容是“User Name:无名”。函数中的“CString”,表示参数使用“传值方式”。
FindUserName中的strUserNameParam与主程序代码中的strUserName是完全不同的两个对象,所以修改他们中的任何一个,都不会反映到另外一个上。strUserNameParam的内容复制了一份strUserName的内容,所以strUserNameParam又称为strUserName的副本,这个复制操作是在函数调用过程中参数压栈时执行的。
如果把查找用户函数修改为:
//查找用户函数
BOOL FindUserName(CString *pstrUserIdParam, CString *pstrUserNameParam)
{
if( *pstrUserIdParam == "001" )
{
*pstrUserNameParam = "王泽宾";
return TRUE;
}
return FALSE;
}
那么主程序代码中调用函数的地方需要略作修改:
BOOL bFind = FindUserName(&strUserId, &strUserName);
FindUserName函数中使用了传址参数。主程序代码最终的运行结果是:弹出一个对话框,内容是“User Name:王泽宾”。函数中的“CString *”,表示参数使用“传址方式”。FindUserName中的pstrUserNameParam与主程序代码中的pstrUserName是两个完全没有关系的指针变量,只是这两个指针变量中存放的内容完全相同,都是指向同一块内存空间的地址,所以修改他们中的任何一个指针变量所指向的内存空间,效果都是完全一样的。pstrUserNameParam的内容复制了一份pstrUserName的内容,pstrUserNameParam是pstrUserName的副本,这个复制操作是在函数调用过程中参数压栈时执行的。
通过以上例子,我们可以看到引用在作为函数参数传递时,具有指针的效果,但是却可以象传值的一样使用,方式比指针简单。当作为在参数时,通过指针和引用,都可以在函数体内操作直接改变他们所指向的地址空间里面存储的内容,无须编译器为他们创建一个临时变量复制并保存他们的值的一个副本。
指针和引用在C/C++使用方法上,略有不同:
1、指针的值可以为 NULL ,而引用不能为 NULL,你如果VC中直接定义变量CString& strVar1;或者CString& strVar1 = NULL;这是行不通的,编译器无法通过语法扫描。编译器要求定义引用变量时,必须指定初值。
2、一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
以下示例程序中,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;
所以,我们得出结论:引用实际上只是编程语言中语法层面的概念,经编译后实际执行的代码中与指针没有任何区别。引入这个概念后带来好处有两条:
1、代码简洁,使用方便。可以对比一下上面的例子。
2、部分消除指针使用的不安全。由于从语法层面就要求必须它初始化,所以不会出现空指针操作这样的危险情况。
Java,C#语言的一个优点就是取消了指针的概念,从而从语法层面就消除了C/C++语言的不安全性,这对于构建健壮的大型应用非常有益。它们对于函数调用的参数传递采用两种方式:对于简单类型的参数实行传值方式,对于对象类型的参数实行传引用方式。
以Java为例:
class ObjUser{
String strName = "init value";
public String toString(){
return strName;
}
}
public class Test{
ObjUser User = new ObjUser();
int Age = 60;
public void changeObj(ObjUser user){
user.strName = "changed value";
}
public void changeAge(int age){
age = 80;
}
public static void main(String[] args)
{
Test objUser = new ObjUser();
System.out.println("==================Print User=================");
System.out.println("Before call changeObj() method: " + objUser.User);
oRef.changeObj(objUser.User);
System.out.println("After call changeObj() method: " + objUser.User);
System.out.println("==================Print Age=================");
System.out.println("Before call changeAge() method: " + Age);
oRef.changePri(objUser.Age);
System.out.println("After call changeAge() method: " + Age);
}
}
运行结果:
==================Print User=================
Before call changeObj() method: init value
After call changeObj() method: changed value
==================Print Age=================
Before call changeAge() method: 60
After call changeAge() method: 60
在Java中如果要实现对象传值方式,可以先借助于clone()方法先来复制对象,然后再调用函数。
C#的情形基本类似,php中也保留了传值和传引用两种方式,跟C++的语法类似。