函数----函数重载,特殊用途语言特性,函数匹配,函数指针

一、函数重载

  如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。

1、定义重载函数

  对于重载的函数来说,它们应该在形参数量或形参类型上有所不同。

2、重载和const形参

  一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来:

1 int func(int x);
2 int func(const int x); // 重复声明了func(int x)
3 
4 int func2(int *);
5 int func2(int *const); // 重复声明了func2(int *)

  如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的:

1 int func(int &);
2 int func(const int &); // 新函数,作用于常量引用
3 
4 int func2(int *);
5 int func2(const int *); // 新函数,作用于指向常量的指针

 3、const_cast和重载

  const_cast在重载函数的情景中最有用。

 1 #include <iostream>
 2 #include <string>
 3 
 4 const std::string &shortString(const std::string &s1, const std::string &s2)
 5 {
 6     std::cout << "const" << std::endl;
 7     return s1.size() < s2.size() ? s1 : s2;
 8 }
 9 std::string shortString(std::string &s1, std::string s2)
10 {
11     std::cout << "not const" << std::endl;
12     auto &r = shortString(const_cast<const std::string &>(s1), const_cast<const std::string &>(s2));
13     return const_cast<std::string &>(r);
14 }
15 int main()
16 {
17     const std::string s1 = "abc", s2 = "ABC";
18     std::cout << shortString(s1, s2) << std::endl;
19     std::cout << "-----------------------" << std::endl;
20     std::string s3 = "abc", s4 = "ABC";
21     std::cout << shortString(s3, s4) << std::endl;
22     return 0;
23 }

4、调用重载的函数

   定义了一组重载函数后,我们需要以合理的实参调用它们。函数匹配是指一个过程,在这个过程中我们把函数调用与一组重载函数中的某一个关联起来,函数匹配也叫重载确定。编译器首先将调用的实参与重载集合中每一个函数的形参进行比较,然后根据比较的结果决定到底调用哪个函数。

  当个调用重载函数时有三种可能的结果:

  a、编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码。

  b、找不到任何一个函数与调用的实参匹配,此时编译器发出无匹配的错误信息。

  c、有多于一个函数可以匹配,但是每一个都不是明显的最佳选择。此时也将发生错误信息,称为二义性调用。

5、重载与作用域

  重载对作用域的一般性质并没有什么改变:如果我们在内层作用域中声明了一个函数名字,它将隐藏外层作用域中声明的同名实体。

二、特殊用途语言特性

1、默认实参

  我们可以为一个或多个形参定义默认值,不过需要注意的是,一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。

  在给定的作用域中一个形参只能被赋予一次默认实参。换句话说,函数的后续声明只能为之前那些没有默认值的形参添加默认值,而且该形参右侧的所有形参必须都有默认值。

  局部变量不能作为默认实参。除此之外,只要表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参。用作默认实参的名字在函数声明所在的作用域解析,而这些名字的求值过程发生在函数调用时

2、内联函数和constexpr函数

  在大多数机器上,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。

1)内联函数可避免函数调用的开销

   声明内联函数只要在函数的返回类型前面加上关键字inline就行了。

2)constexpr函数

  constexpr函数是指能用于常量表达式的函数。定义constexpr函数的方法与其他函数类似,不过要遵循几项规定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句。

 1 #include <iostream>
 2 #include <string>
 3 
 4 constexpr int func() {
 5     return 1024;
 6 }
 7 int main()
 8 {
 9     constexpr int x = func();
10     return 0;
11 }
View Code

  执行初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。

  constexpr函数体内也可以包含其他语句,只要这些语句在运行时不执行任何操作就行。

  constexpr函数不一定返回常量表达式。当把constexpr函数用在需要常量表达式的上下文中时,由编译器负责检查函数的结果是否符合要求。如果结果恰好不是常量表达式。编译器将发出错误信息

3、调试帮助

1、assert预处理宏

  assert宏定义在cassert头文件中。assert是一种预处理宏。所谓预处理宏其实是一个预处理变量,它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件:

  assert(expr);

首先对expr求值,如果表达式为假(即为0),assert输出信息并终止程序的执行。如果表达式为真(即非0),assert什么也不做。

2、NDEBUG预处理变量

  assert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG,则assert什么也不做。默认状态下没有定义NDEBUG,此时assert将执行运行时检查。

  预处理器定义了几个对调试很有用的名字,如下表所示:

名字 说明
__FUNCTION__ 存放函数名的字符串字面值
__FILE__ 存放文件名的字符串字面值
__LINE__ 存放当前行号的整型字面值
__TIME__ 存放文件编译时间的字符串字面值
__DATE__ 存放文件编译日期的字符串字面值

三、函数匹配

1、实参类型转换

   为了确定最佳匹配,编译器将实参类型到形参类型的转换划分成几个等级,具体排序如下所示:

  1)精确匹配,包括以下情况:

  a、实参类型和形参类型相同。

  b、实参从数组类型或函数类型转换成对应的指针类型。

  c、向实参添加顶层const或者从实参中删除顶层const。

  2)通过const转换实现的匹配。

  3)通过类型提升实现的匹配。

  4)通过算术类型转换(所有算术类型转换的级别都一样)或指针转换实现的匹配。

  5)通过类类型转换实现的匹配。

四、函数指针

   函数指针指向的是函数而非对象。和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回类型和形参类型共同决定,与函数名无关。

1 bool lengthCompare(const std::string &s1, const std::string &s2)
2 {
3     return s1.size() > s2.size();
4 }

该函数的类型是bool(const std::string &, const std::string &)。要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可。

1     //pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
2     bool (*pf)(const std::string &, const std::string &); // 未初始化,*pf两端的括号必不可少

1、使用函数指针

1     // 当我们把函数名作为一个值使用时,该函数自动地转换成指针
2     pf = lengthCompare;
3     pf = &lengthCompare; // 等价的赋值语句:取地址符是可选的
4     // 此外,我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针
5     bool b1 = pf("hello", "world");
6     bool b2 = (*pf)("hello", "world");
7     bool b3 = lengthCompare("hello", "world"); // 三条调用语句等价

  在指向不同函数类型的指针间不存在转换规则。但是,我们可以为函数指针赋一个nullptr或者值为0的整型表达式,表示该指针没有指向任何一个函数。

2、函数指针形参

  和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。

1 // 参数是函数类型,它会自动地转换成指向函数的指针
2 void use(bool pf(const std::string &, const std::string &));
3 // 等价的声明:显示地将形参定义成指向函数的指针
4 void use(bool (*pf)(const std::string &, const std::string &));

  可以直接把函数作为实参使用,此时它会自动转换成指针

  use(lengthCompare);

  类型别名可以简化使用了函数指针的代码:

1     // Func和Func2是函数类型
2     typedef bool Func(const std::string &, const std::string &);
3     typedef decltype(lengthCompare) Func2; // 等价的类型
4     // FuncP和FuncP2是指向函数的指针
5     typedef bool(*FuncP)(const std::string &, const std::string &);
6     typedef decltype(lengthCompare) *FuncP2; // 等价的类型

3、返回指向函数的指针

  和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针。然而,必须显示地将返回类型写成指针形式,编译器不会自动地将函数返回类型当成对应的指针类型处理

4、将auto和decltype用于函数指针类型

  当将decltype作用于某个函数时,它返回函数类型而非指针类型。因此,我们必须显示地加上*以表明我们需要返回指针。

原文地址:https://www.cnblogs.com/ACGame/p/10181078.html