函数原来要这样用?(2)

函数原来要这样用(2)

承接上文,这次我们做个比上次那个复杂一些的系统,看看函数如何优化我们的程序

本次我们实现一个最简单的学生管理系统。

需求为

  • 增加学生
  • 查看所有学生
  • 查看指定学生
  • 删除指定学生
  • 修改指定学生

如果没看过第一篇的可以先去看看第一篇文章。

第一篇

看到这些需求后,我们先写出第一份代码,当然是需要优化的啦。

demo1

#include <stdio.h>    

typedef struct student {
	char name[20];
	int age;
	int size;
}Student;

int main(void)
{
	int input = 0;
	int size = 0;
	int num = 0;
	Student studentList[50];
	printf("--------------学生管理系统-----------\n");
	printf("1 增加学生             2 删除学生\n");
	printf("3 查看所有学生         4 修改学生\n");
	printf("5 查看指定学生                      \n");
	printf("--------------输入0退出--------------\n");

	while (1)
	{
		printf("请输入:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出\n");
			return;
		case 1:
			printf("请输入名字:");
			scanf_s("%s", studentList[size].name,20);
			printf("请输入年龄:");
			scanf_s("%d", &(studentList[size].age));
			studentList[size].size = size;
			size++;
			break;
		case 2:
			printf("请输入要删除的学生号数:");
			scanf_s("%d", &num);
			while(num >= size) 
            {
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			for (size_t i = num; i < size - num; num++)
			{
				studentList[num] = studentList[num+1];
				studentList[num].size--;
			}
			size--;
			break;
		case 3:
			for (size_t i = 0; i < size; i++)
			{
				printf("号数:%d   姓名:%s   年龄:%d\n", studentList[i].size, studentList[i].name, studentList[i].age);
			}
			break;
		case 4:
			printf("请输入要修改的学生号数:");
			scanf_s("%d", &num);
			while(num >= size)
            {
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			printf("请输入名字:");
			scanf_s("%s", studentList[num].name, 20);
			printf("请输入年龄:");
			scanf_s("%d", &(studentList[num].age));
			break;
		case 5:
			printf("请输入要查看的学生号数:");
			scanf_s("%d", &num);
			while(num >= size) 
            {
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			printf("号数:%d   姓名:%s   年龄:%d\n", studentList[num].size, 	studentList[num].name, studentList[num].age);
			break;
		default:
			printf("未知的输入\n");
			break;
		}
	}

功能确实实现了,但是依然可以发现,全部的代码都在主函数当中,而且有大量的重复代码,如果还要新增功能什么的就要在这一长串主函数中进行增加,非常不方便,即使变量命名还算符合规范,但不是一份好代码。

于是我打算进行第一次优化,首先我们分析一下功能,很明显能看出,菜单的显示和学生管理系统的逻辑功能可以剥离开,所以我们第一次改造如下。

demo2

抽取出菜单和管理系统,主函数变得简洁

#include <stdio.h>    

//菜单
void menu();
//管理系统
void student_manage_system();

typedef struct student {
	char name[20];
	int age;
	int size;
}Student;

int main(void)
{
	menu();
	student_manage_system();
}


void menu() 
{
	printf("--------------学生管理系统-----------\n");
	printf("1 增加学生             2 删除学生\n");
	printf("3 查看所有学生         4 修改学生\n");
	printf("5 查看指定                      \n");
	printf("--------------输入0退出--------------\n");
}
void student_manage_system()
{
	int input = 0;
	int size = 0;
	int num = 0;
	Student studentList[50];
	while (1)
	{
		printf("请输入:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出\n");
			return;
		case 1:
			printf("请输入名字:");
			scanf_s("%s", studentList[size].name, 20);
			printf("请输入年龄:");
			scanf_s("%d", &(studentList[size].age));
			studentList[size].size = size;
			size++;
			break;
		case 2:
			printf("请输入要删除的学生号数:");
			scanf_s("%d", &num);
			while(num >= size) 
			{
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			for (size_t i = num; i < size - num; num++)
			{
				studentList[num] = studentList[num + 1];
				studentList[num].size--;
			}
			size--;
			break;
		case 3:
			for (size_t i = 0; i < size; i++)
			{
				printf("号数:%d   姓名:%s   年龄:%d\n", studentList[i].size, studentList[i].name, studentList[i].age);
			}
			break;
		case 4:
			printf("请输入要修改的学生号数:");
			scanf_s("%d", &num);
			while(num >= size) 
			{
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			printf("请输入名字:");
			scanf_s("%s", studentList[num].name, 20);
			printf("请输入年龄:");
			scanf_s("%d", &(studentList[num].age));
			break;
		case 5:
			printf("请输入要查看的学生号数:");
			scanf_s("%d", &num);
			while(num >= size) 
			{
				printf("超出范围 重新输入:");
				scanf_s("%d", &num);
			}
			printf("号数:%d   姓名:%s   年龄:%d\n", studentList[num].size, studentList[num].name, studentList[num].age);
			break;
		default:
			printf("未知的输入\n");
			break;
		}
	}
}

记住 修改后一定要进行测试,确保我们的重构没有导致原本可运行的代码崩溃,每次修改一小步时都进行一次测试。防止修改太多然后测试不行后,排查的地方也变多。

本次修改后,主函数变得非常简洁,菜单项也变得简单了起来,那么我们开始继续分析。

管理系统里的增删改查,是不是也是小功能,第一篇文章说过,一个功能,就是一个函数,那么好,我们开始抽取增删改查为函数。进行第三次改造

demo3

#include <stdio.h>    

typedef struct student {
	char name[20];
	int age;
	int size;
}Student;

//菜单
void menu();
//管理系统
void student_manage_system();
//增加学生
void insert_student(Student* studentList, int* size);
//删除学生
void delete_student(Student* studentList, int* size);
//查看所有学生
void search_all_student(Student* studentList, int* size);
//查询指定
void search_student(Student* studentList, int* size);
//修改学生
void update_student(Student* studentList, int* size);


int main(void)
{
	menu();
	student_manage_system();
}

//菜单
void menu() {
	printf("--------------学生管理系统-----------\n");
	printf("1 增加学生             2 删除学生\n");
	printf("3 查看所有学生         4 修改学生\n");
	printf("5 查看指定                      \n");
	printf("--------------输入0退出--------------\n");
}

//管理系统
void student_manage_system() {
	int input = 0;
	int size = 0;
	Student studentList[50];
	while (1)
	{
		printf("请输入:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出\n");
			return;
		case 1:
			insert_student(studentList,&size);
			break;
		case 2:
			delete_student(studentList, &size);
			break;
		case 3:
			search_all_student(studentList, &size);
			break;
		case 4:
			update_student(studentList, &size);
			break;
		case 5:
			search_student(studentList, &size);
			break;
		default:
			printf("未知的输入\n");
			break;
		}
	}
}
//新增学生
void insert_student(Student *studentList,int *size) {
	printf("请输入名字:");
	scanf_s("%s", studentList[*size].name, 20);
	printf("请输入年龄:");
	scanf_s("%d", &(studentList[*size].age));
	studentList[*size].size = *size;
	//先解引用再++
	(*size)++;
}
//删除学生
void delete_student(Student* studentList, int* size) {
	int num = 0;
	printf("请输入要删除的学生号数:");
	scanf_s("%d", &num);
	if (num >= *size) {
		printf("超出范围 重新输入:");
		scanf_s("%d", &num);
	}
	for (size_t i = num; i < *size - num; num++)
	{
		studentList[num] = studentList[num + 1];
		studentList[num].size--;
	}
	(*size)--;
}
//查询所有
void search_all_student(Student* studentList, int* size) {
	for (size_t i = 0; i < *size; i++)
	{
		printf("号数:%d   姓名:%s   年龄:%d\n", studentList[i].size, studentList[i].name, studentList[i].age);
	}
}
//查询指定
void search_student(Student* studentList, int* size) {
	int num = 0;
	printf("请输入要查看的学生号数:");
	scanf_s("%d", &num);
	if (num >= *size) {
		printf("超出范围 重新输入:");
		scanf_s("%d", &num);
	}
	printf("号数:%d   姓名:%s   年龄:%d\n", studentList[num].size, studentList[num].name, studentList[num].age);
}
//修改学生
void update_student(Student* studentList, int* size) {
	int num = 0;
	printf("请输入要修改的学生号数:");
	scanf_s("%d", &num);
	if (num >= *size) {
		printf("超出范围 重新输入:");
		scanf_s("%d", &num);
	}
	printf("请输入名字:");
	scanf_s("%s", studentList[num].name, 20);
	printf("请输入年龄:");
	scanf_s("%d", &(studentList[num].age));
}

这次改造之后,学生系统的增删改查功能被抽离了出来,在student_manage_system()内部的不同case分支只留下了一行函数调用。

这里要解释一下,size需要传指针的原因我没做成线性表,如果直接传局部变量为形参的话,函数里用的只是一份拷贝,这意味着我们外部所使用的size不会被改变。改变的始终只是增删改查这种小功能函数里的那一份拷贝,而增删改查的函数return出去后,拷贝也随着消失

现在程序看起来似乎简洁一点了,有明显的功能划分,可是有一段代码引起了我的主意

int num = 0;
scanf_s("%d", &num);	
if (num >= *size) {
	printf("超出范围 重新输入:");
	scanf_s("%d", &num);
}

似乎很多地方都用到了这段代码,用于判定边界(这里我没做超出50人的判定,偷个懒)

那么根据编程的原则,重复使用的代码,就应该被抽取成一个函数

我们进行第四次改造

demo4

#include <stdio.h>    

typedef struct student {
	char name[20];
	int age;
	int size;
}Student;

//菜单
void menu();
//管理系统
void student_manage_system();
//增加学生
void insert_student(Student* studentList, int* size);
//删除学生
void delete_student(Student* studentList, int* size);
//查看所有学生
void search_all_student(Student* studentList, int* size);
//查询指定
void search_student(Student* studentList, int* size);
//修改学生
void update_student(Student* studentList, int* size);
//输入学生的学号(进行规则检查)
int input_student_number(int* size);

int main(void)
{
	menu();
	student_manage_system();
}

//菜单
void menu() {
	printf("--------------学生管理系统-----------\n");
	printf("1 增加学生             2 删除学生\n");
	printf("3 查看所有学生         4 修改学生\n");
	printf("5 查看指定                      \n");
	printf("--------------输入0退出--------------\n");
}

//管理系统
void student_manage_system() {
	int input = 0;
	int size = 0;
	Student studentList[50];
	while (1)
	{
		printf("请输入:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出\n");
			return;
		case 1:
			insert_student(studentList,&size);
			break;
		case 2:
			delete_student(studentList, &size);
			break;
		case 3:
			search_all_student(studentList, &size);
			break;
		case 4:
			update_student(studentList, &size);
			break;
		case 5:
			search_student(studentList, &size);
			break;
		default:
			printf("未知的输入\n");
			break;
		}
	}
}
//新增学生
void insert_student(Student *studentList,int *size) {
	printf("请输入名字:");
	scanf_s("%s", studentList[*size].name, 20);
	printf("请输入年龄:");
	scanf_s("%d", &(studentList[*size].age));
	studentList[*size].size = *size;
	//先解引用再++
	(*size)++;
}
//删除学生
void delete_student(Student* studentList, int* size) {
	
	printf("请输入要删除的学生号数:");
	int num = input_student_number(size);
	for (size_t i = num; i < *size - num; num++)
	{
		studentList[num] = studentList[num + 1];
		studentList[num].size--;
	}
	(*size)--;
}
//查询所有
void search_all_student(Student* studentList, int* size) {
	for (size_t i = 0; i < *size; i++)
	{
		printf("号数:%d   姓名:%s   年龄:%d\n", studentList[i].size, studentList[i].name, studentList[i].age);
	}
}
//查询指定
void search_student(Student* studentList, int* size) {
	printf("请输入要查看的学生号数:");
	int num = input_student_number(size);
	printf("号数:%d   姓名:%s   年龄:%d\n", studentList[num].size, studentList[num].name, studentList[num].age);
}
//修改学生
void update_student(Student* studentList, int* size) {
	printf("请输入要修改的学生号数:");
	int num = input_student_number(size);
	printf("请输入名字:");
	scanf_s("%s", studentList[num].name, 20);
	printf("请输入年龄:");
	scanf_s("%d", &(studentList[num].age));
}
//输入学生学号
int input_student_number(int *size) {
	int num = 0;
	scanf_s("%d", &num);
	while (num >= *size) {
		printf("超出范围 重新输入:");
		scanf_s("%d", &num);
	}
	return num;
}

所有输入学号的地方都被我们改造成了一个函数,这样如果以后输入学号的部分要加入其他的规则限定,就会方便很多,我们只需要修改 input_student_number这个函数,其他地方都会被对应修改到。

同理 还有两串代码也是比较重复的

//1
printf("号数:%d   姓名:%s   年龄:%d\n", studentList[num].size, studentList[num].name, studentList[num].age);
//2
printf("请输入名字:");
scanf_s("%s", studentList[num].name, 20);
printf("请输入年龄:");
scanf_s("%d", &(studentList[num].age));

那么我们进行这两段代码的重构

demo5

提取input_student_info函数,输入学生详情

print_student(Student student) 打印指定学生

#include <stdio.h>    

typedef struct student {
	char name[20];
	int age;
	int size;
}Student;

//菜单
void menu();
//管理系统
void student_manage_system();
//增加学生
void insert_student(Student* studentList, int* size);
//删除学生
void delete_student(Student* studentList, int* size);
//查看所有学生
void search_all_student(Student* studentList, int* size);
//查询指定
void search_student(Student* studentList, int* size);
//修改学生
void update_student(Student* studentList, int* size);
//输入学生的学号
int input_student_number(int* size);
//输入学生详情
void input_student_info(Student* studentList, int num);
//打印学生详情
void print_student(Student student);

int main(void)
{
	menu();
	student_manage_system();
}

//菜单
void menu() {
	printf("--------------学生管理系统-----------\n");
	printf("1 增加学生             2 删除学生\n");
	printf("3 查看所有学生         4 修改学生\n");
	printf("5 查看指定                      \n");
	printf("--------------输入0退出--------------\n");
}

//管理系统
void student_manage_system() {
	int input = 0;
	int size = 0;
	Student studentList[50];
	while (1)
	{
		printf("请输入:");
		scanf_s("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出\n");
			return;
		case 1:
			insert_student(studentList,&size);
			break;
		case 2:
			delete_student(studentList, &size);
			break;
		case 3:
			search_all_student(studentList, &size);
			break;
		case 4:
			update_student(studentList, &size);
			break;
		case 5:
			search_student(studentList, &size);
			break;
		default:
			printf("未知的输入\n");
			break;
		}
	}
}
//新增学生
void insert_student(Student *studentList,int *size) {
	input_student_info(studentList, *size);
	studentList[*size].size = *size;
	//先解引用再++
	(*size)++;
}

//删除学生
void delete_student(Student* studentList, int* size) {
	
	printf("请输入要删除的学生号数:");
	int num = input_student_number(size);
	for (size_t i = num; i < *size - num; num++)
	{
		studentList[num] = studentList[num + 1];
		studentList[num].size--;
	}
	(*size)--;
}
//查询所有
void search_all_student(Student* studentList, int* size) {
	for (size_t i = 0; i < *size; i++)
	{
		print_student(studentList[i]);
	}
}
//查询指定
void search_student(Student* studentList, int* size) {
	printf("请输入要查看的学生号数:");
	int num = input_student_number(size);
	print_student(studentList[num]);
}
//修改学生
void update_student(Student* studentList, int* size) {
	printf("请输入要修改的学生号数:");
	int num = input_student_number(size);
	input_student_info(studentList, num);
}
//输入学生学号
int input_student_number(int *size) {
	int num = 0;
	scanf_s("%d", &num);
	while (num >= *size) {
		printf("超出范围 重新输入:");
		scanf_s("%d", &num);
	}
	return num;
}
//输入学生详情
void input_student_info(Student* studentList, int num)
{
	printf("请输入名字:");
	scanf_s("%s", studentList[num].name, 20);
	printf("请输入年龄:");
	scanf_s("%d", &(studentList[num].age));
}
//打印学生
void print_student(Student student) {
	printf("号数:%d   姓名:%s   年龄:%d\n", student.size, student.name, student.age);
}

完成这两段抽取后,好处是显而易见的

如果我们的学生结构体以后增加了语数英成绩,那么我们只要在

input_student_info函数中进行输参数增加,在print_student进行打印参数的增加,所有用到这个函数的地方都会随着改变,试想一下如果是原来的那种形式,我们需要在很多地方都改变输入的参数和打印的参数。而现在只需要改变这两个函数就可以了。

写到这,大部分的重构就已经完成了,如果大家愿意的话,也可以把menu()菜单放到student_manage_system()函数中,这样主函数真的就剩下一行代码了。

我们现在进行最后一步。分文件编程

当前我们的各个函数结构体之类的,都在主函数中,而其实这样的模块化是不够明显的,所以我们需要分文件,把学生管理系统,写到学生的头文件和c文件中,操作如下。

先新建头文件和c文件

image-20220415124145374

把声明和全部搬运到头文件中。例如函数的声明,结构体的声明

image-20220415124316707

把定义搬运到c文件中,就是那些函数的实现体,要注意包含student.h的头文件,那样才能定位到函数声明和结构体声明,以及对应的系统函数 stdio.h

image-20220415124544660

最后我们的主函数只剩下了非常简洁的代码

image-20220415124638784

至此,完成。

回顾一下吧

抽出功能区分明显的函数

抽出某个大功能中的小功能

小功能有重复部分就继续抽出

分文件

代码量上来之后,我们其实这个逻辑会反过来,变成

  1. 分文件
  2. 在脑海中分离函数的功能,直接开始分函数编程
  3. 在写的过程又发现有重复的,再进行小步抽离
  4. 写完代码后,重新审查一遍代码(code review),不合理的地方再修改

再次强调 每次改动都要测试 保证改动没有损害原始的程序

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里煤球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值