C语言 函数

1.函数的概念

在数学领域中我们便接触过函数,例如一次函数y=kx+b。我们可以将函数比作一个工厂,你输入原材料,它经过加工输出产品。
在C语言中同样有着函数(function)的概念,也称作:子程序。在C语言中函数就是一个完成某项特定的任务的一小段代码。事实上,C语言的程序就是由无数个小的函数组合而成的。

在C语言当中我们一般会见到两类函数:

  • 库函数
  • 自定义函数

2.库函数

2.1 标准库和头文件

C语⾔标准中规定了C语⾔的各种语法规则,但并不提供库函数;C语⾔的国际标准ANSI规定了⼀些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。库函数也是函数,只不过这些函数是现成的,我们可以直接拿来用,能够极大的提高效率。库函数根据功能的划分,在不同的头文件中进行了声明。

可在库函数相关头文件中找到需要的头文件。

3.自定义函数

与库函数不同,自定义函数是为了满足某种需求自己创造的函数,所以它能带来更大的灵活性。

3.1 函数的语法形式

其语法格式如下:

ret_type fun_name(形式参数)
{

}

其中:

  • ret_type是函数的返回值类型,即函数计算结果的类型,返回类型可以是void,表示什么都不返回。
  • fun_name函数名,可根据实际需求设置
  • 括号中是形式参数,要交代清楚参数的类型名字,以及参数的个数。函数的参数也可以是void
  • {}中是函数体,即完成计算的过程。

例如:
完成一个加法函数,输入两个整型,输出它们的和。

#include <stdio.h>
//加法函数
int Add(int x,int y)
{
	int z = x + y;
	return z;//输出x,y的和
}
int main()
{
	int a = 0, b = 0;
	//输入
	scanf("%d%d", &a, &b);
	int c = Add(a,b);
	printf("%d\n", c);
	return 0;
}

运行结果:
在这里插入图片描述

其中Add函数也可以简化为:

int Add(int x, int y)
{
	return x + y;
}

4.形参和实参

4.1 实参

在函数使用过程中,函数的参数分为实参和形参。
我们先看看前面的代码:

#include <stdio.h>
//加法函数的定义
int Add(int x,int y)
{
	int z = x + y;
	return z;//输出x,y的和
}
int main()
{
	int a = 0, b = 0;
	//输入
	scanf("%d%d", &a, &b);
	int c = Add(a,b);//函数的调用
	printf("%d\n", c);
	return 0;
}

在上述代码中,3~7行是Add函数的定义,第13行是函数的调用,传递给函数的参数ab,称为实际参数,简称实参。实际参数就是真实传递给函数的参数。

4.2 形参

在上述代码中,第三行定义Add函数时,在函数名后的括号中写的xy,称为形式参数,简称形参。通过字面我们可以看出,它只是形式上存在,不会向内存申请空间,只有在函数被调用的过程中来存放实参穿过来的值,才向内存申请空间,并且在函数调用完毕后,形参就会在内存中销毁。

4.3 实参和形参的关系

我们可以通过如下代码来研究实参和形参的关系:

#include <stdio.h>
void Test(int x, int y)
{
	printf("x的地址为:%p\n", &x);
	printf("x的值:%d\n", x);
	printf("y的地址为:%p\n", &y);
	printf("y的值:%d\n", y);
}
int main()
{
	int a = 3;
	int b = 5;
	printf("a的地址为:%p\n", &a);
	printf("a的值:%d\n", a);
	printf("b的地址为:%p\n", &b);
	printf("b的值:%d\n", b);
	Test(a,b);
	return 0;
}

运行结果:
在这里插入图片描述

由上述代码及运行结果可以看出,虽然实参和形参的值相等,但实参与形参的地址并不相同,所以可以理解为形参是实参的一份临时拷贝

5.return 语句

在函数使用中,经常会用到return语句。

  • return后边可以是一个数值,也可以是一个表达式,如果是表达式,则返回表达式的结果。
  • return后边也可以什么都没有,这种写法适合函数返回类型为void的情况。
  • return返回的值和函数返回类型不一致时,系统会自动将返回的值隐式转换为函数的放回类型。
  • return语句执行后,函数就彻底返回,后面的代码不再执行。
  • 如果函数中存在if等分支语句,则要保证每种情况下都有return返回,否则会出现编译错误。

6.嵌套调用和链式访问

6.1 嵌套调用

嵌套调用就是函数之间相互调用。每个函数之间相互调用从而实现了一个大的程序。

例如:输入某年某月,输出这一月有多少天。

