前言:函数是一个应用非常广泛的东西,我们最熟悉的应该就是数学中的函数了,在C语言中也常常需要使用函数,从而更好地完成任务,在这里,我们要好好掌握它。
1.函数的概念
我们不妨回忆一下中学时代学过的函数的定义:
设x , y 两个变量,x 的变化范围是实数集 D 如果对于任何的x ∈ D 按照一定的法则都有唯一确定的 y 值与之对应,则称变量 y 是变量 x 的函数,记为 y = f ( x )
也就是说,x 在经历法则运行以后,会等于 y
这与C语言函数是一个意思
在C语言引入函数(function) →有时候也称之为子程序,不管哪种称呼,我们都可以理解为
C语言中的函数就是一个完成某种特定任务的一小段代码。
C语言程序无非就是无数个小函数组合而成的。在文章的中间部分会展现出它精妙的作用。
2.函数类型
一般我们把函数分为两大类:
库函数
自定义函数
2.1库函数
我们在之前的学习中经常看到
下面红箭头部分
printf
很多初学者并不知道这是什么,其实这就是库函数
我们不妨直接了解库函数使用方法来明白库函数到底是什么和怎么用
在上面的代码中出现了printf和scanf,这两个都是库函数,然而我们如果想使用它 ,就必须先声明这两个函数 也就是 →#include <stdio.h>
#include <stdio.h> 是一个头文件,而这个头文件中包含了这两个函数如何使用
也就是说,如果想使用printf和scanf,我们只需要在代码开头打出 #include <stdio.h>
标准库
在 #include <stdio.h>中,不仅仅只有这两种函数,还有很多事先定义好的函数 这就是一个标准库
头文件
在各种编译器中,基本都提供一系列的库函数,根据函数功能的划分都在不同的头文件中进行声明(定义)
库函数的种类
库函数种类有很多,在这里我就不做一一讲述了,当然有兴趣的小伙伴可以慢慢学习
下面我为大家搜集到的链接供大家使用
库函数相关头⽂件:https://zh.cppreference.com/w/c/header
C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
2.2自定义函数
自定义函数的意义
库函数很方便,但是我们应更加关注自定义函数,库函数只能由编译器为我们提供函数,然而,我们在实际解决任务中,很多时候情况都比较复杂,在这种情况下,库函数的作用微乎其微,甚至不能用,那么我们用更加具有创造性的自定义函数会方便很多
自定义函数如何使用
接下来,我同样用例子来说明自定义函数的语法和使用方法
在一项任务中,我们需要完成两个整型变量的加法操作,首先我们定义两个变量
#include <stdio.h> int main() { int a = 0; int b = 0; scanf("%d %d",&a,&b); printf("%d\n",) }
在输入函数前,我们能做的只有这么多,接下来我们写一个变量相加的函数,
#include <stdio.h> int add(int x,int y) { int n = 0; n = x + y; return n; } int main() { int a = 0; int b = 0; scanf("%d %d",&a,&b); printf("%d\n",) }
好了,接下来我们把我们写的函数嵌入到我们之前的代码中
#include <stdio.h> int add(int x,int y) { int n = 0; n = x + y; return n; } int main() { int a = 0; int b = 0; scanf("%d %d",&a,&b); int r = add(a, b); printf("%d\n", r); return 0; }
最后运行代码
add函数也可以简化成
int add(int x, int y) { return x + y; }
虽然这是一个非常简单的自定义函数,但是它的多样性让我们不难看出,自定义函数需要我们有创造性思维.
3.形参和实参
这两个东西是比较抽象的
在函数使用过程中我们把函数的参数分为实参和形参
我们用之前的代码理解一下
上面代码中,3~8行是加法函数(add)的定义,在第14行调用add函数,我们发现两个部分使用的字母不一样,
实际上,在我们第14行调用函数使用的"a"和"b",就是实际参数
而在定义函数时(3~8行),我们使用的 "x""y" 就是形式参数
在代码运行中,为了存放 实际参数 传递回来的 值,实际参数 会向 形式参数 申请内存空间
为了更好的理解,我用中学学习的一次函数举个例:
f(x)= x+1 定义域是(-∞,+∞),
现在我需要做一项任务
令 x 等于 1
好,那么现在
对于c语言来说 1 就是 实际参数
而 x 就是形式参数
接着,f(x)要去申请内存空间...
注意:形参 和 实参 各自占用独立的内存空间
4.return 语句
在之前我们学过的代码中经常会在代码结尾出现 return
接下来我来说明一下它的语法规则
1. return 语句执行以后,函数彻底返回,后面的代码不再执行
2. return 后可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返 回表达式
3.当我们使用 void函数 时,return 后面可以什么也没有(因为 void函数 不返回)
4.我们之前学习常用的 return 0;通常在 main()函数 末尾使用,这个语句告诉我们,当前程序正常执行,并返回一个整数值,然后顺利退出程序。我们也可以用其他非零值来表示不同的程序状态
5.如果 return 返回的值跟函数返回类型不一样,系统会自动返回函数的返回类型
6.如果函数中存在 if 等分⽀的语句,则要保证每种情况下都有 return返回,否则会出现编译错误
5.数组做函数参数
我们在解决问题的时候,难免会将数组作为参数传递给函数
因我们已经学过数组 也大概了解了函数的使用
接下来我用一个例子简单说一下,如何操作
写一个函数,将一个整型数组的内容全部置为1,再写一个函数打印数组内容
首先,先写出基本数组
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; return 0; }
想一下,如果我们要对数组内容进行设置,我们需要知道数组的元素和个数
这里我们要引进一个新的函数 sizeof
在这里简单说一下它的作用:sizeof 用于获取数据类型和大小,返回以字节为单位的对象大小,更简单来说,就是一个计算大小的运算符
接下来我们利用上 sizeof
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); return 0; }
sizeof(arr):返回整个数组的大小
sizeof (arr[0]):返回数组中第一个元素的大小(因为 arr数组的类型都是整型,大小都一样)
sizeof(arr) / sizeof(arr[0]): 用数组总大小除以数组中一个元素的大小,那么会将得到元素个数
接下来我们开始设计函数
void setarr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { arr[i] = 1; } }
上面是第一个函数:利用for循环将数组里的元素全部覆盖成 1
void printarr(int arr[],int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d", arr[i]); } printf("\n"); }
上面是第二个函数:利用for循环打印数组里的所有元素
然后调用函数,即完成
void setarr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { arr[i] = 1; } } void printarr(int arr[], int sz) { int i = 0; for (i = 0; i < sz; i++) { printf("%d", arr[i]); } printf("\n"); } int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); setarr(arr, sz); printarr(arr, sz); return 0; }
6.嵌套调用
嵌套是计算机程序中不可少的一部分,像之前我们学习的循环嵌套一样,函数嵌套也有一套说法。
下面我用一个例子来给大家演示一下,函数如何嵌套
计算某年某月有多少天
思路:
闰年影响2月天数,首先我们要判断是否是闰年,之后根据月份计算这个月天数
我们设定两个函数
leapyear(): 判断闰年
day(): 判断某个月的天数
int leapyear(int y) { if ( ((y % 4 == 0)&&(y % 100 != 0)) || (y % 400 == 0) ) //判断闰年方法 return 1; else return 0; }//如果是闰年返回1,不是闰年返回0 int monthday(int y, int m) { int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; int day = days[m]; if (leapyear(y) && m == 2) //如果leapyear返回1,并且m==2(二月),则天数加一 day += 1; return day; } int main() { int y = 0; int m = 0; scanf("%d %d", &y, &m); int d = monthday(y, m); printf("%d\n", d); return 0; }
main函数 调用 scanf,printf,monthday
monthday函数 调用 leapyear
(上面数组中从0开始时为了让数组元素下标跟实际月份对应上)
结果
7.链式访问
链式访问就是将一个函数的返回值作为另一个函数的参数,就像链条一样穿起来
我们看一个比较有趣的代码
int main() { printf("%d", printf("%d", printf("%d", 43))); return 0; }
这个代码结果是什么呢?
答案是:打印4321
为什么呢
首先我们要搞清 printf函数返回什么
我替你们搜啦:
printf函数返回的是,打印屏幕上字符的个数
上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个 printf的返回值。
第三个printf打印 43,在屏幕上打印2个字符,再返回2
第⼆个printf打印 2,在屏幕上打印1个字符,再放回1
第⼀个printf打印 1 所以屏幕上最终打印:4321
8.函数的声明和定义
函数的声明
我们回忆一下我们写的代码,函数的内容是不是都在函数调用的前面,那么如果我们把他们的位置交换一下会发生什么呢?
答案是,程序会报错
这是因为,C语言编译器对代码编译的时候是从上往下扫描的,遇到第54行monthday函数调用的时候,发现你前面并没有定义这个函数,就会报错
那么如果你想函数定义放在调用函数下面,就得加上函数的声明
int leapyear(int y);//函数声明 int monthday(int y, int m);//函数声明 int main() { int y = 0; int m = 0; scanf("%d %d", &y, &m); int d = monthday(y, m); printf("%d\n", d); return 0; } int leapyear(int y) { if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) //判断闰年方法 return 1; else return 0; }//如果是闰年返回1,不是闰年返回0 int monthday(int y, int m) { int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; int day = days[m]; if (leapyear(y) && m == 2) //如果leapyear返回1,并且m==2(二月),则天数加一 day += 1; return day; }
多个文件(管理代码)
在我们写的代码过多的时候,不可能将所有代码放到一个文件中,我们往往会根据程序的功能将代码放入不同的文件中
一般情况,我们会把函数声明放在头文件(.h)中,函数的实现(调用)放在源文件(.c)中
下面我来给大家实际操作一下如何使用头文件和源文件
如何创建头文件?(编译器VS2022)
弹出
接着右击头文件在添加这里新建项
然后点击头文件,名称自己根据实际情况起
最后完成( 不要把 #pragma once 删除)
在创建好头文件中,我们就可以把函数声明写在这里,如下
函数定义单独放
我们还可以把函数定义单独放入一个源文件中,这样,我们在写代码时可以直接调用函数
按照刚才创建头文件的方法,再创建一个源文件,这个源文件写函数的定义
多个文件演示
但是如果想让程序正确运行我们还差最后一步,那就是我们前面所讲的包含头文件
这样就大功告成啦
结尾:这样部分相对来说比较杂,但是通过系统的学习,把知识串联起来,我相信这不是我们学习C语言的绊脚石🙂
作者留言:本人是初学者,制作不易,同时希望大家可以纠正我的错误和不恰当的地方,谢谢大家! 创作时间:2023.11.27