前言

参考教程文章/视频

B站UP主-QT-QML教程合集

QT连接MySQL数据库

QT学习教程6.5.2-百度网盘

【ProgrammingKnowledge 】 QT C++GUl Tutorial For Beginners以元件的方式来介绍来教学

【Abdullah Aghazadah 】C++Qt Game Tutorial以游戏实作的方式来教学

qt之常用简单、便捷方法记录(C++)

Qt Creator – 详细安装教程以及配置Android编译器环境

黑马程序员-QT

QT报错参考文章

Cmake用法 和 遇到 <‘cmake‘ 不是内部或外部命令 也不是可运行的程序 或批处理文>的解决方法

环境

注意

使用QtCreator创建的项目目录中 不能包含中文和空格

QtCreator默认使用 Utf8 格式编码对文件字符进行编码

vscode配置QT环境的话需要把cMake路径添加到环境变量然后重启

添加组件

  • 找到并打开Qt维护工具

登陆,然后选择【添加或移除组件】,勾选需要安装的组件【下一步】即可,然后会提示模块大小,直接点击更新即可

卸载

  • 打开Qt 维护工具

选上下方的【仅卸载】,点击【下一步】,最后点击卸载,等待完成即可

创建项目

QwidgetQMainWindowQDialog 的父类

基本

  • 这里可以进行修改构建方式

  • 文件基本构造

主题样式设置

onedark风格主题下载

其他主题

  • 只需要把对应文件添加到对应文件夹里(QT安装路径下)即可

onedark.creatortheme

bash
G:\QT6.5.2\install\Tools\QtCreator\share\qtcreator\themes

onedark.xml

bash
G:\QT6.5.2\install\Tools\QtCreator\share\qtcreator\styles
  • 然后在设置里把环境和文本编辑器里面选择 One Dark 即可,然后再进行对应的单一设置:
  • 编码

这样右上角就会显示编码了,一般我们都是使用 UTF-8

第一个程序

main.cpp
cpp
#include "widget.h"

#include <QApplication> // 包含一个应用程序类的头文件

// main程序入口 argc命令行变量数量  argv命令行变量数组
int main(int argc, char *argv[])
{
    // a应用程序对象,在Qt中,应用程序对象 有且仅有一个
    // 类的作用: 检测触发的事件, 进行事件循环并处理
    QApplication a(argc, argv);
    // 窗口对象 myWidget父类->Qwidget
    Widget w;
    // 显示窗口
    w.show();
    // 应用程序对象开始事件循环,保证应用程序不退出
    return a.exec();
}

widget.h和widget.c在【C++知识点–类相关】

  • widget.ui

可以拉控件等等

  • Qt项目文件

注释需要用 #

查看cMake版本

首先用搜索工具搜索 cmake.exe,一般在QT安装路径下,找到路径下然后在顶部路径直接输入cmd打开当前路径下的cmd窗口,输入 cmake --version 即可查看

CMakeLists.txt
cpp
# CMake的最低版本,低于指定版本,无法生成
cmake_minimum_required(VERSION 3.5)
# 指定项目名、项目版本、编程语言
project(hello VERSION 0.1 LANGUAGES CXX)
# 启用自动UIC(UI编译),自动MOC(元对象编译),自动RCC(资源编译)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# 设置C++标准为C++17,并要求编译器支持该标准
set(CMAKE_CXX_STANDARD 17)
# 如果不设置,或者为OFF,则指定版本不可用时,会使用上一版本
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找Qt库版本,不管你是QT6,QT5,找到这个模块就行,强制查找组件Widgets,如果找不到则报错
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets )
# 查找当前Qt版本的Widgets模块
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets )
# 项目中的源文件、头文件、资源文件
set(PROJECT_SOURCES
        main.cpp
        widget.cpp
        widget.h
        widget.ui
)

# 其实下面的可以直接一句话搞定,如果你不需要安卓的话:
#           qt_add_executable(hello
#       MANUAL_FINALIZATION # 指示要手动终止可执行程序的生成
#       ${PROJECT_SOURCES}
#       )

# 如果是Qt6及以上版本
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(${PROJECT_NAME}
        MANUAL_FINALIZATION # 指示要手动终止可执行程序的生成
        ${PROJECT_SOURCES}
    )
# 安卓开发
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET hello APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(${PROJECT_NAME} SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
# 其他Qt6以下版本使用这个
    else()
        add_executable(${PROJECT_NAME}
            ${PROJECT_SOURCES}
        )
    endif()
endif()
# 把Qt::Widgets模块的库连接到HelloQt
target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.hello)
endif()
set_target_properties(${PROJECT_NAME} PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE  # 在 macOS 或 iOS 上将可执行文件构建为应用程序包
    WIN32_EXECUTABLE TRUE # 在 Windows 上构建一个带有 WinMain 入口点的可执行文件
)

include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME}
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
# 如果大于Qt6 则手动终止可执行程序的生成
if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(${PROJECT_NAME})
endif()
  • 添加文件

在项目构建文件那右键【添加新文件】,比如创建一个类

  • Qt模块详解

Qt把所有类按照不同功能、不同平台支持等等,划分为不同的模块,在需要的时候添加模块即可!

不同的编译套件模块可能不一样,所有模块在编译套件的目录下,随便选择一个套件目录,然后找到【include】目录

里面有很多的目录,这些都是模块,包含模块就是为了直接使用模块中的头文件,包含模块只需要把前缀 Qt 去掉即可,后面部分需要一致复制,在 CMakeLists.txt 里面进行包含

QT中的基础类

基础类型

Qt是一个C++框架, 因此C++中所有的语法和数据类型在Qt中都是被支持的, 但是Qt中也定义了一些属于自己的数据类型

需要包含头文件:

cpp
#include <QtGlobal>

Qt基本数据类型有:

类型名称 注释 备注
qint8 signed char 有符号8位数据
qint16 signed short 16位数据类型
qint32 signed short 32位有符号数据类型
qint64 long long int 或 (__int64) 64位有符号数据类型,Windows中定义为__int64
qintptr qint32 或 qint64 指针类型 根据系统类型不同而不同,32位系统为qint32、64位系统为qint64
qlonglong long long int 或(__int64) Windows中定义为__int64
qptrdiff qint32 或 qint64 根据系统类型不同而不同,32位系统为qint32、64位系统为qint64
qreal double 或 float 除非配置了-qreal float选项,否则默认为double
quint8 unsigned char 无符号8位数据类型
quint16 unsigned short 无符号16位数据类型
quint32 unsigned int 无符号32位数据类型
quint64 unsigned long long int 或 (unsigned __int64) 无符号64比特数据类型,Windows中定义为unsigned __int64
quintptr quint32 或 quint64 根据系统类型不同而不同,32位系统为quint32、64位系统为quint64
qulonglong unsigned long long int 或 (unsigned __int64) Windows中定义为__int64
uchar unsigned char 无符号字符类型
uint unsigned int 无符号整型
ulong unsigned long 无符号长整型
ushort unsigned short 无符号短整型
qsizetype size_t

log输出

在Qt中进行log输出, 一般不使用c中的printf, 也不是使用C++中的cout, Qt框架提供了专门用于日志输出的类, 头文件名为 QDebug.h

基本分类

  • qDebug:调试信息提示
  • qInfo:输出信息
  • qWarning:一般的警告提示
  • qCritical:严重的错误提示
  • qFatal:致命错误提示,会直接中断程序

它会自动换行的,这个不能改变!

c
// C语言风格
qDebug("我是%s, 今年%d岁", "Yang", 21);
qInfo("42423");
qWarning("this is warning");
qCritical("this is Critical");
if (0)
  qFatal("致命打击!"); // 会直接中断程序
cpp
// C++风格
qDebug() << "debug";
qInfo() << "qinfo";
qWarning() << "warning";
qCritical() << "critical";
if (0)
  qFatal() << "致命";

格式化日志

可以通过修改环境变量 QT_MESSAGE_PATTERN 或者调用方法 qSetMessagePattern 来修改日志的输出格式。日志格式中常用的占位符号如下所示:

bash
%{appname}     应用程序的名称(QCoreApplication::applicationName())
%{category}    日志所处的领域
%{file}        打印该日志的文件路径 
%{function}    打印日志的函数
%{line}        打印日志在文件中的行数
%{message}     日志的内容
%{pid}         打印日志的程序的PID(QCoreApplication::applicationPid())
%{threadid}    打印日志的线程ID
%{qthreadptr}  打印日志的线程指针
%{type}        日志级别("debug", "warning", "critical" or "fatal")
%{time process}日志发生时程序启动了多久
%{time boot}   日志发生时系统启动了多久
%{time [format]}以固定时间格式输出日志打印的时间,默认为QISODate格式
  • 普通格式化

注意:在Release模式下,文件名、函数名、行号获取不到,需要添加编译时宏 QT_MESSAGELOGCONTEXTCMakeLists.txt

cpp
// 示例
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} [%{type}] %{file} [%{function}(%{line})] %{message}");

    qInfo() << "info";
    qDebug() << "debug";
    qWarning() << "warning";
    qCritical() << "critical";

    return a.exec();
}

// 打印
2023-12-30 16:28:18 [info] G:/my_code/win_qt_code/qt_test_console/main.cpp [main(10)] info
2023-12-30 16:28:18 [debug] G:/my_code/win_qt_code/qt_test_console/main.cpp [main(11)] debug
2023-12-30 16:28:18 [warning] G:/my_code/win_qt_code/qt_test_console/main.cpp [main(12)] warning
2023-12-30 16:28:18 [critical] G:/my_code/win_qt_code/qt_test_console/main.cpp [main(13)] critical
bash
# 需要添加宏定义 就可以在Release模式下进行日志的输出,否则会显示类似这样:2023-12-30 16:58:40 [debug] unknown [unknown(0)] debug
add_compile_definitions(QT_MESSAGELOGCONTEXT)
  • 条件格式化

在消息参数中还可以使用条件,给不同级别的日志指定不同的格式,语法如下:

cpp
%{if-<debug | info | warning | critical | fatal>} ... %{endif}
cpp
// 示例:在上面示例基础上,比如,只想在debug级别下输出文件名、函数名以及行号
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} [%{type}] %{if-debug}%{file} [%{function}(%{line})]%{endif} %{message}");

// 打印
2023-12-30 16:35:04 [info]  info
2023-12-30 16:35:04 [debug] G:/my_code/win_qt_code/qt_test_console/main.cpp [main(11)] debug
2023-12-30 16:35:04 [warning]  warning
2023-12-30 16:35:04 [critical]  critical
  • 环境变量设置格式化

也可以在运行时通过设置 QT_MESSAGE_PATTERN 环境变量来更改

如果调用了 qSetMessagePattern() 并且设置了 QT_MESSAGE_PATTERN ,则环境变量优先(测试了但是这个好像跟顺序有关,必须先调用QT_MESSAGE_PATTERN,才能优先)

cpp
// 示例
qputenv("QT_MESSAGE_PATTERN", "[%{type}] %{message}");	// 环境设置
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} [%{type}] %{if-debug}%{file} [%{function}(%{line})]%{endif} %{message}");

日志输出位置

QT默认的日志内容是输出到终端的,不会输出到文件里面,如果需要将日志内容输出到文件中我们需要通过 qInstallMessageHandler 设置日志信息处理函数

main.cpp
cpp
// 示例程序
#include <QCoreApplication>
#include <QFile>    // 对文件的读写操作的功能
#include <QTextStream>  // 进行文本输入输出的工具类

/* Private function prototypes===============================================*/
void MessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    // 设置日志信息的格式
    qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} [%{type}] %{if-debug}%{file} [%{function}(%{line})]%{endif} %{message}");
    // 安装自定义的消息处理函数
    qInstallMessageHandler(MessageHandler);

    qDebug() << "hello QT";
    qInfo() << "info";
    qWarning() << "warning";
    qCritical() << "critical";
    qFatal() << "fatal";
    qInstallMessageHandler(nullptr);    // 关闭日志输出
    qCritical() << "critical";  // 此调试信息不会被写入文件
    return a.exec();
}

// 自定义的消息处理函数
void MessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    // 获取格式化的日志信息
    QString typeStr = qFormatLogMessage(type, context, msg);
    // 可以根据日志的级别进行过滤(比如不想要Debug输出,可以直接reutrn)
    switch (type)
    {
        case QtDebugMsg:
            {
                return;
            }
        case QtWarningMsg:
            {
                break;
            }
        case QtCriticalMsg:
            {
                break;
            }
        case QtFatalMsg:
            {
                break;
            }
        case QtInfoMsg:
            {
                break;
            }
        default:
            break;
    }
    // 将日志信息写入文件
    QFile file("myapp.log");    // 创建一个QFile对象,用于操作文件,并指定文件名为"myapp.log"
    // 打开文件,采用WriteOnly和Append模式
    // WriteOnly:只写模式,如果文件不存在则创建新文件,如果文件存在则清空文件内容
    // Append:追加模式,如果文件不存在则创建新文件,如果文件存在则在文件末尾追加内容
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    // 创建一个QTextStream对象,用于进行文本输出
    // 将文本输出流与文件相关联,使得写入的内容可以直接写入到文件中
    QTextStream textStream(&file);
    // 将格式化后的日志信息写入到文件中,并添加换行符
    textStream << typeStr << "\n";
}

执行程序后可在生成的调试文件夹里面找到这个文件夹里面有调试信息

日志输出对象信息

在调试一些复杂对象的时候,我们需要输出对象的成员信息到日志当中。但是 默认情况下qt的日志库是不支持输出自定义对象的。这时候我们可以通过 重写操作符实现对自定义象的日志输出

main.cpp
cpp
#include <QCoreApplication>
#include <QFile>       // 对文件的读写操作的功能
#include <QTextStream> // 进行文本输入输出的工具类

/*
 * @class: Person
 * @brief: 描述
 */
class Person
{
public:
    QString name;
    quint8 age;

    // 声明一个友元函数(加inline是提高程序的执行效率,也可不加)
    inline friend QDebug operator<<(QDebug debug, const Person &person)
    {
        // 在QDebug流中输出Person对象的姓名和年龄
        debug << "Person(" << person.name << "," << person.age << ")";
        // 返回QDebug流,以便进行链式输出
        return debug;
    }

protected:
private:
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 创建一个Person对象
    Person person{"maye", 21};
    // 将Person对象输出到QDebug流中
    qDebug() << person;

    return a.exec();
}

// 运行结果(打印)
Person( "maye" , 21 )

禁用输出

Qt提供了禁用qInfoqWarningqDebug输出的宏,qCriticalqFatal是错误不能屏蔽

CMakeLists.txt 文件中添加即可

bash
add_compile_definitions(QT_NO_DEBUG_OUTPUT QT_NO_INFO_OUTPUT QT_NO_WARNING_OUTPUT)	# 也可以分成3个分开写

QsLog日志库

QsLog 是一个基于Qt的 QDebug类 的易于使用的记录器。 QsLog 是在 MIT许可 下以开源形式发布的

QsLog Github下载

  1. 下载源码,然后删除多余的,留下有用的即可,然后把这个文件夹复制到工程 CMakeList.txt 同级目录中
  2. 在这个库文件夹添加一个 CMakeLists.txt,内容如下:
bash
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.2)

# 指定项目名称
project(QsLog)

# 查找并指定 Qt 库
find_package(Qt NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)

# 设置源文件和头文件列表
set(QsLog_SOURCES 
    QsLogDest.cpp 
    QsLog.cpp 
    QsLogDestConsole.cpp 
    QsLogDestFile.cpp 
    QsLogDestFunctor.cpp
)
set(QsLog_HEADERS 
    QsLogDest.h 
    QsLog.h 
    QsLogDestConsole.h 
    QsLogLevel.h 
    QsLogDestFile.h 
    QsLogDisableForThisFile.h 
    QsLogDestFunctor.h
)

# 添加库文件生成静态库QsLog
add_library(QsLog STATIC ${QsLog_SOURCES} )

# 指定头文件路径
target_include_directories(QsLog PRIVATE ${QsLog_HEADERS})

# 链接 Qt 库
target_link_libraries(QsLog Qt${QT_VERSION_MAJOR}::Core)
  1. 打开工程下的 CMakeLists.txt文件添加
bash
# 添加子目录
add_subdirectory(./QsLog)

# 链接 Qt 和 QsLog 库
target_link_libraries(qt_test_console Qt${QT_VERSION_MAJOR}::Core QsLog)
  1. 然后进行日志记录器的配置
main.cpp
cpp
#include <QCoreApplication>
#include "QsLog/QsLog.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    using namespace QsLogging;

    // 1. 获取日志单例
    Logger &logger = Logger::instance();
    logger.setLoggingLevel(Level::InfoLevel);   // 调试等级有DebugLevel InfoLevel WarnLevel等按枚举的顺序

    // 2. 添加两个日志输出目的地
    DestinationPtr file_dest(DestinationFactory::MakeFileDestination("log.txt"));   // 创建文件日志目的地,将日志记录到"log.txt"文件中
    DestinationPtr console_dest(DestinationFactory::MakeDebugOutputDestination());  // 创建控制台日志目的地,将日志输出到控制台

    logger.addDestination(file_dest);   // 向日志记录器添加文件日志目的地
    logger.addDestination(console_dest);    // 向日志记录器添加控制台日志目的地

    // 3. 开始日志记录
    QLOG_INFO() << "info test";
    QLOG_DEBUG() << "Pragam started";   // 记录一条调试级别的日志

    return a.exec();
}

运行程序,控制台会输出一次,指定的文件里面也会有一条日志记录

  1. 如果要禁用日志输出,则定义宏 QS_LOG_DISABLE 即可

QsLog.h 里,直接进去去到最后一行可看到

bash
add_compile_definitions(QS_LOG_DISABLE)
  1. 输出的日志记录默认是没有文件名和行号的,如果需要,在 CMakeLists.txt 添加宏定义即可
cpp
#开启行号
add_compile_definitions(QS_LOG_LINE_NUMBERS)

字符串类型

C => char*

C++ => std::string

Qt => QByteArray, QString

QByteArray

在Qt中QByteArray可以看做是C语言中 char*的升级版本。我们在使用这种类型的时候可通过这个类的构造函数申请一块动态内存,用于存储我们需要处理的字符串数据

构造函数
cpp
// 构造空对象, 里边没有数据
QByteArray::QByteArray();
// 将data中的size个字符进行构造, 得到一个字节数组对象
// 如果 size==-1 函数内部自动计算字符串长度, 计算方式为: strlen(data)
QByteArray::QByteArray(const char *data, int size = -1);
// 构造一个长度为size个字节, 并且每个字节值都为ch的字节数组
QByteArray::QByteArray(int size, char ch);
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>	// 包含头文件

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QByteArray arr(10, 'A');	// 构造一个长度为10个字节, 并且每个字节值都为A的字节数组
    QByteArray name("hello");	// 构造一个字符串

    qDebug() << arr;	// 输出
    qDebug() << name;

    return a.exec();
}
数据操作
cpp
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::append(const QByteArray &ba);
void QByteArray::push_back(const QByteArray &other);

// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::prepend(const QByteArray &ba);
void QByteArray::push_front(const QByteArray &other);

// 插入数据, 将ba插入到数组第 i 个字节的位置(从0开始)
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::insert(int i, const QByteArray &ba);

// 删除数据
// 从大字符串中删除len个字符, 从第pos个字符的位置开始删除
QByteArray &QByteArray::remove(int pos, int len);
// 从字符数组的尾部删除 n 个字节
void QByteArray::chop(int n);
// 从字节数组的 pos 位置将数组截断 (前边部分留下, 后边部分被删除)
void QByteArray::truncate(int pos);
// 将对象中的数据清空, 使其为null
void QByteArray::clear();

// 字符串替换
// 将字节数组中的 子字符串 before 替换为 after
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::replace(const QByteArray &before, const QByteArray &after);
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>	// 包含头文件

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray arr(10, 'A');
    QByteArray name("helloYang");

    // 【chop】用法
    qDebug() << arr;    // 输出:AAAAAAAAAA
    qDebug() << name;   // 输出:helloYang
    name.chop(5);   // 从尾部开始删除5个字符
    qDebug() << name;   // 输出:hell
    // 【chopped】用法
    qDebug() << name.chopped(3);    // 修改后生成一个副本而不会改变源字符串    输出:h
    qDebug() << name;   // 输出:hell
    // 【truncate】用法
    name.truncate(2);   // 删除此下标后面的全部内容,下标从1开始
    qDebug() << name;   // 输出:he
    name.clear();   // 错误写法:qDebug() << name.clear();
    qDebug() << name;   // 输出:

    return a.exec();
}
子字符串查找和判断
cpp
// 判断字节数组中是否包含子字符串 ba, 包含返回true, 否则返回false
// 默认情况下是区分大小写 希望不区分大小写,可以使用 Qt::CaseInsensitive 枚举值作为第二个参数
bool QByteArray::contains(const QByteArray &ba) const;
bool QByteArray::contains(const char *ba) const;
// 判断字节数组中是否包含子字符 ch, 包含返回true, 否则返回false
bool QByteArray::contains(char ch) const;

// 判断字节数组是否以字符串 ba 开始, 是返回true, 不是返回false
bool QByteArray::startsWith(const QByteArray &ba) const;
bool QByteArray::startsWith(const char *ba) const;
// 判断字节数组是否以字符 ch 开始, 是返回true, 不是返回false
bool QByteArray::startsWith(char ch) const;

// 判断字节数组是否以字符串 ba 结尾, 是返回true, 不是返回false
bool QByteArray::endsWith(const QByteArray &ba) const;
bool QByteArray::endsWith(const char *ba) const;
// 判断字节数组是否以字符 ch 结尾, 是返回true, 不是返回false
bool QByteArray::endsWith(char ch) const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray frame = QByteArray::fromHex("550011223344BB");

    // 【startsWith】和 【endsWith】用法
    if ((frame.startsWith(QByteArray::fromHex("55"))) && (frame.endsWith(QByteArray::fromHex("BB"))))
    {
        qDebug() << "success";
    }
    else
    {
        qDebug() << "faild";
    }

    // 【contains】用法
    QStringList list;   // 创建一个列表
    list << "apple" << "banana" << "orange";    // 添加元素

    if (list.contains("Apple", Qt::CaseInsensitive))    // 不区分大小写
    {
        qDebug() << "same";
    }
    else
    {
        qDebug() << "no same";
    }

    return a.exec();
}
遍历
cpp
// 使用迭代器
iterator QByteArray::begin();
iterator QByteArray::end();

// 使用数组的方式进行遍历
// i的取值范围 0 <= i < size()
char QByteArray::at(int i) const;	// 会进行边界检查并返回指定位置的字符
char QByteArray::operator[](int i) const;	// 不进行边界检查,如果越界访问可能导致未定义行为
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QByteArray frame("hello Yang");

    // 使用迭代器方式进行遍历
    for (QByteArray::iterator it = frame.begin(); it != frame.end(); it++)
    {
        qDebug() << *it;	// 解引用
    }
    
    // 使用数组方式进行遍历
    for (quint8 i = 0; i < frame.size(); i++)
    {
        qDebug() << frame.at(i);
    }

    return a.exec();
}
查看字节
cpp
// 返回字节数组对象中字符的个数(QT会有警告说不建议使用count)
int QByteArray::length() const;
int QByteArray::size() const;
int QByteArray::count() const;

// 返回字节数组对象中 子字符串ba 出现的次数
int QByteArray::count(const QByteArray &ba) const;
int QByteArray::count(const char *ba) const;
// 返回字节数组对象中 字符串ch 出现的次数
int QByteArray::count(char ch) const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    QByteArray byteArray("Hello, World!");

    // 使用 length(), size(), count() 函数获取字节数组对象中字符的个数
    qDebug() << "byteArray length: " << byteArray.length();
    qDebug() << "byteArray size: " << byteArray.size();
    qDebug() << "byteArray count: " << byteArray.count();

    // 使用 count() 函数获取字节数组对象中子字符串出现的次数
    QByteArray subStr("l");
    qDebug() << "subStr count: " << byteArray.count(subStr);
    qDebug() << "\"World\" count: " << byteArray.count("World");

    // 使用 count() 函数获取字节数组对象中字符出现的次数
    char ch = 'o';
    qDebug() << "ch count: " << byteArray.count(ch);
    
    // 16进制字节数值转换为16进制字符串
    uchar arr[] = {0x55, 0x11, 0x33, 0xBB};
    QByteArray frame(reinterpret_cast<const char*>(arr), sizeof(arr));	// 使用 arr 数组的数据和大小来初始化 QByteArray 对象 frame
    QString hexStr = frame.toHex();
    qDebug() << "Hex string: " << hexStr; // Hex string: "551133bb"

    return a.exec();
}
类型转换
cpp
// 将QByteArray类型的字符串 转换为 char* 类型
char *QByteArray::data();
const char *QByteArray::data() const;

// int, short, long, float, double -> QByteArray
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QByteArray &QByteArray::setNum(int n, int base = 10);
QByteArray &QByteArray::setNum(short n, int base = 10);
QByteArray &QByteArray::setNum(qlonglong n, int base = 10);
QByteArray &QByteArray::setNum(float n, char f = 'g', int prec = 6);
QByteArray &QByteArray::setNum(double n, char f = 'g', int prec = 6);
[static] QByteArray QByteArray::number(int n, int base = 10);
[static] QByteArray QByteArray::number(qlonglong n, int base = 10);
[static] QByteArray QByteArray::number(double n, char f = 'g', int prec = 6);

// QByteArray -> int, short, long, float, double
int QByteArray::toInt(bool *ok = Q_NULLPTR, int base = 10) const;
short QByteArray::toShort(bool *ok = Q_NULLPTR, int base = 10) const;
long QByteArray::toLong(bool *ok = Q_NULLPTR, int base = 10) const;
float QByteArray::toFloat(bool *ok = Q_NULLPTR) const;
double QByteArray::toDouble(bool *ok = Q_NULLPTR) const;

// std::string -> QByteArray
[static] QByteArray QByteArray::fromStdString(const std::string &str);
// QByteArray -> std::string
std::string QByteArray::toStdString() const;

// 所有字符转换为大写
QByteArray QByteArray::toUpper() const;
// 所有字符转换为小写
QByteArray QByteArray::toLower() const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 将QByteArray类型的字符串转换为char*类型
    QByteArray byteArray("Hello, World!");
    char *data = byteArray.data();
    qDebug() << "QByteArray to char*: " << data; // QByteArray to char*:  "Hello, World!"

    // int, short, long, float, double -> QByteArray
    int intValue = 123;
    QByteArray intByteArray;
    intByteArray.setNum(intValue);
    qDebug() << "setNum int: " << intByteArray; // setNum int:  "123"

    double doubleValue = 3.14159;
    QByteArray doubleByteArray = QByteArray::number(doubleValue);
    qDebug() << "number double: " << doubleByteArray; // number double:  "3.14159"

    // QByteArray -> int, short, long, float, double
    bool ok;
    int parsedInt = intByteArray.toInt(&ok);
    if (ok)
    {
        qDebug() << "toInt int: " << parsedInt; // toInt int:  123
    }

    double parsedDouble = doubleByteArray.toDouble(&ok);
    if (ok)
    {
        qDebug() << "toDouble double: " << parsedDouble; // toDouble double:  3.14159
    }

    // std::string -> QByteArray
    std::string stdString = "This is a std::string";
    QByteArray fromStdStringByteArray = QByteArray::fromStdString(stdString);
    qDebug() << "fromStdString: " << fromStdStringByteArray; // fromStdString:  "This is a std::string"

    // QByteArray -> std::string
    std::string toStdStringString = fromStdStringByteArray.toStdString();
    qDebug() << "toStdString: " << QString::fromStdString(toStdStringString); // toStdString:  "This is std::string"

    // 字符大小写转换
    QByteArray upperCase = byteArray.toUpper();
    qDebug() << "toUpper: " << upperCase; // toUpper:  "HELLO, WORLD!"

    QByteArray lowerCase = byteArray.toLower();
    qDebug() << "toLower: " << lowerCase; // toLower:  "hello, world!"

    // 字节数组转为16进制字符串
    // 浮点数转换为整数类型输出
    float arr[] = {1.1, 2.2, 3.3, 4.4};
    int len = sizeof(arr) / sizeof(arr[0]);
    
    for (quint8 i = 0; i < len; i++)
    {
        float *frame = reinterpret_cast<float *>(&arr[i]);

        int num = static_cast<int>(*frame);
        std::string str = std::to_string(num);
        qDebug() << str;
    }
    
    return a.exec();
}

QString

QString也是封装了字符串, 但是内部的编码为utf8, UTF-8属于Unicode字符集, 它固定使用多个字节(window为2字节, linux为3字节)来表示一个字符,这样可以将世界上几乎所有语言的常用字符收录其中

QString和QByteArray 定义的ASCII字符串大小是一样的,但是涉及到中文的话就不太一样

cpp
QString string = "你好";	// 一个中文UTF8编码下,是3个字节 输出字节数
QByteArray array("你好");	// 输出的是字符个数,一个中文是不是一个字符

qDebug() << string.size() << array.size();  // 2   6
构造函数
cpp
// 构造一个空字符串对象
QString();
// 将 char* 字符串 转换为 QString 类型
QString(const char *str);
// 将 QByteArray 转换为 QString 类型
QString(const QByteArray &ba);
// 其他重载的同名构造函数可参考Qt帮助文档, 此处略
cpp
// 【测试】
QByteArray array("hello");
QString str(array);
qDebug() << str;    // 输出:"hello"
数据操作
cpp
// 尾部追加数据
QString& append(const QString &str);
QString& append(const char *str);
QString& append(const QByteArray &ba);
void push_back(const QString &other);

// 头部添加数据
QString& prepend(const QString &str);
QString& prepend(const char *str);
QString& prepend(const QByteArray &ba);
void QString::push_front(const QString &other);

// 插入数据, 将 str 插入到字符串第 position 个字符的位置(从0开始)
QString& insert(int position, const QString &str);
QString& insert(int position, const char *str);
QString& insert(int position, const QByteArray &str);

// 删除数据
// 从大字符串中删除len个字符, 从第pos个字符的位置开始删除
QString& remove(int position, int n);

// 从字符串的尾部删除 n 个字符
void  chop(int n);
// 从字节串的 position 位置将字符串截断 (前边部分留下, 后边部分被删除)
void  truncate(int position);
// 将对象中的数据清空, 使其为null
void  clear();

// 字符串替换
// 将字节数组中的 子字符串 before 替换为 after
// 参数 cs 为是否区分大小写, 默认区分大小写
QString& replace(const QString &before, const QString &after, Qt::CaseSensitivity cs = Qt::CaseSensitive);
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    
    // 尾部追加数据
    QString str1 = "Hello";
    str1.append(" World"); // "Hello World"
    qDebug() << str1;      // Output: "Hello World"

    QString str2 = "Hello";
    str2.push_back(" World"); // "Hello World"
    qDebug() << str2;         // Output: "Hello World"

    // 头部添加数据
    QString str3 = "World";
    str3.prepend("Hello "); // "Hello World"
    qDebug() << str3;       // Output: "Hello World"

    QString str4 = "World";
    str4.push_front("Hello "); // "Hello World"
    qDebug() << str4;          // Output: "Hello World"

    // 插入数据
    QString str5 = "Hello";
    str5.insert(5, " World"); // "Hello World"
    qDebug() << str5;         // Output: "Hello World"

    // 删除数据
    QString str6 = "Hello World";
    str6.remove(5, 6); // "Hello"
    qDebug() << str6;  // Output: "Hello"

    QString str7 = "Hello World";
    str7.chop(5);     // "Hello"
    qDebug() << str7; // Output: "Hello"

    QString str8 = "Hello World";
    str8.truncate(5); // "Hello"
    qDebug() << str8; // Output: "Hello"

    QString str9 = "Hello World";
    str9.clear();     // ""
    qDebug() << str9; // Output: ""

    // 字符串替换
    QString str10 = "Hello World";
    str10.replace("World", "Universe"); // "Hello Universe"
    qDebug() << str10;                  // Output: "Hello Universe"
    
    return a.exec();
}
子字符串查找和判断
cpp
// 参数 cs 为是否区分大小写, 默认区分大小写
// 其他重载的同名函数可参考Qt帮助文档, 此处略

// 判断字符串中是否包含子字符串 str, 包含返回true, 否则返回false
bool  contains(const QString &str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;

// 判断字符串是否以字符串 ba 开始, 是返回true, 不是返回false
bool startsWith(const QString &s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;

// 判断字符串是否以字符串 ba 结尾, 是返回true, 不是返回false
bool endsWith(const QString &s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString str = "Hello World";

    bool contains1 = str.contains("World"); // true
    qDebug() << contains1;                  // Output: true

    bool contains2 = str.contains("world", Qt::CaseInsensitive); // true (case-insensitive)
    qDebug() << contains2;                                       // Output: true

    bool startsWith1 = str.startsWith("Hello"); // true
    qDebug() << startsWith1;                    // Output: true

    bool startsWith2 = str.startsWith("hello", Qt::CaseInsensitive); // true (case-insensitive)
    qDebug() << startsWith2;                                         // Output: true

    bool endsWith1 = str.endsWith("World"); // true
    qDebug() << endsWith1;                  // Output: true

    bool endsWith2 = str.endsWith("world", Qt::CaseInsensitive); // true (case-insensitive)
    qDebug() << endsWith2;                                       // Output: true

    return a.exec();
}
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString text = QString("I'm fine.thank!and you!Are you OK?");
    qDebug() << text.indexOf("you");    // 查找字符串中第一个出现指定子字符串的位置(下标从1开始),如果找到了,则返回该位置的索引值,否则返回 -1

    qsizetype pos = 0;

    do
    {
        pos = text.indexOf("you", pos == 0 ? 0 : pos + 2);  // 继续找,找到则返回对应下标,找不到则返回-1
        qDebug() << pos;    // 输出:19 27 -1
    }
    while (pos != -1);


    return a.exec();
}
遍历
cpp
// 使用迭代器
iterator  begin();
iterator  end();

// 使用数组的方式进行遍历
const QChar  at(int position) const
const QChar  operator[](int position) const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString str = "Hello World";

    // 使用迭代器遍历字符串
    for (QString::iterator it = str.begin(); it != str.end(); ++it)
    {
        qDebug() << *it; // Output: 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'
    }

    // 使用数组方式访问字符
    QChar ch1 = str.at(0); // 'H'
    qDebug() << ch1;       // Output: 'H'

    QChar ch2 = str[6]; // 'W'
    qDebug() << ch2;    // Output: 'W'

    return a.exec();
}
查看字节数
cpp
// 返回字节数组对象中字符的个数
int  length() const;
int  size() const;
int  count() const;

// 返回字节串对象中 子字符串 str 出现的次数
// 参数 cs 为是否区分大小写, 默认区分大小写
int  count(const QStringRef &str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const;
cpp
// 【测试】
QString str = "HelLo";

int len1 = str.length(); // 5
qDebug() << len1;        // Output: 5

int len2 = str.size(); // 5
qDebug() << len2;      // Output: 5

int count1 = str.count("l"); // 1
qDebug() << count1;          // Output: 1

int count2 = str.count("L", Qt::CaseInsensitive); // 2 (case-insensitive)
qDebug() << count2;                               // Output: 2
类型转换
cpp
// int, short, long, float, double -> QString
// 其他重载的同名函数可参考Qt帮助文档, 此处略
QString& setNum(int n, int base = 10);
QString& setNum(short n, int base = 10);
QString& setNum(long n, int base = 10);
QString& setNum(float n, char format = 'g', int precision = 6);
QString&QString::setNum(double n, char format = 'g', int precision = 6);
[static] QString QString::number(long n, int base = 10);
[static] QString QString::number(int n, int base = 10);
[static] QString QString::number(double n, char format = 'g', int precision = 6);

// QString -> int, short, long, float, double
int QString::toInt(bool *ok = Q_NULLPTR, int base = 10) const;
short QString::toShort(bool *ok = Q_NULLPTR, int base = 10) const;
long QString::toLong(bool *ok = Q_NULLPTR, int base = 10) const
float QString::toFloat(bool *ok = Q_NULLPTR) const;
double QString::toDouble(bool *ok = Q_NULLPTR) const;


// 所有字符转换为大写
QString QString::toUpper() const;
// 所有字符转换为小写
QString QString::toLower() const;
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    int num1 = 123;
    QString str1;
    str1.setNum(num1);
    qDebug() << str1; // Output: "123"

    float num2 = 3.14159;
    QString str2;
    str2.setNum(num2, 'f', 2);	// 是一个格式化选项,用于指定浮点数的输出格式,并以定点数格式进行输出,小数点后保留两位数字
    qDebug() << str2; // Output: "3.14"

    QString str3 = "456";
    int num3 = str3.toInt();
    qDebug() << num3; // Output: 456

    QString str4 = "7.89";
    double num4 = str4.toDouble();
    qDebug() << num4; // Output: 7.89

    QString str5 = "Hello World";
    QString upperStr = str5.toUpper();
    qDebug() << upperStr; // Output: "HELLO WORLD"

    QString lowerStr = str5.toLower();
    qDebug() << lowerStr; // Output: "hello world"

    return a.exec();
}
字符串格式化

C语言中有 sprintf() 函数,QString也提供了一个 asprintf() 函数

cpp
QString arg(const QString &a, int fieldWidth = 0, QChar fillChar = QLatin1Char( ' ' )) const;
QString arg(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char( ' ' )) const;
// 用于填充字符串中的%1,%2…为给定格式的整形数字,其中第一个参数是要填充的数字,第二个参数为最小宽度,第三个参数为进制,第四个参数为当原始数字长度不足最小宽度时用于填充的字符
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString res  = QString("%1 %2 %3").arg(1).arg(2);
    res = res.arg("hello");
    qDebug() << res << Qt::endl;    // 输出:1 2 hello

    QString text = QString("%1:%2:%3").arg(1, 2, 10, QChar('0')).arg(32).arg(59);   // 输出:01:32:59
    qDebug() << text;

    return a.exec();
}

不同字符串类型相互转换

cpp
// std::string -> QString
[static] QString QString::fromStdString(const std::string &str);
// QString -> std::string
std::string QString::toStdString() const;

#QString -> QByteArray
// 转换为本地编码, 跟随操作系统
QByteArray QString::toLocal8Bit() const;
// 转换为 Latin-1 编码的字符串 不支持中文
QByteArray QString::toLatin1() const;
// 转换为 utf8 编码格式的字符串 (常用)
QByteArray QString::toUtf8() const;

#QByteArray -> QString
//使用QString的构造函数即可
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // std::string -> QString
    std::string str = "Hello, world!";
    QString qstr = QString::fromStdString(str);
    qDebug() << qstr; // Output: "Hello, world!"

    // QString -> std::string
    QString qstr2 = "Hello, world!";
    std::string str2 = qstr2.toStdString();
    qDebug() << str2 << Qt::endl; // Output: "Hello, world!"

    // QString -> QByteArray
    QString qstr3 = "中文 Hello, world!";
    QByteArray qba1 = qstr3.toLocal8Bit();
    QByteArray qba2 = qstr3.toLatin1();
    QByteArray qba3 = qstr3.toUtf8();
    qDebug() << qba1; // Output: "\xD6\xD0\xCE\xC4 Hello, world!"
    qDebug() << qba2; // Output: "?? Hello, world!"
    qDebug() << qba3; // Output: "\xE4\xB8\xAD\xE6\x96\x87 Hello, world!"

    // QByteArray -> QString
    QByteArray qba4 = "\xe4\xb8\xad\xe6\x96\x87 Hello, world!";
    QString qstr4 = QString(qba4);
    qDebug() << qstr4; // Output: "中文 Hello, world!"

    return a.exec();
}
#include <QCoreApplication>
#include <QString>
#include <QByteArray>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // std::string -> QString
    std::string str = "Hello, world!";
    QString qstr = QString::fromStdString(str);
    qDebug() << qstr; // Output: "Hello, world!"

    // QString -> std::string
    QString qstr2 = "Hello, world!";
    std::string str2 = qstr2.toStdString();
    qDebug() << str2 << Qt::endl; // Output: "Hello, world!"

    // QString -> QByteArray
    QString qstr3 = "中文 Hello, world!";
    QByteArray qba1 = qstr3.toLocal8Bit();
    QByteArray qba2 = qstr3.toLatin1();
    QByteArray qba3 = qstr3.toUtf8();
    qDebug() << qba1; // Output: "\xD6\xD0\xCE\xC4 Hello, world!"
    qDebug() << qba2; // Output: "?? Hello, world!"
    qDebug() << qba3; // Output: "\xE4\xB8\xAD\xE6\x96\x87 Hello, world!"

    // QByteArray -> QString
    QByteArray qba4 = "\xe4\xb8\xad\xe6\x96\x87 Hello, world!";
    QString qstr4 = QString(qba4);
    qDebug() << qstr4; // Output: "中文 Hello, world!"

    return a.exec();
}

QVariant

QVariant(变体数据类型)这个类很神奇,或者说方便。很多时候,需要几种不同的数据类型需要传递,如果用结构体,又不大方便,容器保存的也只是一种数据类型,而QVariant则可以统统搞定

QVariant 这个类型充当着最常见的数据类型的联合。QVariant 可以保存很多Qt的数据类型,包括 QBrush、QColor、QCursor、QDateTime、QFont、QKeySequence、 QPalette、QPen、QPixmap、QPoint、QRect、QRegion、QSize和QString,并且还有C++基本类型,如 int、float

标准类型

  • 将标准类型转换为QVariant类型
cpp
// 这类转换需要使用QVariant类的构造函数, 由于比较多, 大家可自行查阅Qt帮助文档, 在这里简单写几个
QVariant(int val);
QVariant(bool val);
QVariant(double val);
QVariant(const char *val);
QVariant(const QByteArray &val);
QVariant(const QString &val);
......
    
// 使用设置函数也可以将支持的类型的数据设置到QVariant对象中
// 这里的 T 类型, 就是QVariant支持的类型
void setValue(const T &value);
// 该函数行为和 setValue() 函数完全相同
// fromValue() 函数是一个模板函数,在使用时需要指定具体的类型
[static] QVariant fromValue(const T &value);
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>
#include <QVariant>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // QVariant(int val)
    QVariant varInt(42);
    qDebug() << varInt; // Output: 42

    // QVariant(bool val)
    QVariant varBool(true);
    qDebug() << varBool; // Output: true

    // QVariant(double val)
    QVariant varDouble(3.14);
    qDebug() << varDouble; // Output: 3.14

    // QVariant(const char *val)
    QVariant varChar("Hello, world!");
    qDebug() << varChar; // Output: "Hello, world!"

    // QVariant(const QByteArray &val)
    QByteArray byteArray("Qt is awesome!");
    QVariant varByteArray(byteArray);
    qDebug() << varByteArray; // Output: "Qt is awesome!"

    // QVariant(const QString &val)
    QString str("Hello, Qt!");
    QVariant varString(str);
    qDebug() << varString; // Output: "Hello, Qt!"

    // setValue(const T &value)
    QVariant var;
    var.setValue(123);
    qDebug() << var; // Output: 123

    // fromValue(const T &value)
    QVariant varFromValue = QVariant::fromValue<QString>("Hello, QVariant!");
    qDebug() << varFromValue; // Output: "Hello, QVariant!"

    return a.exec();
}
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>
#include <QVariant>

