c语言:通讯录管理系统(动态分配内存版)

        前言:在大多数高校内,都是通过设计一个通讯录管理系统来作为c语言课程设计,通过一个具体的系统设计将我们学习过的结构体和函数等知识糅合起来,可以很好的锻炼学生的编程思维,本文旨在为通讯录管理系统的设计提供思路和示例讲解,并且对如何动态的分配内存空间使得程序更高效的运行进行了讲解

        本文是在基础的通讯录管理系统上进行改进,如果对于基础的通讯录管理系统有什么不懂的地方,可以参考笔者的上一篇文章,里面有详细的万字解读:

c语言:通讯录管理系统(增删查改)

目录

一.基础静态版本 (改进前)

头文件部分

函数实现部分

主函数部分

二.结构体的更改

三.扩容的设计

四.释放空间

五.最终完整代码 (改进后)

头文件部分

函数的实现部分 

主函数部分


一.基础静态版本 (改进前)

这里我们给出基础版本的代码,然后本文后续内容都是基于此进行改进

我们分为 3 个文件来设计:

  • Contact.h: 包含头文件的声明,对函数的声明,以及宏的申明
  • Contact.cpp: 通讯录管理系统中具体每一个函数的实现
  • test.cpp: 主函数,根据用户的选择进行调用相应的函数

 

