一文入门Qt Quick

news2024/11/27 21:36:27

以下内容为本人的著作,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/dvamU6q5lZQb5hztfD2zNg

初识Qt Quick

很高兴可以来到这一章,终于可以开始讲讲最近几年Qt的热门技术Quick这一块了。

啥是Qt?

哦,这是一个宣称可以跨任意平台,开发各种场景应用软件的开发框架。从三个维度来讲,就是开发库framework,集成开发平台IDE,以及成熟的开发思维模式。

Qt Quick最早出现在Qt的4.7版本中,目标是在UI设计者与开发者之间搭建一个更高效合作平台,给开发者更好的UI开发体验。虽然几经易手,Qt在digia公司这些年的努力迭代更新下,Qt Quick终于迎来了成熟稳定的版本(这也是我愿意在最近的项目里转用它的原因)。

至于Qt Quick和老一套开发核心Qwidget的区别,其中最重点的就是提供了新的UI描述语言QML(Qt Meta-object Language,Qt元对象描述语言)。QML乍看起来有点像json,但是核心思想却是模仿web页面。没错,在QML文件中允许搭配Javascript代码,就可以辅助实现丰富的UI交互逻辑。

如果你以往习惯QWidget开发,那么Qt Quick真的非常值得上手试试。

好了,口水吐多了招人厌,下面直入庐山一窥真面目!

手剥一个简单的功能程序开发栗子

在Qt开发过程中,Qt官方IDE(Qt Creator)提供了好几种工程构建工具,比如简单易懂的qmake,火上天的cmake,还有貌似没人听说过的Qbs。而目前Qt主推的构建方式就是cmake,下面要讲的例子也是用cmake。

1.开发环境配置

Win10
Qt 6.2.4
Qt Creator 8.0.1
Mingw 11.2.0 64bit
Cmake 3.23.2

这里选用的Qt版本是写作时最新的LTS版本,LTS意思就是官方长期支持更新。比如说,一两年内还会发布一下补丁和安全更新,至于新功能特性就别想了。

2.创建Qt Quick工程

先用Qt Creator创建一个简单的quick工程,工程构建描述的内容就保存在工程根目录的配置文件CMakeLists.txt中,如下:

cmake_minimum_required(VERSION 3.16)

project(instance VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOMOC ON)
#set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 6.2 COMPONENTS Quick REQUIRED)

