请输入图片描述

当教授链表这个知识点时,需要详细地解释链表的概念、种类、基本操作以及与数组的区别。下面详细展开讲解链表的每个知识点:

  1. 链表的概念:

链表是一种线性数据结构,由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储数据,指针域指向下一个节点,通过这种指针关系连接起来。

  1. 链表的种类:

主要有单向链表、双向链表和循环链表。

单向链表:每个节点只有一个指针域,指向下一个节点。
双向链表:每个节点有两个指针域,分别指向前一个节点和后一个节点。
循环链表:最后一个节点的指针域指向第一个节点,形成一个循环。

单链表0.png
双向链表0.png
循环链表0.png

  1. 链表的基本操作:

    创建链表:动态分配内存空间创建节点,构建节点之间的指针关系。
    插入节点:在链表的指定位置插入一个新节点,需要调整指针关系。
    删除节点:从链表中删除指定位置的节点,需要调整指针关系,并释放节点的内存空间。
    查找节点:遍历链表,按照顺序查找或根据条件查找目标节点。

  2. 与数组的区别:

    链表的长度可以动态增长,而数组的长度固定。
    在链表中插入或删除节点的操作效率高,而在数组中这些操作可能涉及到大量的数据移动。
    链表不需要连续的内存空间,而数组需要。

详细展开讲解示例:

单向链表:
    从头节点开始,每个节点包含数据和指向下一个节点的指针。
    插入节点时,需要修改前一个节点的指针指向新节点,以及新节点的指针指向后一个节点。
    删除节点时,修改前一个节点的指针指向后一个节点,然后释放被删除节点的内存空间。

linked-list-add-node-at-end.PNG
linked-list-add-node-at-start.PNG
linked-list-insertat.PNG
linked-list-delete-first-node.PNG
linked-list-delete-last-node.PNG
linked-list-deleteat.PNG

双向链表:
    每个节点包含指向前一个节点和后一个节点的指针。
    插入节点时,需要修改前一个节点和后一个节点的指针指向新节点。
    删除节点时,需要修改前一个节点和后一个节点的指针指向,然后释放被删除节点的内存空间。

循环链表:
    最后一个节点的指针指向第一个节点,形成一个循环。
    插入和删除节点时,需要特别注意边界条件,以防止死循环或指针错误。

通过这些详细的讲解,学生可以更好地理解链表的结构、操作和应用场景,并能够编写简单的链表代码来解决问题。

单向链表应用示例:存储学生信息

单向链表可以用来存储学生信息,每个节点代表一个学生,包含学生的姓名、学号等信息。

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int id;
    Student* next;
};

int main() {
    // 创建链表节点
    Student* head = nullptr;
    head = new Student;
    head->name = "Alice";
    head->id = 1001;
    head->next = nullptr;

    // 添加新节点
    Student* newNode = new Student;
    newNode->name = "Bob";
    newNode->id = 1002;
    newNode->next = nullptr;
    head->next = newNode;

    // 遍历链表
    Student* current = head;
    while (current != nullptr) {
        cout << "姓名: " << current->name << ", 学号: " << current->id << endl;
        current = current->next;
    }

    // 释放内存
    while (head != nullptr) {
        Student* temp = head;
        head = head->next;
        delete temp;
    }

    return 0;
}

C++指针概念

指针是 C++
中一个非常重要的概念,它是一种特殊的变量类型,用于存储内存地址。通过指针,我们可以直接访问和操作内存中的数据,而不是通过变量名来操作。
指针的概念:

指针是一个变量,其值为另一个变量的内存地址。
指针存储的是内存地址,而不是实际的值。
指针可以指向任何类型的数据,包括基本数据类型、数组、结构体、函数等。

指针的基本操作:

取址运算符 &:用于获取变量的地址。
间接访问运算符 *:用于访问指针所指向的内存地址中存储的值。
指针赋值:将变量的地址赋给指针。
空指针:指向空地址的指针,表示不指向任何有效的内存地址。
指针运算:指针可以进行算术运算,如加法、减法等,以移动指针指向的内存位置。

指针的应用:

动态内存分配:通过 new 关键字分配动态内存,并返回指向该内存的指针。
数组和字符串操作:指针可以用于遍历数组和操作字符串。
函数参数传递:可以通过指针在函数之间传递参数,实现对变量的直接修改。
动态数据结构:如链表、树等数据结构的实现常常需要用到指针。
实现复杂数据结构:如图、图形、链表、树等复杂数据结构通常需要使用指针来建立节点之间的连接。
#include <iostream> using namespace std;

int main() {
    int num = 10; // 定义一个整数变量
    int* ptr; // 声明一个整型指针

    ptr = &num; // 将变量 num 的地址赋给指针 ptr

    cout << "变量 num 的值为: " << num << endl;
    cout << "变量 num 的地址为: " << &num << endl;
    cout << "指针 ptr 存储的地址为: " << ptr << endl;
    cout << "通过指针 ptr 访问 num 的值为: " << *ptr << endl;

    return 0; }

C++结构体自定义数据类型

在 C++ 中,struct
关键字用于定义一个结构体(structure),结构体是一种用户自定义的数据类型,用于组合多个不同类型的数据成员。结构体可以包含各种类型的数据,包括整数、浮点数、字符、字符串、指针等。

在给定的例子中,定义了一个名为 Student 的结构体,其中包含了三个数据成员:

string name:用于存储学生的姓名,类型为 string。
int id:用于存储学生的学号,类型为 int。
Student* next:用于指向下一个学生的指针,类型为 Student*,表示该成员存储的是指向 Student 结构体的指针。

这个结构体的定义描述了一个学生的基本信息以及指向下一个学生的指针,可以用于构建链表数据结构,其中每个节点表示一个学生,通过 next
指针连接起来,形成一个链表。

双向链表应用示例:实现浏览器的前进后退功能

双向链表可以用来实现浏览器的前进后退功能,每个节点代表一个网页,包含指向前一个网页和后一个网页的指针。

#include <iostream>
#include <string>
using namespace std;

struct WebPage {
    string url;
    WebPage* prev;
    WebPage* next;
};

int main() {
    // 创建链表节点
    WebPage* current = nullptr;
    current = new WebPage;
    current->url = "www.google.com";
    current->prev = nullptr;

    // 添加新节点
    WebPage* newPage = new WebPage;
    newPage->url = "www.example.com";
    newPage->prev = current;
    newPage->next = nullptr;
    current->next = newPage;

    // 后退到前一个网页
    if (current->prev != nullptr) {
        current = current->prev;
        cout << "当前网页: " << current->url << endl;
    }

    // 前进到下一个网页
    if (current->next != nullptr) {
        current = current->next;
        cout << "当前网页: " << current->url << endl;
    }

    // 释放内存
    delete newPage;
    delete current;

    return 0;
}
## 循环链表应用示例:实现游戏中的循环列表

循环链表可以用于实现游戏中的循环列表,比如玩家的回合顺序,最后一个节点的指针指向第一个节点,形成一个循环。

include

using namespace std;

struct Player {

string name;
Player* next;

};

int main() {

// 创建循环链表
Player* head = nullptr;
head = new Player;
head->name = "Player1";
head->next = nullptr;

Player* second = new Player;
second->name = "Player2";
second->next = nullptr;
head->next = second;

Player* third = new Player;
third->name = "Player3";
third->next = nullptr;
second->next = third;
third->next = head; // 最后一个节点指向头节点,形成循环

// 遍历循环链表
Player* current = head;
do {
    cout << "当前玩家: " << current->name << endl;
    current = current->next;
} while (current != head);

// 释放内存
delete head;
delete second;
delete third;

return 0;

}

## 将前面成绩管理系统,温度记录,电影票售卖系统使用链表
#### 成绩管理系统使用链表改写

include

include

using namespace std;

struct Student {

string name;
int score;
Student* next;

};

int main() {

Student* head = nullptr;
Student* current = nullptr;

// 添加学生成绩
for (int i = 0; i < 5; ++i) {
    Student* newStudent = new Student;
    cout << "请输入第 " << i + 1 << " 个学生的姓名和成绩: ";
    cin >> newStudent->name >> newStudent->score;
    newStudent->next = nullptr;

    if (head == nullptr) {
        head = newStudent;
        current = head;
    } else {
        current->next = newStudent;
        current = newStudent;
    }
}

// 显示学生成绩
cout << "学生成绩列表:" << endl;
current = head;
while (current != nullptr) {
    cout << "姓名: " << current->name << ", 成绩: " << current->score << endl;
    current = current->next;
}

// 释放内存
current = head;
while (current != nullptr) {
    Student* temp = current;
    current = current->next;
    delete temp;
}

return 0;

}

