引
关于C++的函数有很多知识,因为其函数有多种变体,可以说C++创作者为了开发方便,打开了很多个后门让编程人员随心所欲地炫技使用,但私以为这也造成了使用函数时的复杂度,如果真的在代码中使用各种变体,虽然确实可以让代码看上去简洁高级,但是对于代码阅读来说却并不是特别友好。
不过,无论如何,C++提供了很多可能,这里也稍微总结一下其函数的各种变体。
定义常规函数
定义函数最简单也最常见,就是定义最常规的函数,有返回值类型、函数名、参数类型、参数名和代码块:
void swap (int a, int b) {
int temp = a;
a = b;
b = temp;
}
这种函数定义只要是程序员就会熟悉了,不多说。
函数原型
什么是函数原型?为什么要用原型?
看过c/c++代码的人都知道,其代码大都有个main函数,而且一般都放在最前面,而其余自定义的函数都放在后面,这就导致如果你要在main函数中调用一个自定义函数,此时因为你的自定义函数是在后面定义的,编译器不懂你的调用语句,因此我们需要在main函数之前声明一个函数原型,表明函数的返回值类型、函数名、参数类型、参数名,如下:
void swap (int a, int b);
int main (void) {
int a = 1;
int b = 2;
swap (a, b);
return 0;
}
void swap (int a, int b) {
int temp = a;
a = b;
b = temp;
}
值得一提的是,函数原型中的参数名其实可以不用写,因为只有参数类型和数量才是函数的标志,毕竟都只是个带好罢了。
参数传递方式
参数传递方式是什么意思?这里我们指的是传递的到底是变量的拷贝还是变量本身。
如上述swap函数那样的,其实是在调用函数时拷贝了一份变量,因此在函数内随意改变参数变量值,原本的变量都不会变的。
但是我们也可以直接传递原本的变量本身,这可以通过传递指针或者变量引用来做到:
// 指针
void swap (int * a, int * b);
// 引用
void swap (int & a, int & b);
指针会传递变量的地址,引用故名思议也就是传递原变量的一个引用,因此都是指向原变量的,因此这样在函数内修改变量值的话,原变量是会跟着改变的,如果不想改变,可以用const来修饰参数。
函数指针
其实和变量一样,函数也是可以有指针的,创建方法如下:
int pam (int);
int (*pf) (int);
pf = pam;// pf 就指向了函数pam,同样的用*pf来调用函数
这个知识点其实用的并不多。
内联函数
内联函数并不是一种函数变体,而是指编译器的处理方式不同。
普通的函数调用是遇到函数调用时,将跳到函数代码的地址去,执行后返回原地址,而内联函数在编译时会直接编译到调用的代码中去,因此无需做跳转,因此内联函数的运行速度比常规函数稍快,但代价是需要占用更多的内存。显然,内联函数不能递归,否则就是无限地内联去插入代码了。
要定义为内联函数,直接省略原型,将整个定义放上去,并在函数定义前加上关键字inline:
inline double square (double x) { return x * x; }
// 在C语言下使用宏来实现:
#define SQUARE (X) X*X
默认参数
和python一样,c++允许给函数的参数设置默认值,如果在调用时没有给对应参数赋值,那么函数将使用默认值,方法其实就是在声明函数原型时同时声明参数的默认值:
void add (int a, int b = 5);
int main() {
int a = 1;
add(a);
return 0;
}
要注意的是有默认值的参数必须全部在右边,这也是为了调用时方便参数的设置,因为在调用时你要写参数,不可能参数空一个不写,而写完了你要设置的,剩下的就都是右边默认的了:
int func1 (int n, int m = 4, int j = 5); // 有效
int func2 (int n, int m = 6, int j); // 无效
// 不允许这样调用:
func(1, , 2);// 无效
函数重载
c++允许有多个同名函数,只要其参数的类型或者数量不一样就可以了。
比如你可以有同样的名为swap的函数,有的参数是int类型,有的参数是double类型,有的有三个参数,等等,这些c++会认为是不同的函数,在你调用时视你传递的参数类型会自动调用对应函数:
void swap (int a, int b);
void swap (double a, double b);
void swap (int a, int b, int n = 5);
但是只有参数的类型和数量才是函数的特征标,仅仅返回类型不同的同名函数是不行的,必须在参数上有不同。
这还有一个名字,叫多态,有没有熟悉一点了。
函数模板
为了炫技,c++又定了函数模板这种幺蛾子,它定义一种通用函数,函数的实现方法一致,但是不限死参数类型,也就是说同一个函数,你传的参数可以是int型,也可以是double型。注意,这和上面的函数重载是有区别的,实际上也是进一步节省了函数重载的工作,不用定义那么多相同名字参数不同的函数,写那么多代码,只写一个就行了,参数类型根据传递进来的而定,即参数可配置:
// 函数原型
template <class Any> // or typename Any
void Swap (Any &a, Any &b);
// main函数
// 函数定义:
template <class Any> // or typename Any
void Swap (Any &a, Any &b) {
Any temp;
temp = a;
a = b;
b = temp;
}
注意这里参数类型用Any代替了,也就是说你既可以传int也可以传double等等,随你传,反正结果就是交换两个变量的值。
函数模板也是可以重载的,这里既然参数的类型是不定的,但是你可以改变参数数量呀,而且也不要求所有参数类型都是布丁,可以有部分是定死的,这样就可以玩出无数花样来了,比如:
template <class Any> // or typename Any
void Swap (Any &a, Any &b);// 注意这里是传递的变量引用了
template <class Any> // or typename Any
void Swap (Any &a, Any &b, int c);
真是套路多。
显示具体化
上面的函数模板让所有的参数类型使用同一个函数代码块执行同一种操作,如果我这时候又想对某个特定类型做不同的操作怎么办?就你名堂多。
一种方法是使用函数重载。
另一种方式就是显示具体化:
struct job {
// 假设有一个job结构体
}
// 普通的函数模板
template <class Any> // or typename Any
void Swap (Any &a, Any &b);
// job特供版
template <> void Swap<job> (job &, job &);
然后在job特供版的函数你自己就可以去玩出花了。
编译器到底用哪个函数版本?
上面说了这么多种函数,可能在函数原型部分我有一大堆同名但是各个细节部分不同的函数原型,当在代码中调用的时候到底编译器怎么判断用哪个呢?换言之也就是这些调用是怎么排序的呢?
从最佳匹配到最差匹配顺序如下:
- 完全匹配,但常规函数优先于模板。
- 提升转换(需要转换参数类型,如char和shorts自动转换为int,float自动转换为double)。
- 标准转换(需要转换参数类型,如int转换为char,long转换为double)。
- 用户定义的转换,如类声明中定义的转换。
结
目前学习到了这些函数变体,如有其它,继续补充。
网友评论