struct MM
{
    QString name;
    int age;
    // 构造函数,接受一个 QString 和一个 int 参数,并用它们来初始化成员变量
    MM(const QString &_name, int age): name(_name), age(age) {}
    // 友元函数,重载输出流操作符,方便打印 MM 对象的信息
    inline friend QDebug operator<<(QDebug debug, const MM &mm)
    {
        debug << mm.name << " " << mm.age;
        return debug;
    }
};


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
	
    // 如何存储自定义类型
    MM mm("qq", 18);    // 创建 MM 对象 mm
    auto vmm = QVariant::fromValue(mm);  // 使用 QVariant::fromValue() 将 MM 对象转换为 QVariant 对象
    qDebug() << vmm;    // 输出:QVariant(MM, "qq" 18)

    return a.exec();
}
  • 判断 QVariant中封装的实际数据类型

新版本会提示 Type 已被丢弃,建议使用 TypeId 或者 MetaType

cpp
//获取类型,返回的是一个枚举类型;如QVariant::Int ...
Type type() const;
//获取类型名
const char *typeName() const;
//根据类型id(枚举)获取类型名(字符串)
[static] const char *typeToName(int typeId);
//根据类型名(字符串)获取类型id(枚举)
[static] Type nameToType(const char *name);
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>
#include <QVariant>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 定义一个 QVariant 对象,存储 int 类型的值 42
    QVariant v(42);
    qDebug() << "v.type():" << v.type();                                             // 输出:v.type(): QVariant::Int
    qDebug() << "v.typeName():" << v.typeName();                                     // 输出:v.typeName(): int
    qDebug() << "QVariant::typeToName(v.type()):" << QVariant::typeToName(v.type()); // 输出:QVariant::typeToName(v.type()): int

    // 将 QVariant 对象转换为 QString 类型
    QVariant str = QVariant::fromValue(QString("Hello World!"));
    qDebug() << "str.type():" << str.typeId();                                             // 输出:str.type(): QVariant::QString
    qDebug() << "str.typeName():" << str.typeName();                                     // 输出:str.typeName(): QString
    qDebug() << "QVariant::nameToType(\"QString\"):" << QVariant::nameToType("QString"); // 输出:QVariant::nameToType("QString"): QVariant::QString

    return a.exec();
}
  • 将QVariant对象转换为实际的数据类型
cpp
//在转换之前可以先判断能够转换成对应的类型
bool canConvert(int targetTypeId) const
bool canConvert() const

bool 		toBool() const;
QByteArray 	toByteArray() const;
double 		toDouble(bool *ok = Q_NULLPTR) const;
float 		toFloat(bool *ok = Q_NULLPTR) const;
int 		toInt(bool *ok = Q_NULLPTR) const;
QString 	toString() const;
......

T value() const
//v.value<int>();       
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QByteArray>
#include <QVariant>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QVariant v("42");
    qDebug() << "v.canConvert<int>():" << v.canConvert<int>(); // 输出:v.canConvert<int>(): true

    QVariant str = QVariant::fromValue(QString("Hello World!"));
    qDebug() << "str.canConvert<QString>():" << str.canConvert<QString>(); // 输出:str.canConvert<QString>(): true

    QVariant boolVar(true);
    qDebug() << "boolVar.toBool():" << boolVar.toBool(); // 输出:boolVar.toBool(): true

    QVariant byteArrayVar(QByteArray("Hello"));
    qDebug() << "byteArrayVar.toByteArray():" << byteArrayVar.toByteArray(); // 输出:byteArrayVar.toByteArray(): "Hello"

    QVariant doubleVar(3.14);
    bool doubleOk;
    qDebug() << "doubleVar.toDouble(&doubleOk):" << doubleVar.toDouble(&doubleOk); // 输出:doubleVar.toDouble(&doubleOk): 3.14
    qDebug() << "doubleOk:" << doubleOk;                                           // 输出:doubleOk: true

    QVariant floatVar(3.14f);
    bool floatOk;
    qDebug() << "floatVar.toFloat(&floatOk):" << floatVar.toFloat(&floatOk); // 输出:floatVar.toFloat(&floatOk): 3.14
    qDebug() << "floatOk:" << floatOk;                                       // 输出:floatOk: true

    QVariant intVar(42);
    bool intOk;
    qDebug() << "intVar.toInt(&intOk):" << intVar.toInt(&intOk); // 输出:intVar.toInt(&intOk): 42
    qDebug() << "intOk:" << intOk;                               // 输出:intOk: true

    QVariant stringVar("Hello World!");
    qDebug() << "stringVar.toString():" << stringVar.toString(); // 输出:stringVar.toString(): "Hello World!"

    return a.exec();
}

自定义类型

除了标准类型, 我们自定义的类型也可以使用 QVariant 类进行封装, 被QVariant存储的数据类型需要有一个默认的构造函数和一个拷贝构造函数 。为了实现这个功能,首先必须使用 Q_DECLARE_METATYPE() 宏。通常会将这个宏放在类的声明所在头文件的下面, 原型为: Q_DECLARE_METATYPE(Type)

cpp
#include <QCoreApplication>
#include <QString>
#include <QByteArray>
#include <QVariant>

//自定义类型
class Animal
{
public:
    Animal() {} //必须要有默认构造函数
    //拷贝构造函数也必须有,不过没有深、浅拷贝时,用默认的即可
    Animal(QString name): _name(name) {}
    void show()
    {
        qDebug() << "Animal show name is :" << _name << Qt::endl;
    }
private:
    QString _name;
};
//自定义类型注册
Q_DECLARE_METATYPE(Animal);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QVariant vt;
    // QVariant vtt("maya");   // 不可以通过构造函数存自定义类型
    // 有以下两种方法可以,存自定义类型
    vt = QVariant::fromValue(Animal("dog"));    // 方法1
    vt.setValue(Animal("cat")); // 方法2

    // 如果能转换到Animal类型,就转换
    if (vt.canConvert<Animal>())
    {
        Animal animal = vt.value<Animal>();
        animal.show();  // 输出:dog
    }

    return a.exec();
}

正则匹配

文件操作

I/O设备

Qt中的I/O操作通过统一的接口简化了文件与外部设备的操作方式,Qt中文件被当作一种特殊的外部设备,文件操作与外部设备操作相同。I/O操作的本质是连续存储空间的数据读写

QIODevice 为支持读写数据块(如QFile、QBuffer和QTcpSocket)的设备提供了通用实现和抽象接口。 QIODevice 是抽象的,不能被实例化,但是通常使用它定义的接口来提供与设备无关的I/O特性

I/O设备类型

顺序存取设备:只能从头开始顺序读写数据,不能指定数据的读写位置

随机存取设备:可以定位到任意位置进行数据的读写

打开模式

文件打开需要指定模式,模式是一个枚举类型QIODeviceBase::OpenModeFlag,打开模式与 QIODevice::open() 一起使用,描述设备的打开模式。它也由 QIODevice::openMode() 返回。

枚举 描述
QIODeviceBase::NotOpen 设备未打开
QIODeviceBase::ReadOnly 只读设备
QIODeviceBase::WriteOnly 只写打开的设备,注意,对于文件系统子类(例如QFile),这种模式意味着截断(清空文件),除非与ReadOnly、Append或NewOnly结合使用。
QIODeviceBase::ReadWrite 读写设备
QIODeviceBase::Append 以追加模式打开设备,这样所有数据都被写入文件的末尾。
QIODeviceBase::Truncate 如果可以,则打开时会清空设备
QIODeviceBase::Text 读取时,行尾终止符被翻译为’\n’。在编写时,行尾终止符被转换为本地编码,例如Win32中的’\r\n’。
QIODeviceBase::Unbuffered 设备中的任何缓冲区都被绕过。
QIODeviceBase::NewOnly 如果要打开的文件已经存在,则失败。仅在文件不存在时创建并打开该文件。操作系统保证您是唯一创建和打开文件的人。注意,这种模式意味着WriteOnly,并且允许将其与ReadWrite结合使用。此标志目前只影响QFile。
QIODeviceBase::ExistingOnly 如果要打开的文件不存在,则失败。此标志必须与ReadOnly、WriteOnly或ReadWrite一起指定。注意,单独将此标志与ReadOnly一起使用是多余的,因为当文件不存在时,ReadOnly已经失败了。此标志目前只影响QFile。

文件读写

QFile

QFile是一个用于读写文本、二进制文件和资源的I/O设备。 QFile可以单独使用,或者更方便地与QTextStream或QDataStream一起使用

