前言:在前一章,学习了数组相关的知识,在本章将会学到函数相关知识 ,一起加油吧!
--关于函数,学习数学时就对函数有过接触。就比如一次函数:y=kx+b;
--在C语言中也引入了函数概念,我们可以将其理解成“子程序”,意味着函数只是完成某个要实现的功能的一段代码。
C语言中函数会见到两类:
--库函数是由C语言标准库提供的,我们可以直接调用(之前用到的;main、scanf等);
当我们先想学习库函数时,提供了一些网站:
C/C++官方:https://en.cppreference.com/w/c/header.html
cplusplus.com:C library - C++ Reference
形式:
double sqrt (double x);
//sqrt 是函数名;
//x 是函数参数,这表示调用sqrt函数时需要传递一个double类型的数值;、
//double 是返回值类型-表示函数计算结果使都变了类型;Compute square root 计算平方根 Returns the square root of x. (返回平方根)
库函数相关头文件:https://zn.cppreference.com/w/c/header.html
因为函数是在标准库中对应头文件中声明的,所以在使用时要包含相应头文件;
#include <stdio.h>
#include <math.h>//包含头文件
int main()
{
//调用函数提供一个double的数值
double d = 16.0;
//调用函数,将d传递给函数
double r = sqrt(d);
printf("%lf", r);//打印double用lf
return 0;
}
打印:4.000000注:更多详细内容请移步上面网址 ( ͡° ͜ʖ ͡°)
--显然,标准库所提供函数并不能满足所有需求,这时候就需要重视自定义函数的作用;
形式:
return _type fun_name(形式参数)
{
函数体
return 返回值;
}
·return _type 是函数返回类型
·fun_name 是函数名补充说明:
--在说自定义函数时,先了解函数在程序中的调用! (≧∇≦)ノ
--前面说函数是如何创建的,函数调用就是用函数名和实参构成,其中实参用()括起; (关于实参的介绍在后面) (¬‿¬)
Add(x, y)
average(a, b)--对于voide函数,要在函数调用后面始终加“;” ,使其成为一个语句;
printf_pun();
对于将函数值赋给变量时,需要加分号;
avg = average(x, y);--在定义函数时,我们先将基本形式写出来,再对照思路写函数。
例1——写一个函数实现加法运算
思路:首先写通常情况下实现加法的代码,再据思路写函数
#include <stdio.h>
int main()
{
//首先给出两个变量
int a = 0;
int b = 0;
//输入想要加法运算的数值
scanf("%d %d",&a, &b);
//将输入的数值进行加法后赋值给c
int c = a + b;
//打印出最后加法结果
printf("%d", c);
return 0;
}--将加法函数命名Add,返回类型int,可知形参为两个int;
int Add(int a, int b)
{
return a + b;
}整合后:
#include <stdio.h>
int Add(int a, int b)
{
return a + b;
}
int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
//调用加法函数
printf("%d\n", Add(x, y));
return 0;
}--对于函数的定义放置位置没有严格规定,可以放在调用点的前面或后面;
--但放在后面时,最好对函数进行声明,以便函数调用时可以知道函数的信息;
函数声明类似于函数定义的第一行:
return _type fun_name(形参);
函数返回类型 函数名(ノ`Д´)ノ 特别注意:声明在结尾处有分号。
--形参出现在函数定义中,以假名字来表示函数调用时需要提供的值,在前面定义函数时,int Add(int a, int b),其中a、b就是形参;
--对于形参,在不调函数时,不会真实存在;当调函数时,形参用来存放实参数传递过来的值,并且向内存申请空间。
--在前面,printf语句中调用函数:Add(x, y) , x、y称为实参。
实参是通过值传递的:调用函数时,计算每个实参的值并赋给相应的形参。
对比项 | 形参 | 实参 | 联系 |
|---|---|---|---|
定义 | 函数定义中所声明的变量,用于接收实际参数传递值 | 在调用函数时传递的具体内容 | 实参数值会传递给形参 |
位置 | 函数声明部分 | 函数调用时的参数 | 在调用函数时,实参和形参一一对应 |
变量名 | 形参名只在函数内部有效,可以与实参名不同 |
当然,也存在数组做实参的情况,会在后面介绍; (ノ◕ヮ◕)ノ*:・゚✧
形式:
return 表达式;--表达式通常是常量或变量,也可能是更复杂的表达式。
注意事项:
对于return返回void——
void greet()
{
printf("Hello everyone!");
return;//可以省略
}--数组经常被当作实参;当形参是一维数组时,通常不说明数组长度。
int f(arr[])
{
......
}--那么就有一个问题,函数怎么知道数组长度呢? (・_・?)
--由于C语言没有提供方法来确定长度,需要自己来提供长度。这时可能想到在数组部分使用了 sizeof,比如int len = size of(arr) / size of(arr[0]) ,但在函数内部使用是错误的。(原因在指针方面会提到)
--折中的办法是,在main函数中计算出数组长度赋给一个变量,将变量作参数传给函数。
这里先说一下数组作参数的注意事项: (⁄ ⁄>⁄ ▽⁄<⁄ ⁄)
例2——将整型数组内容全置-1、打印出数组的内容;
思路——
#include <stdio.h>
main函数基本实现
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
//将数组元素全部置-1
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = -1;
}
//打印数组元素
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
函数定义
void set_arr(int arr[], int sz)
{
int i = 0;
//下面就要利用循环使数组全部置为-1
for (i = 0; i < sz; i++)
{
arr[i] = -1;
}
}
void printf_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
整合
int main()
{
int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
//调用函数使数组内容置-1
set_arr(arr1, sz);
//调用函数,打印数组内容
printf_arr(arr1, sz);
return 0;
}--函数嵌套调用:指函数之间互相调用,形成大型代码程序;但函数不能嵌套定义。
嵌套定义:在一个函数内部定义另一个函数。(在C语言中是不允许的)
例3——计算某年某月有多少天? ✧。ヾ(◍°∇°◍)ノ✧。
思路——
#include <stdio.h>//函数1
int is_leap_year(int y)
{
if ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0))
{
return 1;
}
else
{
return 0;
}
}
//函数2
int get_days_of_month(int y, int m)
{
int days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
//因为输入时给访问数组的地址,从0开始,而我们要从1开始输入,那可以将第0位占用
int day = days[m];
if (is_leap_year(y) == 1 && m == 2)
{
day++;
}
return day;
}
//主体
int main()
{
int y = 0;
int m = 0;
scanf("%d %d", &y, &m);
int d = get_days_of_month(y, m);
printf("%d \n", d);
return 0;
}--链式访问:将函数的返回值作为另一个函数的参数。
比如——
#include <stdio.h>
#include <string.h>
int main()
{
size_t len = strlen("abcdef");
printf("%zu\n", len);
return 0;
}
//将strlen
int main()
{
printf("%zu\n", strlen("abcdef"));
return 0;
}对与strlen函数,这里做点笔记:
(这是因为strlen计算的是字符串长度,而sizeof计算的是内存大小)
--对于单个文件,前面的( ͡° ͜ʖ ͡°) → 2.2.3 有介绍,不在赘述;
--在实际中,不会将函数的定义、声明、调用等放在同一源文件中。
通常是将它们分在.h文件和.c文件中 ——(以自定义add函数示范/自行添加文件)
add.h
int add(int x, int y);add.c
int add(int x, int y)
{
return x + y;
}test.c
#include <stdio.h>
#include "add.h"
int main()
{
int c = 10;
int d = 20;
int sum = add(c, d);
printf("%d\n", sum);
return 0;
}请注意:包含自定义的头文件时,用""。
--在对解释两种函数作用是,先简单来点铺垫工作。 (。-ω-)zzz`
作用域:限定变量可用范围;生命周期:变量从生成到撤销的时间段(内存分配到内存释放/变量占用内存的时间)
局部变量:作用域—创建变量坐在的小区间;生命周期—进入变量的作用域时开始,出作用域时结束。
全局变量;作用域—整个项目程序;生命周期—整个程序的生命周期。
--static会显著改变变量的存储期、生命周期,不会改变作用域;发生改变实际是延长了生命周期,普通变量存储在栈内存中(函数调用时创建,返回时销毁/生命周期=函数的执行期间)每次调用函数都会重新创建变量,而static修饰变量存储在静态存储区(程序启动时分配,结束释放/生命周期=程序的运行时间)重新调用函数不会再创建变量,对上次结果累加计算。
--static局部变量只初始化一次,在后续函数的调用会保留上一次修改后的数值。
代码实现——
void test()
{
int i = 0;
i++;
printf("%d", i);
}
int main()
{
int i;
for (i = 0; i < 5; i++)
{
test();
}
return 0;
}
输出:11111void test()
{
static int i = 0;
i++;
printf("%d", i);
}
int main()
{
int i;
for (i = 0; i < 5; i++)
{
test();
}
return 0;
}
输出:12345--被static修饰的全局变量只能在所在的源文件(.c文件)中使用。其本质:全局变量默认是有外部链接属性的,当想在其他源文件中使用只需要 extern 进行外部声明;但是static修饰后,外部链接属性就改成了内部链接属性,即使进行 extern 也没用。
--"extern"——“外部的”,即用来声明外部符号的。
--static修饰函数本质上与修饰全局变量一样,函数在整个工程中进行适当声明后都能使用(分为几个.c文件),而修饰后只能在一个文件中使用
结语:本篇文章到此结束,主要讲了函数的一些内容,后续会继续学习函数递归;如果这篇文章对你的学习有帮助的话,欢迎三连~~~