1. 函数的概念
2. 库函数
3. ⾃定义函数
4. 形参和实参
5. return语句
6. 数组做函数参数
7. 嵌套调⽤和链式访问
8. 函数的声明和定义
关于c语言同志们可能会听过一句话,叫做c语言就是由一个一个函数组成的,这句话我感觉是非常对的。我们起始接触c语言写的第一个函数,就使用了main函数。那么接下来我们就来讲解函数,本章是很重要。
一、函数的概念
在数学中函数,就是一个非常重要的知识,y = kx +b,我们可以通过传入一个x值从而得到一个y值
在c语言中我们也引入了函数(有时也被称为子程序),其实C语言的函数就是引入一个完成某个特定任务的一小段代码。我们要实现该代码就要有特定的调用方式。
一般我们会见到两类函数:
1 库函数(编译器自带的)
2 自定义函数
二、库函数
C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀些常⽤的函数的标准,被称为标准库,那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数
printf和scanf 函数都是常见的库函数,在使用的时候需要包含头文件。(库函数的使用为我们节省了时间)
2.2我们来给一个学习库函数的工具
我们举个例子来说明怎么观察和使用库函数
strlen //举例的函数
size_t strlen ( const char * str )
1 .size_t是函数的返回类型。
2.strlen 是函数名
3.const char *str 函数的参数,表示strlen函数传递的是一个地址。
功能:计算的是字符串的长度,但是不包括\0。
我们来举例子
#include<stdio.h>
#include<string.h>
int main()
{
char a[] = "abcdef";
printf("%zd", strlen(a));//strlen该代码的使用需要包括头文件<string.h>
return 0;
}
2.3 库函数的一般格式
三、自定义函数
了解库函数,但是更多更重要还是需要关注的是自定义函数(能给我们提高创作性)
我们来介绍一下,自定义函数的语法
我们可以把自定义函数理解成一个工厂
那么函数也一样的,我们创建一个函数通过形式参数接收数据(原材料),然后通过函数体实现(加工),得到返回数据(产品)。
- type 是用来接收计算后的返回类型,有时候返回类型是void类型(表示没有返回类型)
2.name表示的函数名:我们创建函数名是用来调用函数的,有了函数名我们就可以方便调用函数的。(最好的方式就是名字次要达意)。
3.函数的参数:函数的参数就相当于原材料,如果没有参数就是void函数,有参数我们就要交代好参数的名字,类型,以及参数个数
4、{}括起来的部分称为函数体,函数图就是完成计算的过程。
3.2 我们直接上例子
写一个加法函数,完成两个函数的相加
//所以这里返回值要用int类型接收
int add(int x, int y)//我们传递的a,b,所以形式参数也要声名其类型和名字
{
int z = 0;
z = x + y;
return z; //这里的返回值是int类型的z
}
//这里也可以简化为:
//int add(int x, int y)//我们传递的a,b,所以形式参数也要声名其类型和名字
//{
// return x+y; //这里的返回值是int类型的表达式
//}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数值,进行加法运算\n");
scanf("%d%d", &a, &b);
int ret = add(a, b);//函数名add表示的是相加,我们通过函数来传递整形 a,b
printf("%d ", ret);
return 0;
}
四:实参和形参
在函数的使用的过程中我们把函数的参数分为实参和形参
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数值,进行加法运算\n");
scanf("%d%d", &a, &b);
int ret = add(a, b);
printf("%d ", ret);
return 0;
}
4.1 实参
我们在调用函数的时候已经创建了a,b变量(已经有地址了),我们在调用的时候传递了a,b称为实际参数,简称实参
4.2 形参
在上面的函数中,在add的函数(x,y),称为形式参数简称实参。
为什么叫实际参数呢?事实上如果我们只是定义了add函数而不去调用的话,x,y只是在形式上存在的,但是其并不会创建地址(没有内存),不会真实存在。形式参数只有在调用的时候才会调用函数。
4.3实参和形参的关系
实参和形参是有联系的,但是其实际上两者是有区别的。
int add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数值,进行加法运算\n");
scanf("%d%d", &a, &b);
int ret = add(a, b);
printf("%d ", ret);
return 0;
}
这里我们发现,x和y确实接收了a,b的值,但是它们的地址是不一样的,所以我们可以理解为形参是实参的一份临时拷贝。
5 .return 语句
//11111111
int add(int x, int y)
{
return x+y;
}
int main()
{
int a = 0;
int b = 0;
printf("请输入两个数值,进行加法运算\n");
scanf("%d%d", &a, &b);
int ret = add(a, b);
printf("%d ", ret);
return 0;
}
//222222222222
void print(void)
{
printf("小黑子");
return;
}
int main()
{
print();
return 0;
}
//33333333333
int output(void)
{
double a = 3.14;
return a; //double类型
}
int main()
{
int ret = output();
printf("%d ", ret);
return 0;
}
//444444444444
void output(void)
{
printf("小黑子");
return; //提前返回
printf("真爱粉");
}
int main()
{
output();
}
//555555555555
//返回类型为int
int output(void)
{
int a = 0;
scanf("%d", &a);
if (a >=0)
{
printf("a大于等于0\n");
return a;
}
else
{
printf("a小于0\n");
return a;
}
}
int main()
{
int ret =output();
printf("%d", ret);
}
六、数组做函数参数
在我们使用函数的时候难免会遇见数组做函数的参数,那么当数组做函数的时候我们如何传参的呢?
我们来想一下数组中主要元素有两个(数组名,数组元素的个数)
void print(int arr[], int x) //那么用形参来接收的时候需要用一个数组来接收实参的数组,另一个整形来接收元素个数
{
for (int i = 0; i < x; i++)
{
printf("%d ", arr[i]);
}
}
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int ret = sizeof(arr) / sizeof(arr[0]); //得到的是元素个数
print(arr, ret);//实参传递的一个是数组,一个是元素个数
return 0;
}
这里我们需要注意以下几个重要的知识点
七、函数的嵌套调用和链式访问
我们知道循环可以嵌套的使用,那么函数可以嵌套使用吗?
当然也是可以的
举个例子:我们的手机是由许多部分组成而成的精密文件,那么函数之间相互的使用和调用才可以写出大型的程序
练习:我们计算某年某月有多少天
int is_year(int year)
{
if ((year % 4 == 0) && (year % 100 != 0) || (year % 100 == 0))
{
printf("%d是闰年\n", year);
return 1; //为闰年
}
else
return 0; //不为闰年且返回值均为整数
}
int is_day(int y ,int m )
{
int arr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = arr[m];
if (is_year(y) && m == 2) //函数的嵌套调用(这里用嵌套调用来判断是否为闰年)
{
day = day + 1;
return day;
}
else
{
return day;
}
}
int main()
{
int y = 0; //输入的是年
int m = 0; //输入的是月
scanf("%d%d", &y,&m);
int days = is_day (y,m);
printf("%d天", days);
return 0;
}
函数是可以嵌套使用的,但是不能嵌套定义。
7.2 链式访问
所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。
int main()
{
printf("%zd", strlen("abcdefg"));
return 0;
}
这里我们先把strlen函数计算出字符串的长度,然后又把该函数得到的返回值作为printf函数的的参数进行了打印
这里我们来举个有趣的例子
int main()
{
printf("%d", printf("%d", printf("%d", printf("%d", 54321))));
//printf函数返回值就是打印字符的个数;
return 0;
}
八、函数的声明和定义
1.特殊的声明
int add(int x, int y) //函数定义
{
return x + y; //当函数定义写在函数调用前就是特殊的声明
}
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d%d", &num1, &num2);
int ret =add(num1, num2); //函数调用
printf("%d", ret);
return 0;
}
2.正常的声明
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d%d", &num1, &num2);
int ret =add(num1, num2); //函数调用
printf("%d", ret);
return 0;
}
int add(int x, int y) //函数定义
{
return x + y; //当函数定义写在函数调用后没有声明就不可以正常使用
}
正常的改动应该是这样的
int add(int x, int y); //这里我们需要声明才可以正常的使用
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d%d", &num1, &num2);
int ret = add(num1, num2); //函数调用
printf("%d", ret);
return 0;
}
int add(int x, int y) //函数定义
{
return x + y;
}
进行改动就不会报错的,可以正常使用
8.2 多个文件
我们在写大型代码的时候,往往不会只创建一个文件,而是会创建出许多文件(根据文件所实现的内容不同)
一般情况下我们会把函数的声明,类型的声明放在头文件中(.h)函数的实现是放在源文件(.c)文件中。
这里我们分别给不同的文件中放入实现该函数的不同代码,但是为什么为报错呢?
原因是我们没有调用自己所创建的函数的头文件
如果我们加上就不会出现该问题
add.h文件
int add(int x, int y);//函数声明
add.c 文件
int add(int x ,int y) //函数定义
{
return x+y ;
}
text.c 文件
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include "add.h"
int main()
{
int num1 = 0;
int num2 = 0;
scanf("%d%d", &num1, &num2);
int ret= add(num1, num2);
printf("%d", ret);
return 0;
}
这样分别把代码的定义,调用,声明分别放在不同的文件中,写代码就更加方便。
8.3 static和extern
接下来我们来学习这两个关键字
static是静态的意思,它的作用可以用来
1.修饰局部变量
2.修饰全局变量
3.修饰函数
extern 是用来声明外部符号的。
在我们了解这两个关键字之前,我们先需要掌握作用域和声明周期这两个概念。
作用域:指的是代码的可使用的范围
1.全局变量的作用域是整个项目(工程)
2.局部变量的作用域是变量所在的范围
生命周期:指的是代码进行创建(申请内存)销毁的过程(释放内存)
1.局部变量的生命周期,从进入作用域开始,出作用域结束
2.全局变量的生命周期,整个程序的生命周期
我们来分别举例子说明 static 和 extern的作用
1.修饰局部变量
void text2(void)
{
int a = 0; // 每次进入 a 都会被重新赋值为0 ;
a++;
printf("%d ", a);
}
//1.static 修饰局部变量
int main()
{
for (int i = 0; i < 5; i++)
{
text2(); //
}
return 0;
}
打印出的结果:
void text1(void)
{
static int a = 0; //static修饰局部变量a改变了其内存周期,使其在出函数的时候不会销毁,而是储存其执行的值
a++;//不会再重新创建变量,直接累积上次的数值进行运算
printf("%d ", a);
}
//1.static 修饰局部变量
int main()
{
for (int i = 0; i < 5; i++)
{
text1(); //
}
return 0;
}
结论:经过static修饰的局部变量改变了其生命周期,本质是改变了变量的储存地区,由原来的栈区改变为静态区(静态区的生命周期只有程序结束变量才销毁,内存才回收),但是作用域不变。
建议:在以后我们使用变量后如果还想保留其值,就可以用static来修饰
2.static修饰全局变量
add.c文件的代码
int num1 = 10;
text .c函数的代码
//static修饰全局变量
//extern函数就是声明外部符号的
extern int num1;//我们要使用add.c文件中的数据,就要声明一下
int main()
{
printf("%d", num1);
return 0;
}
这里就可以正常打印
add.c文件的代码
static int num1 = 10;
add.c文件的代码
//static修饰全局变量
//extern函数就是声明外部符号的
extern int num1;//我们要使用add.c文件中的数据,就要声明一下
int main()
{
printf("%d", num1);
return 0;
}
这里我们发现了经过static修饰过后的全局变量在add.c文件中无法使用了。
结论:经过static修饰的全局变量只能在本源文件内使用,及时其它源文件进行声明之后,也不可以使用了(也就是成为了内部的链接属性)
建议:如果一个全局变量只想在本源文件内部使用就可以用static进行限制
3.static 修饰函数
add.c文件的函数
#include<stdio.h>
void text(void)
{
printf("abcdef");
}
text.c文件函数
//static修饰函数
extern void text(void);
int main()
{
text();
return 0;
}
可以正常使用
add.c文件的函数
#include<stdio.h>
static void text(void)
{
printf("abcdef");
}
text.c文件函数
//static修饰函数
extern void text(void);
int main()
{
text();
return 0;
}
结论:这里我们发现static修饰函数和修饰全局变量的作用是一样的。
**使用建议:**⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。