一. 初探函数
我们已经用过 C 标准库的函数,如 printf(),scanf(),sizeof(),strlen()。现在要进一步学习如何创建自己的函数。
什么是函数?函数是完成特定任务的独立程序代码单元。
为什么要使用函数
- 使用函数可以省去编写重复代码的苦差
- 即使程序只完成某项任务一次,也值得使用函数;因为函数让程序更加模块化,从而提高了程序代码的可读性,更方便后期修改、完善。
1.1 函数的定义与声明
函数的定义就是函数体的实现,函数体就是一个代码块,它在函数被调用时执行。
函数的声明出现在函数被调用的地方,它向编译器提供该函数的相关信息,用于确保函数被正确地调用
// 函数定义的语法如下
返回类型 函数名(形式参数)
{
...
}
// 返回类型可以是 void, 代表没有返回值
// 形式参数也可以是 void, 代表没有形式参数
#include <stdio.h>
// 函数定义
void printHello(void)
{
printf("Hello , world\n");
}
int main(void)
{
// 函数调用
printHello();
return 0;
}
// 方法一:同一源文件,调用之前已经出现了该函数的定义,编译器就会记住它的参数数量和类型、返回值类型,确保该函数后续被正确调用
#include <stdio.h>
// 声明函数原型
void printHello(void);
int main(void)
{
// 函数调用
printHello();
return 0;
}
// 函数定义
void printHello(void)
{
printf("Hello , world\n");
}
// 方法二:声明函数原型,它向编译器提供有关该函数应该如何调用的完整信息
// 使用原型最方便(且最安全)的方法是把原型置于一个单独的文件,如果其它源文件需要这个函数的原型,就使用 #include 包含该文件
// 上述技巧避免了错误输入函数原型的可能性,又简化了程序的维护任务,因为这样只需要该原型的一份物理副本。如果原型需要修改,只需要修改它的一处副本即可
1.2 return 语句
return 语句允许从函数体的任何位置返回,不一定要在函数体的末尾。返回到函数被调用的地方。
// 语法
return expression;
expression 是可选的。
- 如果函数的返回类型是 void,即无需向调用程序返回一个值,expression就被省略。
- 通常 expression 类型必须和函数的返回类型对应。只有编译器能将 expression 的类型正确转换为返回类型时,才允许它俩的类型不一样。
1.3 形式参数与实际参数
每个形式参数前必须声明其类型。
形式参数是被调函数中的变量,实际参数是主调函数赋给被调函数中的具体值。调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果
#include <stdio.h>
int Max(int num_1 , int num_2)
{
return num_1 > num_2 ? num_1 : num_2;
}
int main(void)
{
int num_1 = 1;
int num_2 = 2;
printf("Max: %d\n" , Max(num_1 , num_2));
return 0;
}
再次强调,实际参数是具体的值,该值以拷贝的形式赋给被调函数的形式参数,因此无论被调函数对拷贝数据进行什么样的操作,都不会影响主调函数中的原始数据
1.4 函数栈帧
什么是栈帧?在 C 语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。
- 栈帧是一块因函数运行而在栈区临时开辟的空间
- 每调用一次函数便会创建一个独立栈帧
- 栈帧中存放的是函数中的必要信息,如局部变量、形式参数、返回值等
- 当函数运行完毕栈帧将会销毁
二. 作用域,局部变量,全局变量
什么是作用域?作用域就是变量和函数的可访问范围,即变量其实只有在程序的一定区域内才能被访问
位于一对大括号之间的所有语句称为一个代码块,如果代码块处于嵌套状态,内层代码块定义的变量将隐藏外层代码块的变量,即优先使用内层代码块定义的变量
1.4.1 局部变量
- 在代码块内声明的变量称为局部变量,注意函数的形式参数属于函数体内部,也属于局部变量。
- 局部变量又叫做自动变量,进了大括号自动创建,出了大括号,自动销毁,内存空间释放归还给操作系统。
- 局部变量如果不初始化,默认是随机值(包括指针)。所以局部变量一定要初始化
- 局部变量是在 “栈区” 分配空间的,栈区的特点是上面创建的变量出了作用域就销毁,但是有一个例外,被 static 修饰的局部变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长
1.4.2 全局变量
- 位于所有代码块之外的变量称为全局变量
- 全局变量的作用域是整个工程(包括许多源文件)
- 全局变量如果未初始化,执行默认初始化,内置数据类型以对应的 0 值进行初始化。
- 全局变量是在数据段(静态区)分配内存
- 当局部变量与全局变量同名时,在该代码块内,局部变量优先于全局变量
- 使用关键字 extern 可以在一个源文件中使用另一个源文件中定义的全局变量
// 局部变量与全局变量同名
#include <stdio.h>
int num_1 = 999;
int main(void)
{
int num_1 = 1;
printf("%d\n" , num_1);
return 0;
}
// static 测试
#include <stdio.h>
void staticTest(void)
{
//static int num_1 = 1;
int num_1 = 1;
printf("num_1: %d\n" , num_1);
++num_1;
return;
}
int main(void)
{
staticTest();
staticTest();
staticTest();
return 0;
}
// for 循环测试 1
#include <stdio.h>
int main(void)
{
for(int i = 0; i < 5; ++i)
{}
printf("i: %d\n" , i);
return 0;
}
// for 循环测试 2
#include <stdio.h>
int main(void)
{
int i = 0;
for(; i < 5; ++i)
{}
printf("i: %d\n" , i);
return 0;
}
// while 循环测试 1
#include <stdio.h>
int main(void)
{
int times = 3;
while(times--)
{
int num_1 = 1;
++num_1;
printf("%d\n" , num_1);
}
return 0;
}
// while 循环测试 2
#include <stdio.h>
int main(void)
{
int times = 3;
int num_1 = 1;
while(times--)
{
printf("%d\n" , num_1);
++num_1;
}
return 0;
}
// 代码块嵌套
#include <stdio.h>
int main(void)
{
int num_1 = 1;
if(1)
{
int num_1 = 2;
printf("num_1: %d\n" , num_1);
}
return 0;
}
查找变量,会先从内层代码块开始查找,如果没有,去上一层代码块查找,如果上一层代码块也没有,去上上一层,依次类推,直到找到为止。如果直到全局作用域都没有,程序就会报错。
// 变量查找
#include <stdio.h>
int num_1 = 1;
int main(void)
{
int num_2 = 2;
if(1)
{
printf("num_1: %d\n" , num_1);
}
return 0;
}
课堂练习:给出一个不多于5位的整数,要求
- 求出它是几位数
- 分别输出每一位数字
- 按逆序输出各位数字,例如原数为321,应输出123
作业
- 将讲义中的程序自己敲一遍,理解程序为何会这样运行。
- 小明写了一个程序,想要将两个整数的值交换,但程序的运行的结果并没有交换两个整数的值,请帮小明找出错误的原因并写出正确的程序?
#include <stdio.h>
void swapInt(int num_1 , int num_2)
{
int tmp = num_1;
num_1 = num_2;
num_2 = tmp;
return;
}
int main(void)
{
int num_1 = 1;
int num_2 = 2;
printf("num_1:%d num_2:%d\n" , num_1 , num_2);
swapInt(num_1 , num_2);
printf("num_1:%d num_2:%d\n" , num_1 , num_2);
return 0;
}
- 编写一个函数,判断一个整数是不是素数
- 编写一个函数,求两个整数的最大公约数
- 编写一个函数,求斐波那契数列的第 n 项
- 编写一个函数,求 n 的阶乘
- 编写一个函数,给定一个数组和一个数,判断这个数是否在这个数组中,返回布尔值