【QT】初始QT

目录

  • 一.背景
    • 1.GUI开发的各种技术方案
    • 2.什么是框架
    • 3.QT支持的系统
    • 4.QT的版本
    • 5.QT的优点
    • 6.QT的应用常见
  • 二.环境搭建
    • 1.认识QTSDK中的重要工具
    • 2.使用QT Creator创建项目
    • 3.项目解释
      • (1)main.cpp
      • (2)widget.h
      • (3)widget.cpp
      • (4)widget.ui
      • (5)Empty.pro
      • (6)临时文件
  • 三.初始QT
    • 1.Hello World
      • (1)图形化方式
      • (2)代码方式
        • 2.1 内存泄漏问题
        • 2.2 对象树
        • 2.3 乱码问题
          • 背景
          • 解决问题
        • 2.4 小节
      • (3)编辑框实现
      • (4)按钮方式
        • 4.1 图形化方式
        • 4.2 代码方式
    • 2.QT中的命名规则
    • 3.QT Creator中的常见快捷键
    • 4.帮助使用文档
    • 5.窗口坐标体系

一.背景

互联网核心岗位:

  • 开发(程序员)、测试、运维(管理机器)、产品经理(非技术岗,提出需求)
  • 开发岗位:后端开发、前端开发、算法工程师、游戏开发
  • 前端开发:网页前端开发、桌面应用开发、移动端应用开发

qt属于客户端开发,也就是前端开发的一个分支,属于桌面应用开发

  • 客户端:直接和用户打交道的这一端的程序(edge浏览器、qq音乐、steam…)
  • 服务器:站在客户端背后的男人

虽然大部分的客户端程序,是需要有背后的服务器做支持的,但是确实也有些程序,是不需要服务器的,这样的程序(画图板、计算器…)同样也是直接和客户打交道,也可以称之为 “客户端程序”。

准确来说,qt特指用来进行桌面应用开发(电脑上的程序)设计到的一套技术。

qt无法进行网页开发,也不能进行移动端开发(虽然目前qt官方也支持移动应用的开发了,但是目前还没有听说有啥知名的商用移动应用程序是qt开发的)

  • 客户端开发的重要任务:编写和用户交互的界面

和用户交互的界面,两种分格:

  1. 命令行界面/终端界面(黑框框) —— TUI (专业软件,给程序员用的)
  2. 图形化界面 —— GUI (普通用户,会用鼠标即可)

qt是用来编写桌面GUI程序的一套框架~~

在Windows上编写GUI程序,也有很多的解决方案 ~~ qt只是其中的一种(学会了一个,其他的都比较容易上手)

1.GUI开发的各种技术方案

Windows下还有那些方案,可以开发GUI?

  1. Windows API:windows系统提供的原始API
    开发起来非常原始和繁琐,但在windows刚诞生的阶段也没有什么好选的

  2. MFC:利用面向对象的思想,将windows API分装起来变为一个个控件,并且提供图形化界面的方式可以拖拽控件。
    上世纪90年代到今天,影响力非常深远

  3. QT:采取面向对象的方式将系统API进行分装,提供可视化界面
    上世纪1991年诞生,和MFC不同,MFC早以不在更新,而qt仍然在不停的退出新的版本,至今仍然非常有生命力。

以上都是基于C/C++搭建出来的一些 GUI 开发的技术体系~~
后来微软自己做了一个编程语言C#,在进行windows界面开发时,微软逐渐偏向于C#

  1. Windows Forms 给C#量身定做的一套开发GUI技术体系

  2. WPF、UWP:Windows Forms的升级版

  3. Electron:本质上是基于HTML网页,打包成一个基于windows上运行的客户端程序~~
    这个技术体系,最初是用来开发“atom”文本编辑器的,而“atom”是GitHub官方开发的
    后来“atom”不在流行,被微软的VSCode取代
    比较大的缺点是开发出来的程序,运行效率低于上面介绍的一些原生开发的技术体系,所以很少有公司去使用它

QT虽然是上述解决方案中的一种,仍然属于是其中非常能打的(商业公司的产品,使用QT非常多,对应的就业岗位相比于其他的技术方案也是很多的)

并且,qt可以跨平台,支持windows、linux、Mac,对于公司来说,更容易降低开发成本。

2.什么是框架

QT是跨平台的C++ 图形用户界面应用程序框架 。它为应用程序开发者提供了建立艺术级图形界面所需要的所有功能。它是完全面向对象的,很容易扩展。QT为开发者提供了一种基于组件的开发模式,开发者可以基于简单的拖拽和组合来实现复杂的应用程序,同时也可以使用C++语言进行高级开发。

一般而言自由、灵活就意味着容易出错!毕竟不能给它自由过了火。

而框架,本质是一群大佬发明出来,方便让咱们能力低的程序员,写出来的东西比较靠谱的东西,也就是说看框架是用来限制程序员的自由,让代码的下限得到保证。

平时我们了解较多的是库,库和框架都属于大佬把一些程序写好,让你去使用。

  • 库:被程序员调用(程序员是主体)
  • 框架:程序员配合框架,完善填充框架中留出的一些细节(框架是主体)

相对于C++,Java对于框架更加的依赖。

编写C++代码,框架当然也很重要,C++的生态是割裂的,散列的,不像隔壁Java,存在非常大的社区,一统天下。C++不同的开源社区/大厂,各自有各自的框架,各自为政。

相比之下,像QT这种,能够被大家共同认可的框架,在整个C++生态中是不常见的

