1. 局部变量和静态变量的区别
① 存储位置:
- 普通局部变量存储在栈上。
- 静态局部变量存储在静态存储区。
② 生命周期:
- 当函数执行完毕时,普通局部变量会被销毁。
- 静态局部变量的生命周期是整个程序运行期间,即使函数调用结束,静态局部变量的值也会被保留。
③ 初始值:
- 普通局部变量在每次函数调用时都会被初始化,它们的初始值是不确定的,除非显式地进行初始化。
- 静态局部变量在第一次函数调用时,然后保持其值不变,直到程序结束。
示例代码:
#include <stdio.h>
// 定义一个函数normal_func,该函数打印局部变量的值
void normal_func()
{
// 定义一个局部变量i,并初始化为0
int i = 0;
// 将局部变量i的值加1
i++;
// 打印局部变量i的当前值
printf("局部变量 i = %d\n", i);
}
// 定义一个函数static_func,该函数打印静态局部变量的值
void static_func()
{
// 定义一个静态局部变量j,并初始化为0(仅在第一次调用该函数时初始化)
static int j = 0;
// 将静态局部变量j的值加1
j++;
// 打印静态局部变量j的当前值
printf("static局部变量 j = %d\n", j);
}
int main()
{
// 调用normal_func函数三次
// 由于i是局部变量,每次函数调用时都会重新创建i,并初始化为0
// 因此,每次打印的i的值都是1
normal_func(); // 输出:局部变量 i = 1
normal_func(); // 输出:局部变量 i = 1
normal_func(); // 输出:局部变量 i = 1
// 调用static_func函数三次
// 由于j是静态局部变量,它在函数第一次调用时初始化,并保留其值
// 因此,每次函数调用后,j的值都会累加
static_func(); // 输出:static局部变量 j = 1
static_func(); // 输出:static局部变量 j = 2
static_func(); // 输出:static局部变量 j = 3
return 0;
}
运行结果:
2. 预处理
C语言对源程序处理的四个步骤:预处理、编译、汇编、链接。
① 预处理
- 宏定义展开、头文件展开、条件编译,这里并不会检查语法
② 编译
- 检查语法,将预处理后文件编译生成汇编文件
③ 汇编
- 将汇编文件生成目标文件(二进制文件)
④ 链接
- 将目标文件链接为可执行程序
3.文件包含处理
① 文件包含处理
- 指一个源文件可以将另外一个文件的全部内容包含进来
- C语言提供了#include命令用来实现文件包含的操作
② #include< > 与 #include ""的区别
- <> 表示系统直接按系统指定的目录检索
- "" 表示系统先在 "" 指定的路径(没写路径代表当前路径)查找头文件,如果找不到,再按系统指定的目录检索
4.宏定义
在预编译时将宏名替换成字符串的过程称为"宏展开"(也叫宏替换)。
- 宏名一般用大写,以便于与变量区别
- 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错
- 宏定不要不要行末加分号
代码示例:
#define PI 3.14
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define FUNC(a) func(a)
void func(int a) {
int b = a;
}
int main() {
double a = PI;
int temp = MAX(1, 2+3);
FUNC(10);
return 0;
}
5.条件编译
一般情况下,源程序中所有的行都参加编译。但有时希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件。
防止头文件被重复包含、
#ifndef _SOMEFILE_H
#define _SOMEFILE_H
//需要声明的变量、函数
//宏定义
//结构体
#endif
软件裁剪
同样的C源代码,条件选项不同可以编译出不同的可执行程序:
#include <stdio.h>
// #define A 有注释,没有注释,观察运行结果
#define A
int main() {
#ifdef A
printf("这是大写操作\n");
#else
printf("这是小写操作\n");
#endif
return 0;
}
6. 递归
① 函数递归调用:
函数可以调用函数本身(不要用main()调用main(),不是不能这么做,而是不建议)
#include <stdio.h>
int main()
{
printf("调用main\n");
main();
return 0;
}
运行结果:(死循环)
② 递归的优点:
递归给某些编程问题提供了最简单的方法。
③ 递归的缺点:
一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。
7. 普通函数调用
普通函数调用是指在一个程序中,我们直接调用一个已经定义好的函数,并传递必要的参数给该函数,以执行相应的功能。
示例代码:
#include <stdio.h> // 引入标准输入输出库,用于printf函数
// 定义一个函数fun_b,接受一个整数参数b
void fun_b(int b) {
printf("b = %d\n", b); // 打印出传入的整数参数b
return; // 函数返回,结束fun_b的执行
}
// 定义一个函数func_a,接受一个整数参数a
void func_a(int a) {
fun_b(a - 1); // 调用fun_b函数,并传入参数a减1的值
printf("a = %d\n", a); // 打印出传入的整数参数a
}
// 主函数,程序的入口点
int main(void) {
func_a(2); // 调用func_a函数,并传入整数2
printf("main\n"); // 打印出字符串"main"
return 0; // 主函数返回0,表示程序正常结束
}
运行顺序:
先调用,后返回(栈结构)
调用谁,返回谁的位置。
- 程序从
main
函数开始执行。 main
函数调用func_a
并传入整数2
作为参数。func_a
函数被调用,参数a
的值为2
。- 在
func_a
函数内部,首先调用fun_b
函数,并将a - 1
(即1
)作为参数传入。 fun_b
函数被调用,参数b
的值为1
。fun_b
函数内部执行printf
,打印出b = 1
。fun_b
函数执行完毕并返回。func_a
函数继续执行,执行printf
,打印出a = 2
。func_a
函数执行完毕并返回。main
函数继续执行,执行printf
,打印出main
。main
函数执行完毕并返回0
,程序结束。
运行结果:
8. 函数递归调用
函数递归调用是指一个函数在其定义中直接或间接地调用自身。递归调用在解决某些问题,特别是那些可以分解为更小、相同子问题的问题时,非常有用。递归有两个关键部分:递归基(基本情况)和递归步骤(递归调用)。递归基是递归的终止条件,当满足这个条件时,递归停止。递归步骤则是函数如何调用自身来逐步接近递归基。
示例代码:
#include <stdio.h>
// 定义一个递归函数用于计算阶乘
// 参数n为要求阶乘的整数
// 返回值是n的阶乘结果
int factorial(int n) {
// 递归基:当n为0或1时,其阶乘均为1
if (n == 0 || n == 1) {
return 1;
} else {
// 递归步骤:n的阶乘等于n乘以(n-1)的阶乘
return n * factorial(n - 1);
}
}
int main() {
int n; // 定义一个整数变量n,用于存储用户输入的数
// 提示用户输入一个整数
printf("请输入一个整数:");
// 从标准输入读取用户输入的整数并存储到变量n中
scanf("%d", &n);
// 调用递归函数factorial计算n的阶乘,并将结果存储在变量result中
int result = factorial(n);
// 输出n的阶乘结果
printf("%d 的阶乘是 %d\n", n, result);
// 程序正常结束,返回0
return 0;
}
执行顺序:
-
程序开始执行:从
main
函数开始。 -
输入提示:执行
printf("请输入一个整数:");
,在屏幕上显示提示信息。 -
读取输入:执行
scanf("%d", &n);
,等待用户输入一个整数,并将其存储在变量n
中。 -
调用递归函数:执行
int result = factorial(n);
,调用factorial
函数来计算n
的阶乘。- 递归开始:
- 检查
n
的值。如果n
是0或1,递归基成立,函数返回1。 - 如果
n
不是0或1,递归步骤执行,调用factorial(n - 1)
。 - 这个过程会一直递归下去,每次调用
factorial
时n
的值减1,直到n
变为0或1,递归基被触发,开始逐层返回结果。
- 检查
- 递归开始:
-
返回递归结果:当递归调用结束,
factorial
函数返回计算得到的阶乘结果给result
变量。 -
输出结果:执行
printf("%d 的阶乘是 %d\n", n, result);
,在屏幕上显示n
的阶乘结果。 -
程序结束:
main
函数执行完毕,返回0,程序正常结束。
运行结果: