在博客《 C++ 基本知识》已经谈到了函数,但 C++ 还提供了许多新的函数特性,使之有别于 C 语言。新特性包括
内联函数、 按引用传递参数、默认的参数值、函数重载(多态)以及模板哈数
内联函数
内联函数的编译代码与其他程序代码“内联”起来。也就是说,编译器将使用相应的函数代码替换函数调用。对于内联代码,程序无需 跳到函数定义处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。如果程序在 10 个不同 的地方调用同一个内联函数,则该程序将包含该函数代码的 10 个副本。
应有选择地使用内联函数。如果执行函数代码时间比处理函数调用机制时间长,则节省的时间将占整个过程的很小一部分。如果代码时间 执行时间很短,虽然节省时间的比例较大,但节省的时间绝对值并不大,除非该函数经常被调用。
1
要使用内联函数,必须采取下述措施之一:
- 在函数声明前加上关键字 inline;
- 在函数定义前加上关键字 inline;
宏不能按值传递,如果使用 C 语言的宏执行了类似函数的功能,应考虑将它们转换为 C++ 内联函数。
内联函数的链接性是内部的,这意味着函数定义必须在使用函数的文件中。一般在使用函数的文件中包含(声明和定义了内联函数的)头文件可确保将定义 放在正确的地方。也可以将定义放在实现文件中,但必须删除关键字 inline,这样函数的链接性将是外部的。
函数重载
函数多态(函数重载)让你能够使用多个同名的函数。术语“多态”
指的是多种形式,因此函数多态允许函数可以有多种形式。 类似地,术语函数重载
指的是可以有多个同名的函数,因为对名称进行了重载。这两个术语是同一回事。可以通过函数重载来 设计一系列函数—-它们完成相同的工作,但使用不同的参数列表。
1
函数重载条件:
- 函数名相同
参数(不是参数名称)列表不同
- 参数个数不同
- (或者)参数个数相同但对应位存在类型不同的情况
- 引用类型和类型本身,
非指针和非引用类型
带 const 与否,均视为同一个特征标(即视为参数相同) - 对于
指针或引用类型
带 const 与否认为参数不同(主要是因为,将非 const 值赋给 const 变量是合法的,但反之是非法的)
- 返回值类型不是特征标
虽然函数重载很吸引人,但也不要滥用。仅当函数基本上执行相同的任务,但使用不同形式的数据时,才应采用函数重载(实质上是 为了重载函数名,而好的函数名是显示其功能)。事实上,C++ 通过名称修饰(如加后缀)来区分重载函数中不同的函数。
函数模板
前面提到的“函数重载”,重用了函数名和函数逻辑,但仍然要针对不同的数据类型写不同的函数(函数体)。那能不能针对不同的 数据类型但参数个数相同的“函数重载”只用一个函数来表示
呢?这就是函数模板出现时机。
函数模板是通用的函数描述(使用泛型来定义函数)。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也称为参数化 类型。
1
不但普通函数可以声明为函数模板,类的成员函数也可以声明为函数模板。
需要注意的是,函数模板不能缩短可执行程序
,有几个模板实例就有几个独立的函数定义,就像以手工方式定义了这些函数一样。 最终代码不包含任何模板,而只包含了为程序生成的实际函数。可见,函数模板将代表着不同类型的一组函数
,它们都使用相同的代码。 通常模板放在头文件
中。
1
使用模板的好处是,它使生成多个函数定义更简单、更可靠。
模板函数
函数模板
是模板函数的一个样板,它可以生成多个重载的模板函数,这些模板函数重用函数体代码。模板函数
是函数模板的一个实例。是函数模板的实例化(instantiation)
函数模板如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename T1,typename T2>
void add(T1 a[],T2 b[],int size){
for(int i=0;i<size;i++) b[i]+=a[i];
}
//另外一例
//typename 或 class 均可(也可混合使用)
template<class stype>
void bubble(stype *item, int count)
{
stype bubb;
for(int i=0;i<=count-1;i++)
for(int j=i+1;j<count;j++)
if(item[i]>item[j])
{
bubb=item[i];
item[i]=item[j];
item[j]=bubb;
}
}
//模板函数
template<typename T>
T add(T a,T b){return a+b;}
cout<<add<int>(3,5);
//编译系统将生成如下的模板函数
int add(int a,int b){return a+b;}
函数模板重载
函数模板只能简化(大部分) 参数个数相同的函数重载,但对于参数个数不同的函数重载
没有办法。所以此时需要对函数模板进行 重载,和常规重载一样,被重载的模板的函数特征标必须不同。
模板的局限性
编写的模板函数很可能无法处理某些类型(如含 > 的模板对指针不管用),因为某些运算符不适合特定类型,甚至这样做是没有意义的。 如果有意义的话,你可以:
- 通过重载运算符来解决此类问题
- (或者)为特定类型提供具体化的模板定义
普通函数和函数模板可以同名!匹配顺序是:先同名普通函数->同名函数模板
1
同名普通函数(参数个数等也相同)可以看做函数模板的具体化。
重载解析
上述提到了函数重载、函数模板和函数模板重载,这三者的函数名都可以相同,那到底选哪一个呢?
1
重载解析步骤如下:
- 创建候选函数列表(包括三者)
- 使用候选函数列表创建可行函数列表(函数模板实例化)
- 完全匹配顺序:普通函数->模板函数,指向非 const 数据的指针和引用 -> 非 const 指针和引用
- 最佳(含类型转换)匹配顺序:普通函数->模板函数,转换最少优先
- 报错
函数模板的发展
前面提到的函数模板形式化了参数,但返回值可能是不同于形式化参数的类型(如两个 int 相除,出现 float 等)。C++ 给出了方案如下:
- 新增关键字 decltype 用于推断函数中运算得到的新类型
1 2 3 4 5
template <typename T1, class T2> { decltype(x+y) xpy = x + y; //xpy 的类型由 decltype 智能确定(类似 auto ) }
- 后置返回类型 ```cpp template <typename T1, typename T2> ?type? sum(T1 x, T2 y) { return x+y; } //此时还没有声明参数 x 和 y //不能将返回类型 decltype(x + y) //但可以用后置返回类型
template <typename T1, typename T2> auto sum(T1 x, T2 y) -> decltype(x + y) { return x+y; } //decltype 在参数声明后面 //因此 x 和 y 位于作用域,可以使用它们 ```
函数重写(也称为覆盖)
之前的函数重载通常指同一个类中行为(也有非类成员函数)。而函数重写
体现的是父类和子类之间的多态性、子类重新定义父类 中有相同名称及参数列表的虚函数(虚函数只是一个声明)。
1
重写函数必须具有相同的函数头(只是重写函数体及其访问修饰符而已)
- 被重写的函数不能是 static 的,必须是 virtual 的
- 重写函数必须有相同的类型、名称和参数列表(即
相同的函数原型
) - 重写函数的访问修饰符可以不同,尽管 virtual 是 private 的,派生类可以重写为 public等
函数重定义(也称为隐藏)
子类重新定义父类中有相同名称的非虚函数(参数列表可以不同,貌似是重载和重写思想的杂交)。此时的同名函数匹配次序参照重载函数 的匹配顺序,不过此时子类中函数的优先级要大于父类。
父类和子类完全匹配(参照函数重载)时,直接调用子类中的函数,相当于父类的函数被隐藏
。
1
函数重定义只是名字相同而已,分别位于派生类与基类