C++基础-函数

函数声明和定义相分离

在实际的开发实践中,在某个源文件中定义的函数往往会在另一个源文件中被调用。另外,对于一些函数库(比如 DirectX)而言,我们希望他人可以使用函数库中的函数,但又不希望将函数的实现细节暴露给使用者。这时我们通常采取的方法是:只向函数的使用者提供函数的声明,而使用者根据函数声明就知道如何对函数进行调用了。至于具体的函数实现,则写在另外的函数实现文件中,或者是通过动态链接库文件(比如, .dll 文件)或静态链接库文件(比如, .lib 文件)的形式提供给函数的使用者。这样,一个程序的所有源代码文件通常就被分成两类:一类文件主要用来记录函数或者类的声明,提供给他人或者程序中另外的文件使用。这类文件被称为头文件(header file),通常以.h 为文件名后缀;另外一类文件则用来定义函数和类,实现函数和类的具体功能,这类文件被称为源文件(source file),多以.cpp 为文件名后缀。
这样,一个函数的声明和定义被分别放在了两个文件中,实现了接口和实现的相互分离

对函数的调用应该在函数的声明或定义之后

对函数的调用,应该在函数的声明或者定义之后,否则会出现‚找不到标识符‛的编译错误。这是因为函数的声明或定义确定了函数的调用方式(函数名和参数),编译器必须先知道这些信息,然后才知道如何去调用这个函数。这也就意味着,我们在调用一个函数之前,必须先声明或者定义这个函数。而如果这个函数是某个函数库所提供的,则在调用之前需要用‚ #include‛预编译指令引入这个函数所在的头文件,因为其中有这个函数的声明。例如,如果我们想要使用标准函数库中的 strcpy()函数,我们就需要先引入它的声明所在的头文件。 而这也正是要在某些源文件开始部分用‚ #include‛预编译指令引入头文件的根本原因。

自顶向下,逐步求精

在开发实践中,一个程序要完成的任务往往是很复杂的,当无法在一个函数中完成这个复杂任务时,可以考虑采取‚分而治之‛的原则,将较大的任务分成多个较小的任务,如果分解后的小任务仍然十分复杂,则可以继续向下分解,直到任务足够简单可以在一个函数内完成为止。这种‚自顶向下‛逐层将任务分解的方式,反映到程序代码中就是函数的嵌套调用。就像盖一座大楼,首先要将大楼分成很多层,然后每层又分成很多套, 而每一套又分成多个房间。这种将大问题逐渐分解的程序设计方法,被称为‚自顶向下,逐步求精‛的设计方法。也就是说,在写一个程序时,先应该考虑整体的结构,然后再不断细化,最终完成整个任务。‘自顶向下,逐步求精‛的设计思想是结构化程序设计的精髓,这种方法符合人类解决复杂问题的普遍规律,可以显著提高软件开发的效率。同时,用先全局后局部、先整体后细节、先抽象后具体的‚逐步求精‛的过程开发出来的程序有清晰的层次结构,更容易阅读和理解,也更易于实现和维护。

用空间换时间的内联函数

内联是 C++对函数的一种特殊修饰,实际上,内联函数可以更形象地被称为内嵌函数。当编译器编译程序时,如果发现某段代码调用的是一个内联函数,那么它就不再去调用该函数,而是将该函数的代码直接插入当前函数调用的位置,这样就省去了函数调用过程中的那些繁琐的幕后工作,提高了代码的执行效率,这样就以程序空间的增加换取了执行时间的减少。

内联函数是对函数的一种修饰,我们只需要在一个普通函数的定义前加上 inline 关键字,就可以
把它修饰为一个内联函数。其语法格式如下:

inline 返回值类型标识符 函数名(形式参数表)
内联函数的使用规则
  • 内联函数要短小精悍
  • 内联函数执行的时间要短
  • 内联函数应该是被重复多次调用的
  • inline 关键字仅仅是一种建议。inline 关键字只是表示我们建议编译器将函数内联处理,至于到底是否进行内联处理,最终还是要看编译器的脸色。

重载函数

