C语言基础级——函数基本概念
转载请标明出处
大家好,我叫Home。在工作当中,发现自己可以写一篇关于C语言函数
的知识,自己在复习的同时,也能够总结当前对函数的理解与使用是否到位。那本篇博客主要讲解函数的定义和声明
,带大家真正了解函数,并且真正的懂得如何使用函数。那这也是我个人的第一篇博文,如果有不妥之处我们可以共同交流学习,希望读完此博文之后能有收获。
文章目录
1.1 基本思想
首先我们要知道通常在做大型的项目时候,我们是将一个大的程序按功能分割成一些小模块。
打个比方,我们看任何一本书籍,都会先去看下书籍的目录,了解这本书籍都涉及了哪些内容,对这本书籍先有一个大体的方向之后,再着手看你想要了解的内容。这里面其实是不是目录起到了一定作用,第一个作用是,能够对整本书籍的各个章节内容有着大致划分且结构清晰,让读者心里有一个明确的方向。第二个作用是,再更新迭代书籍过程中,笔者对某章节内容想要进行优化的时候,就不需要大范围改动其它章节内容。
那其实程序相当于是"一本书","目录"相当于程序的结构,"章节"相当于程序的函数。
理解这个过程之后,再去了解函数的特性就好理解了,那我们来看下C语言函数的几大特性:
- 各个模块相对独立、功能单一(一个函数完成一个任务)
- 结构清晰、接口简单
- 控制了程序设计的复杂性
- 提高元件的可靠性
- 缩短开发周期
- 避免程序开发的重复劳动
- 易于维护和功能扩充
2.1 定义与声明
2.1.1 定义
函数的定义是具体实现函数的功能,它的格式为:
[类型标识符] [函数名]([形式参数]){}
2.1.1.1 部分源码
这是ANSI C的函数定义的标准形式:
// 类型标识符 函数名(形式参数)
int fun(int param_a)
{
printf("Hello Home!\n");
return 0;
}
ANSI C之前的标准:
int fun(int param_a)
int param_a;
{
printf("Hello Home!\n");
return 0;
}
ANSI C的函数定义标准与之前的标准,那我们采用哪种形式的来些呢?答案是前者,因为本身前者的形式是新的标准,意味着后者形式可能会在今后当中被淘汰掉。即便没被淘汰,新的标准所展现的格式更加合理及人性化。
2.1.2 声明
- 无需函数体,只需要的格式为
[函数返回值] [函数名][函数参数列表]
void fun(void);
- 记得加上
;
哦! - 这种声明的格式,也被称为函数原型。
- 同样,如果函数带有参数列表的,有两种形式。
void fun(int, int);
void fun(int a, int b);
前者函数声明并没有实际地创建变量,所以我们可以使用int
代表了一个int
类型的变量。
- 除此之外,如果调用函数与被调用函数在同一个文件当中,我们把被调用函数放到调用函数之前时,此时函数定义相当于为函数声明,函数的额外声明可不用写。
#include<stdio.h>
// 函数定义
int fun(void)
{
// 返回3
return 3;
}
int main()
{
int res;
// 函数调用
res = fun();
// 打印函数整形返回值
printf("res = %d\n", res);
return 0;
}
输出结果是正确的,再这里呢,要提一点,其实这种写法个人认为是一种“偷懒”的写法。因为当你的项目足够庞大的时候,自己想要找到main
函数,显得格外浪费时间。为了让我们代码设计更加人性化一些,我们还是不要“偷懒”了。老老实实的把函数定义放在main
函数之后,并且在所有函数前面统一放置函数声明。
2.1.3 命名
-
在
C语言
当中,我们以小写字母,如果有多个单词,用_
下划线连接单词,这是C语言
标准写法, -
注意
- 对于大型的项目来说,我们最好再
main
函数内不要涉及太多的代码。
- 对于大型的项目来说,我们最好再
2.1.4 空函数(不推荐使用)
我们先思考,a() {}
函数,是否可以通过可运行成功?我们来试一试。
2.1.4.1 实验
源码
#include<stdio.h>
// 声明空函数
a();
int main()
{
// 调用空函数
a();
return 0;
}
// 定义空函数
a()
{
printf("Hello World!\n");
}
结果
空函数当中,在gcc编译器中并不会报错,而是报警告且能够运行。如上图可知空函数默认为int
类型。但是在老版本编译器当中或者C++当中,编译程序是不通过,原因是新版本的编译器是经过了一定的优化,而C++对代码格式比C严格了。感兴趣的,可以去试一试,再这里就不再做扩展。
3.1 返回值与类型
可以是任意类型,在函数体当中使用return
关键字返回一个确定值,如果定义的函数类型为void
,不需要调用函数从被调用函数带回函数值的,可以不要return
语句或者直接写return;
。
3.1.1 实验
源码
#include<stdio.h>
// 函数声明
void fun(void);
int main()
{
int res;
// 函数调用
res = fun();
// 打印函数整形返回值
printf("res = %d\n", res);
return 0;
}
// 函数定义
void fun(void)
{
// 可写可不写
return;
}
函数值的类型最好和返回值的类型一致。如果函数值的类型和返回值语句中表达的值不一致,以函数类型为准,如果不信,那我们可以打印来看看。
3.1.2 实验
源码
#include<stdio.h>
// 函数声明
int fun(void);
int main()
{
int res;
// 函数调用
res = fun();
// 打印函数整形返回值
printf("res = %d\n", res);
return 0;
}
// 函数定义
int fun(void)
{
// 小数型
return 3.14;
}
结果
不难看出,fun
函数返回的是int类型的数值,也就是最终结果3
,而不是3.14
。
4.1 函数参数
函数之间想要实现,不同参数下,共享同一个变量的情况下,有两种方法,第一种就是在[函数返回值] [函数名][函数参数列表],使用函数的参数传递方式。第二种就是采用全局变量方法也能够完成值的传递。后者方法涉及到变量修饰符,不属于本节知识点,所以在这里我们只讲前者函数的参数传递方式。
- 函数定义时的参数称为形参参数,简称为形参,在函数当中可分为带形式参数和不带形式参数两种。
表达形式:
// 带形参
int fun(int param_a, int param_b) {
}
// 不带形参
int fun(void) {
}
从内存角度来考虑一下,在定义函数形参,如果函数未被调用,形参并不会占用内存空间。只有当函数调用的时候,形参才会被分配内存单元。
- 实参也叫做实际参数,要求在调用之前,它们是一个给定的值,这个值可以是常量也可以是一个地址,并且把它传递给形参。
表达形式:
// 对应上图-1, 实参
fun(3, 10);
// 对应上图-2, 实参
fun();
函数参数有两种传递方式,第一种是值传递,第二种是地址传递。值传递方式就是上述我们采用的方式,我们传递的形参是两个int类型的变量。
4.1.1 值传递
值传递的方式,就是使用变量作为形参,实际上传递过程当中,是将实参的值拷贝一份给形参相应的存储单元中。本质上形参和实参是存在不同的存储空间。
值传递的特点是单向传递的,当主调函数调用结束之后,形参的存储单元被释放,实参单元仍保留并维持原值。
4.1.1.1 实验
源码
#include<stdio.h>
// 函数声明
void swap(int p, int q);
int main()
{
int a = 10;
int b = 20;
// 值传递
swap(a, b);
// 打印函数整形返回值
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
// 函数定义
void swap(int p, int q)
{
int temp = p;
p = q;
q = temp;
}
结果
4.1.2 地址传递
地址传递的方式使用数组名或者指针作为形参,数组的首地址或者指针的值(值可理解为地址)作为实参。这就意味着形参接收到是地址,即形参指针实参的存储单元,形参和实参本质上占用相同的存储单元。
地址传递的特点是形参与实参共用一段内存空间,意味着形参的变化也就是实参的变化。
4.1.2.1 实验
源码
#include<stdio.h>
void swap(int *p, int *q);
int main()
{
int a = 10;
int b = 20;
// 函数调用
swap(&a, &b);
printf("a = %d\n", a);
printf("b = %d\n", b);
return 0;
}
// 函数定义
void swap(int *p, int *q)
{
// 值交换
int temp;
temp = *q;
*q = *p;
*p = temp;
}
结果
由实验结果可知,初始值a = 10; b = 20;
,经过地址传递之后,a = 20; b = 10;
。
5.1 提升!
5.1.1 函数无声明实验
-
如果函数只定义,不声明。
将预处理文件转换成汇编文件。
源文件
#include<stdio.h>
int main()
{
// fun函数调用
fun();
return 0;
}
// fun函数只定义, 无声明
void fun(void)
{
}
结果:
高版本gcc将预处理文件转换成汇编期间会报警告(低版本会报错)。
5.1.2 函数无定义实验
- 如果函数只声明、无定义,
gcc
编译报程序连接期间出现错误。
#include<stdio.h>
// fun函数只声明, 无定义
void fun(void);
int main()
{
// fun函数调用
fun();
return 0;
}
结果
预处理、汇编、转换目标文件都没有问题,在链接期间,编译器会报错,undefined reference
无定义引用。
- 规定放在文件头文件后面,肯定有同学问,为什么要把函数声明放在函数定义前面?
- 从语法来说,如果函数定义与声明在同一文件内,规定声明要在函数定义前面。
- 从编译器角度来说,函数定义在编译期间会警告,告诉你
fun
函数是隐式声明。
如果对程序编译过程存在着疑虑,可查看(还未更新,看心情咯)。