【高质量代码】如何写出更高质量的C/C++代码(2):函数设计

函数是组成C/C++程序的基本元素,是将一段执行某项功能的代码进行了封装的代码段。为了实现设计的功能,函数的功能正确性是首要的前提,但是仅仅是正确还不够,其设计的科学性和合理性也是影响函数使用的重要因素。本文简要讨论C/C++函数设计和实现的一些基本规则。

1、引言:

每一个完整的C/C++函数都至少包含三个部分:返回值、函数名和参数。函数参数和返回值承担了调用者与被调用函数之间数据传递的功能,主要方式有三种:值传递、指针传递和引用传递,前两者为C标准,引用传递为C++标准。其中引用传递的性质类似于指针传递,但是使用方式类似值传递。


2、声明函数:

声明函数主要考虑函数的参数和返回值的问题。当然,函数名也是重要的一部分,在设计函数名时,应当注意函数名体现函数的功能,且不产生歧义。通常使用一句英文表示的一个动作或特性来作为函数名,单词之间通过大小写转换来区分。

2.1、函数的参数:

函数的参数应当书写完整,不应只写类型而省略参数名称。当函数没有参数时,使用void作为参数。

void Function1(int , int);//不良风格
void Function2(int nParam1, int nParam2);//良好风格
void Funciton3();//不良风格
void Function4(void);//良好风格

参数名称要科学命名,应反映传递给该参数的实际意义,不要简单使用a、b等简单的命名。如一个计算矩形面积的函数,可以声明成如下形式:

int GetRectArea(int a, int b);//不良风格
int GetRectArea(int nWidth, int nHeight);//良好风格

如果函数包括输入参数和输出参数,一般输出参数(即目的参数)放在前面,输入参数(即源参数)放在后面。如字符串拷贝函数:

void StringCopy(char *strDst, char *strSrc);
char str[20];
StringCopy(str, “Hello World”);

有些仅仅作为输入数据,在函数内部不能修改的参数指针,可以声明为const类型进行保护,这样一旦函数体内视图修改该参数,则会产生错误。如果传值参数仅作为输入参数,则可声明为const &类型,省去临时对象的构造和析构过程,提高效率。如上面GetRectArea函数的参数都可以声明为const &类型,StringCopy函数的strDst参数可以声明为const类型:

void GetRectArea(const int &nParam1, const int &nParam2);
void StringCopy(const char *strDst, char  *strSrc);

2.2、函数的返回值

一些简单的函数,由于几乎不可能出现错误,且一般只有一个计算结果,那么可以将计算结果通过返回值传递给调用者。对于复杂函数,返回值通常用于返回错误标识,而计算结果通过输出参数获得。如果需要返回多个参数,除了定义多个输出参数外,还可以定义一个输出结构体,在输出之前将输出值封装在一个结构体对象中整体返回。

函数返回值的类型有“返回值”和“返回引用”两种设计。其区别是,“返回值”会产生一个临时变量作为函数返回值的副本,而“返回引用”时不会产生值的副本。有些时候选择“返回引用”可以提高效率,而有些时候必须“返回值”否则可能出错。

首先需要注意到一个问题就是,绝对不要返回局部对象的引用,因为局部对象在函数调用结束之后就会被释放,引用指向的对象会是一片未知区域。比如以下代码是不允许出现的:

int& GetRectArea(const int &nWidth, const int &nHeight)
{
	int area = nWidth * nHeight;
	return area;
}

在类的实现函数中,如果该函数返回的是该对象自身,那么可以通过this指针返回该对象的引用。典型的例子如string的赋值函数:

String& String::operator =(const String &str)  
{
	if (this == &str)
	{
		return *this;  
	}
	delete [] m_string;
	int len = strlen(str.m_string);
	m_string = new char[len+1];
	strcpy(m_string,str.m_string);
	return *this;
}
在通过返回this指针的对象到引用时,不会把本地对象拷贝到上层函数的对象内存,而是直接返回当前对象本身,降低了不必要的开销,提高了效率。


3、函数的实现

函数的实现方法千差万别,然而在函数体的一开始以及返回时可以对参数和返回值进行检查以防止不必要的错误发生。

3.1、在函数入口处,检查参数的有效性。

可以使用assert防止非法参数,这样可以预防许多程序的错误。assert是仅在debug模式下生效的宏定义,用于检查不应该发生的情况。如以下函数中,采用assert防止输入参数为空:

void  * memcpy(void *pvTo, const void *pvFrom, size_t size)
{
	assert((pvTo != NULL) && (pvFrom != NULL));    //  使用断言
	byte *pbTo = (byte *)pvTo;    //  防止改变 pvTo 的地址
	byte *pbFrom = (byte *)pvFrom;   //  防止改变 pvFrom 的地址
	while (size-- > 0)
		*pbTo++ = *pbFrom++;
	return pvTo;
}

3.2、在函数返回之前,检查返回值的正确性和效率。

需要注意的事项有:

  1. 不可以返回指向栈对象的指针或者引用,因为栈对象在返回时将被销毁;
  2. 返回值的类型(值/指针/引用)应与函数声明一致;
  3. 考虑返回对象的效率;

对于提高返回对象的效率而言,临时对象是否在栈中创建和销毁是不同的。如以下代码:

String temp(s1 + s2);
return temp;
此时,程序的流程如下:

  1. 创建temp对象,调用拷贝构造函数初始化;
  2. 调用拷贝构造函数将temp复制到外部存储单元;
  3. 函数结构,调用析构函数销毁temp对象。

而使用以下方式:

return String(s1 + s2);
此时,临时对象将直接创建在外部存储单元中,不再调用拷贝构造函数和析构函数,效率更高。
上一篇:matlab:对一个向量进行排序,返回每一个数据的rank 序号 。。。


下一篇:python数据分析(十四)-matplotlib 绘图与可视化