温度记录器使用链表改写:

#include <iostream>
using namespace std;

struct Temperature {
    int temp;
    Temperature* next;
};

int main() {
    const int DAYS = 7;
    Temperature* head = nullptr;
    Temperature* current = nullptr;

    // 输入每天的温度
    for (int i = 0; i < DAYS; ++i) {
        Temperature* newTemp = new Temperature;
        cout << "请输入第 " << i + 1 << " 天的温度: ";
        cin >> newTemp->temp;
        newTemp->next = nullptr;

        if (head == nullptr) {
            head = newTemp;
            current = head;
        } else {
            current->next = newTemp;
            current = newTemp;
        }
    }

    // 显示每天的温度
    cout << "一周内的温度记录:" << endl;
    current = head;
    while (current != nullptr) {
        cout << current->temp << " ";
        current = current->next;
    }
    cout << endl;

    // 释放内存
    current = head;
    while (current != nullptr) {
        Temperature* temp = current;
        current = current->next;
        delete temp;
    }

    return 0;
}

电影票售卖系统使用链表改写:

#include <iostream>
#include <string>
using namespace std;

struct Movie {
    string name;
    int ticketSales;
    Movie* next;
};

int main() {
    const int NUM_MOVIES = 3;
    Movie* head = nullptr;
    Movie* current = nullptr;

    // 输入每部电影的售票数量
    for (int i = 0; i < NUM_MOVIES; ++i) {
        Movie* newMovie = new Movie;
        cout << "请输入电影 " << i + 1 << " 的名称和售票数量: ";
        cin >> newMovie->name >> newMovie->ticketSales;
        newMovie->next = nullptr;

        if (head == nullptr) {
            head = newMovie;
            current = head;
        } else {
            current->next = newMovie;
            current = newMovie;
        }
    }

    // 统计总票数和最畅销电影
    int totalSales = 0;
    string bestMovieName;
    int maxSales = 0;
    current = head;
    while (current != nullptr) {
        totalSales += current->ticketSales;
        if (current->ticketSales > maxSales) {
            maxSales = current->ticketSales;
            bestMovieName = current->name;
        }
        current = current->next;
    }

    // 显示统计结果
    cout << "总售票数量为: " << totalSales << endl;
    cout << "最畅销电影是: " << bestMovieName << endl;

    // 释放内存
    current = head;
    while (current != nullptr) {
        Movie* temp = current;
        current = current->next;
        delete temp;
    }

    return 0;
}
这些示例展示了如何使用链表数据结构来重新实现之前的成绩管理系统、温度记录器和电影票售卖系统。通过链表,我们可以动态地添加、删除和管理数据,使代码更加灵活和易于扩展。

请输入图片描述

在第一节中,我们将介绍数组的概念、声明和初始化方法,以及演示数组的基本操作。为了使教学内容更加生动有趣,我们可以通过一些比较强烈的例子来说明数组的应用。

教学内容:

#### 数组的概念和特点:

  • 数组是一种线性数据结构,由相同类型的元素组成,这些元素在内存中是连续存储的。
  • 数组的长度是固定的,在声明时就确定了,不能动态改变。

#### 声明和初始化数组:

  • 示范如何声明数组,并介绍如何初始化数组。
  • 例如:int scores[5]; 声明了一个包含5个整数的数组。
    #### 数组的基本操作示例:
  • 访问元素:通过索引访问数组中的元素。
  • 修改元素:可以直接通过索引修改数组中的元素。
  • 遍历数组:使用循环结构遍历数组中的所有元素。

应用例子:

成绩管理系统:

让学生设想一个成绩管理系统,其中需要存储学生的成绩数据。使用数组来存储学生的成绩,每个元素代表一个学生的成绩。
示范如何通过数组实现添加、删除、查找学生成绩的功能。

温度记录器:

想象一个温度记录器,记录每天的气温。使用数组来存储每天的温度数据,每个元素代表一天的温度。
示范如何使用数组计算一周内的平均温度、最高温度和最低温度。

电影票售卖系统:

设想一个电影票售卖系统,记录每天不同电影的售票情况。使用数组来存储每部电影的售票数量,每个元素代表一部电影的售票数量。
示范如何通过数组实现统计每部电影的总票数、最畅销电影等功能。

通过这些应用例子,学生可以更好地理解数组的概念和用途,同时激发他们对学习的兴趣。

成绩管理系统

参考代码
    #include <iostream>
    using namespace std;
    
    int main() {
        const int MAX_STUDENTS = 5;
        int scores[MAX_STUDENTS];
    
        // 添加学生成绩
        for (int i = 0; i < MAX_STUDENTS; ++i) {
            cout << "请输入第 " << i + 1 << " 个学生的成绩: ";
            cin >> scores[i];
        }
    
        // 显示学生成绩
        cout << "学生成绩列表:" << endl;
        for (int i = 0; i < MAX_STUDENTS; ++i) {
            cout << "第 " << i + 1 << " 个学生的成绩为: " << scores[i] << endl;
        }
    
        return 0;
    }

温度记录器

参考代码
    #include <iostream>
    using namespace std;
    
    int main() {
        const int DAYS = 7;
        int temperatures[DAYS];
        int sum = 0;
    
        // 输入每天的温度
        for (int i = 0; i < DAYS; ++i) {
            cout << "请输入第 " << i + 1 << " 天的温度: ";
            cin >> temperatures[i];
            sum += temperatures[i];
        }
    
        // 计算平均温度
        double average = static_cast<double>(sum) / DAYS;
        cout << "一周内的平均温度为: " << average << " 度" << endl;
    
        // 查找最高和最低温度
        int maxTemp = temperatures[0];
        int minTemp = temperatures[0];
        for (int i = 1; i < DAYS; ++i) {
            if (temperatures[i] > maxTemp) {
                maxTemp = temperatures[i];
            }
            if (temperatures[i] < minTemp) {
                minTemp = temperatures[i];
            }
        }
        cout << "最高温度为: " << maxTemp << " 度" << endl;
        cout << "最低温度为: " << minTemp << " 度" << endl;
    
        return 0;
    }

电影票售卖系统

参考代码
    #include <iostream>
    using namespace std;
    
    int main() {
        const int NUM_MOVIES = 3;
        int ticketSales[NUM_MOVIES] = {0};
    
        // 模拟售票过程
        for (int i = 0; i < NUM_MOVIES; ++i) {
            cout << "请输入电影 " << i + 1 << " 的售票数量: ";
            cin >> ticketSales[i];
        }
    
        // 统计总票数和最畅销电影
        int totalSales = 0;
        int bestMovieIndex = 0;
        for (int i = 0; i < NUM_MOVIES; ++i) {
            totalSales += ticketSales[i];
            if (ticketSales[i] > ticketSales[bestMovieIndex]) {
                bestMovieIndex = i;
            }
        }
    
        // 显示统计结果
        cout << "总售票数量为: " << totalSales << endl;
        cout << "最畅销电影是电影 " << bestMovieIndex + 1 << endl;
    
        return 0;
    }

请输入图片描述

以下是一个简单的教学计划,旨在向初中生介绍基础的数据结构概念,并使用C++语言进行说明和示范。这个计划将包括讲解线性数据结构(数组、链表)和非线性数据结构(栈、队列)。

教学计划

第一节:数组(Array)

  • 介绍数组的概念和特点。
  • 讲解如何声明和初始化数组。
  • 演示数组的基本操作,如访问元素、修改元素和遍历数组。
  • 讨论数组的优缺点及应用场景。

第二节:链表(Linked List)

  • 介绍链表的概念和基本类型(单链表、双链表)。
  • 演示如何定义和操作单链表。
  • 讨论链表与数组的区别,以及链表的优势。
  • 分析链表的插入、删除和查找操作的时间复杂度。

第三节:栈(Stack)

  • 介绍栈的概念和特点。
  • 演示如何使用栈实现简单的表达式求值和括号匹配。
  • 讨论栈的应用场景,如函数调用栈和浏览器的前进后退功能。

第四节:队列(Queue)

  • 介绍队列的概念和特点。
  • 演示如何使用队列解决问题,如模拟排队和广度优先搜索(BFS)算法。
  • 讨论队列的应用场景,如任务调度和消息传递。