3.QT支持的系统

  1. Windows系统
  2. Linux系统,尤其是Linux中的KDE桌面基于QT构建的
    Linux主要是给服务器使用的,服务器不需要图形化界面,是TUI界面,基于命令操作,门槛更高, 效率也更高。Linux需要将资源放在更重要的地方,而不是图形化界面。
    Linux桌面环境中存在好几套桌面环境,也有少数的用户,使用Linux做为桌面,但是这些桌面环境都不是很成熟,一起来会很不舒服
    • GNOME:基于GTK创建的
    • KDE:基于QT创建的
  3. Mac系统
  4. 嵌入式系统
    嵌入式系统也是QT实际开发中的主要系统之一,日常使用的冰箱、洗衣机、路由器…这些设备内部也有计算机,这种设备,里面的计算机,配置就不会那么高。
    有先嵌入式系统,也是需要运行图形化界面的程序,这个时候,QT就可以起到作用,尤其是一些工业设备上。
    当然在这个领域,QT也受到了来自安卓的挑战

4.QT的版本

目前最新的版本是Qt6,但是相对来说,qt6和qt5之间的核心功能区别不大,并且企业中也仍然有大量的项目在使用qt5.

另外,qt在发布的时候还提供了两种许可证:

  • 商业许可:你想开发一个QT程序,就可以向所属公司购买商业许可证
  • 开源许可:也就是想要开发一个程序,不需要花钱,直接拿来用

而相比开源序可,商业序可提供的更是一种服务,一种技术支持,当开发程序时若出现问题,可以直接找所属公司,让他们来解决。

5.QT的优点

  • 跨平台,几乎支持所有的平台

  • 接口简单,容易上手,学习qt框架对学习其他框架有参考意义

  • 一定程度上简化了内存回收机制;
    C++是一个讲究效率的语言,对于内存回收重视度不够,而qt采取了一定的策略,实现了半自动的垃圾回收,简化内存释放,尽可能小的影响程序的运行效率。

  • 开发效率高,能够快速的构建应用程序

  • 有很好的社区氛围,市场份额在缓慢上升

  • 可以进行嵌入式开发

6.QT的应用常见

  • 桌面的应用程序
    qt能够创建各种类型的桌面应用程序,包括文件管理器、媒体播放器、绘图程序等。Qt应用程序支持多种操作系统,可以运行在多个桌面操作系统上

  • 移动应用程序
    支持Android和IOS移动操作系统,为应用程序提供强大的跨平台能力。
    Android和IOS系统已经有许多类似的框架,很少有人使用QT去开发

  • 嵌入式系统
    QT在嵌入式领域应用非常广泛,它可以构建面向各种设备的图像应用程序,在机顶盒、车载娱乐系统、安防监控领域具有广泛的应用。

二.环境搭建

注意:具体如何下载安装QT,已经有大量的资料存在,这里不在赘述
QT的开发环境,需要安装3个部分:

  1. C++ 编译器(gcc、cl.exe…)
    注意编译器不是ide,不是visual studio,编译器只是IDE调用的一个程序

  2. QT SDK
    SDK是软件开发工具包,其中包含开发qt程序用到的各种工具,一般SDK中已经内置了一个C++编译器,比如Windows版本的QT SDK里已经内置了C++编译器。(内置的编译器是mingw,widows版本的gcc/g++)
    要是想用VS内置的cl.exe做为编译器,也不是不行,需要配置很多东西,容易出错。
    具体安装的过程中,需要将对应的C++编译器给勾选上。

  3. 需要有一个QT的集成开发环境(IDE)

    1. QT官方提供的QT Creator
      使用这个写qt,是最容易上手的方式,不需要任何额外的配置,开箱即用,在初学阶段建议使用,虽然在使用过程中存在不少bug(有先bug非常影响使用体验),但用起来挺方便的,比较适合初学者。
    2. Visual Studio
      功能更强,但需要额外的配置更多,更容易出错,有先公司开发商业QT程序的时候,可能会使用VS。需要给VS安装QT插件,并且需要QT SDK使用VS的编译器重新编译(可以使用编译好的版本)
    3. Eclipse
      Eclipse并不只是java IDE,本身是一个IDE本身,可以搭配不同的插件构成不同的IDE,但目前Eclipse市场份额受到很大的冲击。

说是安装三个东西,其实只需要安装一个QT SDK,另外两个也就有了。

1.认识QTSDK中的重要工具

  • Assistant 5.15.2:Qt自带的离线版本的官方文档,虽然Qt也有中文文档(非官方的),但主流仍然是去看官网的英文文档。
  • QT Designer:图形化的设计界面的工具,通过拖拽控件的方式来快速的生产界面,后面经常使用但是搭配QT Creator来使用
  • Linguist:QT语言家,作用是对国际化进行支持,有的时候,写的程序,是要和国际接轨,允许你单独创建一个语言配置文件,把界面上需要用到的各种文字,都配置到文件中,并且在文件中提前的把各种语言的翻译都配置进去。
  • QT Creator:QT的集成开发工具,学习QT过程中主要使用的工具

2.使用QT Creator创建项目

在这里插入图片描述
Application:应用程序,如果使用QT写一个GUI程序,就应该选这个

  • QT Widget:传统的开发GUI的方式
  • QT Quick:QT搞出的一套新的用来开发GUI的方式
  • QT for Python:QT不只支持C++,也支持python和Java

在这里插入图片描述
构建系统,通过QT写的程序,涉及到一系列的 “元编程” 技术。通过代码来生成代码。

QT框架会在编译的时候,自动先调用一系列的生成工具,基于你自己写的代码,生成一系列的其他C++代码,最终编译的代码,也就是最后生成的这些代码

  • qmake:老牌的构建工具
  • CMake:并非是QT专属,很多开源项目都会使用CMake
  • Qbs:新一代的QT构建工具,实际上很少人去使用它