file(GLOB_RECURSE SOURCE_FILES
    ./src/*.cpp
    ./src/*.h
)

qt_add_resources(SOURCE_FILES instance.qrc)

qt_add_executable(instance
    ${SOURCE_FILES}
)

set_target_properties(instance PROPERTIES
    MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

target_link_libraries(instance
    PRIVATE Qt6::Quick)

install(TARGETS instance
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

cmake_minimum_required用于声明当前的配置文件适用于的cmake最低版本,同时为了防止使用过于低级的版本来构建当前工程,避免某些指令不支持或者不兼容。

project用于声明当前工程名称,和开发语言。CXX代表了C++。

CMAKE_AUTOMOC用于标记是否开启自动MOC。Qt不仅仅是开发库,它同时也对开发语言(比如C++)做了拓展,那么源码文件中就会多多少少会包含有通用编译器无法识别的部分内容。MOC就是用于对Qt的扩展内容进行转换的工具。

CMAKE_AUTORCC用于标记是否开启自动RCC。在Qt工程中会包含有被最终输出的执行程序所需要的资源内容,比如音视频,图片等等。那么为了高效调用这些资源,势必需要对原本的资源文件进行处理再保存到额外的二进制文件中,甚至内嵌到执行文件中。RCC就是对这些资源文件进行处理和再输出工具。

由于我的例子工程中需要用到MOC和RCC,所以CMAKE_AUTOMOCCMAKE_AUTORCC都打开。

CMAKE_AUTOUIC用于标记是否开启自动UIC。如果开发界面用的技术栈是QWidget,那么在Qt工程中就需要创建.ui文件并保存设计内容到其中,编译的时候也需要用UIC把.ui文件转换成.h文件。不过,这里用Quick技术栈开发界面,因此无需打开CMAKE_AUTOUIC,在最前面添加#表示注释掉该行语句(该行语句会被解析器忽略)。

find_package导入Qt的Quick模块。

由于本工程需要用到多个C++源文件,所以这里采用了递归引用文件的方式把特定文件夹下面的所有.cpp、.h等文件都囊括进来,需要辅以通配符*。所有被囊括的文件路径被追加到动态数组SOURCE_FILES中,方便后边引用。语法格式如下:

file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS]
     [LIST_DIRECTORIES true|false] [RELATIVE <path>]
     [<globbing-expressions>...])

格式里的variable实际使用SOURCE_FILES代替。

qt_add_resources的作用是调用RCC对资源文件(.qrc)编译成qrc_开头的源文件再输出,并且把输出的源码文件路径追加到动态数组SOURCE_FILES中。

当然,动态数组SOURCE_FILES这个名字可以按照需求自定义设定,这里取名为源文件。

qt_add_executable指明构建的目标是二进制文件instance,引用的源文件来自于动态数组SOURCE_FILES。

target_link_libraries用于指明构建时链接Qt6::Quick的相关库。

剩余的语句都是Qt Creator创建工程时自动添加的内容,这里略过。

然后看看我的工程目录结构在Qt Creator中的展示:

如果用VSCODE打开工程目录,可以看到:

3.使用元对象描述文件(QML)描述界面

使用Qt Creator自动创建的Quick应用,除了会自动生成配置文件CMakeLists.txt之外,还包含了main.cpp和main.qml文件,源码只实现了启动之后弹出一个窗口。

这里稍作修改,实现简单的文件选择,以及将选中的文件路径名显示出来。这个功能用QWidget技术栈来实现其实是很简单的啦,不过我们这里目的是演示Quick技术框架怎么用,所以下面来具体看看界面这块怎么玩:

1)主页面

// main.qml

import QtQuick

Window {
    width: 640
    height: 200
    visible: true
    title: qsTr("Tool V1.0.0")
    Viewer {
        anchors.fill: parent
    }
}

main.qml 这个文件是首页元对象的描述文件,一般QML引擎加载的第一个元对象所在的文件就命名为main.qml。不过,命名为main.qml不是硬性规定。

首先,可以看到这里通过import导入了模块QtQuick。同一行,后边还可以加上版本号。

Window是一种模块里预定义的类型,用于窗体描述。当然,类型也可以自定义,下面会说到。这里通过对类型Window的实例化来描述一个窗体对象。

类型后边的{}内部包含了类型实例化后的成员属性、函数、信号、信号处理句柄等,比如width、height、visible、title等都是预定义的属性,这些属性如字面意思比较简单易懂就不一一展开了,大伙要是有兴趣可以反馈给我,我再看看意见给大家细聊。

Viewer其实是自定义的类型,这里通过对类型Viewer的实例化来补充添加新的界面元素。Viewer对象内部的属性anchors.fill: parent描述的是对象在其父对象(Window)中把父对象填充满。QML一般通过anchors属性来锚定对象的位置。

上面这个对象里并没有定义或者引用到函数或者信号等。

2)自定义类型

下面来看看怎么自定义类型

// Viewer.qml

import QtQuick 2.15
import QtQuick.Controls 6.2
import Qt.labs.platform 1.1

Item {
    function log(...msg) {
        let msgs = "";
        msg.forEach((item) => {
                        if (msgs.length != 0) {
                            msgs += " ";
                        }
                        msgs += item;
                    });
        console.log(msgs);
    }

    FileDialog {
        id: fileDialog
        objectName: "fileDialog"
        currentFile: selectedFileTextArea.text

        onFileChanged: {
            log(objectName + ".file =", file.toString().slice(8));
            fileMgrInstance.run(file.toString().slice(8));
        }
    }

    Label {
        id: fileLable
        x: 292
        y: 26
        text: qsTr("文件:")
        verticalAlignment: Text.AlignVCenter
        font.pointSize: 14
    }

    TextField {
        id: selectedFileTextArea
        x: 70
        y: 70
        width: 500
        objectName: "selectedFileTextArea"
        text: fileDialog.file.toString().slice(8)
        font.pointSize: 12
        placeholderText: qsTr("选择文件")
    }

    Button {
        id: selectFileButton
        x: 268
        y: 124
        width: 105
        height: 54
        text: qsTr("选择")
        font.pointSize: 10

        onClicked: {
            log(text, "clicked");
            fileDialog.open();
        }
    }
}

Item是类库QtQuick的预定义组件类型,描述的是一个基础可视组件,quick中所有的可视组件都继承于它。

一般在QML中自定义类型都会使用基础的类型Item,然后在其基础上定制内部属性、函数、信号、信号处理句柄等。

Item的继承链是这样的:

Item -> QtObject -> QObject

看到这里,可以猜测一下,其实所有的Quick预定义组件都是继承于QObject,和QWidget里提供的类库太相似了。

function log(...msg)定义了函数log,function是关键词,log是函数名,后边小括号里的...表示参数不定,这样子在调用log时就可以不限制输入的参数个数了。

要注意的是,QML内部的函数使用的语法是ECMAScript,也就是我们常常听到的JavaScript。

FileDialog是类库Qt.labs.platform的预定义组件类型,描述的是一个文件选择窗口。属性id,继承于QObject,用于标记唯一的对象,也就是说所有对象的id都不能重复,无论对象是否处于同一个QML文件。objectName描述的属性可用于对对象树中的对象进行查找。currentFile描述了当前选中的文件名(包括路径),在确定最后选中的文件之前,此属性也会跟随选择而改变。onFileChanged描述了当属性file值改变时,自动产生的信号的处理句柄(handle),用{}限定处理范围。log是上面定义的函数调用,输入两个参数。fileMgrInstance是C++源文件暴露给QML引擎的特定对象id,通过该id可以调用C++中的相应对象的方法属性(代码中调用了run方法,方法的详情定义看下文)。

C++和QML源文件之间的对象相互调用,会有后续的文章专门介绍,这里不再细聊,敬请关注。

Label是类库QtQuick.Controls的预定义组件类型,描述的是一个文本标签。x、y描述的是坐标。text描述的是显示文本。qsTr()用于标识文本可被翻译,类似Qt C++里的tr()。verticalAlignment描述的是垂直排列方式,这里的属性值标识垂直中间排列。font.pointSize描述字体的大小。

TextField是类库QtQuick.Controls的预定义组件类型,描述的是一个单行文本编辑窗。width是几何宽度。placeholderText描述的是占位符。

Button是类库QtQuick.Controls的预定义组件类型,描述的是一个可点击的按键。height描述的是几何高度。onClicked描述了当信号clicked发生时,该信号的处理句柄(handle),用{}限定处理范围,这里调用了上面的文件选择窗的打开函数。

信号的处理句柄(handle)中,在on后边书写时信号的首字母需要大写。

3)预览界面

什么?代码没写完就可以预览界面了?

是的,QML文件支持用工具预览,非常方便于UI设计过程中的调试。

打开预览的方式是调用qmlscene或者用Qt Design Studio,如下图用的是qmlscene。

看看Viewer.qml页面预览的实际效果。

4.使用C++代码实现逻辑处理

Qt Quick使用QML的目的是为了简化界面的设计开发,而软件除了界面的互动之外还有大量的后台逻辑处理功能也需要实现,针对这块业务,Qt其实还是推荐使用C++,正所谓术业有专攻,毕竟C++对性能的利用还是有两把刷子的。废话不多说,马上看下文。。。

既然如此,那么就来看看负责逻辑处理功能的C++代码部分。不过,这里假设各位看官已经熟悉C++的各项业务技能,所以下面只针对和QML对象的互动来简单介绍一下。

后续也会有更加详细的专题文章介绍这部分,敬请留意哈!

1)QML对象的加载和C++对象传递

QML对象的创建和展现是通过QML引擎来加载的,一般每个程序会由单个引擎对象负责管理。不过,QML引擎对象不是直接管理QML对象,而是通过管理上下文(context)对象来分别管理QML对象。所以在C++里边如果需要往QML对象传递信息也是直接传给对应的上下文对象,然后再在QML对象中通过传入对象时指定的id名调用对应的方法属性。

// main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "./FileMgr/FileMgr.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/src/QML/main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        qInfo("%s start\n", QCoreApplication::applicationName().toLatin1().data());

        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);

    FileMgr fileMgr;
    engine.rootContext()->setContextProperty(QStringLiteral("fileMgrInstance"), &fileMgr);
    engine.load(url);

    return app.exec();
}

上面的main.cpp文件代码中,把对象fileMgr传入引擎的根上下文中,并设定id为fileMgrInstance。传入根上下文,意味着引擎加载的所有QML对象都可以通过id=fileMgrInstance访问fileMgr对象内容。这里要注意,fileMgr对象是在C++源码中实例化了的。

把C++对象暴露给QML对象的方法,除了上面这种通过上下文的方式外,还有一种是通过直接往元对象系统(Meta-Object System)注册类型的方式,这种方式也是最根本的方式,因为Qt Quick框架的底层实现原理就依赖于元对象系统。

使用的接口原型:

template <typename T> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)

如果通过这种注册的方式实现,上面的栗子可以掰成这样:

 qmlRegisterType<FileMgr>("com.englyf.qmlcomponents", 1, 0, "FileMgrItem");

根据上面的定义,com.englyf.qmlcomponents是命名空间,1.0是命名空间的版本号,FileMgrItem是QML中的类型名。

然后在QML文件内,导入并实例化这个类:

 import com.englyf.qmlcomponents 1.0

 FileMgrItem {
     // ...
 }

要强调的是,通过注册的方式,类型的实例化会放在QML里边做,而C++源码就不需要再对类FileMgr作实例化了

2)C++类型的定义

既然Qt Quick依赖于元对象系统,那么对C++类型的定义就有必然的要求了。

C++类型需要继承于QObject,并且类开头应该声明宏Q_OBJECT,这样才可以使用元对象系统提供的服务,包括信号槽机制等等。

需要被QML对象调用的方法应该添加修饰Q_INVOKABLE。这个修饰符表明该方法可被元对象系统调用。同时,该方法的参数类型和返回类型,都推荐使用类型QVariant。

如有开放给QML对象的可访问属性,那么也需要对属性声明为Q_PROPERTY。这里暂不举例,可关注后续的专题文章。

// FileMgr.h

#ifndef FILEMGR_H
#define FILEMGR_H

#include <QObject>
#include <QVariant>

class FileMgr : public QObject
{
    Q_OBJECT
public:
    explicit FileMgr(QObject *parent = nullptr);

    Q_INVOKABLE QVariant run(QVariant file);
};

#endif // FILEMGR_H

// FileMgr.cpp

#include "FileMgr.h"

FileMgr::FileMgr(QObject *parent)
    : QObject{parent}
{}

QVariant FileMgr::run(QVariant file)
{
    QString fileStr = file.toString();
    qDebug("C++ get file:%s selected", fileStr.toStdString().data());

    return 0;
}

自动化部署

这部分讲点高级的内容,以往看到网上的教程都是教初学者部署的时候,进入exe生成的目录,然后手动调用windeployqt执行部署。这个程序是Qt自带的,会自动把所有依赖的动态库拷贝过来存放在指定目录下。

这里就介绍一下怎么在Qt Quick软件工程编译结束时自动部署所有依赖项。

首先,debug开发模式下是不需要部署软件的,那么我们就先切换到release模式下。

然后,在Build的步骤下,Build步骤之后新添加一个Custom Process Step的步骤。

我把配置都拷过来:

Command:            windeployqt
Arguments:          --qmldir %{ActiveProject:NativePath}\src\QML\ %{ActiveProject:RunConfig:Executable:NativeFilePath}
Working directory:  %{Qt:QT_INSTALL_BINS}

由于Qt Quick工程涉及到QML文件,所以这里需要带上选项--qmldir,这个选项后边紧跟着参数值是代码工程中存放自定义的QML文档的根目录。

%{ActiveProject:NativePath}代表着当前工程的主目录的本地化路径。

%{ActiveProject:RunConfig:Executable:NativeFilePath}代表着当前工程的exe文件输出目录的本地化路径。

Working directory项意思是Command命令的工作目录,这里填上%{Qt:QT_INSTALL_BINS},代表Qt安装目录下的bin目录。

按照上面的介绍过程配置完整,以后如果需要部署输出,只需要切换到release模式下,然后点击编译,等编译完成就会自动进入部署流程,整个过程就是这么舒心。

生活简单才是美好,部署也不例外!


到最后,一起来看看跑起来的程序:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt+Opencv:人脸检测

话接上一篇&#xff0c;我们仍使用在上篇《QtOpencv&#xff1a;Qt中部署opencv》创建的Qt项目来测试opencv提供的sample。 在正式开始本篇之前&#xff0c;我们先说做一下准备工作&#xff1a; 一、opencv官方文档 学习最权威和最可靠的方式&#xff0c;就是阅读官方文档和…

太空中的人形机器人:探索新疆界

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

YOLOv5改进 | 2023Neck篇 | CCFM轻量级跨尺度特征融合模块(RT-DETR结构改进v5)

一、本文介绍 本文给大家带来的改进机制是轻量级跨尺度特征融合模块CCFM&#xff08;Cross-Scale Feature Fusion Module&#xff09;其主要原理是&#xff1a;将不同尺度的特征通过融合操作整合起来&#xff0c;以增强模型对于尺度变化的适应性和对小尺度对象的检测能力。我将…

SolidWorks高级装配技能

SolidWorks高级装配技能有协调技能&#xff0c;配合计算速度:关系协调(巧合、平行)&#xff0c;逻辑配合(宽度、凸轮、齿轮)&#xff0c;距离/角度配合&#xff0c;有限配合&#xff0c;使用子组件。 使用子组件根据产品的层次结构组织产品&#xff0c;避免将所有零件添加到一个…

代码随想录-刷题第四十一天

343. 整数拆分 题目链接&#xff1a;343. 整数拆分 思路&#xff1a;动态规划五步曲 dp[i]&#xff1a;拆分数字i&#xff0c;可以得到的最大乘积为dp[i]。 递推公式&#xff1a;dp[i] max(dp[i], max((i - j) * j, dp[i - j] * j)) 从1遍历j&#xff0c;有两种渠道得到dp[…

【STM32】程序在SRAM中运行

程序在RAM中运行 1、配置内存分配。 2、修改跳转文件 FUNC void Setup(void) { SP _RDWORD(0x20000000); PC _RDWORD(0x20000004); } LOAD RAM\Obj\Project.axf INCREMENTAL Setup(); 3、修改下载ROM地址和RAM地址&#xff1b; 中断向量表映射 中断向量表映射到SRA…

需求分析 :不得不重新去面对的一关。

软件需求分析 背景 深入需求产生的背景明确项目目标了解用户群体 需求优先级 需求的分类与整理明确需求优先级让团队成员都参与到需求分析中来&#xff0c;增加团队合作能力与效率 编写需求文档 整理好的需求编写成详细的需求文档包括需求的描述、输入/输出格式、功能流程…

Oracle-审计表AUD$无法正常清理数据问题

问题背景: 用户通过数据库管理审计日志包DBMS_AUDIT_MGMT里面的存储过程CREATE_PURGE_JOB创建了定期清理审计日志数据的job&#xff0c;job每1小时执行一次&#xff0c;清理1天以前的审计表数据&#xff0c;运行了一段时间后&#xff0c;用户发现审计表AUD$里面的数据并没有减少…

STL——list容器

目录 1.list基本概念 2.list构造函数 3.list赋值和交换 4.list大小操作 5.list插入和删除 6.list数据存取 7.list反转和排序 8.排序案例 1.list基本概念 功能&#xff1a;将数据进行链式存储。 链表&#xff08;list&#xff09;是一种物理存储单元上非连续的存储结构&…

2023-12-29 服务器开发-Centos部署LNMP环境

摘要: 2023-12-29 服务器开发-Centos部署LNMP环境 centos7.2搭建LNMP具体步骤 1.配置防火墙 CentOS 7.0以上的系统默认使用的是firewall作为防火墙&#xff0c; 关闭firewall&#xff1a; systemctl stop firewalld.service #停止firewall systemctl disable fire…

redis 三主六从高可用docker(不固定ip)

redis集群(cluster)笔记 redis 三主三从高可用集群docker swarm redis 三主六从高可用docker(不固定ip) 此博客解决&#xff0c;redis加入集群后&#xff0c;是用于停掉后重启&#xff0c;将nodes.conf中的旧的Ip替换为新的IP&#xff0c;从而达到不会因为IP变化导致集群无法…

设计模式-多例模式

设计模式专栏 模式介绍多例模式和单例模式的区别应用场景Spring中多例模式的优缺点代码示例Java实现多例模式Python实现多例模式 多例模式在spring中的应用 模式介绍 多例模式是一种创建型设计模式&#xff0c;属于对象创建类型。多例模式的特点是允许一个类有多个实例&#x…

在Adobe Acrobat上如何做PDF文档签名

Adobe Acrobat如何做PDF文档签名&#xff1f;PDF文档签名是指对PDF文档进行基于证书的数字签名&#xff0c;类似于传统的手写签名&#xff0c;可标识签名文档的人员。与手写签名不同&#xff0c;数字签名难以伪造&#xff0c;因为其包含签名者唯一的加密信息。为PDF文档进行基于…

【计算机视觉】角点检测(Harris、SIFT)

Harris 角点指的是窗口延任意方向移动&#xff0c;都有很大变化量的点。 用数学公式表示为&#xff1a; E(u,v)反映的移动后窗口的差异&#xff0c;w(x,y)为每个像素的点权值&#xff0c;I(xu,yv)是移动的像素值&#xff0c;I(x,y)是移动前的像素值。 将E(u,v)进行泰勒展开&am…

MVC : python实现

不得不承认python是一门强大的语言&#xff0c;也因此才能hold得住人工智能的逻辑实现 MVC的妙处&#xff1a;只要Model的数据结构内容和结构明确&#xff0c;Controller对抽象的View即前面明确的Model的控制逻辑是几乎不怎么改动的&#xff0c;那么。 1、任意替换View的功能已…

MetalLB:本地Kubernetes集群的LoadBalancer负载均衡利器

背景 在本地集群进行测试时&#xff0c;我们常常面临一个棘手的问题&#xff1a;Service Type不支持LoadBalancer&#xff0c;而我们只能选择使用NodePort作为替代。这种情况下&#xff0c;我们通常会配置Service为NodePort&#xff0c;并使用externalIPs将流量导入Kubernetes…

Flink实时电商数仓之DWS层

需求分析 关键词 统计关键词出现的频率 IK分词 进行分词需要引入IK分词器&#xff0c;使用它时需要引入相关的依赖。它能够将搜索的关键字按照日常的使用习惯进行拆分。比如将苹果iphone 手机&#xff0c;拆分为苹果&#xff0c;iphone, 手机。 <dependency><grou…

关于java选择结构switch及反编译

关于java选择结构switch及反编译 在上一篇文章中&#xff0c;我们了解了选择结构中的if else等&#xff0c;本章内容让我们说明一下上一篇文章中的伏笔&#xff0c;switch选择结构&#x1f914; switch多选择结构 多选择结构&#xff1a;多选择结构除了else if &#xff0c;…

Github 2023-12-29 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-29统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Java项目2HTML项目2TypeScript项目2Python项目2非开发语言项目2C项目1JavaScript项目1 精选面试问题列表 创…

Xamarin开发:商场促销(策略设计模式)

Xamarin开发:商场促销&#xff08;策略设计模式&#xff09; 一、介绍二、需求分析三、实现四、需求分析问题1解决方案问题2解决方案 五、增加新需求六、代码优化与分析总结 一、介绍 本文引用《大话设计模式》第二章节的内容进行学习分析&#xff0c;仅供学习使用 这里接着我…