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

初识——QT

QT安装方法

一、项目创建流程
  1. 创建项目

    • 入口:通过Qt Creator的欢迎页面或菜单栏(文件→新建项目)创建新项目。

    • 项目类型:选择「Qt Widgets Application」。

    • 路径要求:项目路径需为纯英文且不含特殊字符。

    • 构建系统:默认选择 CMake

    • 类配置:默认生成MainWindow类(包含UI文件)。

  2. 配置构建套件

    • 选择适用于当前平台的构建套件(如MinGW/MSVC)。

    • 若CMake配置失败,需检查CMake路径或更新组件。

  3. 运行项目

    • 点击「运行」按钮,生成默认窗口界面(含一个空Widget)。

二、工程文件解析
  1. CMakeLists.txt

    • 核心作用:定义项目构建规则、依赖关系及编译选项。

    • 关键配置

      cmake_minimum_required(VERSION 3.10)  # 指定CMake最低版本
      project(qt01 VERSION 0.1 LANGUAGES CXX)  # 设置项目名称及语言
      set(CMAKE_CXX_STANDARD 17)  # 指定C++标准为C++17
      find_package(Qt6 REQUIRED COMPONENTS Widgets)  # 引入Qt6 Widgets模块
      add_executable(qt01 main.cpp)  # 定义可执行文件
      target_link_libraries(qt01 PRIVATE Qt6::Widgets)  # 链接Qt库
    • 注意事项:需通过find_package引入所需Qt模块(如Widgets、Core等)。

  2. mainwindow.h

    • 功能:声明主窗口类,继承自QMainWindow

    • 关键代码

      #include <QMainWindow>
      namespace Ui { class MainWindow; }  // 前向声明UI类
      class MainWindow : public QMainWindow {Q_OBJECT  // 必须包含Q_OBJECT宏以支持信号与槽
      public:MainWindow(QWidget *parent = nullptr);~MainWindow();
      private:Ui::MainWindow *ui;  // UI对象指针
      };
  3. mainwindow.cpp

    • 功能:实现主窗口类的构造函数和析构函数。

    • 关键代码

      #include "mainwindow.h"
      #include "ui_mainwindow.h"
      MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);  // 初始化UI(自动生成)
      }
      MainWindow::~MainWindow() { delete ui; }  // 释放UI对象
  4. main.cpp

    • 功能:应用程序入口,创建主窗口并启动事件循环。

    • 关键代码

      #include "mainwindow.h"
      #include <QApplication>
      int main(int argc, char *argv[]) {QApplication a(argc, argv);  // 管理GUI程序生命周期MainWindow w;w.show();  // 显示窗口return a.exec();  // 进入事件循环
      }
    • 注意a.exec()是Qt事件循环的核心,负责处理用户输入和窗口事件。

  5. ui_mainwindow.h

    • 功能:由Qt Designer自动生成的UI布局代码,描述窗口中的控件及其属性。

三、Qt内存管理机制
  1. 父子对象关系

    • 规则:父对象销毁时自动删除所有子对象。

    • 示例

      QWidget *parent = new QWidget;
      QPushButton *button = new QPushButton(parent);  // button的父对象为parent
      delete parent;  // 自动删除button
  2. 智能指针

    • QScopedPointer:作用域内自动释放内存。

    • QSharedPointer:引用计数智能指针,共享所有权。

四、UI设计与信号槽
  1. 拖拽控件

    • 通过Qt Designer在.ui文件中拖拽控件(如按钮、标签)并设置属性(如文本、大小)。

    • 示例:设置按钮文本:

      ui->pushButton->setText("点我");
  2. 自动连接槽函数

    • 命名规则on_控件对象名_信号名()

    • 示例:按钮点击槽函数:

      void MainWindow::on_pushButton_clicked() {qDebug() << "按钮被点击";
      }
五、注意事项
  1. 路径规范:避免中文和特殊字符,防止构建失败。

  2. CMake配置:确保正确引入Qt模块(如Widgets、Core)。

  3. 内存管理:优先使用父子对象关系或智能指针,避免内存泄漏。

  4. UI更新:修改.ui文件后需重新构建以生成ui_*.h文件。

信号与槽机制