在这里插入图片描述

  • Base class:使用QT Creator创建项目时,会自动生成一些代码出来,生成的代码就是包含一个类。此处就是要选择这选择这个自动生成的类的父类是谁。
    在这里插入图片描述

    • QMainWindow:完整的应用程序窗口,可以包含菜单栏、工具栏、状态栏…
    • QWidget:表示一个控件(窗口上的一个具体的元素,输入框、按钮、下拉框、单选按钮、复选按钮…)
    • QDialog:表示一个对话框

    QT中内置的类都是以Q开头的
    QWidget最简单,初学阶段选QWidget。

  • Class name:自动生成的类的类名,它的父类是Base class选择的类

  • Header File、Source File:类对应的文件
    次数生成的文件名和类名关联,关联并不是强制的,推荐大家使用一样的

  • form file:Qt中创建图形化界面的方式有两种

    1. 直接通过C++代码的方式创建界面
    2. 通过from file(ui文件),以图形化的方式来生成界面
      此时就可使用QT Designed或者QT Creator来编辑这个ui文件,从而以图形化的方式快速的生成图形界面

在这里插入图片描述
选择翻译文件(对应语言),此处暂时不关注,和国际化相关

在这里插入图片描述
选择一下基于那个编译器的QT SDK来构建后续的代码。

3.项目解释

(1)main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[]) // 命令行参数
{
    QApplication a(argc, argv); // 将命令行参数作为参数创建对象
    Widget w; // Widget 刚刚创建项目时,填写了生成的类名,也就是创建一个控件对象
    w.show(); // 显示该控件对象
    return a.exec();
}
  • 编写一个QT的图形化界面程序,一定需要有QApplication对象
  • Widget的父类是QWidget,都是由QWidget提供(QT是很好的面向对象示例,在学习QT的过程中可以很好的理解面向对象)
  • exec在Linux中曾经学过,表示进程中的程序替换,把可执行文件中的代码和数据替换到当前进程中。当然,当前QT中的exec和Linux中的没有如何关系。

(2)widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; } // 命名空间,声明类
QT_END_NAMESPACE

class Widget : public QWidget //QT SDK内置类,要想继承QWidget必须包含头文件,#include <QWidget>
{
    Q_OBJECT  // QT内置的一个宏,本质是文本替换

public:
	// QT中引入了“对象树”机制,创建的QT对象,就可以把这个对象挂在树上,往树上挂的时候需要指定“父节点”(后面讲)
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui; // 与 from file 密切相关
};
#endif // WIDGET_H
  • QT设定,使用QT中内置的类,包含的头文件的名字就是和类名一致的
    也不是用到的所有的QT的类都需要显示包含头文件。参考C++中,头文件可能是间接包含

  • 写代码的原则:一个QT的类,先拿过来,如果直接能用,说明对应的头文件已经被包含过了,无需显示包含。如果这个类提示找不到定义,在手动显示对应的头文件。

  • Q_OBJECT展开之后会生成一大堆代码,当我们使用QT中的 信号 的时候需要引入这个宏(具体后面介绍)

(3)widget.cpp

#include "widget.h"    // 创建项目生成的头文件
#include "ui_widget.h" // form file 被 qmake 生成的头文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this); // 6、8行 把 form file 生成的界面和当前 widget 关联起来
}

Widget::~Widget()
{
    delete ui;
}
  • 关键要点:form file

(4)widget.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Widget</string>
  </property>
 </widget>
 <resources/>
 <connections/>
</ui>
  • 单击Widget.ui文件,进入上述代码编辑部分,该代码格式为 xml
    xmlhtml 非常相似,都是使用成对的标签来表示数据
    xml 这里的标签,有那些标签,都有什么含义,这个是程序员自定义的。此处看到的这些标签,就是QT开发大佬们自己约定的(这里的标签具体是啥含义不需要关注,只需要直到ui文件本质是一个xml即可)
    html虽然也是通过标签来表示,但是它的标签是固定的,每个标签是啥含义,有一个专门的标签委员会约定,所有的浏览器也是按照同样的规则来解释的。
    这里的xml标签的含义,就类似与Linux网络原理中学过的一个话题,自定义应用层协议。

  • QT中使用xml文件就是去描述程序的界面是啥样的,进一步的qmake会调用相关的工具,依据这个文件生成一些C++代码,从而把完整的界面构造出来。

  • 双击Widget.ui文件,QT Creator就会调用QT Designer,打开ui文件,打开图形化界面编辑器

在这里插入图片描述

(5)Empty.pro

QT       += core gui  // 要引入的QT模块,后面学习到一些内容的时候可能会修改这里

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

// 下面三项描述了当前项目中,参与构建的文件有啥(编译器要编译那些文件)
// 这个地方不需要手动修改,QT Creator 帮咱们自动维护好
SOURCES += \
    main.cpp \
    widget.cpp

HEADERS += \
    widget.h

FORMS += \
    widget.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
  • QT项目的工程文件,也是qmake工具构建时候的重要依据

  • .pro类似于在Linux上的makefile
    makefile其实是一个非常古老的技术,是常见的方式,但不是唯一的方式
    qmake搭配 .pro 起到的作用就和 Makefile 是类似的

  • QT Creator将运行过程都分装好了,只需要点击运行即可

(6)临时文件

上面的看到的各个文件都是源代码,如果编译运行QT项目,构建过程中还会生成一些中间文件
在这里插入图片描述
打开文件资源管理器,看看项目对应的目录是什么样子
在这里插入图片描述
运行一次文件,就会在项目目录并列的地方多出一个 build-xxxx 目录,这个目录里面就是该项目运行过程中,生成的一些临时文件。
在这里插入图片描述

  • 编译QT程序,还是会用到makefile,只不过这个makefile不需要手动写,而是 qmake 自动生成的
  • ui_widget: widget.ui xml生成的 .h文件
    在这里插入图片描述
    在这里插入图片描述

三.初始QT

