提示:在编程中,函数是构建复杂程序的基本模块。
目录
前言
9.1 为什么需要函数
9.1.1请大家完成这样一个需求
9.1.2使用传统方法解决
9.2 解决方法-函数
9.3 函数的定义
9.3.1基本语法
9.3.2快速入门案例
9.4 头文件
9.4.1先看一个需求
9.4.2头文件基本概念
9.4.3工作原理图
9.5 头文件快速入门
9.6 头文件的注意事项和细节说明
9.7 函数-调用机制
9.7.1如何理解方法这个概念,给大家举个通俗的示例
9.7.2函数-调用过程
9.7.3举例说明
9.8 函数-递归调用
9.8.1基本介绍
9.8.2递归调用快速入门
9.8.3函数递归需要遵守的重要原则
9.8.4递归课堂练习题
9.9 函数注意事项和细节讨论
9.9.1课堂练习题
9.10 函数参数的传递方式
9.10.1 基本介绍
9.10.2 两种传递方式
9.10.3 值传递和引用传递使用特点
9.11 变量作用域
9.11.1 基本说明
9.11.2 初始化局部变量和全局变量
9.11.3 作用域的注意事项和细节
9.11.4 课堂练习题 1
9.11.5 课堂练习题 2
9.12 static 关键字
9.12.1 基本介绍
9.12.2 局部变量使用 static 修饰
9.12.3 全局变量使用 static 修饰
9.12.4 函数使用 static 修饰
9.13 字符串中常用的系统函数
9.14 时间和日期相关函数
9.15 数学相关函数
9.16 基本数据类型和字符串类型的转换
9.16.1 介绍
9.16.2 sprintf 函数的用法
9.16.3 基本类型转字符串类型
9.16.4 字符串类型转基本数据类型
9.16.5 注意事项
9.17 函数的练习题
9.17.1 编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表:【上机练习】
9.17.2 编写函数,对给定的一个二维数组(3×3)转置,这个题讲数组的时候再完成
9.17.3 定义函数,实现求两个 double 数字的最大值,并返回
9.17.4 定义函数,求出三个 int 类型数的和,并返回
总结
附录
前言
在编程中,函数是构建复杂程序的基本模块。函数不仅使代码更具可读性和可维护性,还能提高代码的复用性。我们将深入探讨函数的概念、定义、使用方法以及相关的编程技巧。我们将从传统的代码实现开始,发现其不足之处,并引入函数来改进代码结构。通过示例和实际应用,我们将展示如何通过定义和使用函数来解决问题,优化代码。
9.1 为什么需要函数
9.1.1请大家完成这样一个需求
输入两个数,再输入一个运算符(+,-,*,/),得到结果.。
先使用传统的方式来解决,看看有什么问题没有?
1) 代码冗余(即有过多重复的代码)
2) 不利于代码的维护
3) 引出函数
9.1.2使用传统方法解决
简单的说就是在需要执行计算时,将这段完成计算任务代码,复制即可, 该传统方案不好,引出函数
9.2 解决方法-函数
1) 为完成某一功能的程序指令(语句)的集合,称为函数。
2) 在 C 语言中,函数分为: 自定义函数、系统函数(查看 C 语言函数手册)
3) 函数还有其它叫法,比如方法等,在本视频课程中,我们统一称为 函数。
9.3 函数的定义
9.3.1基本语法
返回类型 函数名(形参列表){
执行语句...; // 函数体
return 返回值; // 可选
}
1) 形参列表:表示函数的输入
2) 函数中的语句:表示为了实现某一功能代码块
3) 函数可以有返回值,也可以没有, 如果没有返回值,返回类型 声明为 void 。
9.3.2快速入门案例
使用函数来解决前面的计算问题。
#include <stdio.h>
//说明
//1. 函数名 cal
//2. 有返回值 double
//3. 形参列表为 (int n1, int n2, char oper)
//4. 在函数中,我们使用的变量名需要和形参列表中的变量名一样
double cal(int n1, int n2, char oper) {
//定义一个变量 res ,保存运算的结果
double res = 0.0;
switch(oper) {
case '+' :
res = n1 + n2;
break;
case '-':
res = n1 - n2;
break;
case '*':
res = n1 * n2;
break;
case '/':
res = n1 / n2;
break;
default :
printf("你的运算符有误~");
}
printf("\n%d %c %d = %.2f\n", n1, oper, n2, res);
return res;
}
void main() {
int num1 = 10; //第一个数
int num2 = 20;//第二个数
double res = 0.0;//结果
char oper = '-'; //运算符
//我们又要接收两个数和一个运算符
int num3 = 60;
int num4 = 80;
double res2 = 0.0;
char oper2 = '*';
printf("使用函数来解决计算任务~~");
//使用函数来完成两个计算任务
res = cal(num1, num2, oper); //调用函数,使用函数
printf("\n cal 函数返回的结果 res=%.2f", res);
//使用函数完成第二个计算任务
res2 = cal(num3, num4, oper2); //调用函数,使用函数
printf("\n cal 函数返回的结果 res2=%.2f", res2);
//传统方法
/*
switch(oper2) {
case '+' :
res2 = num3 + num4;
break;
case '-':
res2 = num3 - num4;
break;
case '*':
res2 = num3 * num4;
break;
case '/':
res2 = num3 / num4;
break;
default :
printf("你的运算符有误~");
}
printf("%d %c %d = %.2f\n", num3, oper2, num4, res2); */
getchar();
}
|
9.4 头文件
9.4.1先看一个需求
在实际的开发中,我们往往需要在不同的文件中,去调用其它文件的定义的函数,比如 hello.c 中,去使用myfuns.c 文件中的函数,如何实现? -》头文件
9.4.2头文件基本概念
1) 头文件是扩展名为 .h 的文件,包含了 C 函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:程序员编写的头文件和 C 标准库自带的头文件
2) 在程序中要使用头文件,需要使用 C 预处理指令 #include 来引用它。前面我们已经看过 stdio.h 头文件,它是C 标准库自带的头文件
3) #include 叫做文件包含命令,用来引入对应的头文件(.h 文件)。#include 也是 C 语言预处理命令的一种。#include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,特别在程序是由多个源文件组成的时候。
4) 建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件
9.4.3工作原理图
使用头文件的示意图
![d4adc4c51d5f4a7b954bf71394e66dee.png](https://i-blog.csdnimg.cn/direct/d4adc4c51d5f4a7b954bf71394e66dee.png)
9.5 头文件快速入门
说明:头文件快速入门-C 程序相互调用函数,我们将 cal 声明到文件 myFun.h , 在 myFun.c 中定义 cal 函数,当其它文件需要使用到 myFun.h 声明 的函数时,可以#include 该头文件,就可以使用了. 【演示】
myfun.h
#include <stdio.h>
//声明函数
int myCal(int n1, int n2, char oper);
void sayHello() ;
|
myfun.c
#include <stdio.h>
//实现 int myCal(int n1, int n2, char oper)
int myCal(int n1, int n2, char oper) {
//定义一个变量 res ,保存运算的结果
double res = 0.0;
switch(oper) {
case '+' :
res = n1 + n2;
break;
case '-':
res = n1 - n2;
break;
case '*':
res = n1 * n2;
break;
case '/':
res = n1 / n2;
break;
default :
printf("你的运算符有误~");
}
printf("\n%d %c %d = %.2f\n", n1, oper, n2, res);
return res;
}
void sayHello() { //定义函数
printf("say Hello");
}
|
hello.c
#include <stdio.h>
//引入我们需要的头文件(用户头文件)
#include "myfun.h"
#include "myfun.h"
void main() {
//使用 myCal 完成计算任务
int n1 = 10;
int n2 = 50;
char oper = '-';
double res = 0.0;
//调用 myfun.c 中定义的函数 myCal
res = myCal(n1, n2, oper);
printf("\nres=%.2f", res);
sayHello();
getchar();
}
|
9.6 头文件的注意事项和细节说明
1) 引用头文件相当于复制头文件的内容
2) 源文件的名字 可以不和头文件一样,但是为了好管理,一般头文件名和源文件名一样.
3) C 语言中 include <> 与 include "" 的区别
include <>:引用的是编译器的类库路径里面的头文件,用于引用系统头文件。
include "":引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的类库路径的目录下找该头文件,用于引用用户头文件。
所以:
* 引用 系统头文件,两种形式都会可以,include <> 效率高
* 引用 用户头文件,只能使用 include ""
4) 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令
5) 同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制 [举例]
6) 在一个被包含的文件(.c)中又可以包含另一个文件头文件(.h)
7) 不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误(!!!!)
9.7 函数-调用机制
9.7.1如何理解方法这个概念,给大家举个通俗的示例
9.7.2函数-调用过程
Ø 为了让大家更好的理解函数调用过程, 看两个案例,并画出示意图(机制),这个很重要
![217008b0f63b495bbe676355cdc016aa.png](https://i-blog.csdnimg.cn/direct/217008b0f63b495bbe676355cdc016aa.png)
1) 传入一个数+1 test 函数
2) 计算两个数,并返回 getSum 函数
3) 对应示意图
![e1ab1b1b9e274d95b6143120d55c4253.png](https://i-blog.csdnimg.cn/direct/e1ab1b1b9e274d95b6143120d55c4253.png)
![a919493647a64f858231dda5d7b3371f.png](https://i-blog.csdnimg.cn/direct/a919493647a64f858231dda5d7b3371f.png)
4) 函数调用规则
函数调用的规则(适用于 java, c++, php 等)
1. 当调用(执行)一个函数时,就会开辟一个独立的空间(栈)
2. 每个栈空间是相互独立
3. 当函数执行完毕后,会返回到调用函数位置,继续执行
4 如果函数有返回值,则,将返回值赋给接收的变量
5. 当一个函数返回后,该函数对应的栈空间也就销毁
9.7.3举例说明
![3a3271f8806c4619834d96438f3771e4.png](https://i-blog.csdnimg.cn/direct/3a3271f8806c4619834d96438f3771e4.png)
9.8 函数-递归调用
9.8.1基本介绍
一个函数在函数体内又调用了本身,我们称为递归调用
9.8.2递归调用快速入门
![76c9de2599b041288bd47c67534d2705.png](https://i-blog.csdnimg.cn/direct/76c9de2599b041288bd47c67534d2705.png)
Ø 通过两个题,分析递归执行的流程和机制
![02f8504d6a3d4363b7b9bb12d7eabdf5.png](https://i-blog.csdnimg.cn/direct/02f8504d6a3d4363b7b9bb12d7eabdf5.png)
9.8.3函数递归需要遵守的重要原则
1) 执行一个函数时,就创建一个新的受保护的独立空间(新函数栈)
2) 函数的局部变量是独立的,不会相互影响
3) 递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:)
4) 当一个函数执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁
9.8.4递归课堂练习题
Ø 题 1:斐波那契数
请使用递归的方式,求出斐波那契数 1,1,2,3,5,8,13...
给你一个整数 n,求出它的斐波那契数是多少?
Ø 题 2:求函数值
已知 f(1)=3; f(n) = 2*f(n-1)+1;
请使用递归的思想编程,求出 f(n)的值?
Ø 题 3:猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。
当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
Ø 前面三个题的解答如下:
#include <stdio.h>
int fbn(int n) {
if(n == 1 || n==2) {
return 1;
} else {
return fbn(n-1) + fbn(n-2);
}
}
//f(1)=3; f(n) = 2*f(n-1)+1
int f(int n) {
if(n==1) {
return 3;
} else {
return 2*f(n-1)+1;
}
}
//分析:
// 1. day=10 有 1 个桃子
// 2. day=9 有 (day10 + 1) * 2 = (1+1) * 2 = 4
// 3. day=8 有 (day9 + 1) * 2 = (4+1) * 2 = 10
int peach(int day) {
if(day == 10) {
return 1;
} else {
return (peach(day+1) + 1) * 2;
}
}
void main() {
//题 1:斐波那契数
请使用递归的方式,
//求出斐波那契数 1,1,2,3,5,8,13...
给你一个整数 n,求出它的斐波那契数是多少?
//分析
//1. 如果 n=1 n=2 时,返回 1
//2. 从 n=3 开始,对应的斐波那契数是 前面两个数的和
int res = fbn(7); // res = 13
printf("\nres=%d", res);
getchar();
/*题 2:求函数值
已知 f(1)=3; f(n) = 2*f(n-1)+1;
请使用递归的思想编程,求出 f(n)的值?
分析:因为该题的公式已经给出,所以直接使用即可
*/
int res2 = f(30);
printf("\nres2=%d", res2);
getchar();
/*题 3:猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,
并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。
当到第十天时,想再吃时(还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
分析:
1. day=10 有 1 个桃子
2. day=9 有 (day10 + 1) * 2 = (1+1) * 2 = 4
3. day=8 有 (day9 + 1) * 2 = (4+1) * 2 = 10
//......
*/
int peachNum = peach(1);
printf("\n 第一天有%d 个桃子", peachNum);
getchar();
}
|
9.9 函数注意事项和细节讨论
1) 函数的形参列表可以是多个。
2) C 语言传递参数可以是值传递(pass by value),也可以传递指针(a pointer passed by value)也叫引用传递。
3) 函数的命名遵循标识符命名规范,首字母不能是数字,可以采用 驼峰法 或者 下划线法 ,比如 getMax()、get_max()。
4) 函数中的变量是局部的,函数外不生效【案例说明】
5) 基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。【案例演示】
//基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
void f2(int n) {
n++;
printf("\nf2 中的 n=%d", n); // n=10
}
void main() {
//函数中的变量是局部的,函数外不生效
//printf("num=%d", num);
int n = 9;
f2(n);
printf("\nmain 函数中 n=%d", n); //9
getchar();
}
|
6) 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用(即传递指针) 【案例演示:】
//如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,
//函数内以指针的方式操作变量。从效果上看类似引用(即传递指针)
void f3(int *p) {
(*p)++;// 修改会对函数外的变量有影响
}
void main() {
//函数中的变量是局部的,函数外不生效
//printf("num=%d", num);
int n = 9;
f3(&n);
printf("\nmain 函数中 n=%d", n); //10
getchar();
}
|
7) C 语言 不支持函数重载。
8) C 语言支持可变参数函数//知道即可
#include <stdio.h>
#include <stdarg.h>
//说明
//num 表示传递的参数格式
//... 表示可以传递多个参数和 num 一致即可
int fun(int num, ...) //可变函数,即参数的个数可以不确定, 使用... 表示
{
int i, totalSum=0; //totalSum 一定要初始化
int val = 0;
va_list v1; //v1 实际是一个字符指针,从头文件里可以找到
va_start(v1, num); //使 v1 指向可变列表中第一个值,即 num 后的第一个参数
printf("*v = %d\n",*v1);
for(i = 0; i < num; i++) //num 减一是为了防止下标超限
{
val = va_arg(v1, int); //该函数返回 v1 指向的值,并使 v1 向下移动一个 int 的距离,使其指向下一个 int
printf("val = %d\n", val); // val = 10 val=30 val=60
totalSum += val;
}
va_end(v1); //关闭 v1 指针,使其指向 null
return totalSum;
}
void main() {
int res = fun(8, 10,30,60, -100,1,5,7,98);
printf("和是=%d", res);
getchar();
}
|
9.9.1课堂练习题
请编写一个函数 swap(int *n1, int *n2) 可以交换 n1 和 n2 的值
#include <stdio.h>
//请编写一个函数 swap(int *n1, int *n2) 可以交换 n1 和 n2 的值
//说明
//1. 函数名为 swap
//2. 形参是两个指针类型 int*
void swap(int *n1, int *n2) {
int temp = *n1; //表示将 n1 这个指针指向的变量的值赋给 temp
*n1 = *n2; // 表示将 n2 这个指针指向的变量的值赋给 n1 这个指针指向的变量
*n2 = temp; //表示将 temp 值赋给 n2 这个指针指向的变量
}
void main() {
int n1 = 1;
int n2 = 2;
swap(&n1, &n2);
printf("main n1=%d n2=%d", n1, n2);// n1= 2 n2 = 1
getchar();
}
|
Ø 对上面代码的执行机制分析(重要)
![b3083bde2042421eac542c2380e6a9b6.png](https://i-blog.csdnimg.cn/direct/b3083bde2042421eac542c2380e6a9b6.png)
9.10 函数参数的传递方式
9.10.1 基本介绍
我们在讲解函数注意事项和使用细节时,已经讲过 C 语言传递参数可以是值传递(pass by value),也可以传递指针(a pointer passed by value)也叫传递地址或者 引用传递。
9.10.2 两种传递方式
1) 值传递
2) 引用传递(传递指针、地址)
其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定拷贝的数据大小,数据越大,效率越低。
9.10.3 值传递和引用传递使用特点
![cf450172ad704b8ea96493d768a1799d.png](https://i-blog.csdnimg.cn/direct/cf450172ad704b8ea96493d768a1799d.png)
5) 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量(*指针)。
9.11 变量作用域
9.11.1 基本说明
所谓变量作用域(Scope),就是指变量的有效范围
1) 函数内部声明/定义的局部变量,作用域仅限于函数内部。
void sayHello() {
char name[] = "tom";
printf("hello %s \n", name);
}
void main() {
sayHello();
//这里我们不能使用到 sayHello 的 name 变量
printf("name= %s", name); //这里将提示,没有定义 name
}
|
2) 函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用局部变量(编译器使用就近原则)
int n = 20; //函数外部定义的变量,就是全局变量
//函数形参,会被视为 f10 的局部变量
//说明:当局部变量和全局变量同名时,以局部变量为准(就近原则)
void f10(int n) {
printf("\nn=%d", n);
}
void main() {
f10(10); // 输出就是 10
}
|
3) 在一个代码块,比如 for / if 中 的局部变量,那么这个变量的的作用域就在该代码块
void main() {
int i = 0;
for (i = 0; i < 10; i++){
int k = 90; //k 的作用域在 for 代码块中
printf("i=%d k = %d\n", i, k);
}
printf("k=%d", k); // 这里不能使用 for 中定义的 k 变量
getchar();
}
|
4) 在所有函数外部定义的变量叫全局变量,作用域在整个程序有效
9.11.2 初始化局部变量和全局变量
1) 局部变量,系统不会对其默认初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出.
2) 全局变量,系统会自动对其初始化,如下所示
![9b344139cc304d7f81d9eb2e2d0c7def.png](https://i-blog.csdnimg.cn/direct/9b344139cc304d7f81d9eb2e2d0c7def.png)
3) 正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值
9.11.3 作用域的注意事项和细节
1) 全局变量(Global Variable)保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也就是所有的代码文件,包括源文件(.c 文件)和头文件(.h 文件)。【c 程序内存布局图!!!】
![6a39b37a77e3421f82731dbd052412be.png](https://i-blog.csdnimg.cn/direct/6a39b37a77e3421f82731dbd052412be.png)
2) 局部变量(Local Variable)保存在栈中,函数被调用时才动态地为变量分配存储单元,它的作用域仅限于函数内部。【内存布局分析 】
3) C 语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量
4) 在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则.
5) 由{ }包围的代码块也拥有独立的作用域
举例说明:
void f20() { //函数, 定义变量就是局部变量
int num = 90;
if(1) {
int num = 900; //ok
}
printf("a=%d", a);
}
|
9.11.4 课堂练习题 1
思考:下面的代码输出什么内容?
#include <stdio.h>
double price = 200.0; //全局变量
void test01() {
printf("%.2f \n", price); // 就是 全局变量 : 200.0
}
void test02() { //编译器采用就近原则
double price = 250.0; // 如果把 这句话 改成 double price = 250.0 , 输出有什么变化?
printf("%.2f \n", price); // 250.0
}
void main() {//main 函数
printf("main price=%.2f \n", price); // 200.00
test01(); // 200.0
test02(); // 250.0 , 将 全局 price 改成 250
test01();// 200.0
getchar();
}
|
9.11.5 课堂练习题 2
思考:下面的代码输出什么内容?
#include <stdio.h>
int n = 10; //全局变量
void func1(){
int n = 20; //局部变量
printf("func1 n: %d\n", n); //
}
void func2(int n){
printf("func2 n: %d\n", n); // n 是 形参 n
}
void func3(){
printf("func3 n: %d\n", n); //n 是全局变量 n
}
int main(){
int n = 30; //局部变量(main 函数)
func1(); // 20
func2(n); // 30
func3(); //10
//代码块由{}包围
{
int n = 40; //局部变量{}
printf("block n: %d\n", n); // 40
}
printf("main n: %d\n", n); // 30
getchar();
return 0;
}
|
9.12 static 关键字
9.12.1 基本介绍
static 关键字在 c 语言中比较常用,使用恰当能够大大提高程序的模块化特性,有利于扩展和维护。
9.12.2 局部变量使用 static 修饰
1) 局部变量被 static 修饰后,我们称为静态局部变量
2) 对应静态局部变量在声明时未赋初值,编译器也会把它初始化为 0。
3) 静态局部变量存储于进程的静态存储区(全局性质),只会被初始一次,即使函数返回,它的值也会保持不变 [案例+图解]
4) 案例演示
#include <stdio.h>
//void main() {
//
// static int n; //n 就是静态局部变量, 默认初始化值 0
// printf("\n n=%d", n);
// getchar();
//}
void fn(void){
int n = 10; //普通变量, 每次执行都会初始化,n 在栈区
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fn_static(void) {
static int n = 10; //静态局部变量, 放在静态存储区,全局性质空间
printf("\nstatic n=%d\n", n); // 10
n++; // n = 11
printf("\nn++=%d\n", n); // 11
}
int main(void) {
/* fn();
printf("--------------------\n");*/
fn_static();
printf("--------------------\n");
/* fn();
printf("--------------------\n");*/
fn_static();
getchar();
return 0;
}
|
9.12.3 全局变量使用 static 修饰
1) 普通全局变量对整个工程可见,其他文件可以使用 extern 外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量),静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响 [案例]
2) 定义不需要与其他文件共享的全局变量时,加上 static 关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用
3) 案例演示
file01.c
#include <stdio.h>
//在一个文件中,使用另外一个文件的全局变量, 使用 extern 引入即可
extern int num;
//extern int num2;
int num2 = 60;
void main() {
printf("\nnum=%d num2=%d", num, num2);
getchar();
}
|
file02.c
int num = 10; //普通全局变量
static int num2 = 20; //静态全局变量, 只能在本文件中使用,而不能在其它文件使用
|
9.12.4 函数使用 static 修饰
1) 函数的使用方式与全局变量类似,在函数的返回类型前加上 static,就是静态函数
2) 非静态函数可以在另一个文件中通过 extern 引用 【案例】
3) 静态函数只能在声明它的文件中可见,其他文件不能引用该函数[案例]
4) 不同的文件可以使用相同名字的静态函数,互不影响[案例]
5) 案例演示
file03.c
#include <stdio.h>
void fun1(void) {//普通函数(非静态函数)
printf("hello from fun1.\n");
}
static void fun2(void) {//静态函数, 它只能在本文件中使用
printf("hello from fun2.\n");
}
|
file04.c
#include <stdio.h>
extern void fun1(void);
//extern void fun2(void);
void fun2() {
//...
}
void main() {
fun1();
//fun2();
getchar();
}
|
9.13 字符串中常用的系统函数
说明:字符串(即 字符数组)在我们程序开发中,使用的是非常多的,常用的函数需要同学们掌握[带看手册或者官方编程指南 头文件 <string.h>]:
举例说明
1) 得到字符串的长度
size_t strlen(const char *str)
计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。
2) 拷贝字符串
char *strcpy(char *dest, const char *src)
把 src 所指向的字符串复制到 dest。
3) 连接字符串
char *strcat(char *dest, const char *src)
把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
4) 代码演示
#include <stdio.h>
#include <string.h> // 头文件中声明字符串相关的系统函数
void main() {
char src[50] = "abc", dest[50]; //定义了两个字符数组(字符串) , 大小为 50
char * str = "abcdff";
printf("str.len=%d", strlen(str)); //统计字符串的大小
//表示将 "hello" 拷贝到 src
//注意,拷贝字符串会将原来的内容覆盖
strcpy(src, "hello");
printf("\s src=%s", src);
strcpy(dest, "尚硅谷");
//strcat 是将 src 字符串的内容连接到 dest ,但是不会覆盖 dest 原来的内容,而是连接!!
strcat(dest, src); // "hello 尚硅谷"
printf("最终的目标字符串: dest=%s", dest);
getchar();
}
|
9.14 时间和日期相关函数
说明:在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。
头文件是 <time.h>
举例说明:
1) 获取当前时间
char *ctime(const time_t *timer)
返回一个表示当地时间的字符串,当地时间是基于参数 timer。
2) 编写一段代码来统计 函数 test 执行的时间
double difftime(time_t time1, time_t time2)
返回 time1 和 time2 之间相差的秒数 (time1-time2)。
3) 代码演示
#include <stdio.h>
#include <time.h> //该头文件中,声明和日期和时间相关的函数
void test() { // 运行 test 函数,看看执行花费时间
int i = 0;
int sum = 0;
int j = 0;
for(i = 0; i < 77777777;i++) {
sum = 0;
for (j = 0; j< 10;j++) {
sum += j;
}
}
}
int main () {
//time_t curtime; //time_h 是一个结构体类型
//time(&curtime); //time() 完成初始化
ctime 返回一个表示当地时间的字符串,当地时间是基于参数 timer
//printf("当前时间 = %s", ctime(&curtime));
//getchar();
//return(0);
//先得到执行 test 前的时间
time_t start_t, end_t;
double diff_t; //存放时间差
printf("程序启动...\n");
time(&start_t); //初始化得到当前时间
test(); //执行 test
//再得到执行 test 后的时间
time(&end_t);//得到当前时间
diff_t = difftime(end_t, start_t); //时间差,按秒 ent_t - start_t
//然后得到两个时间差就是耗用的时间
printf("执行 test()函数 耗用了%.2f 秒", diff_t);
getchar();
return 0;
}
|
9.15 数学相关函数
math.h 头文件定义了各种数学函数和一个宏。在这个库中所有可用的功能都带有一个 double 类型的参数,且都返回 double 类型的结果
举例说明:
1) double exp(double x)
返回 e 的 x 次幂的值。
2) double log(double x)
返回 x 的自然对数(基数为 e 的对数)
3) double pow(double x, double y)
返回 x 的 y 次幂。
4) double sqrt(double x)
返回 x 的平方根。
5) double fabs(double x)
返回 x 的绝对值。
6) 代码演示
#include <stdio.h> #include <math.h> void main (){ double d1 = pow(2.0,3.0); double d2 = sqrt(5.0); printf("d1=%.2f", d1); printf("d2=%.2f", d2); getchar(); } |
9.16 基本数据类型和字符串类型的转换
9.16.1 介绍
在程序开发中,我们经常需要将基本数据类型转成字符串类型(即 char 数组 )。或者将字符串类型转成基本数据类型。
9.16.2 sprintf 函数的用法
1) sprintf 和平时我们常用的 printf 函数的功能很相似。sprintf 函数打印到字符串中,而 printf 函数打印输出到屏幕上。sprintf 函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛
2) 该函数包含在 stdio.h 的头文件中
9.16.3 基本类型转字符串类型
案例演示:
#include <stdio.h>
void main() {
char str1[20]; //字符数组,即字符串
char str2[20];
char str3[20];
int a=20984,b=48090;
double d=14.309948;
//说明
//1. sprintf 是一个系统函数,可以将结果存放到字符串中
//2. 格式化的结果,会存放到 str1 中
sprintf(str1,"%d %d",a,b);
sprintf(str2, "%.2f", d);
sprintf(str3, "%8.2f", d); //%8.2f 含义是格式化后,一共有 8 位,小数点后占用 2 位, 不够用空格占位
printf("str1=%s str2=%s str3=%s", str1, str2, str3);
getchar();
}
|
9.16.4 字符串类型转基本数据类型
语法:通过<stdlib.h>的函数调用 atoi atof 即可 ,案例演示
#include <stdio.h>
#include <stdlib.h>
void main() {
//字符数组
char str[10] = "123456";
char str2[10] = "12.67423";
char str3[3] = "ab";
char str4[4] = "111";
//说明
//1. atoi(str) 将 str 转成整数
int num1 = atoi(str);
short s1 = atoi(str4);
//说明
//1. atof(str2); 将 str2 转成小数
double d = atof(str2);
//说明
//1. str3[0] 表示获取到 str3 这个字符串(数组)的第一个元素 'a'
char c = str3[0];
printf("num1=%d d=%f c=%c s1=%d", num1, d, c, s1);//
getchar();
}
|
9.16.5 注意事项
1) 在将 char 数组 类型转成 基本数据类型时,要确保能够转成有效的数据,比如 我们可以把 "123" , 转成一个整数,但是不能把 "hello" 转成一个整数
2) 如果格式不正确,会默认转成 0 或者 0.0 [案例演示]
9.17 函数的练习题
1) 函数可以没有返回值案例,编写一个函数,从终端输入一个整数打印出对应的金子塔。 【课后练习】 => 将原来的代码封装到函数中即可
//函数可以没有返回值案例,编写一个函数,从终端输入一个整数(层)打印出对应的金子塔。
//层数 totalLevle 是通过形参传入
void printStar(int totalLevle) {
int i,j,k;
for(i = 1; i <= totalLevle; i++) {//控制层
//输出空格, 使用 k 控制空格的循环
for (k=1; k <= totalLevle - i; k++) {
printf(" ");
}
for(j = 1; j <= 2 * i - 1; j++) {
if(j == 1 || j == 2 * i - 1 || i == totalLevle) { //i ==5 表示最下层
printf("*");
} else {
printf(" ");
}
}
printf("\n");
}
}
void main() {
int totalLevel = 0;
printf("请输入金字塔的层数");
scanf("%d", &totalLevel);
//调用函数
printStar(totalLevel);
getchar();
getchar();
}
|
9.17.1 编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表:【上机练习】
![976f6338c66f436a8d43c24c898996d5.png](https://i-blog.csdnimg.cn/direct/976f6338c66f436a8d43c24c898996d5.png)
//编写一个函数,从终端输入一个整数(1—9),打印出对应的乘法表:
void print99(int n) {
int i,j;
for(i = 1; i <= n; i++) {
//因为每一行都在增加算式, 所以使用 for 循环控制
for(j=1; j <= i; j++) {
printf("%d * %d = %d ", j, i , i * j);
}
printf("\n");
}
}
void main() {
int n = 0;
printf("请输入 n");
scanf("%d", &n);
print99(n);
getchar();
getchar();
}
|
9.17.2 编写函数,对给定的一个二维数组(3×3)转置,这个题讲数组的时候再完成
![eedbc34d5d274068bc5519a68297b1c7.png](https://i-blog.csdnimg.cn/direct/eedbc34d5d274068bc5519a68297b1c7.png)
9.17.3 定义函数,实现求两个 double 数字的最大值,并返回
9.17.4 定义函数,求出三个 int 类型数的和,并返回
//定义函数,实现求两个 double 数字的最大值,并返回
double getMax(double d1, double d2) {
return d1 > d2 ? d1: d2; //三元运算
}
//定义函数,求出三个 int 类型数的和,并返回
int getSum(int n1, int n2, int n3) {
return n1 + n2 + n3;
}
|
总结
本章详细介绍了函数在编程中的重要性及其基本使用方法。通过对比传统的代码实现方式和函数的引入,我们看到函数能够显著减少代码冗余,提高代码的可维护性和清晰度。函数的定义、头文件的使用以及函数调用机制等内容,都为我们提供了更为灵活和高效的编程方式。通过学习函数的递归调用,我们掌握了更高级的编程技巧,为解决复杂问题提供了强有力的工具。掌握这些基础知识后,能够编写出结构更清晰、维护更方便的代码,为开发高质量的软件打下坚实的基础。
附录
参考:【尚硅谷C语言零基础快速入门教程-哔哩哔哩】 https://b23.tv/vS3vTDp