文件名通常在构造函数中传递,但它可以在任何时候使用setFileName()设置。 无论操作系统是什么,QFile都希望文件分隔符是'/', 不支持使用其他分隔符(比如'\\\')

可以使用exists()检查文件是否存在,并使用remove()删除文件。 (更高级的文件系统相关操作由QFileInfo和QDir提供。)

文件用open()打开,用close()关闭,用flush()刷新。 数据通常使用QDataStream或QTextStream读取和写入,但你也可以调用QIODevice继承的函数read(), readLine(), readAll(), write()。 QFile还继承了getChar()、putChar()和ungetChar(),它们每次只工作一个字符

文件的大小由size()返回。 您可以使用pos()获取当前文件位置,或者使用seek()移动到新的文件位置。 如果您已经到达文件的末尾,atEnd()将返回true

基本操作

读取文件的内容如果有中文的话会乱码

它还有很多静态函数,比如复制,移动,重命名等

  • 打开,写入,读取文件
main.cpp
cpp
#include <QCoreApplication>
#include <QString>
#include <QFile>

void testOpen()
{
    {
        // 写文件的方式来创建文件
        QFile file("Maye.txt");

        if (!file.open(QIODevice::WriteOnly)) // 生成Maye.txt 在 bin文件夹下
        {
            qDebug() << "open " << file.fileName() << "faild: " << file.errorString();
            return;
        }

        // 写入数据
        file.write("hello\n"); // 写完换行
        file.write("你好");

        file.close(); // 关闭文件
    }

    {
        // 读取文件内容
        QFile file("Maye.txt");

        if (!file.open(QIODevice::ReadOnly))
        {
            qDebug() << "open " << file.fileName() << "faild: " << file.errorString();
            return;
        }
        // 读取指定字节数据
        auto lineData = file.read(20);
        // 需要转为UTF8输出(看文件的编码是啥就是啥)
        qDebug() << QString::fromUtf8(lineData);    // 输出:"hello\n你好"

        file.seek(0);   // 调整文件指针位置
        qDebug() << file.readLine();    // 读取一行数据  输出:"hello\n"

        // 读取全部内容
        file.seek(0);   // 这里需要调整指针位置,因为上面它移动到第二行开头了
        qDebug() << QString::fromUtf8(file.readAll());  // 输出:"hello\n你好"

        file.close(); // 关闭文件
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    testOpen();

    return 0;
}

Stream

为了简化文本文件和数据文件的读写操作,QT提供了 QTextStreamQDataStream 辅助类。 QTextStream 可将写入的数据全部转换为可读文本, QDataStream 可将写入的数据根据类型转换为二进制数据

  • QTextStream

QTextStream可以在QIODevice, QByteArray或QString上操作。 使用QTextStream的流操作符,可以方便地读和写单词,行和数字。 对于生成文本,QTextStream支持字段填充和对齐的格式化选项,以及数字的格式化

  • QDataStream

使用QDataStream串行化数据,如果数据是字符串则会在前面用4个字节表明字符串长度,如果是整数则直接存储。

cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QFile>
#include <QTextStream>

void Test_Stream(void)
{
    QFile file("Maye.txt");

    if (file.open(QIODevice::ReadOnly))
    {
        QTextStream stream(&file);

        qDebug() << stream.readAll(); // 输出:"hello\n你好"

        QString str;
        stream >> str;
        qDebug() << str; // 输出:""
    }

    // 文本流
    {
        QString str;
        QTextStream stream(&str);

        stream << "你好"
               << "我是xxx"
               << "我不好";
        qDebug() << str; // 输出:"你好我是xxx我不好"
    }

    // 二进制流
    {
        // 一般用来序列化数据
        int age = 12;
        QString name = "maye";

        QByteArray data;

        QDataStream stream(&data, QIODevice::ReadWrite);

        stream << age << name;

        {
            QDataStream rstream(&data, QIODevice::ReadOnly);

            int age1;
            QString name1;
            rstream >> age1 >> name1;
            qDebug() << age1 << name1;  // 输出:12 "maye"
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Test_Stream();

    return 0;
}

QFileInfo

QFileInfo类提供与系统无关的文件信息

cpp
QFileInfo info(file);
QFileInfo info("../QFile-test/what.txt");
qDebug() << info.size();                //文件大小
qDebug() << info.absoluteFilePath();    //文件绝对路径(包括文件名)
qDebug() << info.absolutePath();        //绝对路径(不包括文件名)
qDebug() << info.absoluteDir();         //绝对路径 返回QDir
qDebug() << info.path();                //文件路径
qDebug() << info.filePath();			//返回文件名,包括路径(可以是绝对路径也可以是相对路径)。  

if (info.isFile())  //如果是文件
{
    qDebug() << info.fileName();            //带后缀的文件名
    qDebug() << info.baseName();            //不带后缀的文件名
    qDebug() << info.suffix();              //获取文件后缀
}
cpp
// 【测试】

#include <QFileInfo>

void Test_Stream(void)
{
    QFileInfo info("Maye.txt");
    // 文件大小
    qDebug() << info.size();    // 输出:12
    // 文件绝对路径(包括文件名)
    qDebug() << info.absoluteFilePath();    // 输出:"G:/my_code/win_qt_code/qt_test_console/bin/Maye.txt"
    // 绝对路径(不包括文件名)
    qDebug() << info.absolutePath();    // 输出:"G:/my_code/win_qt_code/qt_test_console/bin"
    // 文件路径
    qDebug() << info.path();    // 输出:"."
    // 返回文件名,包括路径(可以是绝对路径也可以是相对路径)。
    qDebug() << info.filePath();    // 输出:"Maye.txt"

    if (info.isFile()) // 如果是文件
    {
        // 带后缀的文件名
        qDebug() << info.fileName();    // 输出:"Maye.txt"
        // 不带后缀的文件名
        qDebug() << info.baseName();    // 输出:"Maye"
        // 获取文件后缀
        qDebug() << info.suffix();  // 输出:"txt"
    }
}


int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Test_Stream();

    return 0;
}

配置文件

QSettings

用户通常希望应用程序在会话中记住它的设置(窗口大小和位置,选项等)。 这些信息通常存储在Windows上的系统注册表中(HKEY_CURRENT_USERSoftware/MySoft ),以及macOS和iOS上的属性列表文件中。 在Unix系统上,在缺乏标准的情况下,许多应用程序(包括KDE应用程序)使用INI文本文件

QSettings 是对这些技术的抽象,使您能够以可移植的方式保存和恢复应用程序设置。 它还支持自定义存储格式

QSettings 的API基于 QVariant ,因此我们可以保存很多的类型,如 QString、QRect和QImage

如果您所需要的只是一个非持久的基于内存的结构,那么可以考虑使用 QMap<QString,QVariant> 替代

写入配置后可以在注册表里面找到配置

写入ini文件的话会生成一个ini文件

cpp
#include <QCoreApplication>
#include <QString>
#include <QSettings>    // 包含头文件

void Test_Qsetting(void);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    a.setOrganizationName("KKK");   // 手动设置组
    qDebug() << a.organizationName() << a.applicationName();    // 输出:"" "qt_test_console",可以发现默认组是空的,只有当前应用程序的名称

    Test_Qsetting();

    return 0;
}

void Test_Qsetting(void)
{
    /*读出配置*/
    QSettings settings("ceshi", qApp->applicationName());
    auto name = settings.value("name").toString();
    auto version = settings.value("version").toInt();
    qDebug() << name << version;    // 输出:"myApp" 1

    {
        /*写入配置*/
        // 这个qApp就是主函数一开始定义的对象a
        QSettings settings("ceshi", qApp->applicationName()); // 参数1:是设置文件的组名 参数2:返回当前应用程序的名称作为设置文件的键名
        settings.setValue("name", "myApp");
        settings.setValue("version", 1.0);
    }

    {
        /*写入ini文件*/
        QSettings settings("my.ini", QSettings::Format::IniFormat);
        settings.setValue("host", "192.168.1.1");
        settings.setValue("port", 8080);

        // 设置组(成对出现)
        settings.beginGroup("admin");   // 开始组
        settings.setValue("username", "admin");
        settings.setValue("password", 12345678);
        settings.endGroup(); // 结束组

        // 设置数组
        settings.beginWriteArray("ages");
        for (size_t i = 0; i < 5; i++)
        {
            settings.setArrayIndex(i);
            settings.setValue("age", 18 + i);
        }
        settings.endArray();
    }

    {
        /*读取ini文件*/
        QSettings settings("my.ini", QSettings::Format::IniFormat);

        qDebug() << settings.value("host").toString();  // 输出:"192.168.1.1"

        settings.beginGroup("admin");   // 开始组
        qDebug() << settings.value("username").toString();  // 输出:"admin"
        qDebug() << settings.value("passward", 88888888).toString();  // 如果找不到这个键则可以返回默认值
        qDebug() << settings.value("password", 88888888).toString();
        settings.endGroup(); // 结束组

        /*读取数组*/
        qsizetype size = settings.beginReadArray("ages");   // 开始组
        for (size_t i = 0; i < size; i++)
        {
            settings.setArrayIndex(i);
            qDebug() << settings.value("age").toInt();  // 输出:18 19 20 21 22
        }
        settings.endArray();
    }
}

QJsonDocment

QJsonDocument 是一个包装完整JSON文档的类,它可以从基于UTF-8编码的文本表示中读取和写入该文档

可以使用 QJsonDocument::fromJson() 将JSON文档从基于文本的表示形式转换为 QJsonDocumenttoJson() 将其转换回文本。解析器能非常快速且有效的将JSON转换为Qt使用的二进制表示形式

isNull() 可以查询已解析文档的有效性

可以使用 isArray()isObject() 查询文档是否包含数组或对象。可以使用 array()object() 检索文档中包含的数组或对象,然后读取或操作

解析JSON

【格式】

json
# 语法:由键值对构成 key:value

# {}是对象
{
	"name":"杨晓晓",	# 键必须由双引号包裹,值如果是字符串则也需要
    "age":88	# 每对键值对必须用逗号分割
    "tel":111111,
    "family":["xx","cc","ss"]
}

# []是数组
[
    
]

json文件必须是UTF8编码!!!

简单对象
cpp
// 【测试】
#include <QCoreApplication>
#include <QString>
#include <QFile>
#include <QJsonDocument>    // 包含头文件
#include <QJsonObject>  // 需要包含头文件

QJsonDocument parseJson(void)
{
    QFile file("./text.json");

    if (!file.open(QIODevice::ReadOnly))
    {
        qDebug() << "open faild" << file.errorString();
        return QJsonDocument();
    }

    QJsonParseError err;
    QJsonDocument jdoc = QJsonDocument::fromJson(file.readAll(), &err);

    if (err.error != QJsonParseError::NoError)  // json里面不能为空至少要有对象{}
    {
        qDebug() << "json parse faild" << err.errorString();
        return QJsonDocument();
    }

    return jdoc;
}

void Test_QJSON(void)
{
    QJsonDocument jdoc = parseJson();

    // 解析简单的json对象
    {
        // {"name": "Yang","age": 111}
        // qDebug() << jdoc["name"].toString() << jdoc["age"].toInt(); // 如果键值错误了则输出空""
    }
    // 解析简单数组
    {
        for (size_t i = 0; i < 9; i++)
        {
            QJsonValue v = jdoc[i];

            if (v.isDouble())
            {
                qDebug() << v.toDouble();   // 有小数则用这个而不是用toInt
            }
            else if (v.isString())
            {
                qDebug() << v.toString();
            }
            else if (v.isObject())
            {
                QJsonObject obj =  v.toObject();
                qDebug() << obj.value("One").toInt() << obj.value("Two").toInt();
            }
            // else
            // {
            //     qDebug() << v;  // 可以发现是QJsonValue(object, QJsonObject({"One":1,"Two":2}))则可以修改这个else
            // }
        }
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Test_QJSON();

    return 0;
}


// 【输出】
1
2
3
4
"好"
"dasd"
3.14
1 2
json
[
    1,
    2,
    3,
    4,
    "好",
    "dasd",
    3.14,
    {
        "One":1,
        "Two":2
    }
]

目录/路径

待学

云对象系统

基础知识

元对象系统是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统

元对象可能存在这样的信息: 基础对象的类型、接口、类、方法、属性、变量、控制结构等

元对象系统组成:

基于三个东西:

  1. QObject

QObject是 QT 对象模型的核心,绝大部分的 Qt类都是从这个类继承而来

  1. Q_OBJECT

Q_OBJECT宏必须出现在类定义的私有部分,用来开启信号和槽、动态属性系统,或Qt元对象系统提供的其他服务

  1. MOC

MOC编译器为QObject子类提供了一些实现元对象特性所需要的一些代码。就比如说信号,大家只是在类声明的时候声明了所需要的信号,在MOC编译时,会为信号添加函数定义

  • 测试

如果类和main同时写在一个文件里(即没有分离),一个项目只有一个源文件,所以则必须需要在文件底部添加

这行预处理指令告诉moc文件需要进行元编译,以实现Q_OBJECT宏中声明的函数等

cpp
#include "xxx.moc"	// xxx取决于你的主函数所在的文件名
  • 简单的按钮
cpp
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton>  // 按钮头文件

class widget : public QWidget
{
    Q_OBJECT    // 这个是使用信号和槽机制必须包含的一个宏(一般都会包含在类的第一行)
public:
    widget()
    {
        // 调整窗口大小
        resize(640, 480);
        // 玩一个按钮
        QPushButton *btn = new QPushButton(this);
        // 给按钮设置文本
        btn->setText("点我");

        // 当点击按钮时,输出一句话
        // 将一个信号与一个槽函数关联起来 当信号触发时,槽函数会被自动调用
        // 参数1:表示要连接的信号来源 参数2:表示要连接的信号 参数3:表示信号接收者,即连接所属的对象(这里是当前窗口) 参数4:表示要连接的槽函数
        connect(btn, &QPushButton::clicked, this, &widget::on_btn_clicked);
        // 
    }

    void on_btn_clicked()
    {
        qDebug() << "点击了";
    }
private:

};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    widget w;
    // 显示窗口
    w.show();
    return a.exec();
}

#include "main.moc"

信号与槽

信号和槽本质

  • 什么是信号槽?

所谓信号槽,实际就是观察者模式(发布-订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发

信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系

  • 本质

信号是由对象发射出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由Qt的 Qt Meta Object System 实现

槽实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以

当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数

文档助手查找

查找关键字—信号(Signals),槽(Slots)

绑定信号与槽

在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起

信号与槽绑定使用QObject::connent()函数实现,其基本格式如下:

cpp
/*
sender:信号发出者,需要传递一个QObject族的对象
signal:发出的具体信号,需要传递一个函数指针
receiver:信号接收者,需要传递一个QObject族的对象
method:接收到信号之后,信号接收者处理动作,需要传递一个函数指针(槽函数)
type:第一个connect函数独有的参数,表示信号和槽的连接类型;有默认值,一般不需要修改
*/ 
[static] QMetaObject::Connection connect(
     const QObject *sender, 
     const QMetaMethod &signal, 
     const QObject *receiver, 
     const QMetaMethod &method,
 	 Qt::ConnectionType type = Qt::AutoConnection)
     
 [static] QMetaObject::Connection connect(
     const QObject *sender, 
     PointerToMemberFunction signal, 
     Functor functor)

【注意】

connect 函数相对于做了信号处理动作的注册,调用 conenct 连接信号与槽时, sender 对象的信号并没有产生,因此 receiver 对象的 method 也不会被调用, method 槽函数本质是一个回调函数, 调用的时机是信号产生之后。 调用槽函数是Qt框架来执行的, connect 中的 senderrecever 两个指针必须被实例化了, 否则 conenct 不会成功

  • 断开连接

信号与槽连接之后,还可以断开连接,断开之后,信号出发之后,槽函数就不会调用了

cpp
// 信号与槽的断开,必须与连接的时候参数完全一致
bool disconnect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method)
    
// 也可以使用connect的返回值断开连接
bool disconnect(const QMetaObject::Connection &connection)    

标准信号与槽/自定义信号与槽

在Qt提供的很多类中都可以对用户触发的某些特定事件进行检测, 当事件被触发后就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号

同样的,在Qt的很多类内部提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数

  • 标准信号与槽/自定义槽

自定义槽必须遵循:

  1. 槽函数的返回类型必须是void类型,不能是其他类型
  2. 槽函数的参数必须等于或少于信号的参数
  3. 信号也可以当做槽函数

如果有多个槽函数关联同一个信号,那么当信号触发时,槽函数的执行顺序是不定的

如果你想在槽中知道是哪个对象触发的信号,那么你可以使用 QObject *sender() const 函数获取信号的发送者

cpp
// 建议槽函数放在public上且加一个标识符slots(可以不加但是推荐加这个是给元编译识别用的)
// Qt5及以后版本,其实可以不用写slots!!!
calss xxx
{
    public slots:
    	xxx()
        {
            
        }
}
main.cpp【测试】
cpp
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton> // 按钮头文件

void on_btn_clicked_global()
{
    qDebug() << "global func";
}

class widget : public QWidget
{
    Q_OBJECT // 这个是使用信号和槽机制必须包含的一个宏(一般都会包含在类的第一行)
public :
    widget(QWidget *parent = nullptr)
        : QWidget(parent),
          m_btn(new QPushButton("Click me", this))
    {
        // 连接m_btn的信号
        // m_con = connect(m_btn, &QPushButton::clicked, this, &widget::on_btn_clicked);
        // connect(m_btn, &QPushButton::pressed, this, &widget::on_btn_pressed);
        // connect(m_btn, &QPushButton::released, this, &widget::on_btn_released);

        // 把静态函数作为槽函数,由于静态函数不依赖于特定对象的实例所以this参数可以省略
        connect(m_btn, &QPushButton::clicked, &widget::on_btn_clicked_static);
        // 把全局函数作为槽函数
        connect(m_btn, &QPushButton::clicked, on_btn_clicked_global);
        // 用lambda表达式作为槽函数
        connect(m_btn, &QPushButton::clicked, [this]()
        {
            // 访问成员变量的话需要位于捕获列表中
            qDebug() << "lambda" << m_btn->text();  // 输出:lambda "Click me"
        });
        // 信号的参数
        connect(m_btn, &QPushButton::clicked, [this](bool checked)
        {
            // clicked参数默认是false,但是不能超过1个参数(即不能超过)clicked函数的参数,否则会报错:
            // static assertion failed: Signal and slot arguments are not compatible.
            QPushButton *button = qobject_cast<QPushButton *>(sender());    // 尝试把 QObject * 转换为 QPushButton *
            qDebug() << "lambda" << m_btn->text() << checked << button;  // 输出:llambda "Click me" false QWidget(0x0)
        });
        m_btn->setCheckable(true);  // 选中按钮(相当于被按下状态)
    }

public:
    void on_btn_clicked()
    {
        disconnect(m_con);        // 断开连接方法2
        qDebug() << __FUNCTION__; // 打印函数名
    }
    void on_btn_pressed()
    {
        // 按钮按下则断开连接方法1
        disconnect(m_btn, &QPushButton::pressed, this, &widget::on_btn_pressed);
        qDebug() << __FUNCTION__; // 打印函数名
    }
    void on_btn_released()
    {
        qDebug() << __FUNCTION__; // 打印函数名
    }
    static void on_btn_clicked_static()
    {
        qDebug() << "static func";
    }

private:
    QPushButton *m_btn{}; // 加{}表示统一初始化语法
    QMetaObject::Connection m_con{};
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    widget w;
    // 显示窗口
    w.show();
    return a.exec();
}

#include "main.moc"
  • 自定义信号

如果想要使用自定义的信号和槽, 首先要编写新的类并且让其继承Qt的某些标准类,我们自己编写的类想要在Qt中使用使用信号槽机制, 那么必须要满足的如下条件:

  1. 这个类必须从 QObject类或者是其子类 进行派生
  2. 在定义类的第一行头文件中加入 Q_OBJECT
cpp
// 【格式】
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏,单文件的话则按照上面那样
class MyMainWindow : public QWidget
{
    Q_OBJECT
public:
    ......
}

自定义信号需要遵循以下规则:

  1. 信号是类的成员函数,并且返回类型必须是 void 类型

  2. 信号函数只需要声明, 不需要定义(没有函数体实现)

  3. 参数可以随意指定, 信号也支持重载

  4. 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字(注意signals下面只能放信号放别的会报错!!!)

  5. 在程序中发送自定义信号: 发送信号的本质就是调用信号函数

cpp
emit mysignals();	//发送信号

emit是一个空宏,没有特殊含义,仅用来表示这个语句是发射一个信号,不写当然可以,但是不推荐

信号参数的作用是数据传递, 谁调用信号函数谁就指定实参,实参最终会被传递给槽函数

main.cpp【测试】
cpp
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton> // 按钮头文件
#include <QLineEdit>

/*
* @class: Login
* @brief:
*/
class Login : public QWidget
{
    Q_OBJECT
public:
    Login(QWidget *parent = nullptr)
        : QWidget(parent),
          m_UserNameEdit(new QLineEdit(this)),
          m_PasswordEdit(new QLineEdit(this)),
          m_LoginButton(new QPushButton("登陆", this))
    {
        resize(640, 480);   // 调整窗口
        m_UserNameEdit->move((width() - m_UserNameEdit->width()) / 2, 50);  // 移动居中(界面窗口宽度减去输入框宽度再除以2就是居中)
        m_PasswordEdit->move((width() - m_PasswordEdit->width()) / 2, 100);  // 移动居中
        m_LoginButton->move((width() - m_LoginButton->width()) / 2 + 20, 150);  // 移动
        // 信号发出者 发出的具体信号 接收者 处理槽函数
        connect(m_LoginButton, &QPushButton::clicked, [ = ]()
        {
            // 获取账号密码
            auto username = m_UserNameEdit->text();
            auto password = m_PasswordEdit->text();

            qDebug() << username << Qt::endl;
            qDebug() << password << Qt::endl;

            // 验证登陆是否成功,这里可以进行数据库对比等操作,为了实验这里直接默认验证成功
            if (1)
            {
                emit sig_loginSucceed();    // emit没有任何作用,用于标识信号
            }
        });
    }

signals:
    void sig_loginSucceed();    // 登陆成功信号函数
private:
    QLineEdit *m_UserNameEdit{};
    QLineEdit *m_PasswordEdit{};
    QPushButton *m_LoginButton{};

};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Login w;
    // 显示窗口
    w.show();

    // 主函数里面不能直接使用connect,需要使用它的静态成员函数connect
    // 信号发出者是窗口 发出的信号是来自点击登陆后发出的sig_loginSucceed
    QObject::connect(&w, &Login::sig_loginSucceed, []()
    {
        qDebug() << "登陆成功";
    });

    return a.exec();
}

#include "main.moc"

二义性问题

在上面基础上修改:

cpp
...
    
if (1)
{
    emit sig_loginSucceed();                   // emit没有任何作用,用于标识信号
    emit sig_loginSucceed(username, password); // 把有参版本也发送过去
}

...
    
signals:
void sig_loginSucceed();                                                 // 登陆成功信号函数
void sig_loginSucceed(const QString &username, const QString &password); // 重载

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Login w;
    // 显示窗口
    w.show();

    // 主函数里面不能直接使用connect,需要使用它的静态成员函数connect
    // 信号发出者是窗口 发出的信号是来自点击登陆后发出的sig_loginSucceed
    QObject::connect(&w, &Login::sig_loginSucceed, []()
    {
        qDebug() << "登陆成功";
    });

    return a.exec();
}

