如何写优雅的代码(3)——合理选择函数形参

本文探讨了在C++编程中,如何根据需求选择指针或引用作为函数参数的方法,并对比了它们在不同场景下的优劣。通过具体的代码示例,如交换两个整数和复制缓冲区等操作,详细分析了每种方式的特点。

//========================================================================
//TITLE:
// 如何写优雅的代码(3)——合理选择函数形参
//AUTHOR:
// norains
//DATE:
// Tuesday 21-July-2009
//Environment:
// WINCE5.0 + VS2005
//========================================================================

函数形参的确定,其实没有太多的讲究,一般而言,想怎么用就怎么用。但从C升级到C++的时候,偏偏给我们多出个引用。俗语道,当方法只有一种时,那是最幸福的。当我们需要改变形参的数值时,究竟是选择指针还是应用,这是我们追求优雅不得不考虑的幸福烦恼。

以经典的交换两个int数值的代码做样例。

采用指针的写法:

BOOL Swap(int *piA,int *piB) { if(piA == NULL || piB == NULL) { return FALSE; } int iTemp = *piA; *piA = *piB; *piB = iTemp; return TRUE; }

采用引用的写法:

BOOL Swap(int &iA,int &iB) { int iTemp = iA; iA = iB; iB = iA; return TRUE; }

调用的话,也是很简单:

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { int iA = 1,iB = 2; Swap(&iA,&iB); //调用指针 Swap(iA,iB); //调用引用 return 0; }

从功能的角度而言,无论是指针还是引用,都能实现同样的功能。很显然,我们不能通过能否实现某个功能来确定是采用引用还是指针。

我们再来看看这两个函数,最大的区别在于指针还需要判断是否为NULL——对于指针而言,对为NULL值的指针提领,铁定会出现异常。故在比较之前,一定要判断指针的数值。也正是因为这个原因,以指针作为形参的Swap函数还有失败的可能性。但对于引用而言,则没有这个弊端,因为引用不能指向“无物”。由此,上述的采用引用的Swap其实根本不必要返回值,因为一定能够做到交换成功。

精简后采用引用作为传递的Swap函数如下:

void Swap(int &iA,int &iB) { int iTemp = iA; iA = iB; iB = iA; }

是不是比采用指针的更为清晰明了?


是否可以下定论,用指针传递的都应该改用引用?当然答案是否定的。一般而言,当传递NULL可以相对改变函数意义时,我们可以也应该选择指针。

我们在日常编程中会经常会碰到这么一种情形,给某一个函数传入一个缓冲区,然后往缓冲区复制数据。因为每次的数据大小都是不一致的,所以我们的缓冲区必须要以new来创建;而以new来创建缓冲,那么首先要确定数据的大小。这时候,我们就可以传入NULL值,告诉函数这次我们不需要拷贝数据,仅仅是想知道所需的大小而已。

根据该情形,我们可以写出类似函数的模型:

BOOL CopyBuffer(BYTE *pBuf,DWORD &dwLen) { if(IsRead() == FALSE) { return FALSE; } dwLen = g_dwLen; if(pBuf != NULL && dwLen >= g_dwLen) { memcpy(pBuf,g_pBuf,g_dwLen); } if(pBuf != NULL && dwLen < g_dwLen) { return FALSE; } else { return TRUE; } }

调用的时候:

//获取缓冲区大小 if(CopyBuffer(NULL,dwLen) != FALSE) { pBuf = new BYTE[dwLen]; //获取缓冲区数据 CopyBuffer(pBuf,dwLen); }

这样,我们就能够在不增加函数,不增加形参的情形下,只是通过NULL为标志确定缓冲区大小,进而分配相应的空间进行数据拷贝。

细心的朋友看到这里,可能会说,这用引用也可以实现啊!因为对于这个函数而言,当传入的参数为0时,意味缓冲没意义,这个也可以作为标志啊!

这当然也可以,所以CopyBuff就可以这样改:

BOOL CopyBuffer(BYTE &buf,DWORD &dwLen) { if(IsRead() == FALSE) { return FALSE; } DWORD dwLenIn = dwLen; dwLen = g_dwLen; if(dwLenIn != 0) { if(dwLenIn >= g_dwLen) { memcpy(&buf,g_pBuf,g_dwLen); } else { return FALSE; } } return TRUE; }

看起来是不是要比使用指针清爽?但问题不出在CopyBuffer函数,而在于调用上。

因为CopyBuffer为引用,所以我们不能想这样调用:

BYTE *pBuf = NULL; CopyBuffer(pBuf,dwLen);

这段代码会引发编译器会发生抱怨:error C2664: 'CopyBuffer' : cannot convert parameter 1 from 'BYTE *' to 'BYTE &'

为了能让编译器安静,我们只能这样:

BYTE *pBuf = NULL; CopyBuffer(*pBuf,dwLen);

编译器是高兴了,但运行就郁闷了。因为pBuf为NULL,所以*pBuf一定会异常。

为避免这异常发生,我们在调用前只能先预分配一段缓冲区,故调用代码很可能如下类似:

if(pBuf == NULL) { pBuf = new BYTE [DEFAULT_LENGTH]; } //获取缓冲区大小 if(CopyBuffer(*pBuf,dwLen) != FALSE) { //删掉旧缓冲,建新缓冲区。 delete [] pBuf; pBuf = new BYTE[dwLen]; //获取缓冲区数据 CopyBuffer(*pBuf,dwLen); }

相对之前采用指针作为形参的CopyBuffer,这样的调用显得更为累赘和繁琐。

如上总结:作为函数的形参,只有当NULL有意义的时候,才选择指针;除此以外,都应该选择引用。

这个结论,对于从C过渡到C++的朋友可能有点难以让人忍受。在C中,如果要改变形参的数值,就采用指针的方式,这样在调用的时候,能够在一定程度上了解该形参是否会被改变。

如:

//形参有改变 GetData(&dwVal); //形参不改变 SetData(dwVal);

而采用引用的方式,无论是否会改变形参值,在调用上都不会有任何蛛丝马迹:

//形参有改变 GetData(dwVal); //形参不改变 SetData(dwVal);

也许最让郁闷的是,windows的api函数,凡是用来改变形参的,都无一例外是采用指针的形式。在这方面微软有充足的理由:让C程序员也能编写windows程序。只不过,我们已经上了C++这条贼船,为什么我们不用C++的方式呢?



在很多情形下,以结构体作为形参,我们往往会带有const和引用的修饰:

BOOL SetWindowPosition(const RECT &rcPos);

const标明我们的态度,不对rcPos的数值进行变更;引用则是告诉编译器,不必为创建临时变量。

也许这样说有点让人糊涂,还是让我们用实例来说明。当我们去掉const,去掉引用,则函数变成:

BOOL SetWindowPosition(RECT rcPos);

如果形参不是结构体类型,而是诸如DWORD,int等,那则无不妥;但如果是像例子中的结构体,那么编译器就要忙活了:创建一个对象,这就意味着要调用构造函数,使用完毕后还要析构函数清除。我们为了让编译器不那么辛苦,所以我们给形参加上引用的符号;又因为这个数值我们实在没打算更改,所以再加上const的修饰。于是,这函数就变成了之前我们所见到的形式。

末尾,稍作总结:当传递结构体作为形参,不妨考虑一下const+引用的方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值