头文件部分(Contact.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>#define Name_Max 20
#define Tel_Number 12
#define Sex_Max 5
#define Address_Max 30
#define Contact_Max 100//联系人结构体
typedef struct PeopleInformation
{char name[Name_Max];char telnumber[Tel_Number];int age;char sex[Sex_Max];char address[Address_Max];
}PeoInfor;//通讯录结构体
typedef struct Contact
{PeoInfor data[Contact_Max];//结构体数组存放联系人结构体int size;//记录当前通讯录中有多少个联系人
}Contact;//目录
void menu();//初始化通讯录
void InitContact(Contact* cp);//增加联系人
void AddContact(Contact* cp);//删除联系人
void DelContact(Contact* cp);//通过姓名进行查找联系人
int FindPeople(Contact* cp, char name[]);//展示全部通讯录信息
void ShowContact(const Contact* cp);//查询联系人
void SeachPeople(Contact* cp);//修改联系人信息
void ModifyContact(Contact* cp);

函数实现部分(Contact.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"void menu()
{printf("\n");printf("-----------------------------\n");printf("---   1.添加联系人      -----\n");printf("---   2.删除联系人      -----\n");printf("---   3.查找联系人      -----\n");printf("---   4.修改联系人信息  -----\n");printf("---   5.显示全部信息    -----\n");printf("---   0.退出通讯录      -----\n");printf("-----------------------------\n");
}//初始化通讯录
void InitContact(Contact* cp)
{//判断非空assert(cp);cp->size = 0;memset(cp->data, 0, sizeof(cp->data));
}//增加联系人
void AddContact(Contact* cp)
{//判断非空assert(cp);//判断未满if (cp->size == Contact_Max){printf("通讯录已满,无法再添加新的联系人\n");return;}printf("请输入要添加的联系人的姓名:\n");scanf("%s", cp->data[cp->size].name);printf("请输入要添加的联系人的电话号:\n");scanf("%s", cp->data[cp->size].telnumber);printf("请输入要添加的联系人的年龄:\n");scanf("%d", &(cp->data[cp->size].age));printf("请输入要添加的联系人的性别:\n");scanf("%s", cp->data[cp->size].sex);printf("请输入要添加的联系人的住址:\n");scanf("%s", cp->data[cp->size].address);cp->size++;printf("添加成功\n");
}//通过姓名进行查找联系人
int FindPeople(Contact* cp, char name[])
{assert(cp);for (int i = 0; i < cp->size; i++){if (strcmp(cp->data[i].name, name) == 0){return i;}}return -1;
}//删除联系人
void DelContact(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空,无需删除\n");return;}printf("请输入选择删除的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要删除的联系人不存在\n");return;}for (int i = ret; i < cp->size-1 ; i++){cp->data[i] = cp->data[i + 1];}cp->size--;printf("删除成功\n");
}//查询联系人
void SeachPeople(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空\n");return;}printf("请输入选择查找的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要查找的联系人不存在\n");return;}//名字  年龄  性别    电话    地址//xxx   xxx    xxx    xxx     xxxprintf("%-10s%-5s%-5s%-12s%-30s\n", "名字", "年龄", "性别", "电话", "地址");//打印个人的信息printf("%-10s%-5d%-5s%-12s%-30s\n", cp->data[ret].name, cp->data[ret].age, cp->data[ret].sex, cp->data[ret].telnumber, cp->data[ret].address);
}//展示全部通讯录信息
void ShowContact(const Contact* cp)
{assert(cp);if (cp->size == 0){printf("通讯录为空\n");return;}//名字  年龄  性别    电话    地址//xxx   xxx    xxx    xxx     xxxprintf("%-10s%-5s%-5s%-12s%-30s\n", "名字", "年龄", "性别", "电话", "地址");for (int i = 0; i < cp->size; i++){//打印每个人的信息printf("%-10s%-5d%-5s%-12s%-30s\n",cp->data[i].name, cp->data[i].age, cp->data[i].sex, cp->data[i].telnumber, cp->data[i].address);}
}//修改联系人信息
void ModifyContact(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空\n");return;}printf("请输入选择修改的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要修改的联系人信息不存在\n");return;}printf("请输入要修改的联系人的姓名:\n");scanf("%s", cp->data[ret].name);printf("请输入要修改的联系人的电话号:\n");scanf("%s", cp->data[ret].telnumber);printf("请输入要修改的联系人的年龄:\n");scanf("%d", &(cp->data[ret].age));printf("请输入要修改的联系人的性别:\n");scanf("%s", cp->data[ret].sex);printf("请输入要修改的联系人的住址:\n");scanf("%s", cp->data[ret].address);printf("修改成功\n");
}

主函数部分(test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"//枚举,增加程序的可读性
enum options
{EXIT,ADD,DEL,SEACH,MODIFY,SHOW
};int main()
{int input = 0;//创建通讯录Contact con;//初始化通讯录InitContact(&con);do{menu();printf("请输入你的选择: ");scanf("%d", &input);switch (input){//增加联系人信息case ADD:AddContact(&con);break;//删除联系人信息case DEL:DelContact(&con);break;//查找某个联系人的信息case SEACH:SeachPeople(&con);break;//修改某个联系人的信息case MODIFY:ModifyContact(&con);break;//展示通讯录内的每一个联系人的信息case SHOW:ShowContact(&con);break;//退出通讯录管理系统case EXIT:printf("通讯录已退出\n");break;//预防非法输入default:printf("输入错误,请重新输入\n");break;}}while(input);return 0;
}

二.结构体的更改

        动态的分配内存就意味着通讯录这个结构体要动态的分配内存,根据通讯录内的信息进行分配,所以我们在这里对于通讯录结构体进行更改

//通讯录结构体
typedef struct Contact
{PeoInfor* data;//结构体数组存放联系人结构体int size;//记录当前通讯录中有多少个联系人int capacity;//记录当前存放的容量
}Contact;
  •         在这里我们将 data 从一个结构体数组改成了结构体指针,然后后续再使用 这个指针指向我们动态开辟的内存就完成了我们的需求设计
  •         并且新增了个变量 capacity 用来记录当前通讯录内的最大容量,当联系人的数量和容量相同的时候,也就是通讯录满容的时候,我们再使用 realloc 重新分配新的内存空间

三.扩容的设计

        我们封装一个函数方便我们添加新的联系人的时候进行扩容,先判断当前通讯录是否已满,如果满了就进行扩容,每一次扩容扩展 2 个联系人结构体的大小

  •         首先是判断部分,当当前通讯录的容量等于通讯录内实际存放的数据的大小的时候,我们就判定为通讯录已满,然后我们使用 realloc 开辟新的空间,比之前大 2 个联系人结构体的大小
  •         为了程序的健全性,我们也要判断开辟空间是否成功,如果成功就通过 ptr指针 指向联系人的数据部分,用 data 接收,如果开辟失败,我们就打印报错信息
void CheckContact(Contact* cp)
{if (cp->size == cp->capacity){PeoInfor* ptr = (PeoInfor*)realloc(cp->data, (cp->capacity + 2) * sizeof(PeoInfor));if (ptr != NULL){cp->data = ptr;cp->capacity += 2;printf("增容成功\n");}else{perror("AddContact->realloc");return;}}
}

四.释放空间

        由程序员申请开辟的空间也应当由程序员设置进行释放,在这个通讯录管理系统中也是如此,我们需要找到合适的释放位置,也就是当用户退出通讯录的时候,我们手动进行对开辟的空间进行释放,以避免造成内存泄漏

        那我们这里就封装一个释放空间的函数:

//销毁通讯录
void DestoryContact(Contact* cp)
{free(cp->data);cp->data = NULL;cp->size = 0;cp->capacity = 0;
}

五.最终完整代码 (改进后)

头文件部分(Contact.h:

#pragma once
#pragma once
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>#define Name_Max 20
#define Tel_Number 12
#define Sex_Max 5
#define Address_Max 30
#define Contact_Max 100
#define Contact_SZ 3//联系人结构体
typedef struct PeopleInformation
{char name[Name_Max];char telnumber[Tel_Number];int age;char sex[Sex_Max];char address[Address_Max];
}PeoInfor;//通讯录结构体
typedef struct Contact
{PeoInfor* data;//结构体数组存放联系人结构体int size;//记录当前通讯录中有多少个联系人int capacity;//记录当前存放的容量
}Contact;//目录
void menu();//初始化通讯录
void InitContact(Contact* cp);//增加联系人
void AddContact(Contact* cp);//删除联系人
void DelContact(Contact* cp);//通过姓名进行查找联系人
int FindPeople(Contact* cp, char name[]);//展示全部通讯录信息
void ShowContact(const Contact* cp);//查询联系人
void SeachPeople(Contact* cp);//修改联系人信息
void ModifyContact(Contact* cp);//扩容
void CheckContact(Contact* cp);//销毁通讯录
void DestoryContact(Contact* cp);

函数的实现部分 (Contact.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"void menu()
{printf("\n");printf("-----------------------------\n");printf("---   1.添加联系人      -----\n");printf("---   2.删除联系人      -----\n");printf("---   3.查找联系人      -----\n");printf("---   4.修改联系人信息  -----\n");printf("---   5.显示全部信息    -----\n");printf("---   0.退出通讯录      -----\n");printf("-----------------------------\n");
}//初始化通讯录
void InitContact(Contact* cp)
{//判断非空assert(cp);cp->size = 0;cp->capacity = Contact_SZ;cp->data =(PeoInfor*)calloc(cp->capacity, sizeof(PeoInfor));if (cp->data == NULL){perror("InitContact->calloc");return;}
}void CheckContact(Contact* cp)
{if (cp->size == cp->capacity){PeoInfor* ptr = (PeoInfor*)realloc(cp->data, (cp->capacity + 2) * sizeof(PeoInfor));if (ptr != NULL){cp->data = ptr;cp->capacity += 2;printf("增容成功\n");}else{perror("AddContact->realloc");return;}}
}//增加联系人
void AddContact(Contact* cp)
{//判断非空assert(cp);//判断满后扩容CheckContact(cp);printf("请输入要添加的联系人的姓名:\n");scanf("%s", cp->data[cp->size].name);printf("请输入要添加的联系人的电话号:\n");scanf("%s", cp->data[cp->size].telnumber);printf("请输入要添加的联系人的年龄:\n");scanf("%d", &(cp->data[cp->size].age));printf("请输入要添加的联系人的性别:\n");scanf("%s", cp->data[cp->size].sex);printf("请输入要添加的联系人的住址:\n");scanf("%s", cp->data[cp->size].address);cp->size++;printf("添加成功\n");
}//通过姓名进行查找联系人
int FindPeople(Contact* cp, char name[])
{assert(cp);for (int i = 0; i < cp->size; i++){if (strcmp(cp->data[i].name, name) == 0){return i;}}return -1;
}//删除联系人
void DelContact(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空,无需删除\n");return;}printf("请输入选择删除的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要删除的联系人不存在\n");return;}for (int i = ret; i < cp->size - 1; i++){cp->data[i] = cp->data[i + 1];}cp->size--;printf("删除成功\n");
}//查询联系人
void SeachPeople(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空\n");return;}printf("请输入选择查找的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要查找的联系人不存在\n");return;}//名字  年龄  性别    电话    地址//xxx   xxx    xxx    xxx     xxxprintf("%-10s%-5s%-5s%-12s%-30s\n", "名字", "年龄", "性别", "电话", "地址");//打印个人的信息printf("%-10s%-5d%-5s%-12s%-30s\n", cp->data[ret].name, cp->data[ret].age, cp->data[ret].sex, cp->data[ret].telnumber, cp->data[ret].address);
}//展示全部通讯录信息
void ShowContact(const Contact* cp)
{assert(cp);if (cp->size == 0){printf("通讯录为空\n");return;}//名字  年龄  性别    电话    地址//xxx   xxx    xxx    xxx     xxxprintf("%-10s%-5s%-5s%-12s%-30s\n", "名字", "年龄", "性别", "电话", "地址");for (int i = 0; i < cp->size; i++){//打印每个人的信息printf("%-10s%-5d%-5s%-12s%-30s\n", cp->data[i].name, cp->data[i].age, cp->data[i].sex, cp->data[i].telnumber, cp->data[i].address);}
}//修改联系人信息
void ModifyContact(Contact* cp)
{assert(cp);char name[Name_Max];if (cp->size == 0){printf("通讯录为空\n");return;}printf("请输入选择修改的联系人的姓名:\n");scanf("%s", name);int ret = FindPeople(cp, name);if (ret == -1){printf("要修改的联系人信息不存在\n");return;}printf("请输入要修改的联系人的姓名:\n");scanf("%s", cp->data[ret].name);printf("请输入要修改的联系人的电话号:\n");scanf("%s", cp->data[ret].telnumber);printf("请输入要修改的联系人的年龄:\n");scanf("%d", &(cp->data[ret].age));printf("请输入要修改的联系人的性别:\n");scanf("%s", cp->data[ret].sex);printf("请输入要修改的联系人的住址:\n");scanf("%s", cp->data[ret].address);printf("修改成功\n");
}//销毁通讯录
void DestoryContact(Contact* cp)
{free(cp->data);cp->data = NULL;cp->size = 0;cp->capacity = 0;
}

主函数部分(test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"//枚举,增加程序的可读性
enum options
{EXIT,ADD,DEL,SEACH,MODIFY,SHOW
};int main()
{int input = 0;//创建通讯录Contact con;//初始化通讯录InitContact(&con);do{menu();printf("请输入你的选择: ");scanf("%d", &input);switch (input){//增加联系人信息case ADD:AddContact(&con);break;//删除联系人信息case DEL:DelContact(&con);break;//查找某个联系人的信息case SEACH:SeachPeople(&con);break;//修改某个联系人的信息case MODIFY:ModifyContact(&con);break;//展示通讯录内的每一个联系人的信息case SHOW:ShowContact(&con);break;//退出通讯录管理系统case EXIT:DestoryContact(&con);printf("通讯录已退出\n");break;//预防非法输入default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}

本次分享就到此为止了,感谢您的支持,如有错误,欢迎积极指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/150721.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

国科大体系结构习题 | 第三章 二进制与逻辑电路

第三章 Q1: A1:(1) 原码&#xff1a; [ − ( 2 63 − 1 &#xff0c; 2 63 − 1 ] [-(2^{63}-1&#xff0c;2^{63}-1] [−(263−1&#xff0c;263−1] 补码&#xff1a; [ − ( 2 63 &#xff0c; 2 63 − 1 ] [-(2^{63}&#xff0c;2^{63}-1] [−(263&#xff0c;263−1] …

门窗定制销售小程序商城的作用是什么

门窗属于建材物料行业里重要的体系&#xff0c;生活中需求度非常高&#xff0c;虽然门窗产品使用周期较长&#xff0c;客户旧换新复购率较低&#xff0c;但由于产品属于家家户户都需要&#xff0c;因此市场规模依然是稳增不减。 互联网环境下&#xff0c;客户更习惯于线上获得…

cpp primer plus笔记01-注意事项

cpp尽量以int main()写函数头而不是以main()或者int main(void)或者void main()写。 cpp尽量上图用第4行的注释而不是用第5行注释。 尽量不要引用命名空间比如:using namespace std; 函数体内引用的命名空间会随着函数生命周期结束而失效&#xff0c;放置在全局引用的命名空…

Leetcode901-股票价格跨度

一、前言 本题基于leetcode901股票价格趋势这道题&#xff0c;说一下通过java解决的一些方法。并且解释一下笔者写这道题之前的想法和一些自己遇到的错误。需要注意的是&#xff0c;该题最多调用 next 方法 10^4 次,一般出现该提示说明需要注意时间复杂度。 二、解决思路 ①…

云原生微服务 第六章 Spring Cloud中使用OpenFeign

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 文章目录 系列文章目录前言1、OpenFeign的实现…

SAP从入门到放弃系列之QM目录类别、代码组、选择集维护

目录 一、概念相关内容1.1 目录类别1.2 代码组和代码1.3 选择集和选择集代码 二、系统操作相关内容 一、概念相关内容 1.1 目录类别 目录类别是对定性数据的一种归纳&#xff0c;描述了业务的主题。根据PA的教材中表述&#xff0c;目录类型 0 - 9 和 A - O 由 SAP 定义&#…

如何自学(黑客)网络安全技术————(详细分析学习思路)方法

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学&#xff1f;如何学&#xff1f; 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0c…

883. 高斯消元解线性方程组

883. 高斯消元解线性方程组 - AcWing题库 输入一个包含 n 个方程 n 个未知数的线性方程组。 方程组中的系数为实数。 求解这个方程组。 下图为一个包含 m 个方程 n 个未知数的线性方程组示例&#xff1a; 输入格式 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含 n1…

网络安全(黑客)——自学笔记

前言&#xff1a; 想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“…

Java卷上天,可以转行干什么?

小刚是某名企里的一位有5年经验的高级Java开发工程师&#xff0c;每天沉重的的工作让他疲惫不堪&#xff0c;让他萌生出想换工作的心理&#xff0c;但是转行其他工作他又不清楚该找什么样的工作 因为JAVA 这几年的更新实在是太太太……快了&#xff0c;JAVA 8 都还没用多久&am…

《第一行代码Andorid》阅读笔记-第六章

第六章 内容提供器 在上一章中我们学了Android数据持久化的技术&#xff0c;包括文件存储、SharedPreferences存储以及数据库存储。使用这些持久化技术所保存的数据都只能在当前应用程序中访问。 虽然文件和SharedPreferences存储中提供了MODE_WORLD_READABLE和MODE_WORLD_WRI…

Vue中如何进行分布式任务调度与定时任务管理

在Vue中进行分布式任务调度与定时任务管理 分布式任务调度和定时任务管理是许多应用程序中的关键功能之一。它们用于执行周期性的、异步的、重复的任务&#xff0c;例如数据备份、邮件发送、定时报告生成等。在Vue.js应用中&#xff0c;我们可以结合后端服务实现分布式任务调度…

苹果手机的祛魅时刻,国产厂商的颠覆征程

“iPhone翻车了&#xff1f;”有网友如此质疑。 发布未满一个月&#xff0c;iPhone 15系列多次因负面问题登上热搜。 首先曝出钛金属边框容易沾染指纹的情况&#xff0c;尚未涉及功能性方面&#xff0c;但后续接连曝出发热严重、电池循环次数低、外放破音、Wi-Fi链接缓慢的问…

进制转换

1.十进制转化为其他进制 这里可能将十进制转化为14或15进制&#xff0c;所以10用A&#xff0c;11用B表示&#xff0c;依次类推。 2.其他进制转化为10进制&#xff1a; 将其他进制下的数转化为10进制下的数&#xff0c;通常采用秦九韶算法。 上代码&#xff1a; #include &…

新手选MT4还是MT5,anzo capital昂首资本建议选择MT4,一个原因

在交易中就订单执行策略而言&#xff0c;MT4和MT5哪个更好&#xff0c;相信很多交易者和&#xff0c;anzo capital昂首资本一样很难做出判断。在MT5中&#xff0c;虽然开发人员对发送订单的流程进行了额外的复杂化&#xff0c;同时MT5在订单执行政策方面的优势在于其能够调整全…

stm32 - 中断/定时器

stm32 - 中断/定时器 概念时钟树定时器类型基准时钟&#xff08;系统时钟&#xff09;预分频器 - 时基单元CNT计数器 - 时基单元自动重装寄存器 - 时基单元基本定时器结构通用定时器计数器模式内外时钟源选择 定时中断基本结构时序预分频器时序计数器时序 例子通用定时器 - 内部…

OpenCV4(C++) —— 图像数据类型转换和颜色模型转换

文章目录 一、图像数据类型转换二、颜色模型转换三、通道的分离和融合 一、图像数据类型转换 OpenCV中使用imread读取一张彩色图像时&#xff0c;默认采用的是BGR通道和整数类型(0-255&#xff0c;CV_8U)。 在某些情况下&#xff0c;会将整数类型(0-255)转换为浮点类型(0-1)&a…

Windows下Tensorflow docker python开发环境搭建

前置条件 windows10 更新到较新的版本&#xff0c;硬件支持Hyper-V。 参考&#xff1a;https://learn.microsoft.com/zh-cn/windows/wsl/install 启用WSL 在Powershell中输入如下指令&#xff1a; dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsys…

【LeetCode热题100】--543.二叉树的直径

543.二叉树的直径 给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 首先我们知道一条路径的长度为该路径经过的节…

地下水数值模拟软件如何选择?GMS、Visual MODFLOW Flex、FEFLOW、MODFLOW

强调模块化教学&#xff0c;分为前期数据收集与处理&#xff1b;三维地质结构建模&#xff1b;地下水流动模型构建&#xff1b;地下水溶质运移模型构建和反应性溶质运移构建5个模块&#xff1b;采用全流程模式将地下水数值模拟软件GMS的操作进行详细剖析和案例联系。不仅使学员…