一.无参函数的定义
#include<stdio.h>
void print_star() //void表示不接受任何操作
{
printf("**************\n");
}
void print_hun()
{
printf("$$$$$$$$$$$$$\n");
}
int main()
{
print_star(10); //对于无参函数来说,改变括号里的变量值并不会影响输出结果
printf(" Hello World\n");
print_hun();
return 0;
}
运行结果:
二.有参函数的定义
#include<stdio.h>
int get_max(int x, int y) //返回值的类型为int
{ //三目运算符
return (x > y) ? x : y; //如果condition 为真(true),则表达式的结果为 expr1
} //如果 condition 为假(false),则表达式的结果为 expr2
int main()
{
int a = 10;
int b = 20;
int c = 0;
int max = 0;
//函数的调用
max = get_max(a, get_max(b, c)); //链式结构,形参可以作为另一个函数调用时的实参
printf("max = %d\n", max);
return 0;
}
运行结果:
三.传址调用
#include<stdio.h>
void Swap2(int p1, int p2)
{
int tmp = 0;
tmp = p1; //tmp = a;
p1 = p2; //a = b;
p2 = tmp; //b = tmp;
}
int main()
{
int a = 10;
int b = 20;
printf("交换前:a = %d b = %d\n", a, b);
Swap2(&a, &b);
printf("交换后:a = %d b= %d\n", a, b);
return 0;
}
运行结果:
注:
C语言中的常量存储在一个地址中,所谓的交换值不过是交换地址
地址的变化:
四.函数的声明与实现
创建一个求和函数,要求放在.h文件中声明,在.c文件中实现
1.创建一个头文件用来声明要创建的函数
2.创建一个源文件add.c用来定义函数
3.创建一个源文件test.c用来调用创建的函数
运行结果:
五.函数的嵌套调用
5.1嵌套调用
例:调用一个求最大值的函数
#include<stdio.h>
int Max(int x, int y)
{
return(x > y ? x : y);
}
int Max4(int a, int b, int c, int d)
{
int m = Max(d,Max(c,Max(a, b)));
return m;
}
int main()
{
int a, b, c, d;
printf("请输入四个整数:");
scanf("%d%d%d%d", &a, &b, &c, &d);
printf("最大值Max为:%d", Max4(a,b,c,d));
}
运行结果:
5.2嵌套定义(非法)
六.函数的递归调用
6.1等差数列的递归调用
#include<stdio.h>
int age(int x)
{
if (x == 1)
return 10; //第一个学生年龄为10
else
return (age(x - 1) + 2); //后一名学生比前一名学生大两岁
}
int main()
{
int n = 0; //第n个学生
scanf("%d", &n);
int ret = age(n);
printf("%d", ret);
return 0;
}
运行结果:
代码逻辑:
6.2等比数列的递归调用
#include<stdio.h>
int jc(int x)
{
if (x < 0)
return -1;
else if (x <= 1)
return 1; // 阶乘的基条件
else
return x * jc(x - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = jc(n);
printf("%d", ret);
return 0;
}
运行结果:
代码逻辑:
int jc(int x)
{
if (x < 0) //如果实参 n 传递的值使得 x < 0,返回 -1
return -1;
else if (x <= 1)
return 1; // 阶乘的基条件
else //如果 x > 1
return x * jc(x - 1);
}
6.3 递归问题(汉诺塔-Hanoi)
汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
#include <stdio.h>
//定义全局变量来记录移动次数
int moveCount = 0;
//将一个盘子从 A 上移动到 C 上
void Move(int i, char from, char to)
{
printf("%c --> %c\n", from, to);
moveCount++; //每次调用 Move 时增加移动次数
}
//将在 A 上的 n - 1 个盘子借助 B 挪到 C 上
void Hanoi(int n, char one, char two, char three)
{
if (n == 1)
{
//直接挪动这个盘子
Move(1, one, three);
}
else
{
// 1.把 one 上的 n-1个盘子借助于 three 移动到 two
Hanoi(n - 1, one, three, two);
// 2.把 one 上的 1个盘子挪到 three 上
Move(1, one, three);
// 3.把 two 上的 n-1 个盘子借助 one 挪到 three 上
Hanoi(n - 1, two, one, three);
}
}
int main()
{
int n = 0;
printf("盘子的总数:");
scanf("%d", &n);
printf("对%d个盘子的移动过程如下:\n", n);
Hanoi(n, 'A', 'B', 'C'); //把n个盘子从A座挪到C座上,借助于B座
printf("一共挪动了%d次\n", moveCount); //使用全局变量来输出移动次数
return 0;
}
运算结果:
代码逻辑:
一.递归的概念
递归是一种在编程和数学中广泛使用的技术,它指的是一个函数(或过程)直接或间接地调用自身。递归通常用于解决那些可以分解为相似子问题的问题,这些问题被称为递归问题。
递归的基本概念包括:
-
基准情况(Base Case):这是递归的终止条件,用于防止函数无限调用自身。当满足基准情况时,函数将停止递归调用并返回一个结果。
-
递归步骤(Recursive Step):在递归函数中,这一步将问题分解为更小的子问题,并通过递归调用自身来解决这些子问题。每个递归调用都会使问题规模减小,逐渐接近基准情况。
递归通常用于解决如下类型的问题:
-
分而治之(Divide and Conquer):将问题分成若干个子问题,递归地解决这些子问题,然后将结果合并以得到原问题的解。
-
回溯(Backtracking):在搜索问题中,尝试不同的选择,并通过递归探索这些选择是否导致一个有效的解。如果某个选择无效,则通过回溯来撤销该选择并尝试其他选择。
-
动态规划(Dynamic Programming):虽然动态规划通常使用迭代而非递归来实现,但递归思想在动态规划问题的分析中仍然很重要。动态规划问题通常涉及重叠子问题,可以使用递归加记忆化(memoization)或迭代加状态转移方程来解决。
递归的一个著名例子是汉诺塔问题(Tower of Hanoi),需要注意的是,递归可能会导致栈溢出(Stack Overflow)或性能问题,特别是当递归深度很大时。因此,在使用递归时需要谨慎考虑其效率和可行性。在某些情况下,可以使用迭代或其他优化技术来替代递归。
二.移动的步骤与逻辑
三个步骤:
1.将A座上的n-1个盘子借助C柱先移到B座上
2.将A做上剩下的一个盘子直接移动到C柱上
3.将B座上的n-1个盘子借助A柱移动到C柱上
1、3属于同一逻辑,都是借助中间柱子来达到从一个柱子移动到另一个柱子的目的,所以可以归为一类操作
两个逻辑:
1.将n-1个盘子通过媒介柱子从一个柱子移动到另一个柱子
2.将1个盘子从一个柱子直接移动到另一个柱子上
1.定义一个 Move 函数,直接移动
int moveCount = 0;
void Move(int i, char from, char to)
{
printf("%c --> %c\n", from, to);
moveCount++;
}
2.定义一个 Hanoi 函数,间接移动
void Hanoi(int n, char one, char two, char three)
{
if (n == 1)
{
Move(1, one, three);
}
else
{
Hanoi(n - 1, one, three, two);
Move(1, one, three);
Hanoi(n - 1, two, one, three);
}
}
3.主函数
int main()
{
int n = 0;
printf("盘子的总数:");
scanf("%d", &n);
printf("对%d个盘子的移动过程如下:\n", n);
Hanoi(n, 'A', 'B', 'C');
printf("一共挪动了%d次\n", moveCount);
return 0;
}
输入与初始调用
- 用户输入
n = 3
。 main
函数调用Hanoi(3, 'A', 'B', 'C')
,表示有3个盘子需要从柱子A移动到柱子C,同时借助柱子B。
递归调用过程
第一次递归调用
Hanoi(3, 'A', 'B', 'C')
首先调用Hanoi(2, 'A', 'C', 'B')
,意味着需要先将2个盘子从A移动到B,借助C。
第二次递归调用
Hanoi(2, 'A', 'C', 'B')
内部调用Hanoi(1, 'A', 'B', 'C')
:- 调用
Move(1, 'A', 'C')
,打印A --> C
。
- 调用
Hanoi(1, 'A', 'B', 'C')
完成后,Hanoi(2, 'A', 'C', 'B')
继续:- 调用
Move(1, 'A', 'B')
,打印A --> B
。
- 调用
- 接下来,
Hanoi(2, 'A', 'C', 'B')
调用Hanoi(1, 'C', 'A', 'B')
:- 在递归内部,这会导致调用
Move(1, 'C', 'B')
,打印C --> B
(注意:这个调用是在递归的更深层次上发生的,但在解释递归逻辑时我们提到它)。
- 在递归内部,这会导致调用
回到第一次递归调用
Hanoi(2, 'A', 'C', 'B')
完成后,Hanoi(3, 'A', 'B', 'C')
继续:- 调用
Move(1, 'A', 'C')
,这是将最后一个(实际上是第三个)盘子从A直接移动到C的操作,打印A --> C
。
- 调用
第三次递归调用(完成n-1个盘子从two到three的移动)
- 最后,
Hanoi(3, 'A', 'B', 'C')
调用Hanoi(2, 'B', 'A', 'C')
,意味着需要将2个盘子从B移动到C,借助A:Hanoi(2, 'B', 'A', 'C')
首先调用Hanoi(1, 'B', 'C', 'A')
:- 在递归内部,这会导致调用
Move(1, 'B', 'A')
(这个移动在解释中不直接显示,但它是递归过程的一部分)。 - 然后调用
Move(1, 'B', 'C')
,打印B --> C
。
- 在递归内部,这会导致调用
Hanoi(1, 'B', 'C', 'A')
完成后,Hanoi(2, 'B', 'A', 'C')
继续:- 调用
Move(1, 'A', 'C')
,打印A --> C
(这是将最后一个留在A上的(在这个上下文中是之前从B移动到A的)盘子移动到C上)。
- 调用
完成
- 所有的递归调用都完成后,控制流返回到
main
函数,打印出移动的总次数,并结束程序。
简化后的关键步骤
Hanoi(3, 'A', 'B', 'C')
Hanoi(2, 'A', 'C', 'B')
Hanoi(1, 'A', 'B', 'C')
→Move(1, 'A', 'C')
→A --> C
Move(1, 'A', 'B')
→A --> B
Hanoi(1, 'C', 'A', 'B')
→ (内部)Move(1, 'C', 'B')
→C --> B
Move(1, 'A', 'C')
→A --> C
(移动第三个盘子)Hanoi(2, 'B', 'A', 'C')
Hanoi(1, 'B', 'C', 'A')
→ (内部)Move(可能不直接显示)
Move(1, 'B', 'C')
→B --> C
Move(1, 'A', 'C')
→A --> C
(作为递归的一部分)
注意:在简化的关键步骤中,我省略了某些内部递归调用的直接显示,以突出主要的移动步骤和递归结构。实际的递归过程包含了更多的细节和内部调用,但上述步骤提供了对整体流程的一个清晰概述。
七.数组作为函数参数
7.1数组元素作函数实参
#include <stdio.h>
void print(int n)
{
printf("%d\n", n);
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int i = 0;
for (i = 0; i < 5; i++)
{
print(arr[i]); //数组元素作函数实参
}
return 0;
}
运行结果:
注:
1.用数组元素作实参时,向形参传递的是数组元素的值
7.2数组名作函数实参
#include <stdio.h>
void print(int brr[5])
{
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d\n", brr[i]);
}
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
print(arr);
return 0;
}
运行结果:
注:
1.用数组名作实参时,向形参传递的是数组首个元素的地址
八.应用实例
例:8.1 用选择法对数组中的整数按由小到大排序
#include <stdio.h>
void Sort(int arr[], int n)
{
int i = 0, j = 0;
int temp = 0;
for (i = 0; i < n - 1; i++)
{
// 在每次外层循环迭代开始时,将min_index设置为当前索引
int min_index = i;
for (j = i + 1; j < n; j++)
{
if (arr[j] < arr[min_index])
min_index = j; // 更新最小值的索引
}
// 交换找到的最小值与当前i位置的值
temp = arr[i];
arr[i] = arr[min_index];
arr[min_index] = temp;
}
}
int main()
{
int arr1[10] = { 2, 1, 3, 4, 6, 9, 7, 5 , 8, 0 };
int arr2[5] = { 0, 8, 2, 5 ,4 };
int i = 0;
Sort(arr1, 10);
Sort(arr2, 5);
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
printf("\n");
for (i = 0; i < 5; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
运行结果:
8.2 在二维数组中找出最大值
#include <stdio.h>
int Max(int arr[3][4], int row, int col) //行可以省略,列不可以
{
int i = 0, j = 0,temp = 0;
int max = arr[0][0]; //初始化最大值
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (arr[i][j] > max)
{
max = arr[i][j]; //更新最大值
}
}
}
return max; //循环结束,返回最后的最大值
}
int main()
{
int arr[3][4] = { 1, 5, 7, 4, 2, 6, 3, 8 };
int max = Max(arr, 3, 4);
printf("%d", max);
return 0;
}
九.变量的存储方式和生存期
9.1 动态局部变量
运行结果:
具体情况:
9.2 静态局部变量
运行结果:
具体情况:
9.3 寄存器变量
注:
把大量重复使用的变量,建议放在寄存器中(处理速度快),可以提升执行效率
9.4在一个文件内扩展外部变量的作用域
运行结果:
注:
全局变量被定义在后面了,通过 extern关键字 使得 g_val 的作用域得以扩展
9.5 将外部变量的作用域扩展到其它文件