// 【报错】
No matching function for call to 'connect' 这个就是信号重载的二义性问题
  • 解决方法
  1. 通过函数指针解决(不推荐,每一次都要指定有参版本还是无参版本)

函数指针怎么写,首先把函数复制过来,函数名称前加 (*,函数名字后面加 ),然后函数名称前面的 * 前加类名 xxx::,最后把形参名称删除,类型不要删

cpp
// 【测试】
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Login w;
    // 显示窗口
    w.show();

    void (Login::*sig_loginSucceed_2)(const QString &, const QString &) = &Login::sig_loginSucceed;

    QObject::connect(&w, sig_loginSucceed_2, [](const QString &username, const QString &password)   // 参数传入
    {
        qDebug() << "登陆成功" << username << password;
    });


    return a.exec();
}
  1. 通过Qt提供的重载类(QOverload)解决(推荐!!!)
cpp
【语法】
QOverload<参数类型1,...>::of(xxxx)	// xxxx就是需要包住的东西
cpp
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Login w;
    // 显示窗口
    w.show();
    
    //  <>里面的参数必须跟后面[]一致
    QObject::connect(&w, QOverload<const QString &, const QString &>::of (&Login::sig_loginSucceed), [](const QString & username, const QString & password) // 参数传入
    {
        qDebug() << "登陆成功" << username << password;
    });

    return a.exec();
}
  1. Qt4的连接方式与信号转发