1.Hello World

有两种方式实现Hello World

  1. 通过图形化方式,在界面上创建出一个控件,显示 Hello World
  2. 通过重代码的方式,通过编写代码,在界面上创建一个控件,显示Hello World

接下来,我们先分别了解代码和图形化方式,在来看一下不同的控件对应这两种方式的实现过程。

(1)图形化方式

双击Widget.ui文件,进入图形化界面,在控件区的最下方有一个Display Widgets分类,这个分类中的所有控件都是用来显示,使用Label控件(在QT中完成Hello World 可以通过很多种控件实现),拖拽该控件,放入程序窗口,修改内容,得到结果:
在这里插入图片描述
点击左下角运行:

在这里插入图片描述
刚才往界面上拖拽了一个QLabel控件,此时,ui文件的xml中就多出一段代码(如下:16~28行)

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Widget</string>
  </property>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>310</x>
     <y>230</y>
     <width>221</width>
     <height>51</height>
    </rect>
   </property>
   <property name="text">
    <string>Hello World</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

进一步的qmake就会在编译项目的时候,基于这个内容生成一段C++代码,通过这个C++代码构建出界面内容。(自动完成)

而在该项目临时文件ui_widget.h中,就会根据Widget.ui配置文件,创建出对应的方法:

    void setupUi(QWidget *Widget)
    {
        if (Widget->objectName().isEmpty())
            Widget->setObjectName(QString::fromUtf8("Widget"));
        Widget->resize(800, 600);
        // 下面三行创建标签
        label = new QLabel(Widget);
        label->setObjectName(QString::fromUtf8("label"));
        label->setGeometry(QRect(310, 230, 221, 51));

        retranslateUi(Widget);

        QMetaObject::connectSlotsByName(Widget);
    } // setupUi

(2)代码方式

一般我们通过代码来构建界面时,一般会将构造界面的代码放在 Widget/MainWindow 的构造函数中。(以Widget为类)而构造函数在main.cpp中的Widget w;创建对象时调用,调用的是widget.cpp中对应的构造函数。

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget) // ui成员变量是qmake自动生成的
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

使用代码方式要使用Lable标签,那必须要添加头文件,否则报错:
在这里插入图片描述
添加QLable头文件时出现了两个文件:

  • qlabel.h:早期QT使用的头文件分格
    当时C++的标准还没有确立,直到C++98之后,规定包含头文件,比如统一使用#include 代替原有的#include <stdio.h>

  • QLabel:在原来头文件的基础上,重新使用更加规则的头文件名编写该头文件
    目前我们用更加规范的也就是QLabel

#include "widget.h"
#include "ui_widget.h"
#include <QLabel>  // 在QT当中,每个类都有对应的头文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QLabel* lable = new QLabel(this); // 创建Lable对象,参数this表示给当前lable对象指定父对象(父对象:Widget w对象)(对象树,使各个节点连在一起)
    // 创建对象时,可以在堆上创建,也可以在栈上创建,两种写法都行,但是更推荐在堆上创建(原因在对象树中讲)
    
    lable->setText("Hello World!"); // 设置控件中要显示的文件,参数为const QString,QString内部提供构造函数转换C分格字符串,
    // QT诞生在没有标准及标准库的年代,在当时如何表示字符串并不统一,而且当时使用的字符串方式都不好用,于是自己建造轮子,
    // 做了一系列的基础类,来支持QT的开发,如:字符串:QString、动态数组:Qvector、链表:QList等
    // 经过很多年的打磨,C++标准库和QT使用的虽然本质是一个东西,但是两种又是分开的,想要删掉某一方这是不可能的,只能共存。
    // 所以,在开发代码时可以使用标准库或QT提供的,但是在QT原生的api中,涉及到的东西,用的都是自己的这一套东西。
    // 后续代码中会经常遇到QString,很少遇到std::string,但两种可以相互转换
    // QString用起来要比std::string好,因为QString内部已经对于字符编码做了处理(如转码操作),而std::string啥也没干
}

Widget::~Widget()
{
    delete ui;
}

在这里插入图片描述

  • 通过代码创建QLable默认是在左上角,想要换到其他位置也是可以的(后续在下面窗口坐标系中讲)
2.1 内存泄漏问题

在上述代码中我们发现,new出来的对象,并没有释放,难道不会造成内存泄漏吗?

#include "widget.h"
#include "ui_widget.h"
#include <QLabel>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QLabel* lable = new QLabel(this); // new出来的对象没有释放
    lable->setText("Hello World!"); 
}

Widget::~Widget()
{
    delete ui;
}

上述代码中,在QT中不会产生内存显露问题,label对象会在合适的时候被析构释放,虽然没有手动delete,但确实释放了。

之所以能够将该对象释放,主要是因为把这个对象挂在了对象树上。(详细看下面)

2.2 对象树

对象树不只是在QT中用到,前端开发(网页开发)也涉及到类似的对象树(DOM),本质上也是一个树形结构(N叉树),通过树形结构把界面上的各种元素组织起来。

QT中也是类似的,也搞了一个对象树,也是N叉树,把界面上的各种元素组织起来。

在这里插入图片描述
使用这些对象树,将这些内容组织起来,最主要的目的就是在合适的时机(窗口关闭),把这些对象统一进行释放。

这里的树上的对象,统一销毁是最好的,如果提前销毁,就会导致对应的控件在界面上不存在了。

所以创建对象时使用new,就是为了把这个对象的生命周期交给QT对象统一管理。若是在栈上创建对象,在运行时就会出现问题(提前释放),生命周期结束后,对应的栈就会销毁,对象也不会长久存在。