一、概述
  • 核心作用:Qt的信号与槽机制是用于对象间通信的松耦合方式,替代传统回调函数。当对象状态变化(事件)时发送信号,连接的槽函数自动响应。

  • 特点:支持多对多连接(一个信号可绑定多个槽,多个信号可绑定一个槽),支持跨线程通信,参数类型需兼容。 

二、信号与槽的定义
  1. 信号(Signal)

    • 声明方式:在类中使用 signals 关键字声明,无返回值(void),无需实现。

    • 示例: 

      class MyClass : public QObject {Q_OBJECT
      signals:void mySignal(int value); // 信号声明
      };
  2. 槽(Slot)

    • 声明方式:在类中使用 public/private/protected slots 声明,是普通成员函数,可带参数和返回值。

    • 示例:

      class MyClass : public QObject {Q_OBJECT
      public slots:void mySlot(int data); // 槽声明
      }; 
三、信号的发送与槽的调用
  • 信号发送:通过 emit 关键字触发信号。

    emit mySignal(100); // 发送信号
  • 槽调用规则

    • 槽函数按连接顺序依次执行。

    • 槽可以是私有函数,但通过信号连接仍可调用。 

四、信号与槽的连接方式
  1. 手动连接

    • 使用 QObject::connect 函数,语法:

      connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);
    • 示例:

      connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::handleClick);
  2. 自动连接

    • 通过槽函数命名规则 on_对象名_信号名,需在 setupUi 中调用 QMetaObject::connectSlotsByName

    • 示例:

      void MainWindow::on_pushButton_clicked() { ... } // 自动连接 
五、连接类型与规则
  1. 连接类型Qt::ConnectionType

    类型描述
    AutoConnection默认,根据线程自动选择 Direct(同线程)或 Queued(跨线程)。
    DirectConnection立即执行,槽在发送者线程运行。
    QueuedConnection异步执行,槽在接收者线程事件循环中调用。
    BlockingQueuedConnection同步执行,发送者线程阻塞直到槽完成(需跨线程)。
    UniqueConnection避免重复连接,与上述类型按位或使用。
  2. 参数规则

    • 信号参数数量 ≥ 槽参数数量,且类型兼容。

    • 示例:

      // 合法:信号参数多于槽
      connect(obj1, &ClassA::signal(int, QString), obj2, &ClassB::slot(int));
      // 非法:槽参数多于信号
      connect(obj1, &ClassA::signal(int), obj2, &ClassB::slot(int, QString)); 
六、自定义信号与槽的条件
  1. 类必须直接或间接继承 QObject

  2. 类声明中需包含 Q_OBJECT 宏。

  3. 信号用 signals 声明,槽用 slots 声明。 

七、元对象编译器(moc)
  • 作用:处理 Qt 的扩展语法(如信号与槽),生成元对象代码(moc_*.cpp)。

  • 必要性:包含 Q_OBJECT 的类必须通过 moc 编译,否则信号与槽无法正常工作。 

八、关键示例
// 信号与槽定义
class Worker : public QObject {Q_OBJECT
signals:void progressUpdated(int percent);
public slots:void doWork() { // 工作逻辑emit progressUpdated(50); }
};// 连接
Worker worker;
QObject::connect(&worker, &Worker::progressUpdated, this, [](int percent) {qDebug() << "Progress:" << percent;
}); 

简单示例:

teacher.h:

// teacher.h
#include <QObject>
class Teacher : public QObject {Q_OBJECT
public:explicit Teacher(QObject *parent = nullptr) : QObject(parent) {}signals:void hungry();  // 正确信号声明
};

student.h:

// student.h
#include <QObject>
#include <QDebug>
class Student : public QObject {Q_OBJECT
public:explicit Student(QObject *parent = nullptr) : QObject(parent) {}public slots:void treat();   // 槽函数声明
};

student.cpp:

// student.cpp
#include "student.h"
void Student::treat() {qDebug() << "Student treats teacher";  // 修正输出内容
}

window.h:

// window.h
#include <QWidget>
#include "teacher.h"
#include "student.h"class Window : public QWidget {Q_OBJECT
public:explicit Window(QWidget *parent = nullptr);public slots:void xiake();  // 触发信号的方法private:Teacher *teacher;Student *student;
};

