关于我重生到21世纪学C语言这件事——函数详解

在这里插入图片描述

与诸君共进步!!!

文章目录

  • 1. 函数是什么
  • 2. 库函数
  • 3. 自定义函数
  • 4. 函数参数
  • 5. 函数调用
  • 6. 函数的嵌套调用和链式访问
  • 7. 函数的声明和定义
  • 8. 函数递归
    • 8.1 什么是递归
    • 8.2 递归的限制条件
    • 8.3 递归的举例
    • 8.4 递归与迭代

1. 函数是什么

数学中我们常见到函数的概念。但是你了解C语言中的函数吗? 维基百科中对函数的定义:子程序

  • 在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,
    subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组
    成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
  • 一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软
    件库。

C语言中函数的分类:

  1. 库函数
  2. 自定义函数

2. 库函数

库函数:
为什么会有库函数?

  1. 我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想
    把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格
    式打印到屏幕上(printf)。
  2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
  3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

那怎么学习库函数呢?
这里我们简单的看看:www.cplusplus.com

在这里插入图片描述

简单的总结,C语言常用的库函数都有:

  • IO函数
  • 字符串操作函数
  • 字符操作函数
  • 内存操作函数
  • 时间/日期函数
  • 数学函数
  • 其他库函数

我们参照文档,学习几个库函数:

 char * strcpy ( char * destination, const char * source );
 void * memset ( void * ptr, int value, size_t num );

注: 但是库函数必须知道的一个秘密就是:使用库函数,必须包含
#include 对应的头文件。
这里对照文档来学习上面几个库函数,目的是掌握库函数的使用方法。

3. 自定义函数

听老师讲得最多的一句话就是:如果库函数能干所有的事情,那还要程序员干什么?

自定义函数和库函数一样,有函数名,返回值类型和函数参数。 但是不一样的是这些都是我们自己来设
计。这给程序员一个很大的发挥空间。