为了更好的看到这个现象,我们做一个小demo

  1. 创建mylabel类
    在这里插入图片描述

  2. 打开mylabel.h头文件

    #ifndef MYLABEL_H
    #define MYLABEL_H
    #include <QLabel>
    
    // 使用F4可以切换到对应的.cpp文件
    
    class mylabel : public QLabel
    {
    public:
       // 构造函数使用带QWidget* 版本的,这样才能保证添加到对象树上
        mylabel(QWidget* parent);
       ~mylabel();
    };
    
    #endif // MYLABEL_H
    

    注意这里需要手动添加头文件

  3. 点击F4,切换到mylabel.cpp文件,修改该文件如下:

    #include "mylabel.h"
    #include <iostream>
    
    mylabel::mylabel(QWidget* parent)
        :QLabel(parent)
    {
    
    }
    
    mylabel::~mylabel()
    {
        std::cout << "mylable 被销毁!" << std::endl;
    }
    
  4. 打开widget.cpp文件,修改文件如下:

    #include "widget.h"
    #include "ui_widget.h"
    #include "mylabel.h"
    
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
        , ui(new Ui::Widget)
    {
        ui->setupUi(this);
    
        // 使用自己定义的 mylabel 代替原来的 QLabel,所谓的“继承”本质上是扩展,保持原有功能不变的基础上,
        // 给对象扩展一个析构函数,通过这个析构函数,打印一个自定义的日志,方便咱们观察程序的运行效果
        mylabel* label = new mylabel(this);
        label->setText("hello World!");
    }
    
    Widget::~Widget()
    {
        delete ui;
    }
    
  5. 运行该项目,显示hello world!
    在这里插入图片描述

  6. 关闭显示窗口,在version Control模块,查看日志,看到mylabel类执行析构函数时输出内容:
    在这里插入图片描述
    由此,我们可以确定,在QT中我们new创建一个对象,该对象被放入对象树中,当窗口被销毁时,就会自动销毁对象树上所有对象,mylabel的析构函数被执行。

2.3 乱码问题

观察上面的demo,我们看到析构函数所输出的内容与我们想要它输出的存在差异
在这里插入图片描述
预期打印的时“被销毁”三个中文,但是实际显示效果出现了乱码。

对于乱码,我想大多数人会经常涉及到,而乱码问题,出现的原因有且只有一个(不限于C++):编码方式不匹配

背景

首先,我们要知道一个汉字,在计算机中,占几个字节?大多数人回答都是两个字节,其实这是不对的。

针对这个问题,只要你回答出一个具体的数字,就一定是错的,这需要结合前提条件,即当前中文编码使用的是那种方式(字符集)。

中文中的常用字大概是4千多个,算上不常见的,总数差不多是6万左右,参考ASCII码表,将这6万格字整理为一个表格,不同的数字对应不同的字,这对于计算机来说不是难事,对于这些表格称之为字符集,但具体这个表格长什么样子,不同的组织给出的结果不同。

表示汉字的字符集,其实有很多种,不同的字符集,表示同一个汉字,使用的数字不同。

随着时间的推移,很多字符集淹没在了历史中,目前,表示汉字的字符集,主要有两种方式:

  1. GBK(中国大陆):使用2个字节表示一个汉字(Windows简体中文,默认的字符集就是GBK)
  2. UTF-8/UTF8:边长编码,表示一个符号使用的字节数存在变化(2~4不定),但对于汉字,一般是3个字节(Linux默认就是UTF8)

一个汉字,具体的UTF8/GBK的一个汉字具体对于的编码是多少,可以通过在网上查找。

解决问题

了解了背景,我们在回到问题,编码不匹配就是,你的字符串本身是utf8编码的,但是终端(控制台)是按照gbk的方式来解析显示的,此时就会出现乱码问题。

想要解决这个问题,我们就要了解到,那些环境涉及到编码的问题,这些了解后将其统一自然就解决了。

在这里插入图片描述
上述的字符串使用的编码方式,与当前mylabel.cpp文件的编码方式是一致的。

对于如何查看一个文件的编码方式这里教大家一个小技巧:

  1. 在QT Creator中右击文件,选择在Explorer中显示
    在这里插入图片描述
  2. 打开后右击文件,选择打开方式,选择记事本打开
    在这里插入图片描述
  3. 在记事本的右下角给出编码方式
    在这里插入图片描述
    显示是UTF-8说明这个文件是UTF-8编码,显示是ANSI说明这个文件是GBK编码

在QT Creator内置的终端中显示出乱码,说明它不是UTF8编码,而且这个终端好像不能设置字符编码。

想要解决这个乱码问题,只能统一编码格式,要么改终端,要么改文件,终端不好改,而对于改文件,在当前表示中文的编码中,主流的方式就是使用UTF-8,这种编码方式适用范围极广,改它缺点也很大。

这里,我们就要使用到QT中提供的一个 qDebug() 这样一个工具,借助这个,就可以完成打印日志这个工作,很好的处理字符编码(不需要程序员关注,QT内部解决)。

修改mylabel.cpp如下:

#include "mylabel.h"
#include <iostream>
#include <QDebug>

mylabel::mylabel(QWidget* parent)
    :QLabel(parent)
{

}

mylabel::~mylabel()
{
//    std::cout << "mylable 被销毁!" << std::endl;
    qDebug() << "mylable 被销毁!";
}
  • QDebug是QT中的一个类,我们不会直接使用这个类,而是通过qDebug()这个东西,当做cout来使用。

后续在QT中,如果需要通过打印日志的方式,输出一些调试信息,都优先使用qDebug,虽然cout也行,但是凑团对于编码的处理不好,在Windows上容易出现乱码(如果在Linux上使用QT Creator,一般没事,Linux默认的编码一般都是utf8)。

qDebug还有一个好处,可以统一进行关闭,当我们想要它正常显示时它正常显示,若是不想,可以通过编译开关,来实现一键式的关闭。