辅助资源和活动

  • 提供课堂演示和实践练习,让学生动手实践数据结构的基本操作。
  • 组织小组讨论,让学生分享自己对数据结构的理解和应用场景。
  • 分发资料和练习题,供学生课后复习和巩固知识。

评估方式

  • 课堂参与度:评估学生在课堂上的提问和回答情况。
  • 实践练习:评估学生对数据结构基本操作的掌握程度。
  • 课后作业:布置相关的编程作业,检验学生对所学内容的理解和应用能力。

结语

通过这个教学计划,学生将能够了解基本的数据结构概念,并通过C++语言实践掌握数组、链表、栈和队列等常用数据结构。同时,他们也将学会如何分析和应用这些数据结构解决实际问题。

请输入图片描述

一个非递减的整数数组,查找target数字起始和结束的索引,如果没有找到target数字输出-1,-1,确保时间复杂度是logn

代码

def search_range(nums, target):
    def find_first(nums, target):
        start, end = 0, len(nums) - 1
        result = -1
        while start <= end:
            mid = (start + end) // 2
            if nums[mid] == target:
                result = mid
                end = mid - 1  # 继续在左侧搜索
            elif nums[mid] < target:
                start = mid + 1
            else:
                end = mid - 1
        return result

    def find_last(nums, target):
        start, end = 0, len(nums) - 1
        result = -1
        while start <= end:
            mid = (start + end) // 2
            if nums[mid] == target:
                result = mid
                start = mid + 1  # 继续在右侧搜索
            elif nums[mid] < target:
                start = mid + 1
            else:
                end = mid - 1
        return result

    first_occurrence = find_first(nums, target)
    last_occurrence = find_last(nums, target)

    return [first_occurrence, last_occurrence]

# 示例
nums = [5, 7, 7, 8, 8, 10]
target = 8
result = search_range(nums, target)
print(result)  # 输出:[3, 4]

这道题目的时间复杂度?

这个算法分别查找目标数字的第一次出现和最后一次出现的位置,从而确定范围。时间复杂度是 O(logn),因为在二分查找中,每一步都将搜索范围减半。如果目标数字不存在,返回 [-1, -1]。

对于该算法,时间复杂度是 O(logn)。这是因为在每个二分查找迭代中,搜索范围都被减半。具体来说:

找第一次出现的位置:

如果中间元素小于目标值,说明目标值可能在右侧,因此将搜索范围缩小到右半部分。
如果中间元素等于目标值,说明目标值可能在左侧,继续在左侧搜索。
如果中间元素大于目标值,说明目标值可能在左侧,继续在左侧搜索。
每次都将搜索范围缩小一半,因此时间复杂度是 O(logn)。
找最后一次出现的位置:

如果中间元素小于目标值,说明目标值可能在右侧,因此将搜索范围缩小到右半部分。
如果中间元素等于目标值,说明目标值可能在右侧,继续在右侧搜索。
如果中间元素大于目标值,说明目标值可能在左侧,继续在左侧搜索。
每次都将搜索范围缩小一半,因此时间复杂度是 O(logn)。
因此,整体算法的时间复杂度是 O(logn)。

如何数学推导这道题目的时间复杂度?

对于这道题目,可以通过数学推导来分析时间复杂度。首先,我们要理解算法的关键部分是二分查找。

二分查找时间复杂度分析:
在二分查找中,每一轮迭代都将搜索范围减半。设初始数组长度为 n,那么经过 k 轮迭代后,数组长度将缩小为 n / 2^k。当数组长度缩小到 1 时,即 n / 2^k = 1,解得 k = log₂n。

因此,二分查找的时间复杂度是 O(logn)。

针对本题的时间复杂度分析:
找第一次出现的位置:

进行一次二分查找,时间复杂度是 O(logn)。
找最后一次出现的位置:

进行一次二分查找,时间复杂度是 O(logn)。
整体算法是进行两次二分查找,因此总的时间复杂度是 O(logn)。

这样的推导基于二分查找的核心思想,即每一步都将搜索范围减半。这种推导方法是一种常见的对二分查找时间复杂度进行分析的方式。

请输入图片描述

前面的版本有bug,登录界面和主窗口还是会同时显示,更新一下
'''
ver14.py: 登录窗口和主窗口分别显示,前面ver12 ver13 没有解决这个问题
'''

