• 数据结构课程设计题目——链表综合算法设计、带头双向循环链表、插入、显示、删除、修改、排序


    链表综合算法设计——校园人事信息系统

    1.要求

      课程设计题目1–链表综合算法设计

      一、设计内容

      已知简单的人事信息系统中职工记录包含职工编号(no)、职工姓名(name)、部门名称(depname)、职称(title)和工资数(salary)等信息(可以增加其他信息),设计并完成一个简单的人事信息管理系统,要求完成但不限于以下功能:
      (1) 增加一个职工信息;
      (2) 显示所有职工信息;
      (3) 按部门名称分类显示该部门所有职工信息;
      (4) 按部门显示各部门职工工资总额;
      (5) 删除职工信息(可以删除符合条件的一批记录)
      (6) 按职称调整工资;
      (7) 可自行增加功能(例如按职工编号排序等其他功能)

      二、设计要求

      以菜单形式显示,主菜单包括(可以扩展)(1:增加职工信息 2:删除员工信息 3:查找职工信息 4:工资调整 5:统计分析 0:退出)相应的主菜单可以设计子菜单。

                

    2.代码实现(以带头双向循环链表为例)

      除了mian函数和system_start函数外,其他的函数都默认在类中实现

    2.1基本程序结构

      这段代码是一个简单的C++程序,主要包含一个菜单的系统,用于处理输入信息。

      下面是代码的详细解释:

      void system_start(): 这是一个函数,用于启动菜单的程序。

      int main(): 这是程序的主函数。

      system_start 程序开始的函数:

      定义了两个整数变量 option 和 flag。option 用于存储用户的选择,而 flag 用于标记是否退出系统。在创建了链表之后,我们就进入一个无限循环,直到用户选择退出。调用 Init_menu() 函数来初始化菜单。

      接着提示用户输入选项。使用 switch 语句根据用户的选择执行相应的操作。每个 case 对应于一个特定的操作。如果用户选择0,flag 被设置为1,表示用户希望退出系统。检查 flag 是否为1。如果是,打印退出成功的消息并跳出循环。

    #define _CRT_SECURE_NO_WARNINGS 1
    
    #include
    using namespace std;
    
    #include"LinkList.h"
    
    void system_start()
    {
    	int option = 0, flag = 0;
    	staff_linklist::LinkList_Information lt;
    	while(1)
    	{
    		//system("cls");
    		Init_menu();
    		cout << "请输入:";
    		if (cin >> option);
    		switch (option)
    		{
    		case(1):
    			//lt.push_back();
    			break;
    		case(2):
    			//lt.Print_Information();
    			break;
    		case(3):
    			//lt.Print_Depname_Information();
    			break;
    		case(4):
    			//lt.Print_Depname_Salary();
    			break;
    		case(5):
    			//lt.Erase_Person_Information();
    			break;
    		case(6):
    			//lt.Erase_All_Informations();
    			break;
    		case(7):
    			//lt.Adjust_Person_Salary();
    			break;
    		case(8):
    			//lt.Print_Person_No_Information();
    			break;
    		case(9):
    			//lt.Print_Person_Salary_Information();
    			break;
    		case(0):
    			flag = 1;
    			break;
    		default:
    			cout << "输入错误,请重新输入\n";
    			break;
    		}
    		if (flag == 1)
    		{
    			cout << "退出成功\n";
    			break;
    		}
    	} 
    }
    
    int main()
    {
    	system_start();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

      根据自己的喜好设计打印菜单即可:

    //打印菜单
    void Init_menu()
    {
    	printf("————————————————————————————————————————\n");
    	printf("|         校园人事信息系统菜单         |\n");
    	printf("|   ————————————————————————————————   |\n");
    	printf("|       输入1:增加一个职工信息        |\n");
    	printf("|       输入2:显示所有职工信息        |\n");
    	printf("|       输入3:按部门分类显示信息      |\n");
    	printf("|       输入4:按部门工资显示信息      |\n");
    	printf("|       输入5:删除职工信息            |\n");
    	printf("|       输入6:删除所有职工信息        |\n");
    	printf("|       输入7:按职称调整工资          |\n");
    	printf("|       输入8:按职工编号排序          |\n");
    	printf("|       输入9:按职工工资排序          |\n");
    	printf("|       输入0:退出系统                |\n");
    	printf("————————————————————————————————————————\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

                

    2.2节点和链表的初始化

      创建链表节点并且实现节点的构造函数:

      根据上面提供的题目,我们创建一个结构体用于表示一个职工的信息。这个结构体包含了一些数据成员,职工编号、姓名、性别、部门、职称、工资,以及两个指针,指向职工信息链表中的前一个和后一个节点,用于双向链表的连接。

      注意:构造函数中使用了memcpy函数来初始化_name和_sex成员。memcpy函数用于从源地址复制指定数量的字节到目标地址。当使用memcpy来复制字符串时,且必须确保目标地址有足够的空间来存放源地址的字符串,并且源地址的字符串是以null字符(\0)结尾的。

      这里为Staff_Information_Node结构体定义一个构造函数有很多好处:

      (1)初始化成员变量:构造函数允许你在创建结构体对象时,直接为成员变量赋予初始值。这样,你就不需要在创建对象后再逐一设置每个成员变量的值,使代码更简洁。

      (2)提高代码的可读性和可维护性,保证成员变量的正确初始化:通过构造函数,可以清晰地看到在创建对象时需要哪些信息,以及这些信息是如何被用来初始化成员变量的。如果没有构造函数,那么成员变量的初始值将是未定义的,这可能会导致程序行为不确定

      (3)提供类型检查和安全性,也便于扩展和修改:在构造函数中,你可以对传入的参数进行类型检查,以确保它们是正确的类型。如果需要在将来对结构体进行扩展或修改,那么构造函数可以更容易地进行相应的调整。

    //职工信息节点
    typedef struct Staff_Information_Node
    {
    	size_t _no;       //编号
    	char _name[100];  //姓名
    	char _sex[10];    //性别
    	string _depname;  //部门
    	string _title;    //职称
    	size_t _salary;   //工资
    	struct Staff_Information_Node* _prev;//前驱节点
    	struct Staff_Information_Node* _next;//后继节点
    
    	//职工信息节点的构造函数    初始化列表
    	Staff_Information_Node(size_t no, const char* name, const char* sex,
    		const string& depname, const string& title, size_t salary,
    		Staff_Information_Node* prev = nullptr, Staff_Information_Node* next = nullptr)
    		:_no(no)
    		, _depname(depname)
    		, _title(title)
    		, _salary(salary)
    		, _next(next)
    	{
    		memcpy(_name, name, 20);//用内存函数初始化数组,实现深拷贝
    		memcpy(_sex, sex, 10);
    	}
    }SNode;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

      
    创建链表并且实现链表的构造函数和析构函数:

      这里是一个C++的链表类定义,其中使用了之前定义的Staff_Information_Node结构体。这个链表类包含了一个私有成员变量_head,表示链表的头结点,以及一个_size成员变量,表示链表的大小。

      在这个构造函数中,你创建了一个新的SNode对象作为头结点,上面写的SNode的构造函数就可以直接对头结点进行初始化了,同时初始化了成员变量size。 然后,你将头结点的_prev和_next指针都指向了自身,形成一个循环链表。

      在析构函数中,首先将_size成员变量设置为0,然后遍历链表,逐个删除链表中的元素,并释放它们所占用的内存空间。 最后删除了头结点,释放了它所占用的内存空间,并将_head指针设置为nullptr,以确保头结点不再占用任何内存空间。

      这样子析构函数就可以能够正确地释放链表中的所有节点和头结点的内存空间,避免内存泄漏的问题。

    namespace staff_linklist
    {
    	//链表类
    	class LinkList_Information
    	{
    	public:
    		//构造函数
    		LinkList_Information()
    		{
    			_head = new SNode(0, "头结点", "000", "000", "000", 000);
    			//创建初始化头结点
    			_head->_prev = _head;
    			_head->_next = _head;
    
    			_size=0;
    		}	
    
    		//析构函数  
    		~LinkList_Information()
    		{
    			SNode* cur = _head->_next;
    			_size = 0;
    
    			//挨个删除链表中的元素,释放空间,避免内存泄漏
    			while (cur != _head)
    			{
    				SNode* tmp = cur;
    				cur = cur->_next;
    
    				delete tmp;
    				tmp = nullptr;
    			}
    
    			delete _head; // 释放头结点的内存  
    			_head = nullptr;
    		}
    		
    private:
    		SNode* _head;//头结点、哨兵位
    		size_t _size;
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

                

    2.3链表的插入

    实现链表节点的创建和插入:

      Create_SNode函数用于创建一个新的SNode对象。 首先,它提示用户输入一些信息,并使用cin从标准输入流( while (!(cin >> no >> name >> sex >> depname >> title >> salary)))中读取这些信息。如果输入失败,它会清除cin的错误状态,并忽略输入流中的一行,然后输出一个错误消息,并递归调用自身以重新输入。使用输入的信息创建一个新的SNode对象,并返回该对象的指针。

      push_back函数用于在链表的尾部插入一个新的节点。 首先,它调用Create_SNode函数创建一个新的节点。如果创建节点失败,它会直接返回。接着,它找到链表的尾节点,并将新节点连接到尾节点和头结点之间。最后,它增加链表的大小,并输出一个成功消息。

    //创建一个新的节点
    SNode* Create_SNode()
    {
    	size_t no, salary;
    	char name[100], sex[10];
    	string depname, title;
    	cout << "请输入编号、姓名、性别、部门、职称、工资" << endl;
    
    	//如果使用cin想要接收char类型的变量
    	//但是却使用了int类型的变量来接收,这可能会导致死循环
    	while (!(cin >> no >> name >> sex >> depname >> title >> salary))
    	{
    		cin.clear();
    		cin.ignore(256, '\n'); // ignore to next line  
    		cerr << "输入失败,请重新输入:" << endl;
    	}
    
    	//创建新节点
    	SNode* newnode = new SNode(no, name, sex, depname, title, salary);
    	if (newnode == nullptr)
    	{
    		perror("创建节点失败\n");
    		return nullptr;
    	}
    
    	return newnode;
    }
    
    //尾插
    void push_back()
    {
    	SNode* newnode = Create_SNode();//创建节点
    	if (newnode == nullptr)
    	{
    		return;
    	}
    
    	//找到尾结点
    	SNode* tail = _head->_prev;
    
    	//连接尾结点
    	tail->_next = newnode;
    	newnode->_prev = tail;
    
    	//链接头结点
    	_head->_prev = newnode;
    	newnode->_next = _head;
    
    	_size++;
    
    	cout << "添加成功\n";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    一个小优化:

      为了防止之后的按部门打印中,因为没有对应的部门而导致打印的信息变少。我们可以强制检查,输入的信息的部门是否存在且合理。

      使用vector存储所有的部门,这里可以使用static修饰,将数据存放在静态区中,减少程序运行消耗。

    static vector<string> Depname={ "教务处","学生处","人事处","总务处","纪检部"};
    static int Depname_nember = Depname.size();
    
    • 1
    • 2

      我们使用了一个名为Depname的数组来存储有效的部门名称,并使用一个名为Depname_nember的变量来存储数组的大小。然后,使用c_str()函数将输入的部门名称字符串depname转换为C风格字符串,并与Depname数组中的每个元素进行比较。 这样就可以检查是否符合输入的部门信息。

      注意:使用了(depname).c_str()来将depname字符串转换为C风格字符串,然后与Depname[i]进行比较。可能是不安全的,因为c_str()函数返回一个指向内部缓冲区的指针,而这个缓冲区可能在后续操作中被修改或销毁。 直接比较即可 if (Depname[i] == depname)

    //检查,如果输入没有该部门,就重新输入
    for (int i = 0; i < Depname_nember; i++)
    {
    	if (Depname[i] == (depname).c_str())
    	{
    		break;
    	}
    	
    	//if (i == Depname_nember - 1)
    	if (Depname[i] == depname)
    	{
    		cout << "没有该部门,输入失败,请重新输入:\n";
    		return Create_SNode();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

                

    2.4链表的显示

    直接打印链表中的成员:

      Print_Person_Information函数用于打印单个节点的个人信息。 为了排列整齐,我们使用了printf函数来格式化输出,其中%-6d、%-6s、%-6s、%-8s、%-10s和%-5d分别表示输出的字段宽度和对齐方式。它使用了(cur->_depname).c_str()和(cur->_title).c_str()来将std::string类型的部门名称和职称转换为C风格字符串,以便与printf函数一起使用。

      Print_Information函数用于打印整个链表中的个人信息。 首先检查链表是否为空,如果为空,则输出一条消息并直接返回。否则,它输出表头和分隔线,然后遍历链表中的每个节点,并调用Print_Person_Information函数来打印每个节点的个人信息。最后,它输出分隔线。

      这里我们将Print_Person_Information和Print_Information函数分离,通过将打印个人信息的功能封装在Print_Person_Information函数中,我们就可以在其他地方重用它,而无需重复编写相同的代码。这有助于减少重复并降低出错的可能性。而且通过将打印功能分离为两个函数,我们可以更灵活地处理不同的打印需求。

    //打印个人信息
    void Print_Person_Information(SNode* cur)
    {
    	printf("%-6d %-6s %-6s %-8s %-10s %-5d\n", cur->_no, cur->_name,
    		cur->_sex, (cur->_depname).c_str(), (cur->_title).c_str(), cur->_salary);
    }
    
    //打印函数
    void Print_Information()
    {
    	SNode* cur = _head->_next;
    
    	if (cur == _head)
    	{
    		cout << "该系统中信息为空\n";
    		return;
    	}
    
    	cout << "人事信息系统人员名单\n";
    	printf("———————————————————————————————————-----——-———\n");
    	printf("编号   姓名   性别   部门     职称       工资\n");
    	while (cur != _head)
    	{
    		Print_Person_Information(cur);
    		cur = cur->_next;
    	}
    	printf("——————————————————————————————————————------——\n");
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

      
    按部门打印链表中的成员:

      循环逻辑和上面的代码一样,只需要再添加一层循环,分别打印各自的部门即可。

      外层循环遍历Depname向量中的每个部门名称。在每次外层循环迭代中,内层循环遍历链表中的每个节点。

      对于链表中的每个节点,通过比较节点的部门名称与当前外层循环中的部门名称,判断节点是否属于当前部门。如果节点属于当前部门,则调用Print_Person_Information函数打印该节点的个人信息。打印完一个部门的信息后,输出两个空行以分隔不同部门的信息。

    static vector<string> Depname={ "教务处","学生处","人事处","总务处","纪检部"};
    static int Depname_nember = Depname.size();
    
    • 1
    • 2
    for (int i = 0; i < Depname_nember; i++)
    {
    	//双重循环,依次打印各部门的信息
    	SNode* _cur = _head->_next;
    	printf("\t\t%s:\n", (Depname[i]).c_str());
    	while (_cur != _head)
    	{
    		//判断部门
    		if (_cur->_depname == Depname[i])
    		{
    			Print_Person_Information(_cur);
    		}
    
    		_cur = _cur->_next;
    	}
    	cout << endl << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      
    按部门打印链表中的成员和工资总和:

      这段代码和前面的代码逻辑也是一样的,在前面的基础上增加了统计每个部门的工资总额和所有部门的工资总额的功能。

      我们增加了计数器money和sum,依次来统计不同部门工资的总额和所有部门的工资总额。在外层循环中,定义了一个sum变量用于记录所有部门的工资总额。在内层循环中,定义了一个money变量用于记录当前部门的工资总额。

      在判断节点是否属于当前部门后,除了打印该节点的个人信息,还将节点的工资累加到money变量中。内层循环结束后,将money变量累加到sum变量中,并输出当前部门的工资总额。外层循环结束后,输出所有部门的工资总额。

    static vector<string> Depname={ "教务处","学生处","人事处","总务处","纪检部"};
    static int Depname_nember = Depname.size();
    
    • 1
    • 2
    int sum = 0;
    for (int i = 0; i < Depname_nember; i++)
    {
    	SNode* _cur = _head->_next;
    	int money = 0;
    	printf("\t\t%s:\n", (Depname[i]).c_str());
    	while (_cur != _head)
    	{
    		if (_cur->_depname == Depname[i])
    		{
    			Print_Person_Information(_cur);
    			money += _cur->_salary;
    		}
    
    		_cur = _cur->_next;
    	}
    	sum += money;
    	printf("%s工资总计:%d\n", (Depname[i]).c_str(), money);
    	cout << endl << endl;
    }
    printf("所有部门工资统计:%d\n", sum);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

                

    2.5链表的删除

    链表中单个节点的删除:

      这段代码实现了通过用户输入的编号来删除节点。 删除操作和之前链表删除的逻辑类似,先查找再删除。我们定义了两个指针prev和cur,分别指向链表的头部和第一个节点。如果链表为空,函数输出一条消息并直接返回。否则,函数遍历链表,查找编号与输入编号匹配的节点。

      如果找到匹配的节点,函数执行双向循环链表删除操作,将前一个节点的_next指针指向当前节点的下一个节点,并将下一个节点的_prev指针指向当前节点的前一个节点。

      函数释放当前节点的内存,并将cur指针更新为前一个节点的下一个节点。如果遍历完整个链表都没有找到匹配的节点,函数输出一条消息表示删除失败。最后,函数输出一条消息表示删除成功或失败。

      注意:这里在输入之前进行了这个操作:cin.ignore(256, '\n');

      这段代码的作用是忽略输入流中的字符,直到遇到换行符(\n)为止,最多忽略256个字符。

      在C++中,cin.ignore()函数用于从输入流中忽略(即丢弃)一个或多个字符。它接受两个参数:第一个参数是要忽略的最大字符数,第二个参数是终止字符。在这个例子中,256是要忽略的最大字符数,\n是终止字符。

      这段代码通常用于清除输入缓冲区中的残留字符,例如在读取用户输入之前清除上一次输入留下的换行符或其他无关字符。这样可以确保接下来的输入操作不会受到这些残留字符的干扰。

    //删除个人信息
    void Erase_Person_Information()
    {
    	cin.ignore(256, '\n');
    	cout << "请输入要删除人的编号:\n";
    	int num = 0,flag = -1;
    	cin >> num;
    
    	SNode* prev = _head;
    	SNode* cur = _head->_next;
    
    	if (cur == _head)
    	{
    		cout << "该系统中信息为空,无法删除\n";
    		return;
    	}
    
    	while (cur != _head)
    	{
    		if (cur->_no == num)
    		{
    			SNode* tmp = cur;
    			flag = 1;
    
    			//双向循环链表删除操作
    			cur->_next->_prev = prev;
    			prev->_next = cur->_next;
    
    			cur = prev->_next;
    
    			//string类型的成员变量是单个对象,而不是数组,
    			//所以应该使用delete来释放它们的内存。
    			//如果使用delete[]来释放string对象的内存,会导致未定义的行为和可能的内存泄漏。
    			delete tmp;
    			tmp = nullptr;
    		}
    		else
    		{
    			//遍历后面元素
    			prev = prev->_next;
    			cur = cur->_next;
    		}
    	}
    	if (flag==1) cout << "删除成功\n";
    	else cout << "删除失败,没有此人\n";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

      
    链表所有节点的删除:

      实现和析构函数类似:

      函数先检查链表是否为空。如果链表为空,函数输出一条消息并直接返回。如果链表不为空,函数将头节点的_next指针指向自身,将头节点的_prev指针指向自身,以便在删除过程中保持链表的完整性。然后,我们遍历链表,逐个删除节点并释放其内存,以避免内存泄漏。 最后,函数输出一条消息表示删除成功。

      和析构函数不同是是,我们再这里添加一个给用户输入二次确认的机会,防止误删操作,函数首先提示用户确认是否要删除所有信息,并输入Y以确认。如果用户输入的不是Y,函数输出一条消息并直接返回。

    //删除所有信息
    void Erase_All_Informations()
    {
    	cout << "是否要删除所有信息!\n删除请输入[Y]\n";
    	char ch;
    	cin.ignore(256, '\n');
    	cin >> ch;
    	if (ch != 'Y')
    	{
    		cout << "删除失败\n";
    		return;
    	}
    
    	SNode* cur = _head->_next;
    
    	if (cur == _head)
    	{
    		cout << "该系统中信息为空\n";
    		return;
    	}
    
    	_head->_next = _head;
    	_head->_prev = _head;
    
    	//挨个删除链表中的元素,释放空间,避免内存泄漏
    	while (cur != _head)
    	{
    		SNode* tmp = cur;
    		cur = cur->_next;
    
    		delete tmp;
    		tmp = nullptr;
    	}
    
    	cout << "删除成功\n";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

                

    2.6链表的修改

    按编号查找并且修改工资:

      修改的实现逻辑和查找逻辑完全一样,注意一些细节即可。

      函数首先提示用户输入要调整工资的节点的编号。然后,函数提示用户输入调整后的工资金额。函数定义了一个指针cur,指向链表的第一个节点,并定义了一个布尔变量flag,用于记录是否找到了要调整的节点。

      接着,我们遍历链表,查找编号与输入编号匹配的节点。如果找到匹配的节点,函数将节点的工资更新为输入的工资,并将flag设置为true。如果遍历完整个链表都没有找到匹配的节点,函数输出一条消息表示没有找到该节点。最后,函数输出一条消息表示调整成功或失败。

    //调整工资
    void Adjust_Person_Salary()
    {
    	cin.ignore(256, '\n');
    	cout << "请输入要调整工资的人的编号:\n";
    	int num = 0;
    	cin >> num;
    
    	cin.ignore(256, '\n');
    	cout << "请输入调整后的工资金额:\n";
    	int sal = 0;
    	cin >> sal;
    
    	SNode* cur = _head->_next;
    	bool flag = false;
    
    	//寻找需要更改信息的节点
    	while (cur != _head)
    	{
    		if (cur->_no == num)
    		{
    			flag = true;
    			cur->_salary = sal;
    		}
    
    		cur = cur->_next;
    	}
    
    	if (flag) cout << "修改成功\n";
    	else cout << "没有此人\n";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

                

    2.7链表的排序(仿函数实现)

      链表的排序可以使用很多的排序算法,但这里我们实现vector+sort+仿函数实现不同类型的排序功能。

      C++STL库中提供了很多的容器和算法,我们可以尝试将自定义类型SNode放入vector中,用vector的迭代器进行sort排序:

      函数我们创建了一个空的vector< SNode >,用于存储链表中的节点信息。然后函数遍历链表中的每个节点,将节点的信息复制到vector中。函数使用sort算法对vector进行排序。

    //按编号排序
    void Print_Person_No_Information()
    {
    	vector<SNode> v;
    	SNode* cur = _head->_next;
    	while (cur != _head)
    	{
    		v.push_back(*cur);
    		cur = cur->_next;
    	}
    	sort(v.begin(), v.end());
    	_sort_print(v);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      那么就有问题来了,vector中的元素是自定义类型SNode,不是内置类型,不可以使用sort直接进行排序,因为sort不知道我们对于SNode排序的依据是什么,所以我们引入仿函数来解决这个问题。

      
      什么是仿函数?

      仿函数(functor),就是行为类似函数的对象,即它们是具有类似函数调用操作的类的对象。C++的仿函数(functors)是函数对象(functionobjects),它们是重载了“()”运算符的类的对象。由于重载了“()”运算符,因此它们的使用形式看起来就像普通函数一样。

      
      我们定义了一个名为Compare_No的结构体,并在其中重载了operator()操作符。 重载的operator()操作符接受两个Staff_Information_Node类型的常量引用作为参数,并返回一个布尔值。操作符的比较逻辑是比较两个节点的编号,如果第一个节点的编号小于第二个节点的编号,则返回true,否则返回false。

    struct Compare_No
    {
    	//重载operator()操作符以实现函数比较操作
    	bool operator()(const Staff_Information_Node& p1,
    	 const Staff_Information_Node& p2)
    	{
    		return p1._no < p2._no;
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      
    排序的依据是Compare_No仿函数:

    sort(v.begin(), v.end(), Compare_No());
    
    • 1

    最后,函数调用_sort_print函数打印出排序后的人员名单。

    //打印排序结果(vector)
    void _sort_print(vector<SNode>& v)
    {
    	int len = v.size();
    	if (len == 0)
    	{
    		cout << "该系统中信息为空\n";
    		return;
    	}
    
    	cout << "人事信息系统人员名单\n";
    	printf("———————————————————————————————————-----——-———\n");
    	printf("编号   姓名   性别   部门     职称       工资\n");
    	for (int i = 0; i < len; i++)
    	{
    		Print_Person_Information(&v[i]);
    	}
    	printf("——————————————————————————————————————------——\n");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

                

    3.全部源码

    测试结果:

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
                

    LinkList.h

    #pragma once
    
    //
    //
    //
    
    #include
    #include
    #include
    #include
    #include
    
    static vector<string> Depname={ "教务处","学生处","人事处","总务处","纪检部"};
    static int Depname_nember = Depname.size();
    
    //打印菜单
    void Init_menu()
    {
    	printf("————————————————————————————————————————\n");
    	printf("|         校园人事信息系统菜单         |\n");
    	printf("|   ————————————————————————————————   |\n");
    	printf("|       输入1:增加一个职工信息        |\n");
    	printf("|       输入2:显示所有职工信息        |\n");
    	printf("|       输入3:按部门分类显示信息      |\n");
    	printf("|       输入4:按部门工资显示信息      |\n");
    	printf("|       输入5:删除职工信息            |\n");
    	printf("|       输入6:删除所有职工信息        |\n");
    	printf("|       输入7:按编号调整工资          |\n");
    	printf("|       输入8:按职工编号排序          |\n");
    	printf("|       输入9:按职工工资排序          |\n");
    	printf("|       输入0:退出系统                |\n");
    	printf("————————————————————————————————————————\n");
    }
    
    //职工信息节点
    typedef struct Staff_Information_Node
    {
    	size_t _no;       //编号
    	char _name[100];  //姓名
    	char _sex[10];    //性别
    	string _depname;  //部门
    	string _title;    //职称
    	size_t _salary;   //工资
    	struct Staff_Information_Node* _prev;//前驱节点
    	struct Staff_Information_Node* _next;//后继节点
    
    	//职工信息节点的构造函数    初始化列表
    	Staff_Information_Node(size_t no, const char* name, const char* sex,
    		const string& depname, const string& title, size_t salary,
    		Staff_Information_Node* prev = nullptr, Staff_Information_Node* next = nullptr)
    		:_no(no)
    		, _depname(depname)
    		, _title(title)
    		, _salary(salary)
    		, _next(next)
    	{
    		//assert (typeid(_no).name() == typeid(no).name());
    		memcpy(_name, name, 100);//用内存函数初始化数组,实现深拷贝
    		memcpy(_sex, sex, 10);
    	}
    }SNode;
    
    namespace staff_linklist
    {
    	//链表类
    	class LinkList_Information
    	{
    	public:
    		//构造函数
    		LinkList_Information()
    		{
    			_head = new SNode(0, "头结点", "000", "000", "000", 000);//初始化头结点
    			_head->_prev = _head;
    			_head->_next = _head;
    
    			_size = 0;
    		}
    
    		//析构函数  
    		~LinkList_Information()
    		{
    			SNode* cur = _head->_next;
    			_size = 0;
    
    			//挨个删除链表中的元素,释放空间,避免内存泄漏
    			while (cur != _head)
    			{
    				SNode* tmp = cur;
    				cur = cur->_next;
    
    				delete tmp;
    				tmp = nullptr;
    			}
    
    			delete _head; // 释放头结点的内存  
    			_head = nullptr;
    		}
    
    		//创建一个新的节点
    		SNode* Create_SNode()
    		{
    			size_t no, salary;
    			char name[100], sex[10];
    			string depname, title;
    			cout << "请输入编号、姓名、性别、部门、职称、工资" << endl;
    
    			//如果使用cin想要接收char类型的变量,但是却使用了int类型的变量来接收,这可能会导致死循环
    			while (!(cin >> no >> name >> sex >> depname >> title >> salary))
    			{
    				cin.clear();
    				cin.ignore(256, '\n'); // ignore to next line  
    				cerr << "输入失败,请重新输入:" << endl;
    				//return nullptr;
    			}
    
    			//检查,如果输入没有该部门,就重新输入
    			for (int i = 0; i < Depname_nember; i++)
    			{
    				//if (Depname[i] == (depname).c_str())
    				if (Depname[i] == depname)
    				{
    					break;
    				}
    
    				if (i == Depname_nember - 1)
    				{
    					cout << "没有该部门,输入失败,请重新输入:\n";
    					return Create_SNode();
    				}
    			}
    
    			//创建新节点
    			SNode* newnode = new SNode(no, name, sex, depname, title, salary);
    			if (newnode == nullptr)
    			{
    				perror("创建节点失败\n");
    				return nullptr;
    			}
    
    			return newnode;
    		}
    
    		//尾插
    		void push_back()
    		{
    			SNode* newnode = Create_SNode();//创建节点
    			if (newnode == nullptr)
    			{
    				perror("创建节点失败\n");
    				return;
    			}
    
    			//找到尾结点
    			SNode* tail = _head->_prev;
    
    			//连接尾结点
    			tail->_next = newnode;
    			newnode->_prev = tail;
    
    			//链接头结点
    			_head->_prev = newnode;
    			newnode->_next = _head;
    
    			_size++;
    
    			cout << "添加成功\n";
    		}
    
    		//打印个人信息
    		void Print_Person_Information(SNode* cur)
    		{
    			printf("%-6d %-6s %-6s %-8s %-10s %-5d\n", cur->_no, cur->_name,
    				cur->_sex, (cur->_depname).c_str(), (cur->_title).c_str(), cur->_salary);
    		}
    
    		//打印函数
    		void Print_Information()
    		{
    			SNode* cur = _head->_next;
    
    			if (cur == _head)
    			{
    				cout << "该系统中信息为空\n";
    				return;
    			}
    
    			cout << "人事信息系统人员名单\n";
    			printf("———————————————————————————————————-----——-———\n");
    			printf("编号   姓名   性别   部门     职称       工资\n");
    			while (cur != _head)
    			{
    				Print_Person_Information(cur);
    				cur = cur->_next;
    			}
    			printf("——————————————————————————————————————------——\n");
    
    		}
    
    		//打印部门信息
    		void Print_Depname_Information()
    		{
    			SNode* cur = _head->_next;
    
    			if (cur == _head)
    			{
    				cout << "该系统中信息为空\n";
    				return;
    			}
    
    			cout << "人事信息系统人员名单\n";
    			printf("———————————————————————————————————-----——-———\n");
    			printf("编号   姓名   性别   部门     职称       工资\n\n");
    			for (int i = 0; i < Depname_nember; i++)
    			{
    				//双重循环,依次打印各部门的信息
    				SNode* _cur = _head->_next;
    				printf("\t\t%s:\n", (Depname[i]).c_str());
    				while (_cur != _head)
    				{
    					//判断部门
    					if (_cur->_depname == Depname[i])
    					{
    						Print_Person_Information(_cur);
    					}
    
    					_cur = _cur->_next;
    				}
    				cout << endl << endl;
    			}
    			printf("——————————————————————————————————————------——\n");
    		}
    
    		//打印工资信息
    		void Print_Depname_Salary()
    		{
    			SNode* cur = _head->_next;
    
    			if (cur == _head)
    			{
    				cout << "该系统中信息为空\n";
    				return;
    			}
    
    			cout << "人事信息系统人员名单\n";
    			printf("———————————————————————————————————-----——-———\n");
    			printf("编号   姓名   性别   部门     职称       工资\n\n");
    			int sum = 0;
    			for (int i = 0; i < Depname_nember; i++)
    			{
    				SNode* _cur = _head->_next;
    				int money = 0;
    				printf("\t\t%s:\n", (Depname[i]).c_str());
    				while (_cur != _head)
    				{
    					if (_cur->_depname == Depname[i])
    					{
    						Print_Person_Information(_cur);
    						money += _cur->_salary;
    					}
    
    					_cur = _cur->_next;
    				}
    				sum += money;
    				printf("%s工资总计:%d\n", (Depname[i]).c_str(), money);
    				cout << endl << endl;
    			}
    			printf("所有部门工资统计:%d\n", sum);
    			printf("——————————————————————————————————————------——\n");
    		}
    
    		//删除个人信息
    		void Erase_Person_Information()
    		{
    			cin.ignore(256, '\n');
    			cout << "请输入要删除人的编号:\n";
    			int num = 0,flag = -1;
    			cin >> num;
    
    			SNode* prev = _head;
    			SNode* cur = _head->_next;
    
    			if (cur == _head)
    			{
    				cout << "该系统中信息为空,无法删除\n";
    				return;
    			}
    
    			while (cur != _head)
    			{
    				if (cur->_no == num)
    				{
    					SNode* tmp = cur;
    					flag = 1;
    
    					//双向循环链表删除操作
    					cur->_next->_prev = prev;
    					prev->_next = cur->_next;
    
    					cur = prev->_next;
    
    					//string类型的成员变量是单个对象,而不是数组,
    					//所以应该使用delete来释放它们的内存。
    					//如果使用delete[]来释放string对象的内存,会导致未定义的行为和可能的内存泄漏。
    					delete tmp;
    					tmp = nullptr;
    				}
    				else
    				{
    					//遍历后面元素
    					prev = prev->_next;
    					cur = cur->_next;
    				}
    			}
    			if (flag==1) cout << "删除成功\n";
    			else cout << "删除失败,没有此人\n";
    		}
    
    		//删除所有信息
    		void Erase_All_Informations()
    		{
    			cout << "是否要删除所有信息!\n删除请输入[Y]\n";
    			char ch;
    			cin.ignore(256, '\n');
    			cin >> ch;
    			if (ch != 'Y')
    			{
    				cout << "删除失败\n";
    				return;
    			}
    
    			SNode* cur = _head->_next;
    
    			if (cur == _head)
    			{
    				cout << "该系统中信息为空\n";
    				return;
    			}
    
    			_head->_next = _head;
    			_head->_prev = _head;
    
    			//挨个删除链表中的元素,释放空间,避免内存泄漏
    			while (cur != _head)
    			{
    				SNode* tmp = cur;
    				cur = cur->_next;
    
    				delete tmp;
    				tmp = nullptr;
    			}
    
    			cout << "删除成功\n";
    		}
    
    		//调整工资
    		void Adjust_Person_Salary()
    		{
    			cin.ignore(256, '\n');
    			cout << "请输入要调整工资的人的编号:\n";
    			int num = 0;
    			cin >> num;
    
    			cin.ignore(256, '\n');
    			cout << "请输入调整后的工资金额:\n";
    			int sal = 0;
    			cin >> sal;
    
    			SNode* cur = _head->_next;
    			bool flag = false;
    
    			//寻找需要更改信息的节点
    			while (cur != _head)
    			{
    				if (cur->_no == num)
    				{
    					flag = true;
    					cur->_salary = sal;
    				}
    
    				cur = cur->_next;
    			}
    
    			if (flag) cout << "修改成功\n";
    			else cout << "没有此人\n";
    		}
    
    		//仿函数
    		struct Compare_No
    		{
    			//重载operator()操作符以实现函数比较操作
    			bool operator()(const Staff_Information_Node& p1, const Staff_Information_Node& p2)
    			{
    				return p1._no < p2._no;
    			}
    		};
    		struct Compare_Name
    		{
    			bool operator()(const Staff_Information_Node& p1, const Staff_Information_Node& p2)
    			{
    				return p1._name > p2._name;
    			}
    		};
    		struct Compare_Salary
    		{
    			bool operator()(const Staff_Information_Node& p1, const Staff_Information_Node& p2)
    			{
    				return p1._salary > p2._salary;
    			}
    		};
    
    		//打印排序结果(vector)
    		void _sort_print(vector<SNode>& v)
    		{
    			int len = v.size();
    			if (len == 0)
    			{
    				cout << "该系统中信息为空\n";
    				return;
    			}
    
    			cout << "人事信息系统人员名单\n";
    			printf("———————————————————————————————————-----——-———\n");
    			printf("编号   姓名   性别   部门     职称       工资\n");
    			for (int i = 0; i < len; i++)
    			{
    				Print_Person_Information(&v[i]);
    			}
    			printf("——————————————————————————————————————------——\n");
    		}
    
    		//按编号排序
    		void Print_Person_No_Information()
    		{
    			vector<SNode> v;
    			SNode* cur = _head->_next;
    			while (cur != _head)
    			{
    				v.push_back(*cur);
    				cur = cur->_next;
    			}
    			sort(v.begin(), v.end(), Compare_No());
    			_sort_print(v);
    		}
    
    		//按工资排序
    		void Print_Person_Salary_Information()
    		{
    			vector<SNode> v;
    			SNode* cur = _head->_next;
    			while (cur != _head)
    			{
    				v.push_back(*cur);
    				cur = cur->_next;
    			}
    			sort(v.begin(), v.end(), Compare_Salary());
    			_sort_print(v);
    		}
    
    	private:
    		SNode* _head;//头结点、哨兵位
    		size_t _size;
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463

                

    test.cpp

    #define _CRT_SECURE_NO_WARNINGS 1
    
    #include
    using namespace std;
    
    #include"LinkList.h"
    
    void system_start()
    {
    	int option = 0, flag = 0;
    	staff_linklist::LinkList_Information lt;
    	while(1)
    	{
    		//system("cls");
    		Init_menu();
    		cout << "请输入:";
    		if (cin >> option);
    		switch (option)
    		{
    		case(1):
    			lt.push_back();
    			break;
    		case(2):
    			lt.Print_Information();
    			break;
    		case(3):
    			lt.Print_Depname_Information();
    			break;
    		case(4):
    			lt.Print_Depname_Salary();
    			break;
    		case(5):
    			lt.Erase_Person_Information();
    			break;
    		case(6):
    			lt.Erase_All_Informations();
    			break;
    		case(7):
    			lt.Adjust_Person_Salary();
    			break;
    		case(8):
    			lt.Print_Person_No_Information();
    			break;
    		case(9):
    			lt.Print_Person_Salary_Information();
    			break;
    		case(0):
    			flag = 1;
    			break;
    		default:
    			cout << "输入错误,请重新输入\n";
    			break;
    		}
    		if (flag == 1)
    		{
    			cout << "退出成功\n";
    			break;
    		}
    	} 
    }
    
    int main()
    {
    	system_start();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
  • 相关阅读:
    会话跟踪及常用方法
    相机标定、校正和投影
    C 长度为0 的数组
    点成分享 | 微流控集成系统在人体血管研究中的应用
    Mac OS 使用远程桌面登录服务器
    第160场直播带货数据分享
    接口自动化测试:pytest基础讲解
    tensorrt C++推理
    指针进阶(1)
    生成.keystore 安卓签名
  • 原文地址:https://blog.csdn.net/Crocodile1006/article/details/133612089