2.4 小节
  1. 认识QLabel类,能够在界面显示字符串。

    通过 setText 来设置,参数 QString(QT中把C++里的很多容器类,进行了重新分装,历史原因)

  2. 内存泄漏

  3. 对象树,QT中通过对象树来统一释放界面的控件对象。

    QT还式推荐使用new的方式在堆上创建对象,通过对象树,统一释放对象

    创建对象树的时候,在构造函数中,指定父对象(此时才会挂在对象树上)

    如果你的对象没有挂在对象树上,记得手动释放

  4. 通过继承自 QT 内置的类,可以达到堆现有控件进行功能扩展的效果。

    QT内置的 QLabel,没法看到销毁的过程,为了看清,就创建类 mylabel,继承自QLabel,重写了析构函数。

    在析构函数中,加上日志,直观的观察到对象释放的过程。

    (面向对象继承的本质是对现有代码的扩展,也可以重写控件中任何的功能,达到对功能扩展的目的)

  5. 乱码问题 和 字符集问题

  6. 如果 QT 中打印日志,作为调试信息。

    使用cout固然可以,但不是上策(字符编码处理的不好,也不方便关闭)

    QT推荐使用qDebug()完成日志的打印。

  7. 我们之前调试程序的时候,都是使用调试器,VS/gdb,这里为什么打印日志调试?

    其实调试器有很多的局限性,是无法使用的,比如概率性问题,一个bug调试100次,只出现一次甚至更少,要想调试,使用调试器就不可能了。

    而无论使用那种方式,本质上就是观察程序执行的中间过程中间结果,只是在不同的场景下使用哪一个更有效而已。

(3)编辑框实现

编辑框分为如下两种:

  1. 单行编辑框:QLineEdit
  2. 多行编辑框:QTextEdit

这里我们使用单行编辑框即可,和上面的图形化方式相同,编辑框也可以使用代码的方式,这里先介绍控件的方式。

如下图,拖拽 Line Edit 控件到面板,输入字符串,运行后即为所需内容:

在这里插入图片描述
而对于代码的方式,我们需要修改 widget.cpp 内容如下:

#include "widget.h"
#include "ui_widget.h"
#include <QLineEdit>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QLineEdit* q = new QLineEdit(this);
    q->setText("hello World!");
}

Widget::~Widget()
{
    delete ui;
}

(4)按钮方式

4.1 图形化方式

在设计中找到push button按钮(这里主要使用该按钮),拖拽其到面板修改内容即可:
在这里插入图片描述
作为一个按钮,可以进行点击,但当我们运行该程序,点击按钮会发现没有什么反映。

这里就涉及到了QT当中的信号槽机制,本质就是给按钮的点击操作,关联上一个处理函数,当用户点击的时候,就会执行该函数。(这里的函数本质就是一个回调函数)

想要管理这样一个函数需要使用到 connect() 函数(在Linux网络编程中,也学到过这样一个函数connect函数,用来给TCP socket建立连接用的,这里的connect与网络中的没有任何关系),该函数是QObject这个类提供的静态函数,这个函数的作用就是 “连接信号” 和 “槽”。

修改Widget.cpp中代码如下:

#include "widget.h"
#include "ui_widget.h"
#include <QObject>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::handleClick);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handleClick() // 注意:需要在Widget.h文件中添加该函数声明
{
    ui->pushButton->setText("hello QT!");
}

运行结果如下:

在这里插入图片描述
接下来我们来简单了解一下connect这个函数:

static QMetaObject::Connection connect(
    const QObject *sender, 
    const QMetaMethod &signal, 
    const QObject *receiver, 
    const QMetaMethod &method, 
    Qt::ConnectionType type = Qt::AutoConnection);
  • 第一个参数表示谁发出的信号
    这需要访问form file(ui 文件)中创建的控件。
    在QT Designer(设计)中创建一个控件,此时会给这个控件分配一个objectName属性,这个属性的值,要求是在界面中是唯一的(不能和别人重复),这个值可以自己生成,也可以手动修改。
    如下图,为上面按钮控件对于的值:
    在这里插入图片描述

    qmake在预处理.ui文件的时候,就会根据这里的 objectName 生成对于的C++代码。
    若我们打开该项目对应的临时文件,找到 ui_widget.h 文件打开后,会发现如下成员变量:
    在这里插入图片描述

    C++代码中该 QPushButton 类型所创建对象的名字就是这里的 objectName对应的值,这个变量就是ui属性中的成员变量。
    若是我们手动修改QT Designer中该控件的objectName值,ui_widget.h中对应的名称也会改变。(注意:变的是变量名,QPushButton是类型名,没办法变)

  • 第二个参数在点击按钮时就会触发这个信号,表示发出了啥信号
    有了这个信号就要将它关联到具体的槽函数上

  • 第三个参数表示谁来处理这个给信号
    指定关联到那个对象的槽函数

  • 第四个参数表示具体怎么处理这个信号
    指定具体的槽函数

4.2 代码方式

我们新建一个项目,修改widget.cpp文件如下(widget.h文件也需要修改,在代码中有体现):

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    myButton = new QPushButton(this); // 因为需要在处理信号函数中用到myButton变量,将其在Widget类中设置为成员变量
    myButton->setText("Hello World!");

    connect(myButton, &QPushButton::clicked, this, &Widget::handleClick);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::handleClick()
{
    if(myButton->text() == QString("Hello World!"))
    {
        myButton->setText("Hello QT!");
    }
    else
    {
        myButton->setText("Hello World!");
    }
}

对于纯代码版本,按钮对象是咱们自己new的,为了保证其他函数中也可以访问到这个变量,就需要把按钮对象,设定为Widget类的成员变量。

