1.1 内容

本实验使用 C++ 实现一个课程管理系统,这个项目会用到 C++ 的很多特性,同时可以熟悉Linux下的C++语言编译方法及简单的Makefile编写。

1.2 实验知识点

  • C++语言基本语法;
  • 基本的Makefile;
  • C++面向对象程序设计,类,虚函数,继承,重载;
  • C++ 标准库;
  • C++11 的一些用法,例如auto。

1.3 实验环境

  • g++

  • Xfce终端

1.4 适合人群

本课程是初级难度,适合有 C++ 基础的同学,可以提升 Linux 下的 C++ 语言编写的能力。 初学者可以先学习 C++11/14 高速上手教程 及其他 C++ 基础教程。

1.5 代码获取

我们提供本项目完整的代码及详细注释供参考,由于代码比较多,文档中仅对部分代码进行讲解。本实验中使用的代码可以在下面的链接中获取。

# 下载课程管理程序代码
wget http://labfile.oss.aliyuncs.com/courses/1052/cmsys.zip# 解压代码
unzip cmsys.zip# 进入代码文件夹查看
cd cmsys

请尽量按照实验步骤自己完成代码编写。

1.6 效果截图

二、步骤

需求分析:

项目功能需求可以拆分成下面两个模块:

  • 命令输入及处理
  • 课程存储及管理

提炼每个模块中的参与者:

  • 命令管理对象
  • 课程对象
  • 课程管理对象

其中命令管理对象对命令进行管理。课程对象存储每个课程的信息,课程管理对象对课程列表进行维护和管理。

我们需要课程类,课程管理类,命令管理类三个类。如果命令对象包含的信息很复杂的话也可以单独设计成类,在本项目中命令仅仅是一个数字及相关的描述信息,为了便于查找我们只需要把命令定义在命令管理类中。

根据需求,课程类为基础类,表示基础课,子类分别为项目课,评估课。但我们的需求中并没有什么地方需要用到这两个子类的虚函数实现,所以这部分会作为扩展内容。

2.1 抽象与细化