#include <stdio.h>
//判断是否为闰年
int LeapYear(int year)
{
	if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		return 1;
	else
		return 0;
}
//判断天数
int Day(int year, int month)
{
	int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	int day = days[month];
	if(LeapYear(year) && month == 2)//嵌套调用:在Day函数中调用了LeapYear函数
		day++;
	return day;
}
int main()
{
	int year = 0;
	int month = 0;
	scanf("%d%d", &year, &month);
	int d = Day(year,month);
	printf("%d", d);
	return 0;
}

上述代码中:

  • Day函数中嵌套调用了LeapYear函数
  • main函数中调用了scanfDayprintf函数。

6.2 链式访问

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

例如:

#include <stdio.h>
int main()
{
	printf("%d\n", strlen("abcdefgh"));
	return 0;
}

上述代码中,就是将strlen的返回值直接作为printf函数的参数,实现了链式访问。

7.函数的声明和定义

7.1 单个文件

让我们来看以下代码:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 9;
	int b = 4;
	int num = Add(a,b);
	printf("%d", num);
	return 0;
}

其中2~4行是函数的定义,第10行代码是函数的调用。这种情况下,函数的定义在函数的调用前面。

那如果函数的调用在函数的定义前面呢?

#include <stdio.h>
int main()
{
	int a = 9;
	int b = 4;
	int num = Add(a,b);
	printf("%d", num);
	return 0;
}
int Add(int x, int y)
{
	return x + y;
}

上述代码在编译运行时会报错,因为C语言编译器对源代码编译时自上而下执行,当调用Add函数时会发现前面并没有定义Add函数。

那该如何解决上述问题呢?那就是在使用函数之前声明一下函数,需要交代清楚:函数名函数的返回类型函数的参数

#include <stdio.h>
int Add(int x, int y);//函数的声明
int main()
{
	int a = 9;
	int b = 4;
	int num = Add(a,b);
	printf("%d", num);
	return 0;
}
int Add(int x, int y)
{
	return x + y;
}

函数的定义也是一种特殊的声明,如果函数定义放在调用之前也是可以的。

7.2 多个文件

在实际应用中,代码量可能会很大,我们往往会根据功能的不同,将代码拆分在多个文件中。一般情况下,将函数的声明、类型的声明放在头文件中,函数的实现放在源文件中。

例如:

add.c

//函数的定义
int Add(int x, int y)
{
	return x + y;
}

add.h

//函数的声明
int Add(int x, int y);

test.c

#include <stdio.h>
#include "add.h"
int main()
{
	int a = 4;
	int b = 5;
	//函数的调用
	int c = Add(a,b);
	printf("%d", c);
	return 0;
}

在这里插入图片描述

7.3 static

staticextern都是C语言中的关键字。
static的中文是“静态的”,它可以用来:

  • 修饰局部变量
  • 修饰全局变量
  • 修饰函数

首先我们来了解一下作用域生命周期

作用域(scope):限定某个名字的可用性的代码范围就是这个名字的作用域。简单来说,作用域就是某个东西能起作用的区域。

  • 局部变量的作用域是变量所在的局部范围,即它所在的{}内。
  • 全局变量的作用域是整个工程

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

  • 局部变量的生命周期是:进入作用域,变量创建,生命周期开始;出作用域,生命周期结束。
  • 全局变量的生命周期是整个程序的生命周期。

7.3.1 static 修饰局部变量

static修饰局部变量会改变变量的生命周期。生命周期改变的本质是改变了变量的存储类型,由存储在内存的栈区变为存储在静态区。存储在静态区的变量和全局变量一样,生命周期和程序的生命周期一样,但作用域仍然不变。一个变量出了函数后,若我们仍想保留值,就可以使用static修饰

例如:

static int i = 0;
static char b = "abc";

7.3.2 static 修饰全局变量

一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他文件中使用。本质原因是全局变量默认是具有外部链接属性的,在外部的文件中想使用,只要适当的声明就可以使用;但是全局变量被static修饰之后,外部链接属性就变成了内部链接属性,只能在自己所在的源文件内部使用了,其他源文件,即使声明了,也是无法正常使用的。

7.3.3 static 修饰函数

static修饰函数和static修饰全局变量十分相似,都会使其只能在本源文件内使用,其他文件无法正常的链接使用。本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个工程中只要适当的声明就可以被使用。但是被static修饰后变成了内部链接属性,使得函数只能在自己所在源文件内部使用。

7.4 extern

extern用来声明外部符号。如果一个全局的符号在A文件中定义,在B文件中想使用,就可以使用extern进行声明。

例如:

add.c

int a = 0;

test.c

#include <stdio.h>
extern int a;
int main()
{
	printf("%d", a);
	return 0;
}

当然,如果全局变量astatic修饰,即使用extern声明,也不能在其他源文件中使用。

  • 45
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值