import json
import tkinter as tk
from tkinter import messagebox, simpledialog

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.is_checked_out = False

    def display_info(self):
        status = "已借出" if self.is_checked_out else "可借阅"
        return f"《{self.title}》 - 作者: {self.author} - 状态: {status}"

    def check_out(self):
        self.is_checked_out = True

    def check_in(self):
        self.is_checked_out = False

class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password
        self.borrowed_books = []

    def borrow_book(self, book):
        if not book.is_checked_out:
            book.check_out()
            self.borrowed_books.append(book)
            return f"借阅成功:《{book.title}》"
        else:
            return "抱歉,这本书已经被借出了。"

    def return_book(self, book):
        if book in self.borrowed_books:
            book.check_in()
            self.borrowed_books.remove(book)
            return f"归还成功:《{book.title}》"
        else:
            return "这本书没有被这位用户借阅。"

class LoginApp:
    def __init__(self, root, library_app):
        self.root = root
        self.root.title("用户登录")
        self.library_app = library_app

        self.library_app.root.withdraw()  ## key fix: 确保主窗口在登录之前不会显示

        # 创建 GUI 元素
        self.label = tk.Label(root, text="用户登录", font=("Helvetica", 16))
        self.label.pack(pady=10)

        self.username_label = tk.Label(root, text="用户名:")
        self.username_label.pack()
        self.username_entry = tk.Entry(root)
        self.username_entry.pack(pady=5)

        self.password_label = tk.Label(root, text="密码:")
        self.password_label.pack()
        self.password_entry = tk.Entry(root, show="*")
        self.password_entry.pack(pady=10)

        self.login_button = tk.Button(root, text="登录", command=self.login)
        self.login_button.pack(pady=5)

        self.register_button = tk.Button(root, text="注册", command=self.register)
        self.register_button.pack(pady=5)

        root.protocol("WM_DELETE_WINDOW", self.on_closing)
    def on_closing(self):
        # 保存数据并关闭窗口
        print(f"close login window...")
        self.root.destroy()
        self.library_app.root.destroy()

    def login(self):
        username = self.username_entry.get()
        password = self.password_entry.get()
        user = self.library_app.library.find_user(username, password)
        if user:
            # 关闭登录窗口
            #self.root.destroy()
            self.root.withdraw()
            # 显示主窗口
            self.library_app.root.deiconify()
            self.library_app.show_main_interface(user)
        else:
            messagebox.showwarning("登录失败", "用户名或密码错误。")

    def register(self):
        username = self.username_entry.get()
        password = self.password_entry.get()
        if not self.library_app.library.is_username_taken(username):
            new_user = User(username, password)
            self.library_app.library.users.append(new_user)
            messagebox.showinfo("注册成功", "注册成功,请使用新账户登录。")
        else:
            messagebox.showwarning("注册失败", "用户名已被使用,请选择其他用户名。")

class LibraryApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图书馆管理系统")

        self.library = Library()

        # 初始化用户登录界面
        self.login_root = tk.Toplevel(root)
        self.login_app = LoginApp(self.login_root, self)
        # 关闭窗口时触发保存数据操作
        root.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.is_init = False

    def show_main_interface(self, user):
        #self.main_interface = tk.Toplevel(self.root)
        #self.main_interface.title("主界面")
        # 创建 GUI 元素
        if self.is_init:
            return
        self.is_init = True
        self.label = tk.Label(self.root, text=f"欢迎,{user.username}!", font=("Helvetica", 16))
        self.label.pack(pady=10)

        self.listbox = tk.Listbox(self.root, selectmode=tk.SINGLE, font=("Helvetica", 12), height=10, width=50)
        self.listbox.pack(pady=10)

        self.borrow_button = tk.Button(self.root, text="借阅图书", command=self.borrow_book)
        self.borrow_button.pack(pady=5)

        self.return_button = tk.Button(self.root, text="归还图书", command=self.return_book)
        self.return_button.pack(pady=5)

        self.add_book_button = tk.Button(self.root, text="添加图书", command=self.add_book)
        self.add_book_button.pack(pady=5)

        # 在主界面显示图书列表
        self.refresh_books_list()

    def refresh_books_list(self):
        self.listbox.delete(0, tk.END)
        for book in self.library.books:
            self.listbox.insert(tk.END, book.display_info())

    def borrow_book(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            selected_book = self.library.books[selected_index[0]]
            message = self.library.current_user.borrow_book(selected_book)
            messagebox.showinfo("借阅图书", message)
            self.refresh_books_list()
        else:
            messagebox.showwarning("借阅图书", "请选择要借阅的图书。")

    def return_book(self):
        selected_index = self.listbox.curselection()
        if selected_index:
            selected_book = self.library.books[selected_index[0]]
            message = self.library.current_user.return_book(selected_book)
            messagebox.showinfo("归还图书", message)
            self.refresh_books_list()
        else:
            messagebox.showwarning("归还图书", "请选择要归还的图书。")

    def add_book(self):
        title_author = simpledialog.askstring("添加图书", "请输入书名和作者(用逗号分隔):")
        if title_author:
            title, author = map(str.strip, title_author.split(','))
            new_book = Book(title, author)
            self.library.books.append(new_book)
            messagebox.showinfo("添加图书", f"《{title}》已成功添加到图书馆。")
            self.refresh_books_list()
    
    def on_closing(self):
        # 保存数据并关闭窗口
        print(f"close main window...")
        self.library.save_data_to_files()

        #self.root.destroy()
        self.root.withdraw()
        self.login_root.deiconify()


class Library:
    def __init__(self):
        self.books = []
        self.users = []
        self.current_user = None

        # 从文件加载图书信息
        self.load_books_from_file()

        # 从文件加载用户信息
        self.load_users_from_file()

    def load_books_from_file(self):
        try:
            with open("books.json", "r") as file:
                books_data = json.load(file)
                for book_data in books_data:
                    book = Book(book_data["title"], book_data["author"])
                    book.is_checked_out = book_data["is_checked_out"]
                    self.books.append(book)
        except FileNotFoundError:
            # 文件不存在,可能是第一次运行程序
            pass

    def load_users_from_file(self):
        try:
            with open("users.json", "r") as file:
                users_data = json.load(file)
                for user_data in users_data:
                    user = User(user_data["username"], user_data["password"])
                    user.borrowed_books = [Book(book_data["title"], book_data["author"]) for book_data in user_data["borrowed_books"]]
                    self.users.append(user)
        except FileNotFoundError:
            # 文件不存在,可能是第一次运行程序
            pass

    def save_books_to_file(self):
        with open("books.json", "w") as file:
            books_data = []
            for book in self.books:
                book_data = {
                    "title": book.title,
                    "author": book.author,
                    "is_checked_out": book.is_checked_out,
                }
                books_data.append(book_data)
            json.dump(books_data, file, indent=2)

    def save_users_to_file(self):
        with open("users.json", "w") as file:
            users_data = []
            for user in self.users:
                user_data = {
                    "username": user.username,
                    "password": user.password,
                    "borrowed_books": [{"title": book.title, "author": book.author} for book in user.borrowed_books],
                }
                users_data.append(user_data)
            json.dump(users_data, file, indent=2)

    def save_data_to_files(self):
        self.save_books_to_file()
        self.save_users_to_file()

    def find_user(self, username, password):
        for user in self.users:
            if user.username == username and user.password == password:
                self.current_user = user
                return user
        return None

    def is_username_taken(self, username):
        for user in self.users:
            if user.username == username:
                return True
        return False
    
# 创建根窗口并运行程序
root = tk.Tk()
app = LibraryApp(root)

root.mainloop()

books.json 和users.json模板文件

users.json模板文件:

[
  {
    "username": "yli",
    "password": "123",
    "borrowed_books": [
      {
        "title": "To Kill a Mockingbird",
        "author": "Harper Lee"
      }
    ]
  },
  {
    "username": "pipi",
    "password": "456",
    "borrowed_books": []
  }
]

books模板文件:

[
  {
    "title": "The Great Gatsby",
    "author": "F. Scott Fitzgerald",
    "is_checked_out": false
  },
  {
    "title": "To Kill a Mockingbird",
    "author": "Harper Lee",
    "is_checked_out": true
  },
  {
    "title": "1984",
    "author": "George Orwell",
    "is_checked_out": false
  },
  {
    "title": "2024",
    "author": "Michael Li",
    "is_checked_out": false
  },
  {
    "title": "back to 1988",
    "author": "X-Man",
    "is_checked_out": false
  }
]