window.cpp: 

// window.cpp
#include "window.h"Window::Window(QWidget *parent) : QWidget(parent) {teacher = new Teacher(this);student = new Student(this);// 正确连接信号与槽(注意信号拼写)connect(teacher, &Teacher::hungry, student, &Student::treat);
}void Window::xiake() {emit teacher->hungry();  // 触发信号
}
九、注意事项
  1. 线程安全:跨线程通信优先使用 QueuedConnection

  2. 命名规范:自动连接槽需严格遵循 on_对象名_信号名 格式。

  3. 内存管理:避免循环引用,确保对象生命周期可控。

Qt事件处理总结与归纳

一、事件简介
  1. 概念

    • 事件是用户或系统产生的交互操作,通过事件循环处理,用于对象间信息交互。

    • Qt将系统消息转换为QEvent对象,所有QObject子类均可处理事件。

  2. 常见事件类型

    • 用户界面事件:鼠标事件(QMouseEvent)、键盘事件(QKeyEvent)、触摸事件(QTouchEvent)。

    • 系统事件:定时器事件(QTimerEvent)、窗口事件(QResizeEvent)、绘图事件(QPaintEvent)。

    • 自定义事件:继承QEvent实现,用于特定需求。

二、事件处理机制
  1. 事件分发流程

    • 事件队列:操作系统消息被转换为QEvent,由QCoreApplication::exec()驱动的主事件循环处理。

    • 事件传递

      1. 事件先传递到焦点控件。

      2. 若未被处理,逐级传递给父控件。

  2. 事件处理函数

    • 子类可重写event()函数或特定事件处理函数(如mousePressEvent())。

    • 示例:自定义按钮重写鼠标事件:

      // 继承QPushButton并重写mousePressEvent
      void CustomPushButton::mousePressEvent(QMouseEvent *e) {qDebug() << "Custom按钮被按下";QPushButton::mousePressEvent(e); // 调用父类实现,确保信号正常触发
      }
  3. 事件过滤器(Event Filter)

    • 作用:拦截目标对象的事件,在事件到达前处理。

    • 步骤

      1. 安装过滤器targetObj->installEventFilter(filterObj)

      2. 重写eventFilter():判断事件类型并处理,返回true拦截事件,false继续传递。

      bool MainWindow::eventFilter(QObject *obj, QEvent *event) {if (obj == ui->pushButton && event->type() == QEvent::MouseButtonPress) {qDebug() << "拦截按钮点击";return true; // 阻止事件传递}return QMainWindow::eventFilter(obj, event); // 默认处理
      }
三、事件与信号的区别
特性事件(QEvent)信号(Signal)
触发方式由系统或用户操作触发由对象主动发出(如按钮点击触发clicked
处理机制通过事件队列分发,可被过滤或拦截直接调用连接的槽函数
灵活性可自定义事件类型和分发逻辑信号与槽通过connect绑定,不可拦截
典型应用底层交互(如鼠标移动、键盘输入)逻辑响应(如按钮点击后的业务逻辑)
四、QEventLoop 的应用
  1. 作用

    • 在局部范围内启动事件循环,用于等待异步操作完成(如定时器、对话框关闭)。

  2. 使用场景

    • 等待定时器

      QEventLoop loop;
      QTimer::singleShot(3000, &loop, &QEventLoop::quit);
      loop.exec(); // 阻塞3秒后继续执行
    • 模态对话框

      QDialog dialog;
      QEventLoop loop;
      connect(&dialog, &QDialog::finished, &loop, &QEventLoop::quit);
      dialog.show();
      loop.exec(); // 等待对话框关闭
五、常见问题与解决
  1. 头文件缺失

    • 错误customepushbutton.h: No such file or directory

    • 解决:在CMake中添加包含目录:

      include_directories(${PROJECT_SOURCE_DIR})
  2. 类型转换错误

    • 错误invalid conversion from 'QWidget' to 'QPushButton'

    • 解决:确保自定义控件继承自正确的基类(如QPushButton)。

  3. 信号未触发

    • 原因:重写事件处理函数时未调用父类实现。

    • 解决:在自定义的mousePressEvent中调用QPushButton::mousePressEvent(e)

六、总结
  • 事件处理核心:理解事件分发流程、重写事件函数、使用过滤器拦截事件。

  • 事件与信号结合:事件处理底层交互,信号驱动业务逻辑,两者互补。

  • 开发注意事项

    • 继承控件时确保调用父类事件函数以维持原有逻辑。

    • 使用QEventLoop避免主线程阻塞,保持界面流畅。

    • 合理使用事件过滤器实现复杂交互逻辑。

 

  • 这是本人的学习笔记不是获利的工具,小作者会一直写下去,希望大家能多多监督我
  • 文章会每攒够两篇进行更新发布(受平台原因,也是希望能让更多的人看见)
  • 感谢各位的阅读希望我的文章会对诸君有所帮助

相关文章:

初识——QT

QT安装方法 一、项目创建流程 创建项目 入口&#xff1a;通过Qt Creator的欢迎页面或菜单栏&#xff08;文件→新建项目&#xff09;创建新项目。 项目类型&#xff1a;选择「Qt Widgets Application」。 路径要求&#xff1a;项目路径需为纯英文且不含特殊字符。 构建系统…...

几何_平面方程表示_点+向量形式

三维平面方程可以写成&#xff1a; π : n ⊤ X d 0 \boxed{\pi: \mathbf{n}^\top \mathbf{X} d 0} π:n⊤Xd0​ &#x1f4d0; 一、几何直观解释 ✅ 平面是“法向量 平面上一点”定义的集合 一个平面可以由&#xff1a; 一个单位法向量 n ∈ R 3 \mathbf{n} \in \mat…...

学习alpha

(sign(ts_delta(volume, 1)) * (-1 * ts_delta(close, 1))) 这个先用sign操作符 sign.如果输入NaN则返回NaN 在金融领域&#xff0c;符号函数 sign(x) 与 “基础”&#xff08;Base&#xff09;的组合概念可结合具体场景解读&#xff0c;以下从不同金融场景分析其潜在意义&…...

Java - Junit框架

单元测试&#xff1a;针对最小的功能单元(方法)&#xff0c;编写测试代码对该功能进行正确性测试。 Junit&#xff1a;Java语言实现的单元测试框架&#xff0c;很多开发工具已经集成了Junit框架&#xff0c;如IDEA。 优点 编写的测试代码很灵活&#xff0c;可以指某个测试方法…...

秒删node_modules[无废话版]

“npm install”命令带来的便利和高效让人感到畅快&#xff0c;但删除依赖包时却可能带来诸多困扰。特别是在项目依赖关系较为复杂的情况下&#xff0c;node_modules文件夹的体积往往会膨胀至数百MB甚至几个GB&#xff0c;手动删除时进度条长时间转圈&#xff0c;令人感到焦虑和…...

kkFileView文件文档在线预览镜像分享

kkFileView为文件文档在线预览解决方案&#xff0c;该项目使用流行的spring boot搭建&#xff0c;易上手和部署&#xff0c;基本支持主流办公文档的在线预览&#xff0c;如doc,docx,xls,xlsx,ppt,pptx,pdf,txt,zip,rar,图片,视频,音频等等 开源项目地址 https://gitee.com/kek…...

实例分割AI数据标注 ISAT自动标注工具使用方法

文章目录 🌕ISAT安装和启动方法🌕下载和使用AI分割模型🌙SAM模型性能排行🌙手动下载sam模型 & sam模型下载路径🌕使用方法🌙从file中导入图片🌙点击左上角的图标进入分割模式🌙鼠标左键点击画面中的人则自动标注🌙点击右键该区域不标注🌙一个人一个人的…...

Qt图表绘制(QtCharts)- 性能优化(13)

文章目录 1 批量替换代替追加1.1 测试11.2 测试21.3 测试3 2 开启OpenGL2.1 测试12.2 测试22.3 测试32.4 测试4 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;Qt开发 &#x1f448;&#x1f449;QtCharts绘图 &#x1f448;&#x1f449;python开发 &#x1f…...

Spring Cloud动态配置刷新:@RefreshScope与@Component的协同机制解析

在微服务架构中&#xff0c;动态配置管理是实现服务灵活部署、快速响应业务变化的关键能力之一。Spring Cloud 提供了基于 RefreshScope 和 Component 的动态配置刷新机制&#xff0c;使得开发者可以在不重启服务的情况下更新配置。 本文将深入解析 RefreshScope 与 Component…...

部署docker上的redis,idea一直显示Failed to connect to any host resolved for DNS name

参考了https://blog.csdn.net/m0_74216612/article/details/144145127 这篇文章&#xff0c;关闭了centos的防火墙&#xff0c;也修改了redis.conf文件&#xff0c;还是一直显示Failed to connect to any host resolved for DNS name。最终发现是腾讯云服务器那一层防火墙没…...

如何在 Windows 10 或 11 上使用命令提示符安装 PHP

我们可以在 Windows 上从其官方网站下载并安装 PHP 的可执行文件,但使用命令提示符或 PowerShell 更方便。 PHP 并不是一种新的或不为人知的脚本语言,它已经存在并被全球数千名网络开发人员使用。它以开源许可并分发,广泛用于 LAMP 堆栈中。然而,与 Linux 相比,它在 Wind…...

RK3588 ADB使用

安卓adb操作介绍 adb&#xff08;Android Debug Bridge&#xff09;是一个用于与安卓设备进行通信和控制的工具。adb可以通过USB或无线网络连接安卓设备&#xff0c;执行各种命令&#xff0c;如安装和卸载应用&#xff0c;传输文件&#xff0c;查看日志&#xff0c;运行shell命…...

Vue 3.0双向数据绑定实现原理

Vue3 的数据双向绑定是通过响应式系统来实现的。相比于 Vue2&#xff0c;Vue3 在响应式系统上做了很多改进&#xff0c;主要使用了 Proxy 对象来替代原来的 Object.defineProperty。本文将介绍 Vue3 数据双向绑定的主要特点和实现方式。 1. 响应式系统 1.1. Proxy对象 Vue3 …...

Please install it with pip install onnxruntime

无论怎么安装都是 Please install it with pip install onnxruntime 我python 版本是3.11 &#xff0c;我换成3.10 解决了...

java -jar命令运行 jar包时如何运行外部依赖jar包

java -jar命令运行 jar包时如何运行外部依赖jar包 场景&#xff1a; 打包发不完,运行时。发现一个问题&#xff0c; java java.lang.NoClassDefFoundError: org/apache/commons/lang3/ArrayUtils 显示此&#xff0c;基本表明&#xff0c;没有这个依赖&#xff0c;如果在开发…...

低损耗高效能100G O Band DWDM 10km光模块 | 支持密集波分复用

目录 前言 一、产品概述 100G QSFP28 O Band DWDM 10km光模块核心特点包括&#xff1a; 二、为何选择O Band DWDM方案&#xff1f; 1.低色散损耗&#xff0c;传输更稳定 2.兼容性强 三、典型应用场景 1.数据中心互联&#xff08;DCI&#xff09; 2.企业园区/智慧城市组网 3.电信…...

【解决分辨数字】2021-12-16

缘由用C语言解决分辨数字-编程语言-CSDN问答 int a 0, w 0, aa[6]{};cin >> a;while (a)aa[w] a % 10, a / 10, w;cout << w << endl;while (a<w)cout << aa[a] << ends, aa[5] * 10, aa[5] aa[a];cout << endl << aa[5] <…...

el-tree结合checkbox实现数据回显

组件代码 <el-tree:data"vertiList"show-checkboxnode-key"id":props"defaultProps"ref"treeRefx"class"custom-tree"check-change"handleCheckChange"> </el-tree>获取选择的节点 handleCheckChan…...

第二十六天打卡

全局变量 global_var 全局变量是定义在函数、类或者代码块外部的变量&#xff0c;它在整个程序文件内都能被访问。在代码里&#xff0c; global_var 就是一个全局变量&#xff0c;下面是相关代码片段&#xff1a; print("\n--- 变量作用域示例 ---") global_var …...

阿里云ECS部署Dify

一&#xff1a;在ECS上面安装Docker 关防火墙 sudo systemctl stop firewalld 检查防火墙状态 systemctl status firewalld sudo yum install -y yum-utils device-mapper-persistent-data lvm2 设置阿里镜像源&#xff0c;安装并启动docker [base] nameCentOS-$releas…...

【线段树】P4588 [TJOI2018] 数学计算|普及+

本文涉及知识点 C线段树 [TJOI2018] 数学计算 题目描述 小豆现在有一个数 x x x&#xff0c;初始值为 1 1 1。小豆有 Q Q Q 次操作&#xff0c;操作有两种类型&#xff1a; 1 m&#xff1a;将 x x x 变为 x m x \times m xm&#xff0c;并输出 x m o d M x \bmod M…...

日志与策略模式

什么是设计模式 IT⾏业 ,为了让 菜鸡们不太拖⼤佬的后腿, 于是⼤佬们针对⼀些经典的常⻅的场景, 给定了⼀些对应的解决⽅案, 这个就是 设计模式 日志认识 计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件&#xff0c;主要作⽤是监控运⾏状态、记录异常信 息&#xff…...

Jenkins 最佳实践

1. 在Jenkins中避免调度过载 过载Jenkins以同时运行多个作业可能导致资源竞争、构建速度变慢和系统性能问题。分配作业启动时间可以防止瓶颈&#xff0c;并确保更顺畅的执行。如何实现&#xff1f; 在Cron表达式中使用H&#xff1a;引入抖动&#xff08;jitter&#xff09;&a…...

天能股份SAP系统整合实战:如何用8个月实现零业务中断的集团化管理升级

目录 天能股份SAP系统整合案例&#xff1a;技术驱动集团化管理的破局之路 一、企业背景&#xff1a;新能源巨头的数字化挑战 二、项目难点&#xff1a;制造业的特殊攻坚战 1. 生产连续性刚性需求 2. 数据整合三重障碍 3. 资源限制下的技术突围 三、解决方案&#xff1a;S…...

搜索引擎的高级语法

文章目录 精确搜索&#xff1a;双引号站内搜索&#xff1a;site通配符搜索&#xff1a;*减号缩小范围&#xff1a;-文档搜索&#xff1a;filetypeURL搜索&#xff1a; inurl标题搜索&#xff1a;intitle正文搜索&#xff1a;intext参考链接 精确搜索&#xff1a;双引号 “ ” …...

uniapp-商城-59-后台 新增商品(属性的选中,进行过滤展示,filter,some,every和map)

前面讲了属性的添加&#xff0c;添加完成后&#xff0c;数据库中已经存在数据了&#xff0c;这时再继续商品的添加时&#xff0c;就可以进行属性的选择了。 在商品添加过程中&#xff0c;属性选择是一个关键步骤。首先&#xff0c;界面需要展示嵌套的属性数据&#xff0c;用户通…...

linux用户切换

在 Linux 系统中&#xff0c;/etc/shadow 文件存储了用户的加密密码和其他安全相关信息&#xff0c;因此默认只有 root 用户 才有权限读取。当你尝试用普通用户身份查看时&#xff0c;会收到 Permission denied 错误。 如何查看 /etc/shadow 文件&#xff1f; 方法 1&#xff…...

B2C 商城转型指南:传统企业如何用 ZKmall模板商城实现电商化

在数字化浪潮席卷全球的当下&#xff0c;传统企业向电商转型已不再是选择题&#xff0c;而是关乎生存与发展的必答题。然而&#xff0c;缺乏技术积累、开发成本高、运营经验不足等问题&#xff0c;成为传统企业转型路上的 “拦路虎”。ZKmall模板商城以其低门槛、高灵活、强适配…...

鸿蒙OSUniApp 实现的二维码扫描与生成组件#三方框架 #Uniapp

UniApp 实现的二维码扫描与生成组件 前言 最近在做一个电商小程序时&#xff0c;遇到了需要扫描和生成二维码的需求。在移动应用开发中&#xff0c;二维码功能已经成为标配&#xff0c;特别是在电商、社交和支付等场景下。UniApp作为一个跨平台开发框架&#xff0c;为我们提供…...

生成树协议 - STP

目录 BPDU STP选举机制 STP端口状态 STP计时器 STP拓扑变更机制 生成树协议&#xff08;Spanning Tree Protocol&#xff09;&#xff0c;简写为STP。 STP是二层网络中用于消除环路的协议&#xff0c;通过阻塞冗余链路&#xff0c;使可用链路在拓扑上呈现出无环的树结构&…...