ret_type fun_name(para1, * ){statement;//语句项}ret_type 返回类型fun_name 函数名para1    函数参数

我们可以举一个栗子:

写一个函数可以找出两个整数中的最大值。

#include <stdio.h>int get_max(int x, int y)//get_max函数的设计{return (x>y)?(x):(y);}int main(){int num1 = 10;int num2 = 20;int max = get_max(num1, num2);printf("max = %d\n", max);return 0;}

写一个函数可以交换两个整形变量的内容。

#include <stdio.h>void Swap1(int x, int y){int tmp = 0;tmp = x;x = y;y = tmp;}void Swap2(int *px, int *py){int tmp = 0;tmp = *px;*px = *py;*py = tmp;}int main(){int num1 = 1;int num2 = 2;Swap1(num1, num2);printf("Swap1::num1 = %d num2 = %d\n", num1, num2);Swap2(&num1, &num2);printf("Swap2::num1 = %d num2 = %d\n", num1, num2);return 0;}

4. 函数参数

实际参数(实参):
真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类
型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形式参数(形参):
形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配
内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在
函数中有效。

上面Swap1和Swap2函数中的参数 x,y,px,py都是形式参数。在main函数中传给Swap1的num1,num2和传给Swap2函数的&num1,&num2是实际参数。

在这里插入图片描述

我们在调试的时候可以观察到,x和y确实得到了a和b的值,但是x和y的地址和a和b的地址是不⼀样
的,所以我们可以理解为形参是实参的⼀份临时拷⻉。

5. 函数调用

传值调用
函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。

传址调用

  • 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
  • 这种传参方式可以让函数和函数外边的变量建立起正真的联系,也就是函数内部可以直接操
    作函数外部的变量。

6. 函数的嵌套调用和链式访问

6.1 嵌套调⽤

嵌套调⽤就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐⾼的零件互相⽆缝
的配合才能搭建出精美的乐⾼玩具,也正是因为函数之间有效的互相调⽤,最后写出来了相对⼤型的
程序。

#include <stdio.h>void new_line(){printf("hehe\n");}void three_line(){int i = 0;for(i=0; i<3; i++){new_line();}}int main(){three_line();return 0;}

来个小练习
假设我们计算某年某⽉有多少天?如果要函数实现,可以设计2个函数:

  • is_leap_year():根据年份确定是否是闰年
  • get_days_of_month():调⽤is_leap_year确定是否是闰年后,再根据⽉计算这个⽉的天数
 int is_leap_year(int y){if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;elsereturn 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

未来的稍微⼤⼀些代码都是函数之间的嵌套调⽤,但是函数是不能嵌套定义的。

7.2 链式访问

所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数
的链式访问。

 int main(){int len = strlen("abcdef");printf("%d\n", len);return 0;}

前⾯的代码完成动作写了2条语句,把如果把strlen的返回值直接作为printf函数的参数呢?这样就是⼀
个链式访问的例⼦了。

int main(){printf("%d\n", strlen("abcdef"));//链式访问return 0;}

在看⼀个有趣的代码,下⾯代码执⾏的结果是什么呢?

 int main(){printf("%d", printf("%d", printf("%d", 43)));return 0;}

在这里插入图片描述

printf函数返回的是打印在屏幕上的字符的个数。
上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2
第⼆个printf打印2,在屏幕上打印1个字符,再放回1
第⼀个printf打印1
所以屏幕上最终打印:4321

7. 函数的声明和定义

7.1 单个⽂件

⼀般我们在使⽤函数的时候,直接将函数写出来就使⽤了。

⽐如:我们要写⼀个函数判断⼀年是否是闰年。

 int is_leap_year(int y){if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;elsereturn 0;}int main(){int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n")return 0;}

上⾯代码中橙⾊的部分是函数的定义,绿⾊的部分是函数的调⽤。
这种场景下是函数的定义在函数调⽤之前,没啥问题。

那如果我们将函数的定义放在函数的调⽤后边,如下

 int main(){int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n");return 0;}int is_leap_year(int y){if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;elsereturn 0;}

这个代码在VS2022上编译,会出现下⾯的警告信息:在这里插入图片描述
这是因为C语⾔编译器对源代码进⾏编译的时候,从第⼀⾏往下扫描的,当遇到第7⾏的 is_leap_year 函数调⽤的时候,并没有发现前⾯有 is_leap_year 的定义,就报出了上述的警告。

代码变成这样就能正常编译了。

int is_leap_year( inty);//函数声明int main(){int y = 0;scanf("%d", &y);int r = is_leap_year(y);if(r == 1)printf("闰年\n");elseprintf("⾮闰年\n");return 0;}int is_leap_year(int y){if(((y%4==0)&&(y%100!=0))||(y%400==0))return 1;elsereturn 0;}

函数的调⽤⼀定要满⾜,先声明后使⽤;
函数的定义也是⼀种特殊的声明,所以如果函数定义放在调⽤之前也是可以的。

7.2 多个⽂件

⼀般在企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会
根据程序的功能,将代码拆分放在多个⽂件中。

⼀般情况下,函数的声明、类型的声明放在头⽂件(.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;
}

有了函数声明和函数定义的理解,我们写代码就更加⽅便了。

7.3 static 和extern

static 和 extern 都是C语⾔中的关键字。
static 是 静态的的意思,可以⽤来:

  • 修饰局部变量
  • 修饰全局变量
  • 修饰函数

extern 是⽤来声明外部符号的。

在讲解 static 和 extern 之前再讲⼀下:作⽤域和⽣命周期。

作⽤域(scope) 是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤) 的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。
1. 局部变量的作⽤域是变量所在的局部范围。
2. 全局变量的作⽤域是整个⼯程(项⽬)。

⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。

  1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。
  2. 全局变量的⽣命周期是:整个程序的⽣命周期。

使⽤建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤static
修饰。

add.c

 int Add(int x, int y){return x+y;}

test.c

 #include <stdio.h>extern int Add(int x, int y);int main(){printf("%d\n", Add(2, 3));return 0;}

这个代码是能够正常运行的,但是如果加上了static 代码就出现了链接错误。
本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声 明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部 使⽤。

使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。

8. 函数递归

8.1 什么是递归

递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?
递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。
写⼀个史上最简单的C语⾔递归代码:

#include <stdio.h>
int main()
{
printf("hallo\n");
main();
return 0;
}

上述就是⼀个简单的递归程序,只不过上⾯的递归只是为了演⽰递归的基本形式,不是为了解决问
题,代码最终也会陷⼊死递归,导致栈溢出(Stackoverflow)。

8.2 递归的限制条件

递归在书写的时候,有2个必要条件:
• 递归存在限制条件,当满⾜这个限制条件的时候,递归便不再继续。
• 每次递归调⽤之后越来越接近这个限制条件。

8.3 递归的举例

举例1:求n的阶乘
⼀个正整数的阶乘(factorial)是所有⼩于及等于该数的正整数的积,并且0的阶乘为1。
⾃然数n的阶乘写作n!。

我们知道n的阶乘的公式: n ! = n∗(n−1)!
当 n==0 的时候,n的阶乘是1,其余n的阶乘都是可以通过公式计算。
n的阶乘的递归公式如下:
在这里插入图片描述

那我们就可以写出函数Fact求n的阶乘,假设Fact(n)就是求n的阶乘,那么Fact(n-1)就是求n-1的阶
乘,函数如下:

#include <stdio.h>int Fact(int n){if(n==0)return 1;elsereturn n*Fact(n-1);}int main(){int n = 0;scanf("%d", &n);int ret = Fact(n);printf("%d\n", ret);return 0;}

运⾏结果(这⾥不考虑n太⼤的情况,n太⼤存在溢出):
在这里插入图片描述

8.4 递归与迭代

递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误⽤的,就像举例1⼀样,看到推导的
公式,很容易就被写成递归的形式:
在这里插入图片描述
Fact函数是可以产⽣正确的结果,但是在递归函数调⽤的过程中涉及⼀些运⾏时的开销。
在C语⾔中每⼀次函数调⽤,都需要为本次函数调⽤在内存的栈区,申请⼀块内存空间来保存函数调
⽤期间的各种局部变量的值,这块空间被称为运⾏时堆栈,或者函数栈帧

函数不返回,函数对应的栈帧空间就⼀直占⽤,所以如果函数调⽤中存在递归调⽤的话,每⼀次递归
函数调⽤都会开辟属于⾃⼰的栈帧空间,直到函数递归不再继续,开始回归,才逐层释放栈帧空间。
所以如果采⽤函数递归的⽅式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢
出(stackoverflow)的问题。

所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的⽅式(通常就是循环的⽅式)。
⽐如:计算n的阶乘,也是可以产⽣1~n的数字累计乘在⼀起的。

int Fact(int n){int i = 0;int ret = 1;for(i=1; i<=n; i++){ret *= i;}return ret;}

事实上,我们看到的许多问题是以递归的形式进⾏解释的,这只是因为它⽐⾮递归的形式更加清晰,
但是这些问题的迭代实现往往⽐递归实现效率更⾼。

当⼀个问题⾮常复杂,难以使⽤迭代的⽅式实现时,此时递归实现的简洁性便可以补偿它所带来的运
⾏时开销。

有时候,递归虽好,但是也会引⼊⼀些问题,所以我们⼀定不要迷恋递归,适可⽽⽌就好。美酒虽好,不可贪杯哦!
这里就是函数的部分知识了,今后的内容中还会详细使用的,大家加油哦!!!

还有你,过程虽苦,但努力何尝不是一种享受,加油!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/11676.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

想要监控办公电脑,好用的监控软件怎么选择

在现代办公环境中&#xff0c;监控办公电脑不仅能帮助企业确保员工的工作效率&#xff0c;还能够提高数据安全性&#xff0c;防止信息泄露。随着技术的不断发展&#xff0c;市面上涌现了各种监控软件&#xff0c;其中不乏功能强大、使用便捷的工具。今天&#xff0c;我们就来探…

2024-2025第九届华为ICT大赛中国创新赛问题解答

华为ICT大赛2024-2025中国赛区创新赛正火热报名中&#xff0c;吸引了众多高校学生积极报名参赛。创新赛主要考察参赛者对AI技术与其他ICT技术的综合应用和创新能力&#xff0c;参赛者需从行业场景或生活中的真实需求出发&#xff0c;运用华为人工智能技术&#xff0c;或人工智能…

网页设计平台:6个技术亮点

想要创建个人或商业网站来分享知识或推广商品吗&#xff1f;这篇文章将为你介绍6个免费的网页制作平台&#xff0c;帮助你即使没有编程基础也能快速、轻松地搭建出专业且引人注目的网站。让我们一起探索这些平台&#xff0c;发现它们的特色和优势。 即时设计 即时设计是一个云…

您知道康复科是干什么的吗?

康复医学对于医院是一个重要的朝阳科室&#xff0c;正不断向多元化方向发展&#xff0c;并深入临床各学科&#xff0c;成为现代医学不可或缺的一环。 目前&#xff0c;康复范围日益广泛&#xff0c;涵盖骨科康复、神经康复、疼痛康复、儿童康复、产后康复、心肺康复及老年康复等…

Docker 镜像和容器的导入导出及常用命令

Docker 镜像和容器的导入导出 1.1 镜像的导入导出 1.1.1 镜像的保存 通过镜像ID保存 方式一&#xff1a; docker save image_id > image-save.tar例如&#xff1a; rootUbuntu:/usr/local/docker/nginx# docker imagesREPOSITORY TAG IMAGE ID …

202409电子学会青少年机器人技术等级考试(四级)理论综合真题

青少年机器人技术等级考试理论综合试卷&#xff08;四级&#xff09; 分数&#xff1a;100 题数&#xff1a;30 一、单选题(共20题&#xff0c;共80分) 1. Arduino C程序如下&#xff0c;当变量score的值为100时&#xff0c;该段程序运行后&#xff0c;串口监视器输出结果是…

鸿蒙开发,arkts 如何读取普通文件

ArkTS提供了渲染控制的能力&#xff0c;包括条件渲染、循环渲染和数据懒加载等。这些渲染控制语句可以辅助UI的构建&#xff0c;提高应用的性能和用户体验。今天给大家分享arkts 如何读取普通文件知识&#xff0c;如果有所帮助&#xff0c;大家点点关注支持一下&#xff0c;也可…

显卡驱动版本过低怎么办?显卡驱动升级的方法

显卡驱动程序是计算机与显卡之间沟通的桥梁&#xff0c;它负责将操作系统发出的指令翻译成显卡可以理解的语言&#xff0c;从而确保图形显示的流畅与高效。当您遇到显卡驱动版本过低的问题时&#xff0c;升级驱动程序不仅能够提升电脑的图形处理能力&#xff0c;还能解决兼容性…

find 命令是 Linux 系统中用于在文件系统中查找文件和目录的强大工具

功能概述 find 命令用于在指定目录及其子目录下查找符合特定条件的文件和目录。这些条件可以基于文件名、文件类型、文件大小、文件权限、文件修改时间等多种属性来设定。 按文件名查找 -name&#xff1a;精确匹配文件名。 示例&#xff1a;在当前目录及其子目录中查找名为tes…

【专题】2024年数字贸易新图景:把握出海红利报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38218 近年来全球数字贸易规模持续攀升&#xff0c;其中亚太地区更是成为增长最为迅猛的区域之一。在这片充满机遇的数字贸易海洋中&#xff0c;电子商务、移动支付等领域呈现出一片繁荣景象&#xff0c;出海也成为众多企业谋求发展…

半球体容器漏水体积微分问题

问题&#xff1a;半球体的容器中盛满水&#xff0c;容器底部有一个小孔&#xff0c;水从小孔流出。给出水体积的变化量 V 随水面高度 h 变化的微分关系式。 在微小的时间间隔 [ t , t d t ] [t, t\mathrm{d}t] [t,tdt] 内&#xff0c;水面高度由 h h h 降至 h d h , ( d h…

小白NAS磁盘规划实践:一次科学、高效的存储旅程

引言 如今,数字化生活正逐步渗透到我们生活的方方面面。从家庭影音到工作文件,从珍贵的照片到大型游戏库,数据的存储需求日益增加。许多朋友开始关注NAS(网络附加存储)设备。作为一个专为数据存储和管理设计的系统,NAS能为我们提供安全、高效的存储方案。但如何科学地规…

假如 有没有另一种可能

转眼已经十一月 最近是不是很多秋招的小伙伴都开奖了呢&#xff0c;很想听到大家的好消息&#xff01; 近期路桑的本届实习生们和部分路科的同学也都拿到了令自己满意的offer&#xff0c;总体来说虽然大环境不大好&#xff0c;但大家依旧很给力呀。至今仍有部分有实力的同学反…

Web大学生网页作业成品——古诗词网页设计与实现(HTML+CSS)(5个页面)

&#x1f389;&#x1f389;&#x1f389; 常见网页设计作业题材有**汽车、环保、明星、文化、国家、抗疫、景点、人物、体育、植物、公益、图书、节日、游戏、商城、旅游、家乡、学校、电影、动漫、非遗、动物、个人、企业、美食、婚纱、其他**等网页设计题目, 可满足大学生网…

【网络安全 | 漏洞挖掘】隐藏的 DOS 技术

未经许可,不得转载。 文章目录 缺少对图片渲染参数的校验(高度和宽度)服务器根据GET参数获取数据识别从外部资源获取数据的服务缺少对图片渲染参数的校验(高度和宽度) 有时,你可能会上传个人头像或某个产品的图片。在这种情况下,检查渲染页面的行为非常重要,因为该页面…

wsl2更换字体|解决nvim图标无法显示问题

没更换字体前 更换字体后 安装字体 https://www.nerdfonts.com/font-downloads 随便下载一个&#xff0c;我这里下【CascadiaCode】 下载完&#xff0c;进行解压缩&#xff0c;然后选中一个ttf文件&#xff0c;右键安装 配置windows terminal字体 ctrl, ,打开设置界面&…

力扣 LeetCode 59. 螺旋矩阵II

解题思路&#xff1a; 使用左开右闭进行四个边的循环赋值 每次进入新的内圈&#xff0c;需要注意起始位置startx和starty的变化&#xff0c;以及边界n - offset的变化 分奇偶圈&#xff0c;判断为奇数圈后需要为中心点赋一次值 class Solution {public int[][] generateMat…

vue2.0 Cannot read properties of null (reading ‘level‘) level有值但还是报错的话

切到地理划分的时候报错 Cannot read properties of null (reading level) 估计是el-cascader里的数据没刷新之类的导致的 加上key区分一下试试

【STL】带你了解string类

string类 为什么学习string类什么是string标准库中的string类sting类的常用接口构造接口string类对象的容量操作接口string类对象的访问及遍历操作接口string类对象的修改操作string类非成员函数 vs和g下string结构的说明&#xff08;了解即可&#xff09;vs下stirng的结构g下s…

谭滨锴:《唤醒内在的智慧》人生要学会正确看待世界

在这个充满挑战的现实世界中&#xff0c;如何才能获得内心的平静呢&#xff1f;又该如何避免生活中的陷阱和障碍&#xff0c;顺利前行于正确的道路&#xff1f;在追求物质成功的同时&#xff0c;是否还能够实现自我价值&#xff0c;感受到内心的满足与成就感&#xff1f;谭滨锴…