目录
3. #pragma once vs. 传统的 #ifndef 方式
作者使用的Visual Studio版本为VS2019,操作系统为WindowsX64
一、管理系统的需求
职工管理系统可以用来管理公司内所有员工的信息
本教程主要利用C++来实现一个基于多态的职工管理系统
公司中职工分为三类:普通员工、经理、老板,显示信息时,需要显示职工编号、职工姓名、职工岗位、以及职责
普通员工职责:完成经理交给的任务
经理职责:完成老板交给的任务,并下发任务给员工
老板职责:管理公司所有事务
管理系统中需要实现的功能如下:
- 退出管理程序:退出当前管理系统
- 增加职工信息:实现批量添加职工功能,将信息录入到文件中,职工信息为:职工编号、姓名、部门编号
- 显示职工信息:显示公司内部所有职工的信息
- 删除离职职工:按照编号删除指定的职工
- 修改职工信息:按照编号修改职工个人信息
- 查找职工信息:按照职工的编号或者职工的姓名进行查找相关的人员信息
- 按照编号排序:按照职工编号,进行排序,排序规则由用户指定
- 清空所有文档:清空文件中记录的所有职工信息(清空前需要再次确认,防止误删)
二、创建项目
打开Visual Studio 2019,点击创建新项目,创建新的C++项目,如下所示:
按照如下步骤创建空的C++项目,如下:
然后确定项目名称、路径,最后点击创建,如下所示:
按照如下步骤添加文件:
然后根据如下步骤添加源文件:
点击添加按钮之后,得到如下所示的界面:
至此,项目已创建完毕,在项目文件夹中你可以找到如下所示的文件:
三、创建管理类
管理类负责的内容如下:
- 菜单功能:与用户的沟通菜单界面
- 对职工增删改查的操作
- 与文件的读写交互
3.1 创建文件
在头文件和源文件的文件夹下分别创建 workerManager.h 和 workerManager.cpp 文件,如下所示:
workerManager.h 中的内容如下,主要是类的声明,没有具体的实现,具体的实现会在workerManager.cpp文件中。
#pragma once // 防止头文件重复包含
#include<iostream>
class workerManager {
public:
// 构造函数
workerManager();
// 析构函数
~workerManager();
};
workerManager.cpp文件中的内容如下:
#include "workerManager.h"
workerManager::workerManager(){
// 具体实现
}
workerManager::~workerManager() {
// 具体实现
}
至此,职工管理类创建完毕。
3.2 知识扩展
3.2.1 头文件和源文件各司其职
在 C++ 代码组织中,
.h
头文件和.cpp
源文件各有明确的职责:
.h
头文件(声明)头文件的主要作用是声明类、函数、常量等接口,让其他文件可以引用它,而不包含具体的实现。
通常放在
.h
文件中的内容:
- 头文件保护(防止重复包含)
- 类的声明
- 成员变量和成员函数的声明
- 常量、宏定义
- 类型定义(
typedef
、using
、enum
)- 结构体、模板
- 头文件的引用(
#include
)示例:workerManager.h
#ifndef WORKERMANAGER_H #define WORKERMANAGER_H #include <iostream> #include <vector> using namespace std; class WorkerManager { public: WorkerManager(); // 构造函数 ~WorkerManager(); // 析构函数 void addWorker(); // 添加员工 void showWorkers(); // 显示所有员工 private: vector<string> workerList; // 员工列表 }; #endif // WORKERMANAGER_H
.cpp
源文件(实现)源文件主要实现
.h
头文件中声明的内容,用于存放代码逻辑。通常放在
.cpp
文件中的内容:
- 包含对应的
.h
头文件- 类的具体实现
- 函数的具体实现
- 局部变量、辅助函数
- 业务逻辑
示例:workerManager.cpp
#include "workerManager.h" WorkerManager::WorkerManager() { cout << "WorkerManager 构造函数调用!" << endl; } WorkerManager::~WorkerManager() { cout << "WorkerManager 析构函数调用!" << endl; } void WorkerManager::addWorker() { string name; cout << "请输入员工姓名:"; cin >> name; workerList.push_back(name); } void WorkerManager::showWorkers() { cout << "员工列表:" << endl; for (const auto& worker : workerList) { cout << worker << endl; } }
总结
文件类型 主要内容 示例 .h
头文件类的声明,函数原型,宏定义,类型定义 class WorkerManager { ... };
.cpp
源文件类的实现,成员函数的定义,逻辑代码 void WorkerManager::addWorker() { ... }
这样分离的好处是:
- 提高代码复用性(多个
.cpp
文件可以共享同一个.h
)- 编译加速(修改
.cpp
不会导致所有代码重新编译)- 增强可读性(头文件只关注接口,
.cpp
处理具体实现)
3.2.2 头文件中第一行的#pragma once语句
.h文件中第一行一般是#pragma once,这是什么?
#pragma once
是一个头文件保护机制,用于防止头文件被多次包含,导致编译错误。
1. 为什么需要头文件保护?
在 C++ 中,如果一个头文件被多个
.cpp
文件包含,或者间接被自身包含,就可能导致重复定义错误。示例(没有头文件保护,可能导致重复定义):
// workerManager.h #include <iostream> class WorkerManager { public: void show(); };
// main.cpp #include "workerManager.h" #include "workerManager.h" // 头文件被重复包含
⚠ 错误:重复定义 WorkerManager
2.
#pragma once
的作用
#pragma once
让编译器在同一个编译单元中只包含一次头文件,即使多次#include
也不会重复编译。推荐写法:
// workerManager.h #pragma once #include <iostream> class WorkerManager { public: void show(); };
3.
#pragma once
vs. 传统的#ifndef
方式在早期 C++ 代码中,头文件保护通常使用
#ifndef
宏:#ifndef WORKERMANAGER_H #define WORKERMANAGER_H #include <iostream> class WorkerManager { public: void show(); }; #endif // WORKERMANAGER_H
这种方式的原理:
#ifndef WORKERMANAGER_H
:如果宏WORKERMANAGER_H
未定义,则继续执行代码。#define WORKERMANAGER_H
:定义WORKERMANAGER_H
,确保下一次包含时被跳过。#endif
:结束#ifndef
保护范围。两者的比较:
方式 优点 缺点 #pragma once
代码简洁,避免命名冲突,编译器优化 可能不被非常旧的编译器支持(但现代编译器都支持) #ifndef
宏保护适用于所有编译器 代码冗长,容易因宏命名冲突导致错误 在现代 C++ 开发中,一般推荐
#pragma once
,除非需要兼容极老的编译器。
四、菜单功能
功能描述:与用户的沟通界面
4.1 添加成员函数
在管理类 workerManager.h中添加成员函数 void Show_Menu();
4.2 菜单功能实现
在管理类 workerManager.cpp 中实现成员函数 void Show_Menu();
void workerManager::Show_Menu() {
// 具体实现
std::cout << "**********************************************" << std::endl;
std::cout << "************* 欢迎使用职工管理系统 ***********" << std::endl;
std::cout << "*************** 0.退出管理程序 ***************" << std::endl;
std::cout << "*************** 1.增加职工信息 ***************" << std::endl;
std::cout << "*************** 2.显示职工信息 ***************" << std::endl;
std::cout << "*************** 3.删除离职职工 ***************" << std::endl;
std::cout << "*************** 4.修改职工信息 ***************" << std::endl;
std::cout << "*************** 5.查找职工信息 ***************" << std::endl;
std::cout << "*************** 6.按照编号排序 ***************" << std::endl;
std::cout << "*************** 7.清空所有文档 ***************" << std::endl;
std::cout << "**********************************************" << std::endl;
std::cout << std::endl;
}
4.3 测试菜单功能
在 职工管理系统.cpp 文件中测试菜单功能:
#include<iostream>
#include"workerManager.h"
int main() {
workerManager wm; // 实例化管理类对象
wm.Show_Menu(); // 调用菜单功能函数
return 0;
}
运行效果图:
五、退出功能(0.退出管理程序的设计)
5.1 提供功能接口
在main函数中提供分支选择,提供每个功能接口:
#include<iostream>
#include"workerManager.h"
int main() {
workerManager wm; // 实例化管理类对象
int choice = 0; // 定义用户选择的选项
while (true) {
wm.Show_Menu(); // 调用菜单功能函数
std::cout << "请输入您的选择:" << std::endl;
std::cin >> choice;
switch (choice) {
case 0: // 退出系统
break;
case 1: // 添加职工
break;
case 2: // 显示职工
break;
case 3: // 删除职工
break;
case 4: // 修改职工
break;
case 5: // 查找职工
break;
case 6: // 排序职工
break;
case 7: // 清空文件
break;
default:
system("cls");
break;
}
}
return 0;
}
5.2 实现退出功能
在workerManager.h 中提供退出系统的成员函数 void exitSystem();
// workerManager.h
#pragma once // 防止头文件重复包含
#include<iostream>
class workerManager {
public:
// 构造函数
workerManager();
// 展示菜单
void Show_Menu();
// 退出系统
void exitSystem();
// 析构函数
~workerManager();
};
在workerManager.cpp 中提供具体的功能实现:
// workerManager.cpp
// 退出系统的具体实现
void workerManager::exitSystem(){
std::cout << "欢迎下次使用:" << std::endl;
system("pause");
exit(0);
}
5.3 测试退出功能
在main函数(main函数位于 职工管理系统.cpp 文件)switch-case 语句分支0选项中,调用退出程序的接口:
运行测试效果如图:
六、 创建职工类
6.1 创建职工抽象类
职工的分类为:普通员工、经理、老板
将三种职工抽象到一个类(worker)中,利用多态管理不同职工种类
职工的属性为:职工编号、职工姓名、职工所在部门编号
职工的行为为:岗位职责信息描述,获取岗位名称
头文件文件夹下创建文件worker.h文件并且添加如下代码:
// worker.h
#pragma once
#include<iostream>
#include<string>
// 职工抽象基类
class Worker {
public:
// 显示职工个人信息
virtual void showinfo() = 0;
// 获取岗位名称
virtual std::string getDeptName() = 0;
private:
int m_Id; // 职工编号
std::string m_Name; // 职工姓名
int m_DeptId; // 职工所在部门名称编号
};
6.2 创建普通员工类
普通员工类继承职工抽象基类,并重写父类中的纯虚函数
在头文件和源文件的文件夹下分别创建 employee.h 和employee.cpp文件
employee.h 中代码如下:
// employee.h
#pragma once
#include"worker.h"
#include<iostream>
// 创建普通员工类
class Employee:public Worker {
public:
// 构造函数
Employee(int id, std::string name, int dId);
// id:员工编号
// name:员工姓名
// dId:员工部门编号
// 显示个人信息
void showinfo()override;
// 获取职工岗位名称
std::string getDeptName()override;
};
employee.cpp 中代码如下:
// employee.cpp
#include"employee.h"
#include<iostream>
// 构造函数
Employee::Employee(int id, std::string name, int dId) {
this->m_Id = id;
this->m_Name = name;
this->m_DeptId = dId;
}
// 显示个人信息
void Employee::showinfo(){
std::cout << "职工编号:" << this->m_Id
<< "\t职工姓名:" << this->m_Name
<< "\t岗位:" << this->getDeptName()
<< "\t岗位职责:完成经理交给的任务" << std::endl;
}
// 获取职工岗位名称
std::string Employee::getDeptName() {
return std::string("员工");
}
6.3 创建经理类
经理类继承职工抽象类,并重写父类中纯虚函数,和普通员工类似
在头文件和源文件的文件夹下分别创建manager.h和manager.cpp文件
manager.h中代码如下:
// manager.h
#pragma once
#include"worker.h"
#include<iostream>
// 创建经理类
class Manager :public Worker {
public:
// 构造函数
Manager(int id, std::string name, int dId)