而对于图形界面,不需要自己new对象操作,这已经被QT自动生成了,而这个按钮对象,已经作为 ui 对象里的一个成员变量。无需作为 Widget 成员

对于这两种方法,在实际开发中使用谁没有主次之分。

如果当前的界面内容比较固定,此时就用图形化的方式来构建界面。

如果当前界面经常要动态变化,此时就以代码的方式来构建界面。

2.QT中的命名规则

在给变量/函数/文件/类 起名字是非常有讲究的:

  1. 起名要有描述性
  2. 如果名字有点长,有多个单词构成的,就需要使用适当的方式来区分不同单词

如下是QT中类名、函数名和变量名的命名规则:

  • 类名:首字母大写,单词和单词之间首字母大写;
  • 函数名及变量名:首字母小写,单词和单词之间首字母小写

3.QT Creator中的常见快捷键

  • F4:头文件和源文件之间切换
  • Alt + Enter:将光标移动到h文件中的方法声明,按Alt+Enter,再按回车键将在cpp中添加对应的方法实体
  • ctrl + / :注释
  • ctrl + R :运行
  • ctrl + B :编译
  • ctrl + 鼠标滚动 : 字体缩放
  • ctrl + F : 查找
  • ctrl + i :自动对齐
  • F1:帮助文件

4.帮助使用文档

打开帮助文件有三种方式(在QT Creator中使用):

  1. 光标选中想要查找的类或函数,点击F1
  2. QT Creator右侧栏边框中直接使用鼠标单机 “帮助” 按钮
  3. 直接在Windows搜素框搜素QT Assistant或是在QT安装目录下的mingw**/bin/assistant下直接点击该应用程序

本质上上述三种方式查找的都是同一份文档。

在实际开发中,一定会用到很多的第三方库和框架,很可能这些库会比较小众,网上很难找到一些相关资料,这就需要我们来查找官方文档,而且这个官方文档的作者很可能来自于其他国家,但只要我们所用到的东西走向世界,那一定会有英文的文档。

5.窗口坐标体系

坐标体系:以左上角为原点(0,0),X向右增加,Y向下增加。

在这里插入图片描述
计算机中采用左手坐标系,它的原点(0,0)在屏幕/窗口的左上角。

给QT中某个控件设置位置时,就需要指定坐标,相对于控件来说,坐标系的原点就是相对于它的父窗口/控件。

在这里插入图片描述
在前面讲对象树时已经提到,QPushButton的父元素/父控件/父窗口 就是QWidget。

QWidget没有父元素(NULL),所以它的父元素就是整个显示器桌面了。

重新创建一个项目,修改widget.cpp文件并运行如下:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    myButton = new QPushButton(this);
    myButton->setText("Hello World!");
}

Widget::~Widget()
{
    delete ui;
}

关于像素:
显示器本质上是由一大堆可以发光的小亮点/小灯泡来构成的
如果将自己的手机摄像头对准显示器,焦距放到最大,距离合适是可以看到的。
也可以在自己windows设置中查看自己显示器上的分辨率,这样我们就可以知道自己电脑横纵坐标最大取值范围。
在这里插入图片描述
move函数中参数为1,表示移动1像素。

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    myButton = new QPushButton(this);
    myButton->setText("Hello World!");
    myButton->move(100, 300);
}

Widget::~Widget()
{
    delete ui;
}

在这里插入图片描述
同样的,不只是控件可以设置位置,运行出来的窗口也可以设置相对于桌面的位置,这需要使用在qmake创建的widget类对象来调用,也就是在widget构造函数中使用this指针调用,如下(这里就不掩饰结果了):

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    myButton = new QPushButton(this);
    myButton->setText("Hello World!");
    myButton->move(100, 300);


    this->move(100, 0);
}

