函数
1.概念
C语⾔中的函数就是⼀个完成某项特定的任务的一小段代码。
这段代码是有特殊的写法和调用方法的。
在C语⾔中我们⼀般会见到两类函数:
• 库函数
• ⾃定义函数
2.库函数
(i)标准库:C语⾔标准中规定了C语⾔的各种语法规则,C语⾔并不提供库函数;C语⾔的国际标准ANSIC规定了⼀些常⽤的函数的标准,被称为标准库。
那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列函数的实现。这些函数就被称为库函数。简单来说就是库函数是现成的,我们可以直接使用。
(ii)头文件:各种编译器的标准库中提供了⼀系列的库函数,这些库函数根据功能的划分,都在不同的头⽂件中进行了声明。
库函数是在标准库中对应的头⽂件中声明的,所以库函数的使用,务必包含对应的头⽂件,不包含是可能会出现⼀些问题的。
库函数相关头⽂件查询网址:1.https://zh.cppreference.com/w/c/header
2.cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
(iii)库函数文档的⼀般格式
(即点进上方网址后,点进一个库函数后,内部的大致格式)
a. 函数原型
b. 函数功能介绍
c. 参数和返回类型说明
d. 代码举例
e. 代码输出
f. 相关知识链接
3.自定义函数
根据命名便可知就是程序员自己定义出的一个函数。
(i)函数的语法形式
ret_type fun_name(形式参数)
{
}
• ret_type 是函数返回类型
• fun_name 是函数名
• 括号中放的是形式参数
• {}括起来的是函数体
我们可以把函数想象成⼀个加工厂,工厂得输⼊原材料,经过工厂加⼯才能⽣产出产品,那函数也是⼀样的,函数⼀般会输⼊⼀些值(可以是0个,也可以是多个),经过函数内的计算,得出结果。
ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表示什么都不返回
• fun_name 是为了方便使用函数;就像⼈的名字⼀样,有了名字方便称呼,函数有了名字方便调用,所以函数名尽量要根据函数的功能起的有意义。
• 函数的参数就相当于,工厂中送进去的原材料,函数的参数也可以是void 明确表示函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
• {}括起来的部分被称为函数体,函数体就是完成计算的过程。
(ii)举例
#include <stdio.h>
int Add(int x, int y)//建立时需要写出接收的参数的类型
//并且该函数的返回类型也要写出
{
int z = 0;
z = x+y;
return z;
}
int main()
{
int a = 0;
int b = 0;
//输⼊
scanf("%d %d", &a, &b);
//调⽤加法函数,完成a和b的相加
//求和的结果放在r中
int r = Add(a, b);//调用时不需要写出参数类型
//输出
printf("%d\n", r);
return 0;
}
(iii)形参和实参
在函数使用的过程中,把函数的参数分为,实参和形参。
以刚才为例我们来解释说明一下:
#include <stdio.h>
int Add(int x, int y)
//括号中写的 x 和 y ,称为形式参数,简称形参。
{
int z = 0;
z = x+y;
return z;
}
//上面这部分函数是Add函数的定义,有了定义后,下面的主函数才能调用。
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int r = Add(a, b); //在调用函数时,只需要输入函数名和所需要的参数即可
//这边调用时传递给函数的参数a和b,称为实际参数,简称实参
printf("%d\n", r);
return 0;
}
实参是传递给形参的,他们之间是有联系的,但是形参和实参各自是独立的内存空间。而如何证明呢,我们可以将程序运行,并查看他们的地址。
如下:
很明显我们在调试的可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样的,所以我们可以理解为形参是实参的⼀份临时拷贝。
4.return语句
在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式
的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会自动将返回的值隐式转换为函数的返回类型。
把刚才的例子进行改变一下为例,如下:
int Add(float x, float y)
{
float z = 0;
z = x + y;
return z;
}
int main()
{
float a = 0.0;
float b = 0.0;
scanf("%f %f", &a, &b);
int r = Add(a, b);
printf("%d\n", r);
return 0;
}
我们会发现此时,z的结果为浮点型,但是返回值的要求类型整型,数据类型不同,但最终的返回值,会根据返回值的类型进行输出,即把浮点型小数点后面省略变成整型来输出。
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
5.数组作为函数参数
在使⽤函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进行操作。那么我们要如何进行呢?举个例子:写⼀个函数对将⼀个整型数组的内容,全部置为-1,再写⼀个函数打印数组的内容。如下:
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
set_arr();//设置数组内容为-1
print_arr();//打印数组内容
return 0;
}
这里的set_arr函数要能够对数组内容进⾏设置,就得把数组作为参数传递给函数,同时函数内部在设
置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数。所以我们需要给set_arr传递2个参
数,⼀个是数组,另外⼀个是数组的元素个数。print_arr同理。
经过修改代码就变成了如下:
void set_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
arr[i] = -1;
}
void print_arr(int arr[], int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
//以上对数组初始化和打印的内容可以浏览我的博客中的对数组的讲解
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
set_arr(arr, sz);//设置数组内容为-1
print_arr(arr, sz);//打印数组内容
return 0;
}
注意点:
• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式的
• 形参如果是⼀维数组,数组大小可以省略不写
如下(对上面例子的截取):
void set_arr(int arr[], int sz)//这边便省略了大小
{
int i = 0;
for(i=0; i<sz; i++)
{
arr[i] = -1;
}
• 形参如果是⼆维数组,行可以省略,但是列不能省略
例子如下:
void set_arr(int arr[][10], int row, int col)//这里便只省略行没有省略列
{
int i = 0;
for (i = 0; i < col; i++)
{
int j = 0;
for (j = 0; j < row; j++)
{
arr[i][j] = -1;
}
}
}
int main()
{
int arr[10][10] = {0};
set_arr(arr,10,10);//设置数组内容为-1
print_arr(arr, 10, 10);//打印数组内容
return 0;
}
• 数组传参,形参是不会创建新的数组的
• 形参操作的数组和实参的数组是同⼀个数组
6.函数的嵌套调用和链式访问
(i)嵌套调用:就是函数之间的互相调用
例如:
int is_leap_year(int y)
{
if(((y%4==0)&&(y%100!=0))||(y%400==0))
return 1;
else
return 0;
}
int get_days_of_month(int y, int m)
{
int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day = days[m];
if (is_leap_year(y) && m == 2)
day += 1;
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;
}
在以上例子中main 函数就调用了 scanf 、 printf 、 get_days_of_month等函数,而get_days_of_month 函数调用了 is_leap_year函数。简单来说就是在一个函数中存在着其他函数的使用。
(ii)链式访问: 就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。例如:
#include <stdio.h>
int main()
{
int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度
printf("%d\n", len);//2.打印⻓度
return 0;
}
此时,该例子仅仅只是函数的嵌套调用,还不是链式访问,要形成链式访问就要将它们合并,如下:
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));//链式访问
return 0;
}
即将strlen函数的返回值作为printf函数的参数。
7.函数的声明和定义
函数在使用之前一定要先进行声明,没有声明怎么会知道有这个函数存在呢?
(i)单文件
但函数的声明又分为两种情况,一种是函数的定义在函数的使用前,另一种是函数的定义在函数的使用之后。
那么我们来举个例子
int is_leap_year(int y)//函数的定义
{
if(((y%4==0)&&(y%100!=0)) || (y%400==0))
return 1;
else
return 0;
}
int main()
{
int y = 0;
scanf("%d", &y);
int r = is_leap_year(y);//函数的使用
if(r == 1)
printf("闰年\n");
else
printf("⾮闰年\n");
return 0;
}
在以上例子中,定义在函数之前,那么这时候我们便不需要声明了,对于定义我们可以简单的理解为更详细的声明,因为函数的声明只需要函数名,函数的返回类型和函数的参数,不需要函数体,而定义就是在声明基础上追加上了函数体,即函数内部具体的代码过程。
int is_leap_year(int y);//函数声明
int main()
{
int y = 0;
scanf("%d", &y);
int r = is_leap_year(y);//函数的使用
if(r == 1)
printf("闰年\n");
else
printf("⾮闰年\n");
return 0;
}
int is_leap_year(int y)//函数的定义
{
if(((y%4==0)&&(y%100!=0)) || (y%400==0))
return 1;
else
return 0;
}
以上例子就是定义在使用之后,那么我们便要进行简单的声明,来保证后面代码中的函数能够正常执行。
总之以上所讲仅仅针对于,函数在单个文件之中的使用。
(ii)多文件
因为以后写代码时可能所需代码很多,所以我们不会把所以代码都放在同一个文件之中,我们会放在多个文件中。
⼀般情况下,函数的声明、类型的声明放在头文件(.h)中,函数的实现是放在原文件(.c)⽂件中。
举个例子:
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 = 10;
int b = 20;
//函数调⽤
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
这时我们便可以运行代码得出结果,如上图所示。
以上就是关于函数的所以内容啦,内容若有不妥恳请各位大佬指点,萌新一位制作不易,感谢大家的支持!!!