C++和标准库速成(十一)——简单雇员系统
目录
- 1. 雇员记录系统
- 2. Employee类
- 2.1 Employee模块接口文件
- 2.1.1 实现细节
- 2.1.2 完整代码
- 2.2 Employ模块实现文件
- 2.2.1 实现细节
- 2.2.2 完整代码
- 2.3 Employee测试文件
- 3. Database类
- 3.1 Database模块接口文件
- 3.1.1 实现细节
- 3.1.2 完整代码
- 3.2 Database模块实现文件
- 3.2.1 实现细节
- 3.2.2 完整代码
- 3.3 Database测试文件
- 4. 用户界面
- 4.1 实现细节
- 4.2 完整代码
- 参考
1. 雇员记录系统
管理公司雇员记录的程序应该灵活并且具有有效的功能。这个程序包含的功能有:
1. 添加和解雇雇员
2. 雇员晋升和降级
3. 查看所有雇员,包括过去以及现在的雇员
4. 查看所有当前雇员
5. 查看所有以前雇员
程序的代码分为三部分:Employee类封装了单个雇员的信息,Database类管理公司的所有雇员,单独的UserInterface提供程序的交互接口。
2. Employee类
Employee类维护某个雇员的全部信息,该类的方法提供了查询以及修改信息的途径。Employee还知道如何在控制台显示自身。此外还存在调整雇员薪水和雇佣状态的方法。
2.1 Employee模块接口文件
2.1.1 实现细节
Employee模块接口文件定义了Employee类,此文件的各部分随后进行描述。文件的前几行如下:
export module employee;
// import <string>;
import std.core; // msvc
namespace Records {
第一行是模块声明,并声明该文件导出一个名为employee的模块,然后导入<string>标准库。此代码还声明花括号中包含的后续代码位于Records名称空间中,为使用特定代码,整个程序都会用到Records名称空间。
接下来,在Records名称空间内定义以下两个常量:
const int DefaultStartingSalary { 30'000 };
export const int DefaultRaiseAndDemeritAmount { 1'000 };
第一个常量代表新雇员的默认起薪,这个常量没有被导出,因为它不需要被本模块之外的代码访问。employee模块内的代码可以通过Records::DefaultStartingSalary访问它。
第二个常量是用于晋升或降级雇员的默认薪资涨幅或跌幅。此常量被导出,因此此模块外部的代码可以操作它。
接下来,声明Employee类及其public方法。
export class Employee {
public:
Employee(const std::string& firstName, const std::string& lastName);
void promote(const int raiseAmount = DefaultRaiseAndDemeritAmount);
void demote(const int demeritAmount = DefaultRaiseAndDemeritAmount);
void hire(); // hires or rehires the employee.
void fire(); // dismisses the employee.
void display() const; // output employee info to console.
// getters and setters
void setFirstName(const std::string& firstName);
const std::string& getFirstName() const;
void setLastName(const std::string& lastName);
const std::string& getLastName() const;
void setEmployeeNumber(const int employeeNumber);
int getEmployeeNumber() const;
void setSalary(const int newSalary);
int getSalary() const;
bool isHired() const;
提供一个接受名字和姓氏的构造函数。promote()和demote()方法都具有整型参数,其默认值等于DefaultRaiseAndDemeritAmount。这样,其他代码可以省略该参数,并且将自动使用默认值。还提供了雇佣和解雇雇员的方法,以及显示雇员相关信息的方法。许多获取器和设置器提供了更改信息或查询雇员当前信息的功能。
将数据成员声明为private,这样其他部分代码将无法直接修改它们。
private:
std::string m_firstName;
std::string m_lastName;
int m_employeeNumber { -1 };
int m_salary { DefaultStartingSalary };
bool m_hired { false };
};
}
获取器和设置器提供了修改或查询这些值的唯一public途径。数据成员在类定义中而非构造函数中进行初始化。默认情况下,新雇员无姓名,雇员编号为-1,起薪为默认值,状态为未受雇。
2.1.2 完整代码
export module employee;
// import <string>;
import std.core; // msvc
namespace Records {
const int DefaultStartingSalary { 30'000 };
export const int DefaultRaiseAndDemeritAmount { 1'000 };
export class Employee {
public:
Employee(const std::string& firstName, const std::string& lastName);
void promote(const int raiseAmount = DefaultRaiseAndDemeritAmount);
void demote(const int demeritAmount = DefaultRaiseAndDemeritAmount);
void hire(); // hires or rehires the employee.
void fire(); // dismisses the employee.
void display() const; // output employee info to console.
// getters and setters
void setFirstName(const std::string& firstName);
const std::string& getFirstName() const;
void setLastName(const std::string& lastName);
const std::string& getLastName() const;
void setEmployeeNumber(const int employeeNumber);
int getEmployeeNumber() const;
void setSalary(const int newSalary);
int getSalary() const;
bool isHired() const;
private:
std::string m_firstName;
std::string m_lastName;
int m_employeeNumber { -1 };
int m_salary { DefaultStartingSalary };
bool m_hired { false };
};
}
2.2 Employ模块实现文件
2.2.1 实现细节
模块实现文件的前几行如下:
module employee;
// import <iostream>;
// import <format>;
import std.core; // msvc
第一行指定了此源文件的模块,接下来是<iostream>和<format>标准库的导入。
构造函数接收姓和名,只设置相应的数据成员。
namespace Records {
Employee::Employee(const std::string& firstName, const std::string& lastName) :
m_firstName { firstName }, m_lastName { lastName } {
}
promote()和demote()方法只是用一些新值调用setSalary()方法。注意,整型参数的默认值不显示在源文件中;它们只能出现在函数声明中,不能出现在函数定义中。
void Employee::promote(int raiseAmount) {
setSalary(getSalary() + raiseAmount);
}
void Employee::demote(int demeritAmount) {
setSalary(getSalary() + demeritAmount);
}
hire()和fire()方法正确地设置了m_hired数据成员。
void Employee::hire() {
m_hired = true;
}
void Employee::fire() {
m_hired = false;
}
display()方法使用控制台输出流显示当前雇员的信息。由于这段代码是Employee类的一部分,因此可直接访问数据成员,而不需要使用获取器。然而,使用获取器和设置器(当存在时)时是一种好的风格,甚至在类的内部也是如此。
void Employee::display() const {
std::cout << std::format("Employee: {}, {}\n", getLastName(), getFirstName());
std::cout << "---------------------------------------------\n";
std::cout << (isHired() ? "Current Employee\n" : "Former Employee\n");
std::cout << std::format("Employee Number: {}\n", getEmployeeNumber());
std::cout << std::format("Salary: ${}\n\n", getSalary());
}
最后,许多获取器和设置器执行获取值以及设置值的任务。
void Employee::setFirstName(const std::string& firstName) {
m_firstName = firstName;
}
const std::string& Employee::getFirstName() const {
return m_firstName;
}
void Employee::setLastName(const std::string& lastName) {
m_lastName = lastName;
}
const std::string& Employee::getLastName() const {
return m_lastName;
}
void Employee::setEmployeeNumber(const int employeeNumber) {
m_employeeNumber = employeeNumber;
}
int Employee::getEmployeeNumber() const {
return m_employeeNumber;
}
void Employee::setSalary(const int newSalary) {
m_salary = newSalary;
}
int Employee::getSalary() const {
return m_salary;
}
bool Employee::isHired() const {
return m_hired;
}
}
即使这些方法微不足道,但是使用这些微不足道的获取器和设置器,仍然优于将数据成员设为public。例如,你将来可能想在setSalary()方法中执行边界检查,获取器和设置器也能简化调试,因为可在其中设置断点,在检索或设置值时检查它们。另一个原因是决定修改类中存储数据的方式时,只需要修改这些获取器和设置器,而其他使用该类的代码可以保持不变。
2.2.2 完整代码
module employee;
// import <iostream>;
// import <format>;
import std.core; // msvc
namespace Records {
Employee::Employee(const std::string& firstName, const std::string& lastName) :
m_firstName { firstName }, m_lastName { lastName } {
}
void Employee::promote(int raiseAmount) {
setSalary(getSalary() + raiseAmount);
}
void Employee::demote(int demeritAmount) {
setSalary(getSalary() + demeritAmount);
}
void Employee::hire() {
m_hired = true;
}
void Employee::fire() {
m_hired = false;
}
void Employee::display() const {
std::cout << std::format("Employee: {}, {}\n", getLastName(), getFirstName());
std::cout << "---------------------------------------------\n";
std::cout << (isHired() ? "Current Employee\n" : "Former Employee\n");
std::cout << std::format("Employee Number: {}\n", getEmployeeNumber());
std::cout << std::format("Salary: ${}\n\n", getSalary());
}
void Employee::setFirstName(const std::string& firstName) {
m_firstName = firstName;
}
const std::string& Employee::getFirstName() const {
return m_firstName;
}
void Employee::setLastName(const std::string& lastName) {
m_lastName = lastName;
}
const std::string& Employee::getLastName() const {
return m_lastName;
}
void Employee::setEmployeeNumber(const int employeeNumber) {
m_employeeNumber = employeeNumber;
}
int Employee::getEmployeeNumber() const {
return m_employeeNumber;
}
void Employee::setSalary(const int newSalary) {
m_salary = newSalary;
}
int Employee::getSalary() const {
return m_salary;
}
bool Employee::isHired() const {
return m_hired;
}
}
2.3 Employee测试文件
当编写一个类时,最好对其进行独立测试。以下代码在main()函数中针对Employee类执行了一些简单操作。当确信Employee类可正常运行后,应该删除这个文件,或将这个文件注释掉,这样做不会编译具有多个main()函数的代码。
// import <iostream>;
import std.core; // msvc
import employee;
int main() {
std::cout << "Testing the Employee class.\n";
Records::Employee emp { "Jane", "Doe" };
emp.setFirstName("John");
emp.setLastName("Doe");
emp.setEmployeeNumber(7);
emp.setSalary(50'000);
emp.promote();
emp.promote(50);
emp.hire();
emp.display();
}
3. Database类
3.1 Database模块接口文件
Database类使用标准库中的std::vector类来存储Employee对象。
3.1.1 实现细节
下面是Database模块接口文件中的前几行:
export module database;
// import <string>;
// import <vector>;
import std.core;
import employee;
namespace Records {
const int FirstEmployeeNumber { 1'000 };
由于数据库会自动给新雇员指定一个雇员号,因此定义一个常量作为编号的开始。
接下来,Database类被定义和导出。
export class Database {
public:
Employee& addEmployee(const std::string& firstName, const std::string& lastName);
Employee& getEmployee(int employeeNumber);
Employee& getEmployee(const std::string& firstName, const std::string& lastName);
数据库可根据提供的姓名方便地添加一个新雇员。为方便起见,这个方法返回一个新雇员的引用。外部代码也可以通过调用getEmployee()方法来获得雇员的引用。为这个方法声明了两个版本,一个允许按雇员号进行检索,另一个要求提供雇员的姓名。
由于数据库是所有雇员记录的中心存储库,因此具有输出所有雇员、当前在职雇员以及已离职雇员的方法。
void displayAll() const;
void displayCurrent() const;
void displayFormer() const;
最后,private数据成员被定义如下。
private:
std::vector<Employee> m_employees;
int m_nextEmployeeNumber { FirstEmployeeNumber };
};
}
m_employees数据成员包含Employee对象,数据成员m_nextEmployeeNumber跟踪新雇员的雇员号,使用FirstEmployeeNumber常量进行初始化。
3.1.2 完整代码
export module database;
// import <string>;
// import <vector>;
import std.core;
import employee;
namespace Records {
const int FirstEmployeeNumber { 1'000 };
export class Database {
public:
Employee& addEmployee(const std::string& firstName, const std::string& lastName);
Employee& getEmployee(int employeeNumber);
Employee& getEmployee(const std::string& firstName, const std::string& lastName);
void displayAll() const;
void displayCurrent() const;
void displayFormer() const;
private:
std::vector<Employee> m_employees;
int m_nextEmployeeNumber { FirstEmployeeNumber };
};
}
3.2 Database模块实现文件
3.2.1 实现细节
addEmployee()方法的实现如下。
module database;
// import <stdexcept>;
import std.core;
namespace Records {
Employee& Database::addEmployee(const std::string& firstName, const std::string& lastName) {
Employee theEmployee { firstName, lastName };
theEmployee.setEmployeeNumber(m_nextEmployeeNumber++);
theEmployee.hire();
m_employees.push_back(theEmployee);
return m_employees.back();
}
addEmployee()方法创建一个新的Employee对象,在其中填充信息并将其添加到vector中。注意,使用了这个方法后,数据成员m_nextEmployeeNumber的值会递增,因此下一个雇员获得新编号。vector的back()方法返回vector中最后一个元素的引用,即最新添加的雇员。
getEmployee()方法之一的实现如下。第二版本以类似的方法实现。它们都使用基于范围的for循环遍历m_employees中的所有雇员,并检查Employee是否与传递给该方法的信息匹配。如果找不到匹配项,则会引发异常。
Employee& Database::getEmployee(int employeeNumber) {
for (auto& employee : m_employees) {
if (employee.getEmployeeNumber() == employeeNumber) {
return employee;
}
}
throw std::logic_error { "No employee found." };
}
所有显示方法都采取相似的算法。这些方法遍历所有雇员,如果符合显示标准,就通知雇员将自身显示到控制台中。displayFormer()类似于displayCurrent()。
void Database::displayAll() const {
for (auto& employee : m_employees) {
employee.display();
}
}
void Database::displayCurrent() const {
for (auto& employee : m_employees) {
if (employee.isHired()) {
employee.display();
}
}
}
3.2.2 完整代码
module database;
// import <stdexcept>;
import std.core;
namespace Records {
Employee& Database::addEmployee(const std::string& firstName, const std::string& lastName) {
Employee theEmployee { firstName, lastName };
theEmployee.setEmployeeNumber(m_nextEmployeeNumber++);
theEmployee.hire();
m_employees.push_back(theEmployee);
return m_employees.back();
}
Employee& Database::getEmployee(int employeeNumber) {
for (auto& employee : m_employees) {
if (employee.getEmployeeNumber() == employeeNumber) {
return employee;
}
}
throw std::logic_error { "No employee found." };
}
Employee& Database::getEmployee(const std::string& firstName, const std::string& lastName) {
for (auto& employee : m_employees) {
if (employee.getFirstName() == firstName && employee.getLastName() == lastName) {
return employee;
}
}
throw std::logic_error { "No employee found." };
}
void Database::displayAll() const {
for (auto& employee : m_employees) {
employee.display();
}
}
void Database::displayCurrent() const {
for (auto& employee : m_employees) {
if (employee.isHired()) {
employee.display();
}
}
}
void Database::displayFormer() const {
for (auto& employee : m_employees) {
if (!employee.isHired()) {
employee.display();
}
}
}
}
3.3 Database测试文件
用于数据基本功能的简单测试如下所示。
// import <iostream>;
import std.core;
import database;
int main() {
Records::Database myDB;
Records::Employee& emp1 { myDB.addEmployee("Greg", "Wallis") };
emp1.hire();
Records::Employee& emp2 { myDB.addEmployee("Marc", "White") };
emp2.setSalary(100'000);
Records::Employee& emp3 { myDB.addEmployee("John", "Doe") };
emp3.setSalary(10'000);
emp3.promote();
std::cout << "all employees:\n\n";
myDB.displayAll();
std::cout << "\ncurrent employees:\n\n";
myDB.displayCurrent();
std::cout << "\nformer employees:\n\n";
myDB.displayFormer();
}
4. 用户界面
4.1 实现细节
程序的最后一部分是基于菜单的用户界面,可让用户方便地使用雇员数据库。
main()函数是一个显示菜单的循环,执行被选中的操作,然后重新开始循环。对于大多数的操作都定义了独立的函数。对于显示雇员之类的简单操作,则将实际代码放在对应的情况中。
import database;
int displayMenu();
void doHire(Records::Database& db);
void doFire(Records::Database& db);
void doPromote(Records::Database& db);
int main() {
Records::Database employeeDB;
bool done{ false };
while (!done) {
int selection{ displayMenu() };
switch (selection)
{
case 0:
done = true;
break;
case 1:
doHire(employeeDB);
break;
case 2:
doFire(employeeDB);
break;
case 3:
doPromote(employeeDB);
break;
case 4:
employeeDB.displayAll();
break;
case 5:
employeeDB.displayCurrent();
break;
case 6:
employeeDB.displayFormer();
break;
default:
std::cerr << "Unknown command.\n";
break;
}
}
}
displayMenu()函数输出菜单并获取用户输入。在此假定用户能够正确输入,当需要一个数字时就会输入一个数字,这一点很重要。后面会讲解如何防止输入错误信息。
int displayMenu() {
int selection{ -1 };
std::cout << "\nEmployee Database\n";
std::cout << "-------------------------\n";
std::cout << "1) Hire a new employee\n";
std::cout << "2) Fire an employee\n";
std::cout << "3) Promote an employee\n";
std::cout << "4) List all employees\n";
std::cout << "5) List all current employees\n";
std::cout << "6) List all former employees\n";
std::cout << "0) Quit\n\n";
std::cout << "---> ";
std::cin >> selection;
return selection;
}
doHire()函数获取用户输入的新雇员姓名,并通知数据库添加这个雇员。
void doFire(Records::Database& db) {
int employeeNumber { -1 };
std::cout << "Employee number? ";
std::cin >> employeeNumber;
try {
auto& emp { db.getEmployee(employeeNumber) };
emp.fire();
std::cout << std::format("Employee {} terminated.\n", employeeNumber);
} catch ( const std::logic_error& exception ) {
std::cerr << std::format("Unable to terminate employee: {}\n", exception.what());
}
}
doFire()和doPromote()都会要求数据库根据雇员号找到雇员记录,然后使用Employee对象的public方法进行修改。
void doFire(Records::Database& db) {
int employeeNumber { -1 };
std::cout << "Employee number? ";
std::cin >> employeeNumber;
try {
auto& emp { db.getEmployee(employeeNumber) };
emp.fire();
std::cout << std::format("Employee {} terminated.\n", employeeNumber);
} catch ( const std::logic_error& exception ) {
std::cerr << std::format("Unable to terminate employee: {}\n", exception.what());
}
}
void doPromote(Records::Database& db) {
int employeeNumber { -1 };
std::cout << "Employee number? ";
std::cin >> employeeNumber;
int raiseAmount { -1 };
std::cout << "How much of a raise? ";
std::cin >> raiseAmount;
try {
auto& emp { db.getEmployee(employeeNumber) };
emp.promote(raiseAmount);
} catch (const std::logic_error& exception) {
std::cerr << std::format("Unable to terminate employee: {}\n", exception.what());
}
}
4.2 完整代码
import database;
int displayMenu();
void doHire(Records::Database& db);
void doFire(Records::Database& db);
void doPromote(Records::Database& db);
int main() {
Records::Database employeeDB;
bool done{ false };
while (!done) {
int selection{ displayMenu() };
switch (selection)
{
case 0:
done = true;
break;
case 1:
doHire(employeeDB);
break;
case 2:
doFire(employeeDB);
break;
case 3:
doPromote(employeeDB);
break;
case 4:
employeeDB.displayAll();
break;
case 5:
employeeDB.displayCurrent();
break;
case 6:
employeeDB.displayFormer();
break;
default:
std::cerr << "Unknown command.\n";
break;
}
}
}
int displayMenu() {
int selection{ -1 };
std::cout << "\nEmployee Database\n";
std::cout << "-------------------------\n";
std::cout << "1) Hire a new employee\n";
std::cout << "2) Fire an employee\n";
std::cout << "3) Promote an employee\n";
std::cout << "4) List all employees\n";
std::cout << "5) List all current employees\n";
std::cout << "6) List all former employees\n";
std::cout << "0) Quit\n\n";
std::cout << "---> ";
std::cin >> selection;
return selection;
}
void doHire(Records::Database& db) {
std::string firstName;
std::string lastName;
std::cout << "First Name? ";
std::cin >> firstName;
std::cout << "Last Name? ";
std::cin >> lastName;
auto& employee { db.addEmployee(firstName, lastName) };
std::cout << std::format("Hired employee {} {} with employee number {}.\n",
firstName, lastName, employee.getEmployeeNumber());
}
void doFire(Records::Database& db) {
int employeeNumber { -1 };
std::cout << "Employee number? ";
std::cin >> employeeNumber;
try {
auto& emp { db.getEmployee(employeeNumber) };
emp.fire();
std::cout << std::format("Employee {} terminated.\n", employeeNumber);
} catch ( const std::logic_error& exception ) {
std::cerr << std::format("Unable to terminate employee: {}\n", exception.what());
}
}
void doPromote(Records::Database& db) {
int employeeNumber { -1 };
std::cout << "Employee number? ";
std::cin >> employeeNumber;
int raiseAmount { -1 };
std::cout << "How much of a raise? ";
std::cin >> raiseAmount;
try {
auto& emp { db.getEmployee(employeeNumber) };
emp.promote(raiseAmount);
} catch (const std::logic_error& exception) {
std::cerr << std::format("Unable to terminate employee: {}\n", exception.what());
}
}
参考
[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)