Widget::~Widget()
{
    delete ui;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/613940.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv8预测流程-原理解析[目标检测理论篇]

接下来是我最想要分享的内容&#xff0c;梳理了YOLOv8预测的整个流程&#xff0c;以及训练的整个流程。 关于YOLOv8的主干网络在YOLOv8网络结构介绍-CSDN博客介绍了&#xff0c;为了更好地介绍本章内容&#xff0c;还是把YOLOv8网络结构图放在这里&#xff0c;方便查看。 1.前言…

以太网技术介绍

随着通信和计算机技术的不断发展&#xff0c;无论是骨干网还是接入网&#xff0c;以太网都已成为应用场景最多&#xff0c;应用范围最广泛的技术之一。对于初次应用以太网的读者&#xff0c;本文主要给出以太网技术的基础知识&#xff0c;并对以太网涉及的部分协议进行简要说明…

找不到msvcr120.dll无法执行代码?几种方法一键修复msvcr120.dll难题

电脑出现“找不到msvcr120.dll无法执行代码”是什么情况&#xff1f;msvcr120.dll文件是Microsoft Visual C Redistributable的一部分&#xff0c;它是应用程序在Windows操作系统上正常运行所必需的动态链接库文件之一。因此&#xff0c;缺少了msvcr120.dll文件&#xff0c;相应…

Sora惊艳亮相:AI技术掀起创作革命,影视产业迎来新风貌!

Sora平台近期发布了名为"Sora首次印象"的更新&#xff0c;为用户带来了令人瞩目的变化。该更新不仅展示了Sora平台的发展方向&#xff0c;还介绍了其在电影制作、广告宣传等领域的潜在应用。 同时&#xff0c;Sora的首席执行官Sam Altman与好莱坞影视工作室进行了会…

电火灶是燃气灶吗?节能、环保效果怎么样?

随着科技的进步&#xff0c;厨房中的传统设备也逐步被新型、高效且环保的设备所替代。电火灶&#xff0c;作为一种新型的电火烹饪设备&#xff0c;逐渐进入人们的视野。那么&#xff0c;电火灶是否与传统的燃气灶有所区别&#xff1f;其节能与环保效果又如何呢&#xff1f;下面…

精益生产咨询公司:深入探讨其独特魅力与核心竞争力

精益生产咨询公司&#xff0c;作为专注于帮助企业实现精益转型和效率提升的专业机构&#xff0c;在现代工业生产中扮演着不可或缺的角色。这些公司不仅具备深厚的行业经验和专业知识&#xff0c;还能够根据企业的实际情况和需求&#xff0c;提供个性化的解决方案和持续的支持服…

stm32f103c8t6之4x4矩阵按键

基于普中精灵开发板 1、矩阵按键原理 当我们需要使用较多的按键时&#xff0c;单片机的IO口可能不够用,这是就需要使用矩阵按键。 对应IO口如下&#xff1a; 步骤解析&#xff1a; 1、全部按键都没有按下时&#xff0c;全行IO为低电平&#xff08;全列对应的IO设置为下拉低…

maven mirrorOf的作用

在工作中遇到了一个问题导致依赖下载不了&#xff0c;最后发现是mirror的问题&#xff0c;决定好好去看一下mirror的配置&#xff0c;以及mirrorOf的作用&#xff0c;以前都是直接复制过来使用&#xff0c;看了之后才明白什么意思。 过程 如果你设置了镜像&#xff0c;镜像会匹…

基于Springboot+Vue的Java项目-车辆管理系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

网易云新玩法:教你赚取第一桶金!

在现今的音乐应用市场中&#xff0c;有几款软件备受广大用户的青睐。 其中&#xff0c;QQ音乐、酷狗音乐以及网易云音乐都是大家耳熟能详的名字。 这些平台不仅提供了丰富的音乐资源&#xff0c;还具备了许多便捷的功能&#xff0c;使得用户们能够享受到更为优质的音乐体验。…

Covalent Network(CQT)通过 “新曙光” 计划实现重要里程碑,增强以太坊时光机,提供 30% 的年化质押收益率

Covalent Network&#xff08;CQT&#xff09;作为集成超过 280 条区块链&#xff0c;并服务于超过 2.8 亿个钱包的领先结构化数据基础设施层&#xff0c;宣布了其战略计划 “新曙光” 中的一个重要进展。随着网络升级并完成了准备工作的 75%&#xff0c;这将为即将部署的以太坊…

Jsp+Servlet实现图片上传和点击放大预览功能(提供Gitee源码)

前言&#xff1a;在最近老项目的开发中&#xff0c;需要做一个图片上传和点击放大的功能&#xff0c;在Vue和SpringBoot框架都有现成封装好的组件和工具类&#xff0c;对于一些上世纪的项目就没这么方便了&#xff0c;所以需要自己用原生的代码去编写&#xff0c;这里分享一下我…

Nextcloud私有云盘-重新定义云存储体验

Nextcloud私有云盘-重新定义云存储体验 1. 什么是Nextcloud ​ Nextcloud是一个开源的云存储和协作平台&#xff0c;旨在为个人用户、企业和团队提供安全、隐私保护的数据存储和共享解决方案。它允许您在不同设备之间同步、共享文件&#xff0c;提供了强大的协作工具和应用生…

C++初阶之stack,queue,priority_queue的使用和模拟以及仿函数的创建和使用

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.stack,queue,priority_queue简介以及代码模拟 1.1 stack …

Java | Leetcode Java题解之第74题搜索二维矩阵

题目&#xff1a; 题解&#xff1a; class Solution {public boolean searchMatrix(int[][] matrix, int target) {int m matrix.length, n matrix[0].length;int low 0, high m * n - 1;while (low < high) {int mid (high - low) / 2 low;int x matrix[mid / n][m…

激光雷达在工厂散料体积测量中的经济效益分析

随着市场竞争的加剧&#xff0c;企业对于成本控制和效率提升的需求越来越迫切。激光雷达作为一种高效、准确的测量工具&#xff0c;在工厂散料体积测量中发挥着重要作用。本文将对激光雷达在工厂散料体积测量中的经济效益进行分析。 一、减少人工成本 传统的散料体积测量方法…

人工智能_大模型049_模型微调009_llama2模型训练_代码分析和实现_代码记录---人工智能工作笔记0184

以上是项目的整体结构,其中上一节我们看了chatglm3目录下,对chatglm3模型的训练部分的代码,然后 这里的llama2目录下是对llama2模型进行训练的代码. 然后web_demo目录是,对web浏览器中,使用chatglm3,以及llama2.py进行的封装下一节我们再看这个部分 E:\2024\人工智能\fine-tun…

Stable Diffusion写真完整教程

前言 最近自己对AI非常痴迷&#xff0c;并且今后也会一直在这个领域深耕&#xff0c;所以就想着先入门&#xff0c;因此花时间研究了一番&#xff0c;还好&#xff0c;出了点小成果&#xff0c;接下来给大家汇报一下。 AI绘画 提到AI绘画&#xff0c;大家可能立马会想到made…

JMeter 如何应用于 WebSocket 接口测试

WebSocket: 实时双向通信的探索及利用 JMeter 进行应用性能测试 WebSocket 是一项使客户端与服务器之间可以进行双向通信的技术&#xff0c;适用于需要实时数据交换的应用。为了衡量和改进其性能&#xff0c;可以通过工具如 JMeter 进行测试&#xff0c;但需要先对其进行适配以…

光栅测长机高精度检定量规量具

在制造业中&#xff0c;量规、量具等精密测量工具的准确性直接影响着产品质量和制造效率。为了确保这些测量工具的精准度&#xff0c;光栅测长机应运而生&#xff0c;成为了检定量规量具的利器。 光栅测长机是一种高精度的长度测量设备&#xff0c;它是利用光栅的精密刻度和光…