一、前言
函数是个好东西,尤其是C/C++里面的函数,有时候会感觉复杂的难以言表,可以参考文章函数对其有一个总体了解。
二、理解函数
函数名,也就是函数的名字,比如f(x)
中,这个字母f
就是这个函数的名字。
但你也可以叫其它名字,比如kucoding(x)
,这个函数就是以本站的域名为名字,这个没有任何影响,因为这仅仅只是个标识符而已。
而其中的x
,就是这个函数所需要的输入变量,在编程中,我们也称这种为函数参数。
它当然不是固定不变的,比如这个函数里面你只想要计算个1+1
,根本用不到函数参数,那就可以不写:kucoding()
。
又或者你需要计算一个长方形的面积,需要长宽,那就两个参数好了:area(x,y)
。
也就是说,函数的参数是可以任意的,这取决于你的需求。
有了输入,一般也得有输出,比如y=f(x)
,这里的y
就是函数f(x)
的计算结果,即输出内容。
一般来说,一个函数可以没有输入,但一定会有输出,否则这个函数的存在就没有意义了。
但要注意:y=f(x)
这种得到函数结果的方式,在编程领域,至少在C/C++中,仅仅只是函数输出方式的一种,比如你还可以直接打印结果到控制台、文件,那也叫输出。
三、函数基础
C语言中的函数和数学中的函数很像:
int f(int x){
return x*x;
}
int y=f(100)
上面我定义了一个函数,用来计算输入参数x*x
的值,并通过关键字return
将结果返回出去。
然后你就看到,我后面就可以用一个变量来接收这个函数的运行结果了。
这里唯一与数学不同的地方是,多了一个数据类型。
因为程序需要知道存储数值的变量的大小,而数据类型就是这个作用,这里为int
类型,主要就是告诉计算机,我要存储的是一个32位大小的整形数值。
同时,由于编程中的函数里面可以写很多语句,而不是像数学里面将全部内容都揉成一个式子。
因此函数里面就会有一个关键字return
来返回这个结果,也就是外面的y
将等于多少。
函数有很多形式,比如一个既有参数又有返回值的函数写法如下:
#include<iostream>
using namespace std;
int main() {
int x = sqrt(9);
cout << x;
}
sqrt
是标准库中的函数,开平方根的,上面的代码就是求9的平方根,然后将结果返回给变量x
,也就是结果3
。
其执行流程如下图:
sequenceDiagram
participant A as main
participant B as sqrt
A->>B: 执行x=sqrt(9)语句,调用sqrt函数
B-->>B: sqrt根据传入的数值执行代码计算
B->>A: sqrt函数执行结束,返回结果
A-->>A: 得到结果,继续执行cout<<x语句。
代码依旧是从main
函数开始往下执行,执行到这个函数后,就会跳转到这个函数的内部去执行代码。
直到这个函数执行完毕,才会将结果返回,并从这条调用语句后面继续执行下去。
更详细的信息如下图:
分号结尾代表它是一条语句,才会执行调用过程。
此时可能你存在的问题是,我怎么知道它需要一个参数?而不是两个、三个?
这就是函数声明的作用,比如上面的sqrt
函数声明大概就长下面这样:
double sqrt(double num);
注意区分函数声明与函数实现这两者之间的差异:
double sqrt(double num){
//....
return result//返回一个计算后的结果
}
声明没有{}
,而实现有。
声明可以随便写很多个,但实现只能写一次。
声明的作用就是告诉编译器我有这个函数,并且它可以接受哪些参数,返回哪种结果。
而实现的作用就是执行具体的代码。
一个基本的函数格式如下:
type
:函数的返回类型,比如整数int
,小数double
等等,后面会提。functionname
:函数名字argumentlist
:参数列表statements
:一系列的执行语句
对于没有返回值的,可以使用类型void
:
void testfun();
那么此时你调用它的时候就不能用变量去接受它的返回值了:
int x=testfun();//错误,它没有返回值
四、函数进阶
所谓函数进阶,就是一些更麻烦的函数用法(懂的人觉得简单,不懂的人看不懂)。
比如最基本的函数参数,在C语言中就有指针的写法:
void f(int *x) {
*x = *x * *x;
}
int main() {
int y = 100;
f(&y);
}
怎么样?是不是一下就觉得C语言中的函数也不是那么的简单了?
实际上,上面的代码与前面的代码功能是完全一样的,即:得到100*100
的值。
只不过不同地方在于,这里我们不再通过返回值传递结果回来,而是直接用函数的参数传递结果。
也就是说,函数的参数,不仅仅可以用来传入值,也可以用来传出值,而实现这一点的关键就是指针。
因为变量实际上就是电脑中的某块内存,内存主要有两个属性:所在的地址、以及内存上存的值。
而指针其实也是一个普通的变量,只不过这个变量稍微不同的地方在于,它里面存的另外一个变量的内存地址。
更多内容可参考本站另一篇文章:指针与引用。
因此上面的操作,实际上就是将y变量的内存地址传了进去。
有了这个地址,我们就可以通过*
符号,取得该内存上的值,并进行操作,所以就有了*x
,这个时候你要将两者看作一个整体,相当于y
变量。
由于运算符优先级决定右边比左边先执行,所以就有了上面这种写法。
但如果不采用指针呢?
void f(int x) {
x = x * x;
}
int main() {
int y = 100;
f(y);
}
那此刻y的值依旧还是100
,同时由于这个函数没有任何有效的输出,也就没有存在的必要。
因为此时你传入的不是y
变量的地址,而是y
变量的值,函数内部实际操纵的也仅仅只是变量x
的值而已,和y
没有任何关系。
C/C++复杂就复杂在指针上面,想要理清楚这个关系,你得理解指针。
简单来说就是,指针也是一个变量,只不过一般存储的是另外一个变量的地址而已,如果直接操作,那就相当于在操作它保存的地址,如果带*
符号操作,就相当于操作它保存的地址所在的变量值,套了个娃。
由于返回值为void
类型,所以你可以不写return
,因为本身也没有你需要返回的。
但实际上你也可以写,只要不返回值就行了:
void f(){
return;
}
上面介绍的是函数的参数,实际上函数本身也有很多有趣的点。
比如……函数名其实也可以当作变量使用,它相当于就是一个特殊一点的变量而已:
#include<stdio.h>
int f(int x) {
return x * x;
}
int main() {
printf("%p",f);
}
就像上面这样,我直接打印这个函数名的地址,是完全可以打印出来的:00007FF6AE1513D9
当然,这个很随机,不同电脑打印出来的内容一般不一样,你可以自己试一下。
也就是说,这个函数名其实也是一个变量,它和指针很像,保存的不过是函数的地址而已。
然后呢,想要使用这个函数,你就可以通过在后面添加小括号的方式()
来使用真正的函数。
既然都是变量了,那我自然是可以赋值的:
#include<stdio.h>
//省略上面的f函数
int main() {
int (*fun)(int);
fun = f;
int y=fun(100);
}
比如像上面这样,将函数f
的值赋值给fun
之后,那我们就可以直接使用fun
来调用原函数了。
注意这里的写法为声明函数指针的写法:int (*fun)(int);
这个也可以称作函数签名,想要正常赋值不报错,那你就得按照原函数的方式声明。
主要的区别是:函数名也需要用一个小括号括起来了,而且前面要添加一个指针符号*
,其后紧跟你的函数指针名字。
返回值为int
,是因为原函数返回值为int
。
后面小括号中的一个int
类型,同样也是因为原函数只有一个int类型的参数,这里没有添加实际的参数名,是因为参数名对函数签名来说没有意义。
函数签名只想知道这个函数需要哪种类型的参数,更深入的来说,它只是想知道这个参数占据多大的内存空间而已。