例10-1. 有序表的增删查操作
首先输入一个无重复元素的、从小到大排序的有充表,并在屏幕上显示以下菜单(编号和选项),用户可以反复对该有序表进行插入、删除和查找操作,也可以选择结束。当用户输入编号1~3和相关参数时,将分别对该有序表进行插入、删除和查找操作,输入其他编号,则结束操作
[1] Insert
[2] Delete
[3] Query
[Others] End
使用结构化程序设计方法解决复杂的问题
总共定义4层结构,7个函数
能够降低程序的构思、编写和调试的复杂度,可读性好
#include<stdio.h>
#define MAXN 100 /* 定义符号变量表示数组a的长度 */
int Count=0; /* 用全局变量Count表示数组a中待处理的元素个数 */
void select(int a[], int option, int value) /*决定对有序数组a进行何种操作的控制函数 */
{
switch(option){
case 1:
insert(a, value);
break;
case 2:
remove(a, value);
break;
case 3:
query(a, value);
break;
}
}
void input_array(int a[]) /* 输入有序数组a的函数 */
{
printf("input the number of array elements:");
scanf("%d"< &Count);
printf("Input an ordered array of elements:");
for(int i=0; i<Count; i++)
scanf("%d", &a[i]);
}
void print_array(int a[]) /* 输出有序数组a的函数 */
{
printf("The ordered array is:\n");
for(int i=0; i<Count; i++){
printf("%d", a[i]);
if(i!=Count-1)
printf(" ");
else
printf("\n");
}
}
void insert(int a[], int value) /* 往有序数组a中插入一个值为value的元素的函数 */
{
int i, j;
for(i=0; i<Count; i++){
if(value<a[i])
continue;
}
for(j=Count-1; j>=i; j--)
a[i+1]=a[i];
a[i]=value;
Count++;
print_array(a);
}
void remove(int a[], int value) /* 删除有序数组a中值等于value的元素的函数 */
{
int i, index=-1;
for(i=0; i<Count; i++){
if(value==a[i]){
index=i;
break;
}
}
if(index=-1)
printf("Failed to find the data, deletion failed\n");
else{
for(i=index; i<Count-1; i++)
a[i]=a[i+1];
}
Count--;
print_array(a);
}
void query(int a[], int value) /* 用二分法在有序数组a中查换值为value的元素的函数 */
{
int mid, left=0, right=Count-1;
while(left<=right){
mid=(left+right)/2;
if(value<a[mid])
right=mid-1;
else if(value>a[mid])
left=mid+1;
else{
printf("The index=%d\n", mid);
return;
}
}
printf("The element does not exist.\n");
}
int main()
{
int option, value, a[MAXN];
input_array(a); /* 调用函数输入数组 */
printf("[1] Insert:\n");
printf("[2] Delete:\n");
printf("[3] Query:\n");
printf("[Other] Exit:\n");
while(1) /* 循环,注意循环条件 */
{
printf("Input option:"); /* 输入选项 */
scanf("%d", &option);
if(option<1 || option>3) /* 如果1-3以外的编号,退出,结束循环 */
break;
printf("Input an element:"); /* 显示输入参数 */
scanf("%d", &value); /* 读入用户输入的参数value */
select(a, option, value); /* 执行相应的操作 */
printf("\n");
}
printf("Thanks. Bye!\n"); /* 退出操作 */
return 0;
}
函数的顺序调用
int main()
{
......
y=fact(3);
......
z=mypow(3.5, 2);
}
double fact(int m)
{
......
}
double mypow(double x, int n)
{
}
int main()
{
......
select(a, option, value);
......
}
void select(int a[], int option, int value)
{
......
insert(a, value);
......
}
void insert(int a[], int value)
{
......
print_array(a);
......
}
void print_array(int a[])
{
......
}
在一个函数中再调用其它函数的情况称为函数的嵌套调用
自顶向下,逐步求精,函数实现
将n(n=64)个盘子从座A搬到座B,搬动规则如下
(1). 一次只能搬动一个盘子
(2). 盘子只能插在A、B、C三个杆子上
(3). 大盘不能压在小盘上
开始状态
中间状态
hanoi(n个盘,A→B, C为过渡)
{
if(n==1)
直接将盘子从A→B
else{
hanoi(n-1个盘, A→C, B为过渡)
将第n号盘子从A→B
hanoi(n-1个盘,C→B, A为过渡)
}
}
例10-2. 用递归函数实现求n!
求n!有两种方法
递推法
在学习循环时,计算n!采用的就是递推法
$$
n!=1\times2\times3\times\cdots\times n
$$
用循环语句实现就是
result=1;
for(i=1; i<=n; i++)
result*=i;
递归法
$$\begin{cases}
n!=n\times(n-1)! & n>1\\
1 & n=1 or n=0
\end{cases}$$
求n!可以在(n-1)!的基础上再乘上n,即若将求n!的函数写成fact(n),则fact(n)的实现依赖于fact(n-1)
#include<stdio.h>
double fact(int n) /* 函数定义 */
{
double result;
if(n==0 || n==1) /* 递归出口 */
result=1;
else{
result=n*fact(n-1); /* 递归调用 */
}
return result;
}
int main()
{
int n;
printf("Enter n:");
scanf("%d", &n);
printf("%d!=%.0lf\n", n, fact(n));
return 0;
}
| 函数直接递归调用 | 函数间接递归调用 | |
|---|---|---|
|
int f(int x) { int y ...... y=f(x) ...... return y; } |
int f(int x) { int y ...... y=g(x) ...... return y; } |
int g(int x) { int z ...... z=f(x) ...... return z; } |
Compile, Run and Debug!
能用递归实现的问题,两个条件,缺一不可,也是递归问题求解的两个着眼点
例10-3. 求最大公约数
定义函数gcd(m,n),用递归法求m和n的最大公约数,其中,用辗转相除法求最大公约数的递归算法描述如下:
$$
gcd(m,n)=\begin{cases}
n & m\%n==0\\
gcd(n, m\%n) & m\%n\neq 0
\end{cases}
$$
递归实现的两个关键点:
递归出口为当m%n==0时返回n
递归式子为当m%n!=0时,返回gcd(n, m%n)
int gcd(int m, int n)
{
if(m%n==0)
return n; /* 递归出口 */
else
return gcd(n, m%n); /* 递归条件 */
}
例10-4. 整数逆序输出
编写递归函数reverse(int n)实现将整数逆序输出
将整数n逆序输出,可以用循环实现,将整数的位数作为控制条件
用递归实现,两个关键点分别为:
递归出口: 如果n为1位数,即0≤n≤9,直接输出n
递归条件: 如果n为多位数,输出个位数n%10,再递归调用reverse(n/10)输出前n-1位数
void reverse(int n)
{
if(num<=9)
printf("%d", n);
else{
printf("%d", n%10);
reverse(n/10);
}
}
#include<stdio.h>
void hanoi(int n, char a, char b, char c)
{
if(n==1)
printf("%c-->%c\n", a, b);
else{
hanoi(n-1, a, c, b);
printf("%c-->%c\n", a, b);
hanoi(n-1, c, b, a);
}
}
int main()
{
int n;
printf("Input the number of disks:");
scanf("%d", &n);
printf("The steps for %d disks are:\n", n);
hanoi(n, 'a', 'b', 'c');
return 0;
}
利用递归函数计算x的n次幂
int power(int x, int n)
{
if(n==1)
return x;
else{
return x*power(x, n-1);
}
}
例10-6. 分治法求解金块问题
老板有一袋金块(共n块,2≤n≤100),两名最优秀的雇员每人可以得到其中的一块,排名第一的得到最重的金块,排名第二的得到袋子中最轻的金块。输入n及n个整数,用分治法求出最重金块和最轻金块
即定义递归函数找数组的最大值和最小值,函数形式为max(int a[], int m, int n),即从a[m]到a[n]中找到最大值
递归出口: 当m==n时,即a中只有1个元素时,返回a[m]
递归条件:将数组分为两部分,分别递归来求最大
k=(m+n)/2
u=max(a, m, k)
v=max(a, k+1, n)
int max(int a[], int m, int n)
{
int k, u, v;
if(m==n)
return a[m];
k=(m+n)/2; /* 计算中间元素的下标k */
u=max(a, m, k); /* 在a[m]~a[k]中找最大值 */
v=max(a, k+1, n); /* 在a[k+1]~a[n]中找最大值 */
return (u>v)?u:v; /* 返回u和v中最大的值 */
}
例10-7. 单位换算
欧美国家长度使用英制单位,1英里=1609米,1尺=30.84厘米,1英寸=2.54厘米,请编写程序进行转换
#include<stdio.h>
#define MILE_TO_METER 1609 /* 1英里=1609米 */
#define FOOT_TO_CENTIMETER 30.48 /* 1英尺=30.48厘米 */
#define INCH_TO_CENTIMETER 2.54 /* 1英寸=2.54厘米 */
int main()
{
float foot, inch, mile;
printf("Input miles, feet, and inches:");
scanf("%f%f%f", &miles, &feet, &inches);
printf("%f miles=%f meters\n", miles, miles*MILE_TO_METER); /* 英里转换为米 */
printf("%f feet=%f centimeters\n", feet, feet*FOOT_TO_CENTIMETER); /* 英尺转换为厘米 */
printf("%f inches=%f centimeters\n", inches, inches*INCH_TO_CENTIMETER); /* 英寸转换为厘米 */
return 0;
}
宏定义的格式是:
编译时,将程序源代码中所有与宏名相同的字符串,用宏定义中的字符串替换
#define PI 3.14
#define ARR_SIZE 4
宏定义可以写在程序中任何位置,其作用范围从定义书写处到文件结尾
可以通过
#define A "This is the first macro" /* A的有效范围开始,为整个文件 */
void f1()
{
printf("A\n");
}
#define B "This is the second macro" /* B的有效范围开始 */
void f2()
{
printf(B);
}
#undef B /* B的有效范围结束 */
int main()
{
f1();
f2();
return 0;
}
例10-8. 简单的带参数的宏定义
#include<stdio.h>
#define MAX(a, b) a>b ? a:b
#define SQR(x) x*x
int main()
{
int x, y;
printf("Enter x, y:");
scanf("%d%d", &x, &y);
x=MAX(x,y); /* 引用宏定义 */
y=SQR(x); /* 引用宏定义 */
printf("%d %d\n", x, y);
return 0;
}
例. 寻找三位数中各位数字的立方和等于其自身的数,如$153=1^3+3^3+5^3$
#include<stdio.h>
#define f(a) (a)*(a)*(a)
int main()
{
int i, x, y, z;
for(i=1; i<1000; i++)
{
x=i%10; y=i/10%10; z=i/100;
if(f(x)+f(y)+f(z)==i)
printf("%d\n", i);
}
return 0;
}
括号可以去除吗?
定义宏,将两个变量的值交换
#include<stdio.h>
#define f(a,b,t) t=a;a=b;b=t;
int main()
{
int x, y, t;
printf("Enter x, y:");
scanf("%d%d", &x, &y);
f(x,y,t);
printf("%d %d\n", x, y);
return 0;
}
带参数的宏定义不是函数,宏与函数是两种不同的概念
但宏可以实现简单的函数功能
#define F(x) x-2
#define D(x) x*F(x)
int main()
{
printf("%d, %d", D(3), D(D(3)));
return 0;
}
阅读带宏定义的程序,先全部替换好,再最后统一计算
D(D(3))=D(x*x-2)=x*x-2*F(x*x-2)=x*x-2*x*x-2-2=3*3-2*3*3-2-2=-13
D(3)=x*F(x)=x*x-2=3*3-2=7
D(D(3))=D(x*x-2)=x*x-2*F(x*x-2)=x*x-2*x*x-2-2=3*3-2*3*3-2-2=-13
#include <需包含的文件名> /* 系统文件夹中的文件 */
#include "需包含的文件名" /* 自定义的文件,在当前文件夹或系统文件夹 */
将指定的文件模块内容插入到#include所在的位置,当程序编译连接时,系统会将所有的#include指定的文件拼接成可执行代码
要注意的是,编译预处理命令以#开头
在程序编译时起作用,但不是真正的C语句,因此行尾没有分号
例10-9. 将例10-7中长度转换的宏,定义成头文件length.h,写出主函数文件
/* length.h */
#define MILE_TO_METER 1609
#define FOOT_TO_CENTIMETER 30.48
#define INCH_TO_CENTIMETER 2.54
/* prog.c */
#include<stdio.h>
#include "length.h"
int main()
{
float foot, inch, mile;
printf("Input miles, feet, and inches:");
scanf("%f%f%f", &miles, &feet, &inches);
printf("%f miles=%f meters\n", miles, miles*MILE_TO_METER); /* 英里转换为米 */
printf("%f feet=%f centimeters\n", feet, feet*FOOT_TO_CENTIMETER); /* 英尺转换为厘米 */
printf("%f inches=%f centimeters\n", inches, inches*INCH_TO_CENTIMETER); /* 英寸转换为厘米 */
return 0;
}
| 文件 | 描述 |
|---|---|
| ctype.h | 字符处理 |
| math.h | 与数学处理函数有关的说明和定义 |
| stdio.h | 输入输出函数中使用的有关说明和定义 |
| string.h | 字符串函数的有关说明与定义 |
| stddef.h | 定义某些常用内容 |
| stdlib.h | 杂项说明 |
| time.h | 支持系统时间函数 |
编译预处理是C语言编译程序的组成部分,其用于解释处理C语言程序中的各种预处理命令
文件包含(#include)和宏定义(#define)都是编译预处理命令,二者在形式上都是以"#"开头,不属于C语言中真正的语句
编译预处理命令增强了C语言的编程功能,改进了C语言程序设计的环境,提高了编程效率
C程序的编译处理,目的是将每一条C语句用若干条机器指令来完成,生成目标程序
由于#define等编译预处理指令不是C语句,不能被编译程序翻译,需要在真正编译之前作一个预处理,解释完成编译预处理指令,从而将预处理指令转换成相应的C程序段,最终成为由纯粹的C语句构成的程序,经编译后得到目标代码
编译预处理的主要功能包括:
条件编译的形式:
#define FLAG 1
#if FLAG
程序段1
#else
程序段2
#endif
例10-10. 学生信息库系统
请综合例9-1, 例9-2, 例9-3, 分模块设计一个学生信息库系统。该系统包含学生基本信息的建立和输出、计算学生平均成绩、按照学生的平均成绩排序以及查询、修改学生的成绩等功能
所有文件分为5个程序模块,所有文件存放在同一个文件夹下,采用文件包含形式进行连接和调用
#include<stdio.h>
#include<string.h>
#define MAXSIZE 50
#include "input_output.c"
#include "computing.c"
#include "update.c"
#include "search.c"
struct student{
int num; /* 学号 */
char num[10]; /* 姓名 */
int computer, english, math; /* 三门课程成绩 */
double average; /* 平均成绩 */
}
int Count=0;
int main()
{
struct student stus[MAXSIZE]; /* 定义学生信息结构数组 */
new_student(stus); /* 输入学生信息结构数组 */
output_student(stus); /* 显示输入的学生信息结构数组 */
average(stus); /* 计算每一个学生的平均成绩 */
sort(stus); /* 按学生的平均成绩排序 */
output_student(stus); /* 显示排序后的结构数组 */
modify(stus); /* 修改指定输入的学生信息 */
output_student(stus); /* 显示修改后的学生信息结构数组 */
return 0;
}
/* 输入输出程序文件 input_output.c */
extern int Count; /* 外部变量声明 */
void new_student(struct student stus[]) /* 新建学生信息 */
{
......
}
void output_student(struct student stus[]) /* 输出学生信息 */
{
......
}
/* 计算平均成绩程序文件 computing.c */
extern int Count;
void average(struct student stus[]) /* 计算每个人平均成绩 */
{
......
}
/* 修改排序程序文件 update.c */
extern int Count;
void modify(struct student *p) /* 修改学生成绩 */
{
......
}
void sort(struct student stus[]) /* 平均成绩排序 */
{
......
}
/* 查询程序文件 search.c */
extern int Count;
void search_student(struct student stus[], int num) /* 查询学生信息 */
{
......
}
程序--文件--函数之间的关系:
整个程序只允许有一个main()函数
文件模块与变量间的通信可通过