根据上面的需求分析,设计所需的类:

  1. 定义课程类Course及课程管理类CourseManager,命令处理类CmdManager
  2. Course类成员包括:
    • 课程ID(课程对象创建时自动生成)
    • 课程名称
  3. Course类成员函数至少包括:
    • 构造函数(参数课程名称)
    • 拷贝构造函数
    • 返回和设置课程名称
    • 获得课程ID的函数
    • 打印信息函数(考虑重载 <<
  4. CourseManager类成员至少包括:
    • 课程列表(可以思考下使用什么数据类型存储?)
  5. CourseManager类成员函数至少包括:
    • 构造函数(参数为课程对象vector
    • 获取课程数量函数
    • 添加课程函数(参数为课程名称)
    • 添加课程函数(参数为课程对象)
    • 删除最新课程函数
    • 删除课程函数(指定ID或指定课程名称)
    • 打印课程列表
    • 打印指定课程(指定ID或指定课程名称)
    • 打印最长名称课程函数
  6. CmdManager类成员至少包括:
    • 命令列表(可以思考下使用什么数据类型存储?)
    • 课程管理对象(用在需要调用课程管理的地方)
  7. CmdManager类成员函数至少包括:
    • 初始化函数:课程及命令信息初始化
    • 打印帮助信息
    • 命令处理函数
  8. 实现中请注意:
    • 哪些函数可以定义为const?
    • 是否有类成员可以定义为静态或const?
    • 哪些函数可以定义为友元?
    • 哪些构造函数等于默认构造函数?

根据这些细化的思路,我们先创建必要的程序文件,每个类都会创建一个头文件和一个cpp文件,另外为了未来命令对象的扩展,我们先创建了一个Cmd.h文件定义所有支持的命令数字。

首先为要实现的程序命名为cmsys(Course Manager System缩写):

# 在 /home/shiyanlou/ 目录下创建代码目录
cd ~
mkdir cmsys
cd cmsys# 创建所需的文件
touch Course.h Course.cpp
touch CourseManager.h CourseManager.cpp
touch Cmd.h CmdManager.h CmdManager.cpp
touch main.cpp
touch Makefile

这就是我们这个项目所需要的所有文件:

  1. Course.h 与 Course.cpp:定义课程类
  2. CourseManager.h 与 CourseManager.cpp:定义课程管理类
  3. Cmd.h,CmdManager.h,CmdManager.cpp:定义命令管理类
  4. Makefile:实现自动化编译

其中 CourseManager 中需要包含一个 Course 容器,存储课程列表。CmdManager 中需要存储一个 CourseManager对象,以便于调用相应的接口对课程进行访问及处理。

下面我们将开始实现这三个类。

2.2 课程类

根据上一节的分析,课程类需要包含课程ID和课程名称两个基本信息,因此在Course.h文件中添加:

// 课程类:存储和处理课程信息
class Course {// 课程IDint id;// 课程名称string name;
};

为了能够让子类可以访问课程ID及名称,将这几项内容定义为protected

为了支持自动生成不同的ID,我们定义一个int类型的静态成员变量,每次创建新的对象都自动加一。

class Course {// 设置静态变量来生成唯一的ID值static int currentId;
};

根据上一节的接口设计,分别定义如下接口,接口的功能对应上一节的设计:

class Course {// 友元函数:读取输入创建新的课程friend void read(istream &inputStream, Course &course);public:// 无参数构造函数Course();// 课程类构造函数:根据课程名称创建课程对象Course(const string& cName): Course() { name = cName; };// 课程类拷贝构造函数Course(const Course& course);// 打印课程信息virtual void PrintInfo() const;// 返回课程名称inline string GetName() const { return name; };// 设置课程名称inline void SetName(const string& newName) { name = newName; };// 返回课程IDinline const int GetId() const { return id; };// 操作符<<重载函数,当cout<<输出课程信息时使用friend ostream& operator <<(ostream& out, const Course& course);
};

其中我们发现有两个友元函数,一个是read从标准输入中读取信息创建课程对象,另一个是重载的输出操作符<<将课程信息打印输出。read我们本项目中不会用到,可以根据实现简单理解。<<操作符重载将在打印课程信息时多次用到。

上述接口中有一个虚函数virtual void PrintInfo() const,之所以这样定义是因为项目课及评估课对应的子类都含有新的成员需要显示,所以会在继承Course后重新实现PrintInfo()

我们在Course.h头文件中增加两个新的子类,其中项目课增加了标签,评估课增加了限制时间,示例代码中为项目课类型,可以自己添加评估课类型:

class ProjectCourse: public Course {
public:// 设置标签inline void SetTag(const string& newTag) { tag = newTag; };// 返回标签inline string GetTag() const { return tag; };// 打印课程信息virtual void PrintInfo() const override;
private:string tag;
};

请注意在修改Course.h时需要添加必要的头文件和using namespace std;

完成头文件后,在Course.cpp文件中实现上面的每个函数,部分函数已经在Course.h中以inline的方法实现,稍微复杂的函数我们都放到cpp文件中。

首先为了支持ID唯一,我们使用静态成员,并在构造函数中对静态成员进行增加:

// 初始化静态成员,默认第一个课程ID为1
int Course::currentId = 1;// 课程类构造函数
Course::Course(){// 将currentId当前值赋值给id,再将currentID自增id = currentId++;// 默认课程名称为空字符串name = "";
}

重载的操作符,这个函数的实现仅仅是按照一定格式输出课程ID和课程名称:

// 友元函数:操作符<<重载函数,当cout<<输出课程信息时使用
ostream &operator<<(ostream &os, const Course& course)
{os << "Course: " << course.id << " : " << course.name;return os;
}

根据上面的描述,请将代码片段进行组合实现课程类,即完成Course.hCourse.cpp文件,尽量自己实现,实在有困惑的地方可以参考本节实验提供的完整代码。任何问题,都欢迎到实验楼问答中提问。

代码完成后,我们先编译成目标文件:

g++ -std=c++11 -c Course.cpp

如果编译没有报错的话,会在目录下生成 Course.o 文件。

2.3 课程管理类

根据2.1的分析,课程管理类需要包含课程列表,因此在 CourseManager.h 文件中添加:

// 课程管理类:用来维护课程列表,执行课程处理任务
class CourseManager {
private:// 存储课程列表vector<Course> courseList;
};

使用vector的原因是因为我们需要根据ID和名称查找课程,并且需要删除最后的课程操作,顺序容器是最好的选择,不适合用map或set等关联容器。

根据2.1的接口设计,分别定义如下接口,接口的功能对应2.1的设计:

class CourseManager {public:// 默认构造函数CourseManager() = default;// 构造函数:根据课程vector创建CourseManagerCourseManager(const vector<Course>& courseVect);// 获取课程列表长度inline int GetCourseNum() { return courseList.size(); }// 添加新的课程void AddCourse(const Course &course);void AddCourse(const string& courseName);// 删除课程:删除最后一个课程void RemoveLast();// 删除课程:删除指定名称的课程void RemoveByName(const string &name);// 删除课程:删除指定ID的课程void RemoveById(int id);// 打印课程列表信息void PrintAllCourse();// 根据课程名称打印指定课程信息void PrintCourse(const string &name);// 根据课程ID打印指定课程信息void PrintCourse(int id);// 打印名称最长的课程void PrintLongNameCourse();
};

这些都是功能性质的函数,部分函数在项目需求中可能没有用到,可以用来扩展支持新的操作命令。每个函数的意义见注释,没有继承关系,不存在虚函数及重载。

请注意在修改CourseManager.h时需要添加必要的头文件和using namespace std;

完成头文件后,在CourseManager.cpp文件中实现上面的每个函数,部分函数已经在CourseManager.h中以inline的方法实现,比如获取课程列表长度的操作,稍微复杂的函数我们都放到cpp文件中。

函数实现中,我们需要注意的是课程vector的遍历方法和增删方法。遍历采用下面的代码片段:

for (auto curs = courseList.begin(); curs != courseList.end(); ++curs){}

增加新的课程对象:

courseList.push_back(course);

删除最后一个课程时需要先判断是否为空,若为空可以抛出异常或简单的打印提示信息,不为空再删除:

courseList.pop_back();

删除指定ID或名称的课程时,先搜索查到课程在vector中的索引,再使用vector的erase函数删除:

int index = FindCourse(id);
if (index > 0)courseList.erase(courseList.begin()+index);
elsecout << "NOT FOUND" << endl;

查找函数的实现基于上面提到的遍历方法。

根据上面的描述,请将代码片段进行组合实现课程类,即完成CourseManager.hCourseManager.cpp文件,尽量自己实现,实在有困惑的地方可以参考本节实验提供的完整代码。任何问题,都欢迎到实验楼问答中提问。

代码完成后,我们先编译成目标文件:

g++ -std=c++11 -c CourseManager.cpp

如果编译没有报错 的话,会在目录下生成CourseManager.o文件。

2.4 命令管理类

根据2.1的分析,我们需要支持六种命令,因此在Cmd.h中,我们定义一个enum类型包含这六种命令数字:

enum CourseCmd
{// 打印命令帮助信息Cmd_PrintHelp = 0,// 打印课程信息Cmd_PrintCourse = 1,// 打印课程数量Cmd_PrintCourseNum = 2,// 打印最长的课程名称Cmd_PrintLongName = 3,// 删除最后一个课程Cmd_RemoveLastCourse = 4,// 退出程序Cmd_Exit = 5,
};

命令管理类需要包含课程管理对象及命令列表,命令列表为了支持根据ID查找命令帮助信息的功能,我们优先选择map实现,因此在CmdManager.h文件中添加:

// 命令管理类
class CmdManager {
private:// 课程管理对象CourseManager manager;// 使用map存储命令及帮助信息map<CourseCmd, string> cmdMap;
};

根据2.1的接口设计,分别定义如下接口,接口的功能对应2.1的设计:

// 命令管理类
class CmdManager {public:// 构造函数CmdManager() = default;// 初始化函数,初始化支持的命令及课程信息void Init();// 打印帮助信息void PrintAllHelp() const;// 根据命令查询帮助信息void PrintCmdHelp(const CourseCmd cmd) const;// 处理命令操作bool HandleCmd(const CourseCmd cmd);// 返回课程管理对象CourseManager& GetCourseManager() { return manager; }
};

请注意在修改CmdManager.h时需要添加必要的头文件和using namespace std;

完成头文件后,在CmdManager.cpp文件中实现上面的每个函数。

这些函数中需要注意的一个是Init()函数,用来初始化CmdManager中的类成员CourseManager和cmdMap:

// 初始化函数
void CmdManager::Init(){// 初始化课程列表manager.AddCourse("Linux");// 初始化命令列表cmdMap.insert(pair<CourseCmd, string>(Cmd_PrintHelp, "Help Info"));
}

另一个函数是HandleCmd()处理命令,根据命令值分别调用CourseManager对象的接口来实现课程操作:

// 处理命令操作,如果返回false则表示退出程序,其他情况返回true
bool CmdManager::HandleCmd(const CourseCmd cmd){// 查找命令是否支持auto iter = cmdMap.find(cmd);// 如果命令不支持则打印NOTFOUND退出if (iter == cmdMap.end()) {cout << "NOT FOUND" << endl;return true;}// 根据命令值选择不同的操作switch(cmd) {case Cmd_PrintHelp: PrintAllHelp(); break;case Cmd_PrintCourse: manager.PrintAllCourse(); break;case Cmd_PrintCourseNum: cout << manager.GetCourseNum() << endl; break;case Cmd_PrintLongName: manager.PrintLongNameCourse(); break;case Cmd_RemoveLastCourse: manager.RemoveLast(); break;// return false用来帮助外层的循环退出case Cmd_Exit: return false;default: break;}return true;
}

函数实现中,我们需要注意的是课程map的遍历方法和查找方法。遍历采用下面的代码片段:

for (auto iter = cmdMap.begin(); iter != cmdMap.end(); iter++)cout << iter->first << ":" << iter->second << endl;

查找要处理的命令:

auto iter = cmdMap.find(cmd);

根据上面的描述,请将代码片段进行组合实现课程类,即完成CmdManager.hCmdManager.cpp文件,尽量自己实现,实在有困惑的地方可以参考本节实验提供的完整代码。任何问题,都欢迎到实验楼问答中提问。

代码完成后,我们先编译成目标文件:

g++ -std=c++11 -c CmdManager.cpp

如果编译没有报错的话,会在目录下生成CmdManager.o文件。

2.5 主函数

完成了三个主要的类后,主函数就很简单了,只需要初始化及处理cin的输入就可以:

主函数的核心代码片段:

// 创建命令管理对象
CmdManager cmdManager;
cmdManager.Init();// 打印帮助信息
cmdManager.PrintAllHelp();cout << "New Command:";// 进入主循环并处理输入信息
while (cin >> cmd) {if (cin.good()) {bool exitCode = cmdManager.HandleCmd((CourseCmd)cmd);if (!exitCode)return 0;}cout << "-------------------------" << endl;cout << "New Command:";// 清理输入流,避免刚才流中的字符影响后续输入cin.clear();cin.ignore();
}

请根据上面的代码片段实现主函数。

代码完成后,我们先编译成目标文件:

g++ -std=c++11 -c main.cpp

当所有的目标文件都生成后,我们需要最后一步链接成可执行文件:

g++ -std=c++11 main.o CmdManager.o Course.o CourseManager.o -o cmsys

cmsys就是我们生成的可执行文件。只需要运行./cmsys就进入到课程管理程序,可以根据提示输入命令了。

2.6 编译及运行

Linux 环境下通常使用GNU make来构建和管理C/C++语言工程项目,虽然单文件程序看不出太大优势,但当程序规模增大时,make是非常必要的,因为源文件之间的依赖关系会非常复杂。make执行时需要一个Makefile文件,Makefile定义了一系列的规则来指定哪些文件先编译,哪些文件后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

将你完成的文件保存为/home/shiyanlou/Code/shiyanlou_cs454/cmsys,在同样的目录下我们编辑Makefile文件:

cd /home/shiyanlou/cmsys/
vim Makefile

Makefile文件里的内容为我们上述每个编译和链接步骤的整合:

CC = g++
CFLAGS = -std=c++11all: main.o CmdManager.o Course.o CourseManager.o$(CC) $(CFLAGS) main.o CmdManager.o Course.o CourseManager.o -o cmsysmain.o: main.cpp$(CC) $(CFLAGS) -c main.cppCmdManager.o: CmdManager.cpp CmdManager.h$(CC) $(CFLAGS) -c CmdManager.cppCourseManager.o: CourseManager.cpp CourseManager.h$(CC) $(CFLAGS) -c CourseManager.cppCourse.o: Course.cpp Course.h$(CC) $(CFLAGS) -c Course.cppclean:rm *.o cmsys

请注意(CC)(CC)(CFLAGS) ...前为一个tab,不是空格。

我们依次解释下:

  • all 或 xxx.o 为编译的目标,当执行make时会依次执行每个目标下的操作,当然目标之间也会有依赖关系,例如all依赖于其他目标。
  • $(CC) $(CFLAGS) -c Course.cpp 为执行的编译或链接命令,这里将Course.cpp文件进行编译生成目标文件
  • clean为当执行make clean时删除编译的目标文件和可执行文件

这只是最简单的一个Makefile范例,推荐大家学习Makefile 基础教程。

保存Makefile后,我们只需要在目录下执行make就可以生成可执行文件cmsys,运行./cmsys即可进入到我们的程序执行过程,如果中间有任何问题,需要根据输出的错误信息查验下代码是否有BUG。

2.7 执行程序

程序运行的截图如下所示:

三、项目扩展

本实验只实现了一个简单的课程管理程序。基于本课节学习,大家可以在此代码基础上实现扩展:

  1. 课程持久化:将课程信息存储到文件中,程序启动时读取
  2. 支持不同类型的课程管理:使用到代码中已经实现的项目课及评估课
  3. 课程运行时添加:根据用户的输入添加新的课程
  4. 命令扩展:增加课程查找,添加及指定课程的删除命令

四、总结

通过本节实验的学习,我们开发了课程管理程序,学习了C++语言的基本语法及面向对象的一些概念,标准库,auto的用法及最基本的Makefile编写。

c++实现课程管理系统相关推荐

  1. Atutor、 Claroline、 Moddle及Sakai号称为四大开源课程管理系统

    1.ATutor是一个开源基于Web的学习管理系统(LCMS).管理员在几分钟之内就能够安装或更新ATutor,为它更换一个新的皮肤(通过自定义模板实现).ATutor还提供一些扩展模块以便扩展其功能 ...

  2. 创新课程管理系统数据库设计心得

    因为创新课程管理系统这一个项目,是一个从无到有,没有标准可以去参考的一个项目. 这个项目专门针对该课程进行设计,所以需求的功能点很多,因此数据库有多次设计,更改再推翻重新设计再更改. 因为用户有多个类 ...

  3. 开放课程管理系统(Moodle)的介绍(转)

    一.虚拟学习环境 关于虚拟学习环境,很难用一个简单的定义来描述.可以说是,支持和管理教与学的各项活动的基于网络的环境.也可以认为是学习管理系统的组成部分.当应用于远程教育时,通常认为它包括" ...

  4. 基于Python的课程管理系统【100010023】

    课程管理系统简介 1.1需求分析 实现一个具体的课程管理系统.按照软件工程思路设计简化的专业课数据库,尽量模拟现有专业课程一个学期的选课排课原型实际情况.(注:本系统由本人单独设计.开发完成) 1.2 ...

  5. 基于SpringBoot的在线课程管理系统

    文末获取源码 开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7/8.0 数据库工具:Navicat11 开发软件:eclip ...

  6. 【计算机毕业设计】133在线课程管理系统

    一.系统截图(需要演示视频可以私聊) 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟.本文介绍了在线课程管理系统的开发全过程.通过分析在线课程管理系统管理的不足 ...

  7. java计算机毕业设计小学教师课程管理系统源程序+mysql+系统+lw文档+远程调试

    java计算机毕业设计小学教师课程管理系统源程序+mysql+系统+lw文档+远程调试 java计算机毕业设计小学教师课程管理系统源程序+mysql+系统+lw文档+远程调试 本源码技术栈: 项目架构 ...

  8. 基于SSM框架的学生在线教育教学课程管理系统

    基于SSM框架的学生在线教育教学课程管理系统 1.1 项目概述 开发语言:Java8 数据库:Mysql5 前端技术:echarts图表 渲染数据:thymeleaf模板引擎 ${ } 后端框架:Sp ...

  9. java课程管理系统_基于JAVA学生课程管理系统.doc

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp计算机&nbsp>&nbspJava 基于JAVA学生课程管理系统.doc62页 本文档一共 ...

  10. 基于Java毕业设计早教课程管理系统源码+系统+mysql+lw文档+部署软件

    基于Java毕业设计早教课程管理系统源码+系统+mysql+lw文档+部署软件 基于Java毕业设计早教课程管理系统源码+系统+mysql+lw文档+部署软件 本源码技术栈: 项目架构:B/S架构 开 ...

最新文章

  1. MATLAB计算杨氏模量,四阶弹性模量Cijkl如何在matlab里表示啊? - 计算模拟 - 小木虫 - 学术 科研 互动社区...
  2. svn 主干(trunk)、分支(branch )、标记(tag) 简介
  3. 深入浅出 SpringMVC - 2 提升篇
  4. 基于Elasticsearch实现搜索建议
  5. convex function
  6. k8s使用port-forward访问集群中的应用程序(只能在执行命令的机器上开放端口)
  7. java中打印输出数组内容的三种方式
  8. [设计模式]单例模式
  9. 2022-2027年(新版)中国产权交易行业前景动态与未来发展形势报告
  10. vscode 代码片段如何输出$符号
  11. Flutter 全局弹窗
  12. html文件用ppt打开,Win10如何打开html文件课件.ppt
  13. kotlin开发Android入门篇一
  14. 第二章 ObjectARX类库简介
  15. 转--Android 文件外/内部存储的获取各种存储目录路径
  16. Java精品项目源码第47期房屋租赁管理系统
  17. 【Vue项目实战】vue.js2.5 饿了么APP(1)概述+项目准备
  18. 在OpenCV里使用SVM识别手写数字
  19. Web(一)Web前端开发概述
  20. 一个假冒的序列号被用来注册Internet Download Manager。IDM即将退出…

热门文章

  1. IGBT芯片赛道竞争激烈,水光半导体Wassersun推出全新IGBT制程技术
  2. 笔记本功耗测试软件,笔记本功率一般多大 笔记本功率检测【图文】
  3. 龙格现象python程序_基于切比雪夫多项式的函数插值逼近
  4. 初用vue遇到的一些问题
  5. 高频电子线路实验 03 - | 环形混频与直接调频
  6. 如何长时间保存记忆,分享我的数据备份大法
  7. 主流NoSQL数据库评测之HandlerSocket
  8. IP这么火究竟什么才是有价值的IP
  9. flac文件转换成mp3格式
  10. 动态推荐系统关键技术研究(一)