函数计划分成3篇来讲: 上篇是函数的基础篇,中篇是函数的进阶篇,下篇是函数的高级篇。
一、函数介绍
函数其实在任何编程语言下,作用都只有一个: 就是用来包裹共同完成同一个功能的代码,以便这份代码看起来成一个独立的整体, 并且未来如果需要多次使用这份代码,就不需要重复编写,只需要简单的一行调用代码。即可完成功能的复用,极大的方便我们管理代码和提升了代码的复用率。
#include <stdio.h>
// 打印HELLO
void printHello() {
printf("H H EEEEE L L OOO \n");
printf("H H E L L O O \n");
printf("HHHHH EEEE L L O O \n");
printf("H H E L L O O \n");
printf("H H EEEEE LLLLL LLLLL OOO \n");
}
int main() {
// 调用打印HELLO的函数
printHello();
// 可以多次调用
printHello();
return 0;
}
二、函数基本使用
与变量一样,函数要想使用必须先完成声明定义,然后才能使用。所不同的是函数远比变量要复杂的多,但是万法不离其宗,我们时钟按照先定义,后使用的规则去使用函数即可。
2.1 函数语法
C语言的函数语法并不难记,从1972年C语言诞生至今就一直是这个样子。如果我们多写几次,它的构成部分还是很容易记住的。
//定义函数
函数返回值类型 函数名称(参数列表){
函数体
return 返回值;
}
//调用函数
函数名称(具体的参数);
柯南有话说:
1. 函数返回值: 这个函数执行结束之后需要返回什么数据吗?如果有数据返回,就写数据的类型,如果没有数据返回就写 void 。
2. 函数名称: 可以自定义、只要不是关键字,通俗易懂即可。如果有多个单词可以使用下划线连接,也可以使用驼峰式命名。
3. 参数列表: 调用这个函数的时候需要传递什么数据给它吗?如果有需要则需要声明参数,声明语法为 参数类型 参数名称。可以声明多个参数,使用 逗号 间隔即可。若没有传递参数的必要,则空着不写,保留()即可。
4. 函数体: 这个函数要包装的代码。具体实现的什么功能,把代码写在 {} 里面。
5. 返回值: 表示函数执行结束要返回什么数据出去给调用者吗?如果有则使用 return 关键字 搭配需要返回的数据写上即可。
6. 调用函数: 调用函数需要使用函数名称配合 () 来调用, 这个() 在这里称之为调用符号。目的就是为了运行函数的代码。如果函数有参数的话,可以在调用的时候从() 传递进去具体的参数。多个参数使用 逗号 间隔。
2.2 函数示例
#include <stdio.h>
// 定义 say_hello 函数
void say_hello() {
printf("Hello, World!\n");
}
int main() {
// 调用 say_hello 函数
say_hello();
return 0;
}
三、函数种类(四种写法)
根据函数返回值和函数参数列表,可有可无的情况,我们可以组合出来函数的4种分类,分别是: 无参无返回值、无参有返回值、有参无返回值、有参有返回值。所有的函数都被囊括在这这种函数分类中,虽然对于大牛、老人家程序员来说,这多么简单呀,但是对于小白还是先熟悉熟悉函数的分类写法,未来写多了也就自然而然成为大牛了(^_^)
3.1 无参无返回值
无参无返回值即调用函数时不需要传递参数,函数运行结束也不会有返回值,声明为 void 即可
#include <stdio.h>
// 定义函数
void fun01() {
printf("fun01...\n");
}
int main() {
// 调用 fun01函数
fun01();
return 0;
}
3.2 无参有返回值
无参有返回值即没有参数,但是可以有返回值,返回值的类型由返回值来决定。 不建议随便写返回值类型,返回值类型和返回值匹配上,这样方便告诉外界调用者,调用我们的函数最终会返回什么样的数据。
#include <stdio.h>
// 定义函数
int fun01(){
return 6;
}
int main() {
// 调用 fun01函数
int result = fun01();
printf("调用函数得到结果: %d \n" , result);
return 0;
}
3.3 有参无返回值
有参无返回值即有参数传递,但是没有返回值。有多个参数的话,需要使用逗号间隔声明。没有返回值时,则可以声明为 void
#include <stdio.h>
// 定义函数
void fun01(int a, int b , char c) {
printf("fun01:: %d, %d, %c\n" , a , b ,c );
}
int main() {
// 调用 fun01函数
fun01(3,4 , 'a');
return 0;
}
3.4 有参有返回值
有参有返回值,与无参无返回值是两个极端,注意参数和返回值即可,其他与前面3个一样
#include <stdio.h>
// 定义函数 计算两数之和
int fun01(int a, int b ) {
return a + b;
}
int main() {
// 调用 fun01函数
int result = fun01(3,4);
printf("调用函数得到结果: %d \n" , result); // 打印 7
return 0;
}
四、函数声明
函数在某种程度上与变量、指针一样,都需要实现声明定义,然后才能使用。只不过相比于后两者,函数的体量更大一些。在上面的函数4种分类写法的示例中,我们都是先给出了函数的定义(也就是函数到底是执行什么代码),给出了函数的全貌,然后才在后面的main函数中调用。假设我们颠倒一下两者的顺序会出现什么现象呢?
4.1 函数声明的原因
下面的代码演示了,把函数fun01定义在后面,但是选择在前面调用函数fun01。
#include <stdio.h>
int main() {
// 调用 fun01函数
fun01();
return 0;
}
// 定义函数
void fun01() {
printf("fun01...\n");
}
柯南有话说:
1. 编译器编译代码,属于自上而下编译,在执行到调用函数 fun01()的时候,依然没有发现过它的踪迹。在不同的编译器里可能会出现如下情况:
1.1 出现函数没有定义的提示,或者不认识这个符号 fun01的提示。
1.2 会出现重复声明的问题,在前面调用fun01的时候,由于前面没有发现这个函数fun01的踪迹,所以默认根据调用信息,隐式生成了一个函数声明,但是到后面又发现了正儿八经的fun01的函数定义,就出现了假李鬼遇上真李逵的场景了。
2. 编译器之所以这么做,也是有原因的,自上而下的编译,如果在前面遇到了函数fun01的声明定义、那么即可收集到该函数的特征,比如: 函数返回值类型、函数名称、函数参数列表(类型和个数) 这些信息。这就使得在编译后面调用函数 fun01() 的时候识别出我们是否是按照规矩来调用,以便给出错误的提示。
3. 既然有这个缘由在,那么不能把所有的函数都放在上面统一写就,然后把main函数放在最后不行么? 答案是可以的。但是我们也要意识到这种问题的存在,假设以后有多个函数相互纠缠存在这样的调用关系(箭头表示调用)A -->B , C--->D 和 E , B ---> D 和 F 。我们会疲于应付符合先声明定义、后使用的原则,因为我们不得不去计算调用的先后顺序。
4. 好吧,那么有没有什么办法可以根本不用操心这种调用顺序呢?即便有一百个函数,一千个函数,甚至更多,我们都不担心这个问题,该怎么解决呢? 此时就不得不说: 函数声明(或者叫做函数原型 ,在C++里面就是这么叫的)的概念了。
4.2 函数声明示例
函数其实可以做成与变量一般,先声明后赋值,函数只要在前面表示出来:返回值类型、函数名称、参数列表即可,编译器掌握这些信息之后,才能够在后面正常编译我们调用函数的代码。
#include <stdio.h>
//声明函数:: 只要表示出函数的返回值、函数名称、参数列表即可
void fun01()
//声明函数:: 只要表示出函数的返回值、函数名称、参数列表即可
int fun02(int a , int b);
int main() {
// 调用 fun01函数
fun01();
//调用 fun02函数
int result = fun02(3 ,4);
printf("result=%d\n",result);
return 0;
}
// 定义函数:: 给出函数的具体运行代码
void fun01() {
printf("fun01...\n");
}
// 定义函数:: 给出函数的具体运行代码
int fun02(int a , int b) {
return a + b;
}
柯南有话说:
1. 上面的示例就是掩饰了先声明函数,后调用函数的写法。我们一般会按照这种标准来写代码,大家要勤加练习,即便未来在学习C++的时候,也完全是使用这种风格。
2. 虽然这么写函数,看起来代码量变多了,工作量随之也多了起来。但是注意观察,我们从把代码从上往下看。从我们声明函数到调用函数,中间可以不用出现函数的全貌(也就是函数运行什么代码),只要未来这个函数的代码能够正常参与到程序编译中来即可。
3. 这就为了头文件和源文件的出现提供了契机,我们可以把关于函数的声明统一放在头文件中,然后把函数的定义(函数的全貌)放在源文件中。只要有哪个地方需要用到这个函数,只需要引入头文件即可(这可以看作是完成函数的声明)。当编译运行整个程序的时候,顺便编译刚才我们存放函数代码的源文件即可。这样子做,极大的方便我们管理代码,可以使用模块化的思维来管理代码。【不知道小伙伴能不能理解~~若不能理解,请务必给我来信,我一定会耐心解答~】。
五、总结
好啦,欢乐的时光总是如此的短暂,又到了要说再见的时候。在这我们稍微总结一下基础篇的内容,看起来文章篇幅挺长,包括了函数的介绍、作用体现、函数的语法,以及函数的四种分类。这些内容大家完全可以通过多写几遍来熟悉它们,尤其对于新手小白来说,更要这么做。至于后面的函数原型声明这小节内容,除了需要去动手实践之外,还需要大家多思考为什么需要这么做。很多时候我们的编程思想就是这么锻炼出俩的,如果觉得谁又想到这茬呢,那么好好跟着咱们得C语言基础连载篇走下去,相信你在提升的同时,思维逻辑也会有质的飞跃。
六、思考
按照惯例还是留一些思考题吧:
1. 函数为什么要先声明,后使用呢?(自己尝试组织一下语言回答)
2. 能否出现同名的函数呢?