函数在程序中分为有返回值和无返回值。函数是一种问题的通用方法,既可以使用标准函数库中的函数也可以自定义需要的函数又分为按值传递、指针传递和引用传递,在C++
中函数的种类是十分丰富的。
使用规则
在使用之前提供函数的定义
提供函数的返回值类型
调用函数时参数对照
函数只能有一个返回值其余要用其他形式返回
实参虚参在内存中的关系复杂
内联函数
内联函数是C++
为提高程序运行速度的一次改进,它和编写普通函数没有差别但是在编译时编译器会对他们做不同的处理。在编译后的机器码中函数的地址被编写进去并实现函数的调用,但在内联函数的编译中,函数和其他程序代码在“内部”链接起来了,可以免去函数的调用所带来的代价。
使用
// Using inline functions is same as normal functions, but compiling is different.
int c = Add(a, b);
声明内联函数
- 关键字 inline
inline int Add(int a, int b) { return a + b; }
内联函数不能递归
内联函数一般较为简单
有选择的内联函数
编译器不能满足所有内联需求即使有
inline
关键字
内联函数无差别的使用,这样其实没有作用,因为编译器无法满足所有的需求,只能自主选择一些适合内联的函数进行操作,所以程序速度的提高不能只依赖内联函数,而是要通过数据结构与算法。
重载函数
函数的重载给了函数使用上的更多的可能,函数的重载是在一定基础上利用同名函数的更合适的版本去适应运算。通常是名字相同的函数,有不同的参数列表来定义一个函数的重载函数,这样编译器在参数列表时会判断使用哪一种函数最为合适。
使用前提
什么才能算是重载呢?这就要提到参数列表了,参数列表又叫参数签名,包括参数的类型、参数的个数和参数的顺序,只要有一个不同就叫做参数列表不同。
// These functions redefine the functions that correspond to different parameter lists.
void Swap(int a, int b);
void Swap(double a, double b);
bool Swap(bool a, bool b, bool c);
- 重载规则
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。
内部原理
在C++
代码在编译的过程中会对函数进行重命名,比如void Swap(int a,int b)
就会变成_Swap_int_int
,这样就能知道为什么同名函数如何重载的了。发生函数调用的时候就会按这个表进行匹配,如果匹配不到则会报错。这叫做重载决议。
模板函数
为了实现泛型编程,函数模板是一种通用化的函数的描述,把相应的参数传递给函数之后,编译器会自动生成该类型的函数,但是这种模板的范围也不是无限的,如遇到特殊情况无法重载就会报错。模板函数给程序提供了代码重用的机会和便利。也可以使固定的代码适应多变的条件。
注意
函数模板无法适用于所有的类型有一定的而范围
不可以提供产生矛盾的重载
将同一算法用于不同类型时要用函数模板
使用
声明函数模板
为了有通用性,在声明时和定义时必须采取比较有通用性的关键字代替固定的类型。引入关键字class
或typename
来代替固定的类型。还要用template
来说明这是一个模板函数。
- 用
typename
// For general use, variables in functions typically use T or auto type.
template <typename T> void Swap(T &a, T &b) {
T temp;
temp = a;
a = b;
b = temp;
}
- 用
class
template <class T> void Change(T &a) { a = a + 1; }
生成的泛型的类型名字不一定是
T
其他的名字只要符合标准也可以尽量使用
C++98
标准的typename
来使用
使用模板函数
模板函数的调用方法和普通函数看起来并无差别但是编译器帮我们实现了函数的重载
但是重载有限度。
- 编译器隐式的重载
double a, b;
Swap(a, b);
// In fact, when a function is called, it is converted to following function.
Swap(double &a, double &b) {
double temp;
temp = a;
a = b;
b = temp;
}
- 显式的重载模板
对于不可以通用模板的类型比如数组或者结构体我们可以使用其他手段重载原来的模板函数,使其实现更大程度上的模板化。
template <typename T> void OutAdd(T a, T b) { cout << a + b; }
template <typename T> void OutAdd(T a[], T b[], int size) {
for (int i = 0; i < size; i++) {
cout << a[i] + b[i] << endl;
}
}
在重载后的模板函数使用时,编译器会选择参数列表对应的模板并使用,其中具有一些优先级别的不同。
区分具体和实例
具体化
当函数遇到更加复杂的不同结构体甚至类时函数的重载无法满足。对结构体更细致的要求,只对特定元素而不是整体如果不具体化将无法使用。
struct Student {
int grade;
string name;
} a, b;
template <typename T> void Swap(T &a, T &b) {
// code...
}
template <> void Swap(Student &x, Student &y);
template <> void Swap<Student>(Student &x, Student &y) {
int temp;
tmp = y.grade;
y.grade = x.grade;
x.grade = temp;
}
实例化
需要区分实例化和具体化,对于声明后的模板函数,存在的只有一种生成函数的模板,但他和实际的函数有区别。最初编译器只能自动的隐式的把函数模板实例化用来运用,如果现实的实例化可以直接命令编译器创建特定的实例,需要用到<>
来指明实例化的类型。
template <typename T> void Swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
template void Swap<int>(int &a, int &b);
// You can explicitly instantiate in a process that you can use.
double a = 1.0, dobule b = 2.0;
Swap<double>(a, b);
区分具体化和实例化
声明方式 | 是否重实现 | |
---|---|---|
具体化 | template <> 函数名<类型> |
是 |
实例化 | template 函数名<类型> |
否 |
编译使用优先级
- 编译流程如下
创建候选函数列表,包含所有名字相同的函数
选择可行函数列表,将所有参数个数匹配的函数选出
查找是否有最佳的可行函数,如果有则使用,如果没有调用出错
- 如果有多个可行函数则遵循一下规则
非模板函数优先于模板函数
显式具体化优于隐式具体化
还有一种特殊的方法可以引导编译器选择模板函数的优先级大于非模板函数。
// The <> symbols indicates that you want to use template functions, which,
// while instantiated, do not have actual types.
Swap<>(a, b);
函数指针
和其他数据一样,函数也有它的地址
。函数的地址存储的时其机器语言
代码的内存的开始位置,通常用函数指针实现函数作为其他函数的参数。函数指针的作用可以允许他在不用换的时间传递不同的函数的地址,可以在不同的时间使用不同的函数。
基本需要
函数指针需要函数的地址
函数的指针需要声明
函数指针可以通过指针调用所指向的函数
指针使用
函数指针的声明和使用基本和普通指针相同,可以类比普通指针。
声明
- 获取地址声明
double fun(int a, int b) { return a + b; }
double (*p)(int, int);
void cal(int x, int y, double (*pf)(int, int)) { cout << x + y + (*pf)(x, y); }
// You can call a function by replacing it with a function pointer.
需要注意函数指针的函数列表同一种指针也会有多种情况比如对待数组时。
double *f1(const double[], int);
double *f2(const double ar[], int n);
double *f3(const double *, int);
// Three function pointers can point to the same function.
在不想写参数列表时可以直接用auto
变量直接等于函数地址,函数指针和函数本身同样灵活,同时也有指针的各种性质。