当前位置: 首页 > article >正文

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高级编程(第五版)


http://www.kler.cn/a/597560.html

相关文章:

  • 360度用户信息赋能老客运营自动化
  • 【AVRCP】深度剖析 AVRCP 中 Generic Access Profile 的要求与应用
  • vue如何获取 sessionStorage的值,获取token
  • 【分布式】冰山(Iceberg)与哈迪(Hudi)对比的基准测试
  • MyBatis-Plus的加载和初始化
  • OpenCV Imgproc 模块使用指南(Python 版)
  • S32K144外设实验(二):ADC单通道单次采样(软件触发)
  • 基于 pyflink 的算法工作流设计和改造
  • OpenCV Video 模块使用指南(Python 版)
  • 第七节 MATLAB数据类型
  • Git复习
  • 思源配置阿里云 OSS 踩坑记
  • leetcode—203. 移除链表元素(数据结构算法)
  • Unity urp实现红外效果(分层渲染,特效覆盖)
  • Python入门基础
  • Java主流开发框架之请求响应常用注释
  • 避雷 :C语言中 scanf() 函数的错误❌使用!!!
  • Axios 和 跨域 这两个概念
  • IDEA 快捷键ctrl+shift+f 无法全局搜索内容的问题及解决办法
  • 自用基于 TypeScript 的 WebSocket 客户端封装