您现在的位置是:首页 >学无止境 >简单易懂-6000字详解C语言中函数的种种知识,初学者必看网站首页学无止境
简单易懂-6000字详解C语言中函数的种种知识,初学者必看
一.函数的概念
函数原本是数学中的一个概念,在英文中函数为funtion翻译过来有“功能“、”职能“等另外意思,在C中功能、职能也是函数
的一个重要属性
以下是维基百科对函数的定义:函数,又名子程序
在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method,
subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组
成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库
函数声明:函数声明描述了函数的名称、返回类型和参数类型。函数声明通常位于程序文件的开头,以便在函数被调用之前进行编译。
举一个例子来讲,如果将编程比喻为盖房子,那么普通的代码就是一砖一瓦,函数就是已经由预制板搭建好的小房间,多使用这样的“小房间”盖房子的速度不仅会更快,而且搭出来的房子还会更整洁美观
函数由以下基本部分组成
名词解释
1.函数声明:函数声明描述了函数的名称、返回类型和参数类型。函数声明通常位于程序文件的开头,以便在函数被调用之前进行编译。
2.函数定义:函数定义包括函数的实际代码,即一组执行特定任务的语句。函数定义包括函数头和函数体。函数头包括函数名称、返回类型和参数列表,函数体则包括函数的实际代码。
3.函数参数:函数参数是传递给函数的值。函数参数可以为空,也可以是值、指针或数组等类型。在使用过程中函数参数可以分为形式参数和实际参数,为了方便一般叫形参 和实参
4.函数调用:函数调用是程序中调用函数的过程。调用函数时,程序传递参数给函数,函数执行其代码并返回一个值。函数可以被调用多次,并且可以嵌套调用,另外调用可分为传值调用 和传址调用
(文中加粗字体在后文中都会有详细介绍,继续往下阅读即可)
没有代码就没有发言权!
下面我将构造一个Add()函数,并且在分别中标注这四个部分
首先,代码的运行流程为:
1.输入a和b的值。
2.构造和调用Add()函数,Add函数的功能是求出两个数的和。函数调用结束后会将Add()的返回值赋值给ret。
3.在屏幕上打印a和d的和·。
以下为具体代码
1. #include<stdio.h>
2.
3. int Add(int x,int y);
4. int main()
5. {
6. int a = 0,b = 0;
7. scanf("%d %d",&a,&b);
8. int ret = Add(a,b);
9. printf("%d",ret);
10. return 0;
12. }
13. int Add(int x,int y)
14. {
15. int a = x+y;
16. return a;
17. }
1.函数声明部分
第一行为标准输入输出库的头文件声明
第三行为自定义函数Add()的函数声明部分,描述了函数的名称、返回类型和参数类型。
这里我要特地的说一下,一般程序编写中,主函数main()都是放在最后面,我这里将主函数放在自定义函数Add()前是为了使用Add函数的函数声明,因为如果将Add函数放在主函数前,可以省略函数声明,这里我也更推荐将自定义函数放在主函数前,这会令代码简洁美观
1.#include<stdio.h>
2.
3.int Add(int x,int y);
2.函数定义部分
函数定义由函数头和函数体构成
第十三行内容为函数头。函数头包括函数名称、返回类型和参数列表。
在 int Add(int x,int y) 中 int为函数的返回类型、Add为参数名称、(int x,int y)为参数列表
第十四到第十七行内容为函数体。函数体则包括函数的实际代码。
13 int Add(int x,int y)
14 {
15 int a = x+y;
16 return a;
17 }
3.函数参数
第九行为a、b都是函数参数,并且是实参
第十三行同理a、b都是函数参数,但是是形参
(两者区别将在后文解释,一句话来说主函数中Add()里a、b为实参,外部的函数头中a、b为形参)
9 int ret = Add(a,b);
13 int Add(int x,int y)
4.函数调用
函数调用实际就是使用函数的过程,根据数值传递形式,可分为 传值调用和传址调用
二.为什么会有函数
理论上来讲,完全不使用函数一样也是可以实现C语言的绝大部分功能,但此时的程序会变得十分的复杂,感觉就像从和水泥开始一样盖好了一栋房子,进了房子会发现电线和水管都是露在外面的,使用函数就是像搭积木一样把房子里大的部件搭在一起,干净整洁,并且还省事,这种比喻概括了使用函数编程和不使用函数编程的区别
函数在定义上的优点有这么些
1.模块化:函数可以将一个大的程序划分成小的模块,每个模块实现一个特定的任务,这样可以使程序更易于理解和维护。
2.可重用性:函数可以被重复使用,这可以减少开发工作量和提高代码的可重用性。
3.代码的可读性和可维护性:将程序分解成小的函数可以使代码更易于理解和修改,从而提高代码的可读性和可维护性。
4.隐藏实现细节:函数可以将实现细节隐藏起来,使得调用函数的程序员不需要知道具体实现的细节,从而提高代码的安全性和可靠性。
三.库函数
在日常的编程中,我们已经习惯了诸如“printf”和:“scanf”等库函数的存在,但在早期编程中,是没有库函数存在的
在上个世纪的80年代,C语言凭借着自己出色的优点迅速的引起了一波浪潮,众多的公司引进了C语言进入自己公司的系统,但当时的C并没有现在像现在这样功能固定的的函数(如”printf()“、”scanf()“),各个公司定义出许多不同的函数,一个实现简单功能的程序,每个公司所使用的函数都不尽相同,这为当时的C语言发展带来了极大的不便。
为了规范C语言,在多方努力下,终于,在1989年C语言迎来了自己的第一个标准,后世称其为 C89标准。C89标准首次规定了C语言的语法和语义,统一了C语言的行为。
不止如此,C89标准在多方意见下,将当时的一些经常使用的函数收纳为了库函数,库函数由此诞生,库函数的诞生极大的便利来后来的程序员,人们终于不用苦与重复的编程了,到了现在,我们已经是站在巨人的肩膀上进行编程了。
PS-
C89标准只是规定了库函数的”外部”如返回类型和参数列表等,对其“内部”的实现并没有进行定义,C89把“内部”留给了各编译器自由发挥,这体现出了C语言自由灵活的特点。
另外需要注意的是,使用库函数,必须要引用头文件,头文件中包含了各个库函数的具体实现代码
目前常用的库函数头文件有
有一些朋友可能会有疑惑,这么多库函数,难道我都要去背吗?,我该如何去学习?不用担心,下面将向大家介绍如何学习库函数
3.1如何学习库函数
就像我们学英语,碰到不会的单词要去查字典一样,在编程过程中,如果遇到了不认识的函数,我们也需要去翻翻“字典“
当然,我们不可能真的去买一本字典,下面我将提供两个库函数查找网站
1.C++网 https://cplusplus.com/reference/
2.C++中文官网 https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5
这两个网站我都经常使用,一个是英文环境,一个是中文环境,我个人是比较推荐第一个网站,第二个网站经常需要梯子才连得上,在我们以后工作的过程中不可避免的要接触英文文档,再加上目前主流的编译器都是外国的,所以目前就可以慢慢的培养英文阅读能力了,并且这种技术的东西,里面的介绍都较为易懂,实在不行就百度翻译,方法总比困难多。
下面我来介绍第一个网站的使用方法
1.登录网站,在网站右上角点击老版本链接入口,进入老版本
新版本没有搜索框,使用较为僵硬,老版本有搜索框
2.搜索框里输入相要查找的函数,这里我们输入”scanf()“
3.搜索成功,开始阅读"字典",里面会提供输入格式、功能、返回值、例子,比较适合学习
当然,在使用过程中,我们也可以在搜索框里直接输入头文件名,这样可以查看该头文件中包含的所有库函数
四.自定义函数
讲了库函数,那就肯定不能不讲自定义函数,自定义函数在c语言中可以说是占了相当大一环,在日后的编程中也经常会使用
自定义函数的概念,再用之前的盖房子来比喻,库函数所代表小房间的功能已经由c89标准确定了,是厨房的那它就只能是厨房;是卧室的那它就只能是卧室,用户也更改不了函数功能,只使用库函数,搭搭小房子还过得去,但在一个大型工程中,里面能全是厨房和卧室吗?很显然,库函数解决不了所有问题。自定义函数就显得很有必要了。
自定义函数中,用户可以自己搭建房间,房间的功能完全由用户决定,你让它是会议室那它就是会议室;让它是书房那它就是书房,有这么方便的功能,我们能盖不好楼吗?
或许有些人觉得听完之后还是觉得函数有些难以理解,会觉得,啊?我也没写过函数啊。不不不,从你编写第1个程序“hello world”开始,函数就一直在你左右,我们一直使用的main()也叫做main函数。mian函数的结构如下,其结构大致与自定义函数相同,
我们编写自定义函数,其实跟编写main函数差不多
五.函数参数-实参和形参
要介绍实参和形参,我需要先构造一个使用函数的模型
#include<stdio.h>
int add(int x,int y)
{
语句;
}
int main()
{
语句;
add(a,b);
语句;
return 0 ;
}
常见使用函数的模型大致如此
其中main()中add()函数,括号中的a和b为实际参数,而main()函数前int add(int x,int y)中的int x和int y为形式参数
实参: 真实传给函数的参数,叫实参。实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
形参:指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内
存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数
中有效。
当我们运行这个程序的时候,程序将由主函数开始,运行到主函数中的add(a,b)的时候,语句执行将会跳转到上面的add(int x,int y)中,其中实参a和b的值将会被传递到add函数上,由相应的形参int x和 int y接收
概括地说,实参就是要传递数值的参数,形参就是要接收传递数值的参数
需要注意的是形参只是实参的一份拷贝,实参能影响形参但形参不能影响实参
例如,我将在vs2019j编写出上面的add()函数,并且显示实参和形参的地址
可以看出实参和形参的地址是完全不相同的,形参只是拷贝了数组
更加形象的记忆函数运行,我们可以想象成在考试的情景,主函数就是试卷,试卷上的应用题add()提供了a,b两个数值,要求我们我们计算,实参数值传递给形参,就是拿出草稿本,将a和b的数值抄到草稿本上,用x和y代替a和b,你在草稿本上的操作并不会改变试卷上的内容,当你在草稿本上计算完,把返回的数值写在试卷上,你就直接把草稿本撕掉了,此时x和y的生命就结束了
ps-在这里对实参和形参的区别进行大篇幅的讲解是为了对函数调用的传值和传址进行更好的解释
六. 函数调用-传值和传址
函数调用:就是使用函数的过程,具体来说,就是主函数运行到库函数或是自定义函数部分实参数值传递给形参,执行相关函数,并且返回数值的过程(有一些函数也可设置不返数值)
这一过程的书写可以千奇百怪,如add(7,b)、add(add(7,8),a) 都是可以的,只要最后代表的是具体的数值就行。实际上总结其类型的话,函数调用就只有两种调用:传值调用和传址调用
传值调用:传值调用是我们用的比较多的调用,实际特点就是实参将数值传递给形参,比喻来说的话将实参和形参都比作房子,传值调用就是将实参中的东西复制一份,搬到了形参房子中
以下的代码都是传值调用
求最大值
#include<stdio.h>
int get_max(int x,int y)
{
int max;
if(x>y)
max=x;
else
max=y;
return max;
}
int main()
{
int a = 0,b = 0;
int max=add(a,b);
printf("max:%d",max);
return 0;
}
求平方
#include<stdio.h>
int pow(int x)
{
return x*x;
}
int main()
{
int a = 0;
int p=pow(a);
printf("pow:%d",p)
return 0;
}
传址调用:讲清传址调用,我们先来做一个题吧,题目要求构造一个swap()函数,该函数功能是交换两个变量的值,以下是我书写的代码,请你猜一下会发生什么?
#include<stdio.h>
void swap(int a,int b)
{
int tmp = a;/构造临时变量tmp
a = b; /交换a和b的值
b = tmp;
}
int main()
{
int a = 3,b = 5;
swap(a,b);//交换数值
printf("a=%d ,b=%d",a,b);
return 0;
}
运行程序
a=3,b=5
啊呀,为什么a和b没有交换数值?
哈哈哈,那是肯定的。在编写函数的时候,我特地将实参和形参都命名成了a和b,我前面也介绍过,形参只是实参的一份拷贝,对形参的更改不会影响到实参,既然不会影响命名一样也无所谓,在自定义函数中swap()我对a和b的数值进行交换,并不会影响到主函数中的a和b,
比喻一下的话,虽然这两个房子名字一样,但是地址不同,两者不会影响
在这种情况下,使用传值调用的方式,显然无法完成交换的功能,但我们就是想用自定义函数的方式交换a和b的数值,该如何操作呢?没错,答案就是传址调用
传值调用:传值调用是将实参的数值传给形参,那么传址调用就是将实参的地址传递给形参,并在运行中通过地址找到实参,并对其进行操作。用我们之前的比喻来说的话,传址调用就是将房子的地址信息告诉给形参,在调用的时候,函数就可以凭借这个地址直接找到实参的房子,对其进行修改。
具体实现需要用到以下操作符
操作符&:取地址操作符,功能是获取地址
操作符*:解引用操作,放在指针前,能将指针内地址直接解引用
我们对代码进行修改
#include<stdio.h>
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 3, b = 5;
swap(&a, &b);
printf("a=%d ,b=%d", a, b);
return 0;
}
运行程序`
a=5,b=3
在上面的程序中,我使用&将a和b的地址取了出来,并且作为了实参,形参我定义成了指针变量,用来接收a和b的地址,然后运用解引用操作符成功进行了交换
说到这里,我相信大家可能会有一点疑惑,什么时候我们要使用传值调用?什么时候我们应该使用传值用?
判断的关键点为:在函数调用时,函数的操作是否会更改实参,如果会,使用传址调用,不会就使用传值调用
七.函数使用原则
模块化:如果经常需要使用的功能,可以编写一个函数,可以使程序整洁美观
模块化前
#include<stdio.h>
int main()
{
int a = 1, b = 2, c = 3, d = 4, e = 5,f=6;
int tmp = a;
a = b;
b = tmp;
tmp = c;
c = d;
d = tmp;
tmp = e;
e = f;
f = tmp;
return 0;
}
模块化后
#include<stdio.h>
void swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 1, b = 2, c = 3, d = 4, e = 5,f=6;
swap(&a, &b);
swap(&c, &d);
swap(&e, &f);
return 0;
}
简单化:指的是自定义函数功能的简单化纯粹化,如果不是特殊需要,一个自定义函数,给他一个功能就行了,多个功能的函数没有必要反而显得多余,库函数的设计都遵循了简单化,所以通用性很高,用户可以对他有很多的操作。
结语:哈哈哈,如果本篇文章有帮助,那就给个三联吧,谢谢。