初识C语言·函数

1 函数的概念

2 函数的分类

3 形参和实参

4 有关return语句

5 数组作函数参数

6 嵌套调用和链式访问

7 函数的声明与定义

8 static和extern


1

函数的概念可以参照数学中的y = kx + b,给一个x的值,让它计算y的值,同理,函数就是给一个执行指令,让它完成后续操作。

C语言也是由各种各样的函数组成的,函数利用的好,程序执行效率不免会提高。


2

C语言中的函数分为两种函数,一种是库函数,一种是自定义函数。

在学习这两种函数之前,我们不妨学习一下如何学习一个尚未谋面的函数,现在有许多网站可以提供相应的知识点,比如cplusplus,那么假设现在我们查阅strlen怎么用,应该了解什么,就可以去这个网站上学习。

初识C语言·函数_第1张图片

这里面就包括了函数的原型,返回类型和返回值,函数的参数,引用的头文件,当然,,慢慢看吧,全是英文哈哈哈,介绍比较费时间,就交给读者了。

我要表达的是,学习一个函数,应该从 函数原型函数的返回类型返回值头文件功能实现参数这几个点进行了解。

好了现在介绍库函数。

1)库函数

C语言并不提供库函数,C语言的国际标准ANSI C规定了常用的函数的标准,不同的编译器厂商提供的C语言标准就给出了一系列的函数的实现,这些实现的函数被称为库函数,比如 printf scanf qsort都是库函数,库函数只要在引用了相应的头文件,直接进行使用就可以。

2)自定义函数

库函数常用的就那么几个,但是这些库函数可不能解决所有的问题,于是有了自定义函数的出现,自定义函数的创建离不开 返回类型返回值参数。当然,有时候会用到自己创建的头文件的。

比如,现在我要创建一个能实现三个数相加的函数,库函数没有吧?

来看看

int Add(int a, int b, int c)
{
	return a + b + c;
}
int main()
{
	int a = 0, b = 0, c = 0;
	scanf("%d%d%d", &a, &b, &c);
	int ret = Add(a, b, c);
	printf("%d", ret);
	return 0;
}

运行结果

初识C语言·函数_第2张图片

对的吧?

初识C语言·函数_第3张图片

按照学习陌生函数来看一下这个函数,int 是Add函数的返回类型,所以我们应该用int 类型的ret来接收它,int a,int b,int c是这个函数的参数,这里这三个是形参,下面的是实参,一会儿再看,return a + b + c,是这个函数的返回值,不过是把函数的功能实现和返回值放在一起的而已,神奇吧。

细心的人会发现scanf那里有警告,那么这个警告是因为scanf的返回值没有int类型的变量接受,给它一个int类型的接收就行了。


3

形参,实参,比较好理解,形式参数和实际参数。这里为了好区分,我们把实参换成m,n,z

int Add(int a, int b, int c)
{
	return a + b + c;
}
int main()
{
	int m = 0, n = 0, z = 0;
	scanf("%d%d%d", &m, &n, &z);
	int ret = Add(m, n, z);
	printf("%d", ret);
	return 0;
}

m,n,z是函数的实际参数,那么函数的形式参数就是用来接受实际参数的地址或者是值的

由上上面的代码可以发现,形式参数和实际参数的名字是可以一样的,这是因为内存给它们的函数栈帧不同,即空间不同,名字一样无所谓啦。

那么

为什么a,b,c是形式参数呢?因为如果你不调用这个函数的话,这几个数实际是不存在的,没有向内存申请空间。不信?看看

初识C语言·函数_第4张图片

你看,在还没执行到这一步的时候,a,b,c是没有申请空间的。

初识C语言·函数_第5张图片

执行了之后,a,b,c,有了自己的空间,只不过是还没有接收到实参的值而已。

初识C语言·函数_第6张图片

这是已经实现的这个函数,又重新进入到主函数里面了,但是可以发现a,b,c的值变灰了。

初识C语言·函数_第7张图片

这是因为形参用完了,然后像内存申请的这块空间就被释放了,所以它们不存在了。

那形参和实参是同一个数吗?辨别的方法很容易,看看地址就行了。

初识C语言·函数_第8张图片

看吧,地址也不一样,所以我们常说,形参是实参的一份临时拷贝。这是传值的情况下,

传址的时候,形参就可以操控实参咯。

当然,形参和实参的个数一定要相等。


4

return语句,没错,就是刚才那个实现了函数功能和返回值一起的东西。

关于return记住这几点就行

1) return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执行表达式,再返回表达式 的结果。

2)return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。

3)return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。

4)return语句执⾏后,函数就彻底返回,后边的代码不再执行。

5)如果函数中存在if等分支的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

第一个,比如刚才实现的Add函数,我们不是要三个数相加的结果吗,那么我们让return帮我们实现计算了,就不过多的写多余代码了。同理,代码实现到最后我们要实现加减乘除,也可以直接用return语句了。

第二个,因为返回类型是void的话,就代表没有返回值,那么直接return;也是可以的。

第三个,比如返回类型是int,你返回一个a,那么返回的值就是97,上图

初识C语言·函数_第9张图片

字符a的ASCII的值是97,所以ret接收到的就是97咯。

第四个,return代表的就是这个函数执行完毕了,后续当然不会执行了,来看看

初识C语言·函数_第10张图片

第五个,一个程序要结束,就要保证每个情况都有返回值,话不多说,直接上图

初识C语言·函数_第11张图片

vs里面如果没有返回值的话就默认返回1了,在其他的编译器就会报错了。


5

先看一个对数组概念混淆的例子

初识C语言·函数_第12张图片

这串代码是想把数组的所有元素改成1 ,然后进行打印,可是为什么只打印了两个数?

问题出在sz上面,初识C语言·函数_第13张图片

你又再看,sizeof(array)hi不是有警告,且听我一一道来,这是因为数组传参不同于刚才的Add函数,Add函数传的是值,数组传参传的是地址,也就是说,修改XY函数里面的数组会让主函数数组的值得到改变,那么为什么sz等于2呢?

因为传的是地址,地址是指针,在64位环境下,指针的值是8,8 / 2 肯定就是4咯,那个警告其实就是说咱们用另一个值除指针的大小值。

那么我们该怎么在自定义函数里面求得数组的大小呢?既然传的是地址,就免不了使用指针,所以这里不进行介绍,最好的办法就是在主函数里面求完了,在传过去。

如图

初识C语言·函数_第14张图片

看,好了。谨记数组传参传的是首元素地址

所以数组传参的时候是不会创建一个新的数组的,要是创建一个新数组,对内存的空间浪费多大呀。


6

1)嵌套调用

嵌套嘛,无非就是一个函数里面包含其他函数,嵌套实在是太常见了。

void XY(int array[],int sz)
{
	for (int i = 0; i < sz; i++)
	{
		array[i] = 1;
	}
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", array[i]);
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	size_t sz = sizeof(arr) / sizeof(arr[0]);
	XY(arr,sz);
	return 0;
}

就比如这个,XY函数里面调用了printf函数,这也是一种调用,所以不做过多介绍。

当然,函数是可以嵌套调用的,但是不能嵌套定义,就像一个人不能同时拥有两个名字一样。


2)链式访问

链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数 的链式访问。

比如 

初识C语言·函数_第15张图片

strlen的返回值作为printf的参数,就是一种链式访问。

那么来看一段有意思的代码。

int main()
{
 printf("%d", printf("%d", printf("%d", 43)));
 return 0;
}

来看看

初识C语言·函数_第16张图片

这是因为printf的返回值是成功写入的字符数。最后一个printf有两个字符,4 3,所以返回值是2,第二个有一个字符,1,所以返回值是1,那么第一个printf打印出来的就是1,所以最后的结果是4321。


7

初识C语言·函数_第17张图片

红色的部分就是函数的定义,绿色的部分是函数的调用,没错,函数的定义就是整个函数的执行过程。

欸?那函数的声明呢?

因为我把自定义函数放在main函数的前面了,这也算是函数的声明了,虽然程序是从main函数开始的,但是编译器会从第一行开始“扫视”,在函数调用前看到了这个函数,都算声明了。

初识C语言·函数_第18张图片

当我把自定义函数放在下面,就需要先声明了,只要声明的位置在函数调用之前就行,

蓝色是函数的声明,红色的是函数的调用,绿色就是函数的定义。

函数的声明其实就是写清楚函数的返回类型,参数就可以了。

当然,实际的多文件运用中,函数的声明一般放在头文件,函数的实现一般放在源文件里面,最后引用一下自己创建的头文件就可以了。

比如你可以试一下能实现加减乘除的代码呢?在使用多个文件的情况下。


8

1)extern

extern也是关键字,是声明外部符号的,比如我在A文件里想要使用B文件的内容,就可以用到extern。

初识C语言·函数_第19张图片初识C语言·函数_第20张图片

这样程序就可以执行了,最后打印2024。

在介绍static之前,需要解释一下生命周期作用域

作用域是 某个变量只能在某一块代码块使用的那块代码块,局部变量的作用域是该局部变量所在的局部范围,全局变量的作用域就是整个程序。

生命周期是指变量从创建到销毁所消耗的时间,局部变量的声明周期是创建到销毁,全局变量的生命周期是整个程序的生命周期。

2)static

i)static修饰局部变量

void test()
{
	static int i = 1;
	printf("%d ", i);
	i++;
}
int main()
{
	for (int i = 0; i < 10; i++)
	{
		test();
	}
	return 0;
}

初识C语言·函数_第21张图片

初识C语言·函数_第22张图片

不难发现,有了static之后,局部变量i的生命周期,让它从栈区转到了静态区,静态区的变量的生命周期和全局变量是一样的,所以每次进入函数test之后,i不会随着函数使用结束而销毁,所以i会自增下去。

ii) static修饰全局变量

初识C语言·函数_第23张图片初识C语言·函数_第24张图片

打印!

然后就报错啦。

同理,static改变了val的生命周期,变成了只能在该源文件使用的变量,本质是因为全局变量是有外部链接属性了,使用extern就可以在其他文件使用了,可是static使外部链接属性变成了内部链接属性,所以就报错咯。

iii) static修饰函数

这个!对!和全局变量是一样的,偷懒了,不写了哈哈哈。

同样只能在当前的源文件使用,没有外部链接属性了,没了。


感谢阅读。

你可能感兴趣的:(c语言,开发语言)