这种旧的信号槽连接方式在Qt6中是支持的, 但是不推荐使用, 因为这种方式在进行信号槽连接的时候, 信号槽函数通过宏SIGNALSLOT转换为字符串类型

因为信号槽函数的转换是通过宏来进行转换的, 因此传递到宏函数内部的数据不会被进行检测, 如果使用者传错了数据,编译器也不会报错,但实际上信号槽的连接已经不对了,只有在程序运行起来之后才能发现问题,而且问题不容易被定位

所以QT4方式不推荐!!

cpp
// 【测试】
/*
* @class: Test_sig
* @brief:
*/
class Test_sig : public QObject
{
    Q_OBJECT
public:
    Login w;
    Test_sig(QObject *parent = nullptr)
        : QObject(parent)
    {
        // 显示窗口
        w.show();
        // QT4连接方式,需要注意槽函数必须放在调用此函数的类中 public slots下
        // SIGNAL(xxx),xxx为函数名称(参数...)  SLOT(yyy),yyy为函数名称(参数...)
        connect(&w, SIGNAL(sig_loginSucceed(const QString &, const QString &)), this, SLOT(on_btn_loginSucceed()));
                // QT4方式 --- 转发信号login_ok
        connect(&w, SIGNAL(sig_loginSucceed(const QString &, const QString &)), this, SIGNAL(login_ok()));
        // QOverload方式 --- 转发信号login_ok
        connect(&w, QOverload<>::of(&Login::sig_loginSucceed), this, &Test_sig::login_ok);            
    }
public slots:
    // 槽函数
    void on_btn_loginSucceed()
    {
        qDebug() << __FUNCTION__;
    }
signals:
    void login_ok();    
protected:
private:
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Test_sig test;
    
    QObject::connect(&test, &Test_sig::login_ok, []()
    {
        qDebug() << "login_ok";
    });    

    return a.exec();
}

内存管理

父子对象关系

QObject 以对象树的形式组织起来。当为一个对象创建子对象时,子对象会自动地添加到父对象的 children() 列表中。父对象拥有子对象的所有权,比如父对象可以在自己的析构函数中删除它的孩子对象。使用 findChild()findChildren() 通过名字和类型查询孩子对象

  1. QObject及其派生类的对象,如果其 parent 非nullptr,那么其parent析构时会析构该对象
  2. 父子关系:父对象、子对象、父子关系。这是Qt中所特有的,与类的继承关系无关,传递参数是与parent有关(基类、派生类,或父类、子类,这是对于派生体系来说的,与parent无关)
cpp
//【测试】
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton> // 按钮头文件
#include <QLineEdit>
#include <QObject>
#include <QRadioButton> // 单选框头文件



int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Widget w;

    // 指定了父对象则不需要手动show

    // 指定方式1
    // auto btn = new QPushButton("按钮", &w);
    auto rbtn = new QRadioButton("男", &w);
    rbtn->setObjectName("_rbtn");  // 设置名字

    // 指定方式2
    auto btn = new QPushButton("按钮");
    btn->setParent(&w);
    btn->setObjectName("man_btn");
    btn->move(150, 0);

    // 按键对象被销毁时触发
    QObject::connect(btn, &QPushButton::destroyed, []()
    {
        qDebug() << "delete ..";
    });

    // 获取btn父对象
    // 方式1 --- 需要转换类型
    // auto ww = dynamic_cast<Widget *> (btn->parent());
    // 方式2 --- 不需要转换
    auto ww = btn->parentWidget();

    if (ww)
    {
        qDebug() << ww << Qt::endl;
    }

    // 获取子对象
    const QObjectList &list = w.children();

    // 打印子对象
    for (auto i : list)
    {
        qDebug() << i << Qt::endl;
    }

    // 查找指定类型的子对象
    qDebug() << "sub object" << w.findChild<QPushButton *>() << Qt::endl;

    // 根据名字查找子对象
    qDebug() << "sub object2---" << w.findChild<QWidget *>("man_btn");
    // qDebug() << "sub object2---" << w.findChild<QPushButton *>("man_btn");

    w.show();

    return a.exec();
}

延时释放

cpp
// 【测试】
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    auto w = new QWidget;

    w->show();

    QObject::connect(w, &QObject::destroyed, []()
    {
        qDebug() << "w delete...";
    });

    int res = a.exec();
    // 直接使用delete释放(对于直接或间接继承QObject的类对象,不推荐使用delete来释放)
    delete w;    // 此处只能调用这个因为循环在上面结束了
    // 使用QObject提供的安全释放的函数(下一次运行到事件循环的时候,才去释放对象)
    // w->deleteLater();   // 此处不能使用,因为事件循环已经结束


    return res;
}

智能指针

为了管理内存等资源,C++程序员通常采用RAII(Resource Acquisition Is Initialization)机制:在类的构造函数中申请资源,然后使用,最后在析构函数中释放资源。

如果没有智能指针,程序员必须保证new对象能在正确的时机delete,四处编写异常捕获代码以释放资源,而智能指针则可以在退出作用域时(不管是正常流程离开或是因异常离开)总调用delete来析构在堆上动态分配的对象

智能指针 描述
QPointer [QObject专享指针] QObject或子类对象释放时会自动指向nullptr
QScopedPointer [独享指针] 超出作用域自动释放管理的对象
QSharedPointer [共享指针]
QWeakPointer [监视指针]
QScopedArrayPointer [独享数组指针] 超出作用域自动释放管理的对象数组
QSharedDataPointer [隐式共享指针] 读时共享,写时拷贝
QExplicitlySharedDataPointer [显示共享指针] 读时共享,写时需要手动拷贝(通过detach()函数)
QPointer

受保护指针 QPointer<T> 的行为类似于普通c++指针 T *,只是当被引用的对象被销毁时它会自动清除( 不像普通c++指针,在这种情况下它会变成“悬浮指针”)。T必须是 QObject的子类

当您需要存储一个指向别人拥有的QObject的指针时,保护指针非常有用,因为它可能在您仍然持有对它的引用时被销毁。您可以安全地测试指针的有效性

注意Qt 5在使用QPointer时在行为上做了轻微的改变:

在QWidget(或QWidget的子类)上使用QPointer时, 以前QPointer会被QWidget析构函数清除。现在, QPointer由QObject析构函数清除 (因为这是在清除QWeakPointer对象时)。在QWidget析构函数销毁被跟踪小部件的子对象之前,跟踪小部件的任何QPointer都不会被清除

cpp
// 【测试】
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton> // 按钮头文件
#include <QLineEdit>
#include <QObject>
#include <QRadioButton> // 单选框头文件
#include <QPointer>

/*
* @class: Test
* @brief:
*/
class Test : public QWidget
{
    Q_OBJECT
public:
    Test(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        test_QPoint();
    }
    void test_QPoint()
    {
        QPointer btn = new QPushButton("按钮", this);

        if (btn)
        {
            qDebug() << "在在在";
        }

        // btn->deleteLater(); // 调用也不会触发下面的!btn
        delete btn; // 这个会触发下面!btn,因为会把对象置nullptr

        if (!btn)
        {
            qDebug() << "不在";
        }
    }
protected:
private:
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Test test;
    test.show();

    return a.exec();
}

#include "main.moc"
QScopedPointer

手动管理堆分配对象非常困难而且容易出错,通常的结果是代码泄漏内存并且难以维护。QScopedPointer是一个小型实用程序类,它通过将基于堆栈的内存所有权分配给堆分配(更通常称为资源获取初始化(RAII)),极大地简化了这一点

QScopedPointer保证当当前作用域消失时,所指向的对象将被删除

QSharedPointer

QSharedPointer是c++中的一个自动共享指针。它的行为和普通指针完全一样

如果没有其他QSharedPointer对象引用它,当它超出作用域时,QSharedPointer将删除它所持有的指针

QWeakPointer

在c++中,QWeakPointer是对指针的自动弱引用。它不能用于直接解引用该指针,但可以用于验证该指针是否已在另一个上下文中被删除

QWeakPointer对象只能通过从QSharedPointer赋值来创建

QScopedArrayPointer

是一个QScopedPointer,默认使用delete[]操作符删除它所指向的对象。为了方便,它还提供了操作符[]

QSharedDataPointer

表示指向隐式共享对象的指针

QExplicitlySharedDataPointer

表示指向显式共享对象的指针

属性系统

获取/设置属性值

  • 可在文档找

通过获取属性值的函数获取objectName的值

通过修改属性值的函数修改objectName的值

QFlags

类似标志位,方便操作每一个位

cpp
// 【测试】
#include "widget.h"
#include <QApplication> // 包含一个应用程序类的头文件
#include <QtGlobal>
#include <QPushButton> // 按钮头文件
#include <QLineEdit>
#include <QObject>
#include <QRadioButton> // 单选框头文件
#include <QPointer>
#include <QFlags>


enum MyFlags
{
    Flag1 = 0x01,
    Flag2 = 0x02,
    Flag3 = 0x04,
    Flag4 = 0x08
};

// 声明相应的 QFlags 类型
Q_DECLARE_FLAGS(MyFlagSet, MyFlags)

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    MyFlagSet flags;    // 创建对象

    // 设置标志某位(会叠加 相当于 '|' 操作)
    flags.setFlag(Flag1);
    flags.setFlag(Flag3);
    flags.setFlag(Flag4);


    int value = flags;
    qDebug() << value;  // 输出:13

    if (flags.testFlag(Flag3))  // 判断标志某位是否被设置
    {
        qDebug() << "flag2 set";
    }


    MyFlagSet newFlags = MyFlagSet(value);
    if (newFlags.testFlag(Flag3))  // 判断标志某位是否被设置
    {
        qDebug() << "newFlag flag3 set";
    }
    return a.exec();

}