所谓重载( overload)函数,就是让具有相似功能的函数拥有相同的函数名。反过来讲,也就是同一个函数名可以用于功能相似但又各有差异的多个函数。
准确地讲,两个函数,因为实现的功能相似,所以取相同的函数名,但是参数的个数或类型不同,就构成了函数重载,而这两个函数就被称为重载函数。

// 定义第一个 Add()函数,使其可以计算两个 int 型数据的和
int Add( int a, int b )
{
	cout<<"int Add( int a, int b )被调用! "<<endl;
	return a + b;
}
// 重载 Add()函数,对其进行重新定义,
// 使其可以计算两个 double 型数据的和
double Add( double a, double b )
{
	cout<<" double Add( double a, double b )被调用! "<<endl;
	return a + b;
}
int main()
{
	// 因为参数是整型数,其类型、个数
	// 与 int Add( int a, int b )匹配
	// 所以 int Add( int a, int b )被调用
	int nSum = Add(2,3);
	cout<<" 2 + 3 = "<<nSum<<endl;
	// 作为参数的小数会被表示成 double 类型,
	// 其类型、个数与 double Add( double a, double b )匹配
	// 所以 double Add( double a, double b )被调用
	double fSum = Add(2.5,10.3);
	cout<<" 2.5 + 10.3 = "<<fSum<<endl;
	return 0;
}

经过以上这样的函数重载,编译器会根据实际调用函数时不同的参数类型和个数调用与之匹配的重载函数,这样虽然我们在代码形式上调用的都是 Add()函数,可实际最终调用的却是重载函数的不同版本,从而得到正确的结果。
特别注意的是,如果两个函数仅仅是返回值类型不同,并不能构成函数重载,例如:

// 仅仅是函数返回值不同,不能构成合法的函数重载
int max(int a, int b);
float max(int a, int b);

这是因为函数调用时,函数的返回值有时并不是必需的,如果只是单纯调用函数而没有返回值,这时编译器就无法判断到底该调用哪一个重载函数了。

如果编译器发现某个重载函数的参数类型和个数都跟函数调用时的实际参数相匹配,则优先调用这个重载函数。
如果无法找到严格匹配的重载函数,那么编译器会尝试将参数类型转换为更高精度的类型去进行匹配,比如将 char 类型转换为 int 类型,将 float 类型转换为 double 类型等。如果转换类型后的参数能够找到与之匹配的重载函数,则调用此重载函数。

在重载函数中应该尽量避免使用默认参数,让编译器能够准确无误地找到匹配的重载函数

函数设计的基本规则

函数声明的设计规则
  • 使用“动词+名词”的形式给函数命名
  • 使用完整清晰的形式参数名,表达参数的含义
  • 参数的顺序要合理
// 规范的接口顺序——先左上角的 X 和 Y,后右下角的 X 和 Y
void SetRect(int nLeft, int nTop, int nRight, int nBottom);
  • 避免函数有太多的参数(5 个以内)
  • 使用合适的返回值
函数体的设计规则
  • 在函数体的“入口处”,对参数的有效性进行检查
  • 谨慎处理函数返回值
  • 函数的职责应当明确而单一
  • 函数的代码应当短小而精干
  • 函数应当避免嵌套层次太多
  • 函数应当避免重复代码

应明确单一,宜短小精干;忌嵌套太多,勿重复代码

静态(编译期)断言 – static_assert

除了可以使用 assert 断言在运行时期对参数的有效性进行检查之外,我们还可以使用静态(编译期)断言 static_assert 对某些编译时期的条件进行检查。一个静态断言可以接受一个常量条件表达式和一个字符串作为参数:

static_assert(常量条件表达式, 字符串);

在编译时期,编译器会对静态断言中的常量条件表达式进行求值,如果表达式的值为 false,亦即断言失败时,静态断言会将字符串作为错误提示消息输出。例如:

static_assert(sizeof(long) >= 8, "编译需要 64 位平台支持");

静态断言中的条件表达式必须是一个常量表达式,能够在编译时期对其进行求值。如果我们需要对运行时期的某些条件进行检验,则需要使用运行时 assert 断言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值