运行结果:
9.6将外部变量的作用域限制在本文件范围内
注:
即使声明了 g_val 也无法调用,因为 static 限制了 g_val的作用域只能在add.c中
9.6 外部函数的实现
有一段字符串,现输入的一个字符,要求程序将该字符串中的字符删去。用外部函数实现
void Input(char arr[])
{
gets(arr);
}
void Del(char arr[], char ch)
{
int i = 0;
int j = 0;
while (arr[i] != '\0') //遍历数组,直到遇到字符串的结束符 '\0'
{
if (arr[i] != ch) //如果不是要找的字符
{
arr[j] = arr[i];
j++;
}
i++;
}
arr[j] = '\0'; //如果 arr[i] 是 ch,直接跳过这个字符
}
#include <stdio.h>
//加上形参可以解决警告问题,不加也可以运行
extern void Input() //extern void Input(char arr[]);
extern void Del() //extern void Del(char arr[], char ch);
int main()
{
char ch = 0;
char arr[20] = { 0 };
Input(arr);
printf("请输入要删除的字符:");
scanf("%c", &ch);
Del(arr, ch);
printf("%s\n", arr);
return 0;
}
运行结果:
代码逻辑:
while (arr[i] != '\0') //遍历数组,直到遇到字符串的结束符 '\0'
{
if (arr[i] != ch) //如果不是要找的字符
{
arr[j++] = arr[i];
}
i++;
}
arr[j] = '\0'; //如果 arr[i] 是 ch,直接跳过这个字符