10. 深入理解C/C++中的函数:从基础到进阶技巧

一、前言

函数是个好东西,尤其是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函数开始往下执行,执行到这个函数后,就会跳转到这个函数的内部去执行代码。

直到这个函数执行完毕,才会将结果返回,并从这条调用语句后面继续执行下去。

更详细的信息如下图:

image.png

分号结尾代表它是一条语句,才会执行调用过程。

此时可能你存在的问题是,我怎么知道它需要一个参数?而不是两个、三个?

这就是函数声明的作用,比如上面的sqrt函数声明大概就长下面这样:

double sqrt(double num);

注意区分函数声明函数实现这两者之间的差异:

double sqrt(double num){
	//....
   return result//返回一个计算后的结果
}

声明没有{},而实现有。

声明可以随便写很多个,但实现只能写一次。

声明的作用就是告诉编译器我有这个函数,并且它可以接受哪些参数,返回哪种结果。

而实现的作用就是执行具体的代码。

一个基本的函数格式如下:

image.png

  • 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类型的参数,这里没有添加实际的参数名,是因为参数名对函数签名来说没有意义。

函数签名只想知道这个函数需要哪种类型的参数,更深入的来说,它只是想知道这个参数占据多大的内存空间而已。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux