C语言的函数

本文详细介绍了C语言中的函数概念,包括库函数的使用、自定义函数的定义与形参实参的区别,以及return语句、数组作为参数、嵌套调用和链式访问的示例。还讨论了static和extern关键字在函数声明和定义中的作用。
摘要由CSDN通过智能技术生成

一、函数的概念

C语言其实是无数个小的函数组成的,可以说一个大的计算机任务可以分成若干个小的函数完成。C语言中有两种函数类型:库函数和自定义函数。

二、库函数

C语言并不提供库函数,C语言的国际标准ANSI规定了一些常用的函数标准,被称为标准库。不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列 函数的实现。这些函数就被称为库函数。

我们学过的scanf、printf都是库函数,库函数也称函数。这些函数是现成的,直接拿来使用即可。用库函数时需要用引用相应的头文件。

举例:

#include<stdio.h>

#include<math.h>

int main()

{

        double  n = 0;
        printf(" %lf \n ",sqrt(n));

}

我们要求n的平方根,可以使用sqrt函数,但是使用sqrt函数的时候需要引用头文件“math.h”。

三、自定义函数

函数形式:

ret_type  fun_name(形式参数)

{

}

(1)ret_type:表示的是函数的返回值类型,可以是int,也可以void,什么都不返回。

(2)fun_name:表示的是函数名,根据自己的需求起名即可。

(3)( ):括号内放的是形式参数。

(4){ }:大括号括起来的称为函数体。

举例:

接下来我们讲仔细讲解这段代码。

四、形参和实参

1.实际参数

在上面的第13行代码中,调用Add函数时,传递给函数的参数m,n就是实际参数,也称实参。

实际参数就是真实传递给函数的参数。

2.形式参数

在上面的第4行代码中,定义Add函数时,括号里面的x,y就是形式参数,也称形参。

如果只是定义了Add函数,而没有调用,那么参数x和y就是形式上存在的,不会向内存申请空间,不是真实存在的,所以叫形式参数。形式参数只有在函数调用时,用来存放传递过来的实参,才会向内存申请空间,这个过程就是形参的实例化。

3.实参和形参的关系

虽然说实参是传递给形参的,但是他们的是独立的内存空间。

通过调试可以观察到这个现象。虽然x和y得到了m和n的值,但是地址不一样,所以我们可以理解为形参是实参的一份临时拷贝。

五、return语句

1.return后面可以是表达式,也可以是一个数值。如果是表达式,先执行表达式,再返回表达式的结果。

2.return后边也可以什么都没有,这种情况一般适用于返回类型是void的情况。

3.return返回值与函数返回类型不一致,系统会自动将返回的值隐式转换成函数的返回类型。

4.return返回后,函数就彻底返回,后面的语句将不再执行。

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

六、数组做函数参数

 在使用函数解决问题的时候难免会将数组作为参数,传递给函数,在函数内部对数组进行操作。

举例:

#include<stdio.h>

void set_arr(int arr[],int m)
{
	for (int i = 0; i < m; i++)
	{
		arr[i] = -1;
	}
}
void print_arr(int arr[], int m)
{
	for (int j = 0; j < m; j++)
	{
		printf("%d ", arr[j]);
	}
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int n = sizeof(arr) / sizeof(arr[0]);
	set_arr(arr,n);
	print_arr(arr,n);
	return 0;
}

七、嵌套调用和链式访问

1.嵌套调用

嵌套调⽤就是函数之间的互相调⽤。

举例:

//假设我们计算某年某⽉有多少天?
#include<stdio.h>

//判断是否是闰年
int is_leap_year(int x)
{
	if ((x % 4 == 0 && x % 100 != 0) || x % 400 == 0)
		return 1;
	else
		return 0;
}

//计算这个月有多少天
int get_days_of_month(int x,int y)
{
	int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = days[y];
	if (is_leap_year(x) && y == 2)
		day += 1;
	return day;
}
int main()
{
	int m = 0;
	int n = 0;
	scanf_s("%d %d", &m, &n);
	int r = get_days_of_month(m, n);
	printf("%d年%d月有%d天\n", m, n, r);
	return 0;
}

这段代码中在get_days_of_month函数中调用了函数is_leap_year,这就属于函数的嵌套调用。

分析一下这段代码:

      首先,我们要实现计算某一年的某一月有多少天,我们先要判断这一年是不是闰年(因为闰年的2月有29天),所以我们创建了is_leap_year函数。之后我们要计算这个月有多少天,创建了get_days_of_month函数,其中用数组存储十二个月的天数,再通过if语句判断判断,如果is_leap_year返回1,并且月份是2月,这时天数加1。

2.链式访问

链式访问就是将一个函数的返回值做另一个函数的参数,像链条一样把函数穿起来,就是链式访问。

举例:

#include<stdio.h>

int main()
{
	int r = strlen("abcdef");   //求字符串长度
	printf("%d\n", r);    //打印字符串长度
	return 0;
}

这段代码用2条语句完成了动作,如果我们用strlen的返回值直接作为printf的参数,就形成了一条链式访问。代码如下:

#include<stdio.h>

int main()
{
	printf("%d\n", strlen("abcdef"));
	return 0;
}

八、函数的声明和定义

1.单个文件

在这段代码中,225-231行是这函数的定义,第236行是函数的调用。函数的定义在函数调用之前,这样写没啥问题。但是如果函数定义在函数调用之后呢,代码如下:

这样写,在不同的编译器,可能会发出警告,这时候我们需要在函数调用前对函数进行声明。

函数的定义也是一种特殊的声明,所以函数定义放在函数调用之前也是可以的。我们平时写代码的时候就可以直接把函数定义写在main函数前。

2.多个文件

当我们多人协作写一个项目的时候,不可能所有人在一个.c文件里面进行编辑,这时候就可以创建多个文件。一般函数的声明,类型的声明我们存放在(.h)文件里,函数的实现是放在(.c)源文件里,如下图所示:

3.static和extern

(1)static

        static是静态的意思,它可以用来:

        ①修饰全局变量

        ②修饰局部变量

        ③修饰函数

(2)extern

        extern是用来声明外部符号的。        

(3)作用域和声明周期

        作用域:一般来说,一段程序代码中用到的名字不总是有效的,而限定这个名字可用性的代

                      码范围叫这个名字的作用域。

        ①局部变量的作用域是:变量所在的局部范围。

        ②全局变量的作用域是:整个工程。

        生命周期:指的是变量的创建(申请内存)到销毁(收回内存)的这一时间段内。

        ①局部变量的生命周期是:进入作用域变量创建,生命周期开始,出作用域变量销毁,生命 

                                                   周期结束。

        ②全局变量的生命周期是:整个程序的生命周期。

(4)static修饰局部变量:
#include<stdio.h>

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

这是没有用static修饰局部变量,我们来看他的运行结果。

接下来我们用static修饰局部变量,看看会发生什么变化。

#include<stdio.h>

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

通过观察发现:

(1)第一段代码,每次运行,进入test函数都会重新创建i变量,并赋值为0。函数执行完后变量被销毁,所以每次打印i的值都是1。

(2)第二段代码,用了static修饰局部变量i,我们可以看出,i是累加的,并没有销毁,所以重新进入函数也不会重新创建,直接累加上次的值进行计算。

结论:static改变了函数的生命周期。生命周期改变的本质是改变了储存类型,正常局部变量是存储在内存的栈区的,被static修饰后,就存到了静态区。存在静态区的变量和全局变量是一样的,所以生命周期就和程序的生命周期一样了,只有程序结束了,变量才会销毁,但是作用域是不变的。因此,一个变量出了函数后,我们想要保留变量的值,等下次进来继续使用,就可以使用static。

(5)static修饰全局变量:

代码1:

代码2:

extern 是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使用,代码1正常,代码2在编译的时候会出现链接性错误。

结论:⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。 本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源 ⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。因此,如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤ static修饰。

  • 33
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值