函数的概念
C语言中的函数是一个完成某项特定任务的一段代码,又称为子程序。我们可以类比成数学中的函数,两者同一性质,两者的函数都是一个工厂,对于数据进行储存加工处理,得到我们想要的结果。
函数分为库函数和自定义函数
这里我们着重讨论自定义函数
自定义函数
基本语法形式:
返回类型 函数名(形式参数)
{
}
//大括号括起来的部分是函数体,代表函数完成任务的过程
例如:
double sum(double x, double y)
{
.....
}
返回类型用来表示函数计算结果的类型,有些时候函数并不进行计算,没有计算结果,因此返回类型为void,即什么都不返回
函数名依据具体情况而定,英语不好可以查的doge_
函数举例
举个例子,我们想要完成两个浮点型的乘法运算
#include <stdio.h>
float mul(float a,float b)
{
float c = 0;
c = a * b;
return c;
}
//这个函数体还可以简化一下
//{
// return a * b;
//}
//直接返回两个浮点型的计算结果,同样的效果
int main()
{
float x = 0;
float y = 0;
printf("请输入两个浮点数\n");
scanf("%f%f",&x,&y);
float z = mul(x , y);//调用我们定义的函数
printf("%f\n",z);
}
函数部分我们需要交代清楚参数的个数,参数的类型和形参的名字
参数的个数依实际情况而定,函数可以有返回值也可以没有,不返回就用void
形参和实参
我们看一下前面的代码
形参
在函数名mul后的a,b称为形式参数
如果只定义了mul函数,而不去调用,mul的参数a,b只是形式上存在(虚拟存在),不会向内存申请空间,形参只有在函数被调用时才向内存申请空间。
实参
调用mul函数时,传递给函数的参数x,y,称为实参,是真实传递给函数的值
形参和实参的关系
实参是传递给形参的,这个过程称为形式的实例化,形参是实参的一份临时拷贝,对于形参的修改,不会改变实参
return语句
return后可以是一个值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果
return后也可以什么都没有,直接写“return;”,此时是函数返回类型是void的情况
return返回的值和函数返回类型不一致时,系统自动就返回值转化为函数的返回类型
如果函数中存在if等分支语句,要保证每种情况都有return返回,否则会报错
例如:
此时报出警报,原因是if的情况考虑不周,如果不等于2,其他情况呢?
数组做函数的参数
写一个函数将一个整型数组的内容全部置换成6,再写一个函数打印数组的内容
函数的框架应该如下
int main()
{
int arr1[5] = { 0,1,2,3,4};
int size1 = sizeof(arr1) / sizeof(arr1[0]);
replace_arr(arr1, size1);//将数组的元素全部置换为6
print_arr(arr1, size1);//打印数组的所有内容return 0;
}
我们想要对数组内容进行置换,得把数组作为参数传给函数,在函数内部设置数组时,需要遍历数组,也需要知道数组元素个数,所以要给 replace_arr()传两个参数,同样print_arr()也需要两个参数
在设计之前,需要了解以下知识:
函数形式参数要和函数实际参数个数匹配
函数的实参是数组,形参也可以为数组
形参如果是一维数组,数组大小可省略不写
形参如果是二维数组,行可省略,列不可省略
数组传参,形参不会创建新的数组
形参操作的数组和实参操作的数组是同一个数组,改变形参数组就是在改变实参数组(这需要到后面的知识才能理解)
同时置换和打印不许需要返回类型,用void即可
void replace_arr(int arr2[], int size2)
{
int i = 0;
for (i = 0; i < size2; i++)
{
arr2[i] = 6;
}
}void print_arr(int arr[],int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
我们将两部分进行组合
#include <stdio.h>
void replace_arr(int arr2[], int size2)
{
int i = 0;
for (i = 0; i < size2; i++)
{
arr2[i] = 6;
}
}void print_arr(int arr[],int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}int main()
{
int arr1[5] = { 0,1,2,3,4};
int size1 = sizeof(arr1) / sizeof(arr1[0]);print_arr(arr1, size1);//打印数组的所有内容
replace_arr(arr1, size1);//将数组的元素全部置换为6
print_arr(arr1, size1);//打印数组的所有内容return 0;
}
链式访问
链式访问是将一个函数的返回值作为另外一个函数的参数,像链条一样将函数穿起来,就像复合函数求导的链式法则一样
比如
printf("%d\n", strlen("1234"));
这里就是将strlen("1234")的返回值传给printf()函数的
再看一个例子
printf("%d ", printf("&d ",printf("%d ",12)));
这段代码打印结果是什么?首先我们需要知道printf()的函数返回是打印在屏幕上字符的个数
看上面的例子
第三个printf打印12 ,在屏幕上打印3个字符,返回3
第二个printf打印3 ,在屏幕上打印2个字符,返回2
第一个printf打印2 。
所以最后屏幕打印12 3 2 。
这个地方需要注意我在%d后面多打了一个空格,空格也算字符
函数的定义和声明
在单个文件中,函数的定义在主函数前,就不需要再声明了,如果在主函数之后定义,则需要在主函数前声明定义的函数,因为C语言编译是从上到下一条语句一条语句执行的。函数声明中,只保留类型,省略掉参数也是可以的,但是我推荐就按规矩办事,不要偷这个懒了
这里着重讨论多个文件
在一项工程中,代码量很大,我们需要分工合作,每个人解决一个板块,如果放在一个文件中,只能一个人做完下一个人继续,效率极低,所以我们需要将代码拆分成多个文件。
一般来说,函数的声明、类型的声明放在头文件中,函数的实现放在源文件中,函数的定义再放一个源文件中,比如我们一开始的乘法代码
此时我们的代码依旧正常运行,这个地方需要注意声明文件用""双引号
static(静态的)和extern(外部)
static用来
修饰全局变量 修饰局部变量 修饰函数
extern用来声明外部变量
在这之前先了解作用域和生命周期
作用域是一段代码作用的范围
局部变量的作用域是变量所在的局部范围
全局变量作用域是整个项目
生命周期是一个变量从创建到销毁的时间段
局部变量的生命周期是进入作用域变量创建,生命周期开始,出作用域变量销毁,生命周期结束
全局变量的生命周期是整个程序的生命周期
static修饰局部变量
第一段代码a出了作用于就被销毁
第二段代码a出了作用域并未销毁,也没有重新创建变量,二十累计上次的数值继续计算
结论:static修饰局部变量改变了局部变量的生命周期,本质是改变了变量的存储类型,局部变量位于栈区,被修饰后存储到了静态区,和全局变量的生命周期一样了,但是不改变作用域
使用:未来一个局部变量出了函数作用域,还想保留值,等下次进入函数继续使用
static修饰全局变量
第二张图片中多加了个static,文件链接错误
extern用于声明外部符号
结论:
全局变量默认具有外部链接属性,一个全局变量经过static修饰后,使全局变量作用域变小,外部连接属性变为内部连接属性
使用:一个全局变量只想在本文件中使用,不想被其他文件发现
同理,static修饰函数后,函数默认具有外部链接属性变为内部链接属性,只能在本文件中使用,不能跨文件使用
使用:一个函数只想在所在源文件中使用,不想被其他源文件使用