【一】前言:

经过一段时间的C++和Qt学习,作为对这一阶段学习成果的检验,我决定使用Qt平台模仿C++的控制台输入输出编写一个项目。

初学C++的时候,程序获取用户输入是通过标准输入输出流对象实现的,如下图:

我们编写的项目也按照这个思路来,也编写输入输出流对程序的输入输出进行封装

【二】相关的知识点:

C++基础部分:

类的编写,以及类的继承

利用友元函数对运算符重载

指针的基本应用

迭代器的应用(QString的迭代器)

多文件组织

Qt部分:

Qt常用控件

混合式ui设计技巧

利用QThread类实现和管理多线程

Qt信号与槽机制

【三】UI设计:

为了方便设计,这里使用Qt Creator的可视化设计来设计我们交互的窗口界面:

本项目的ui设计比较简单,包含两个控件,一个是文本浏览器(对应的类是QTextBrowser, 命名为:textBrowser 用来显示用户的输入内容以及程序的输出内容),另一个是行文本编译器(对应的类是QLineEdit,命名为lineEdit 用来接收用户的输入内容),布局采用垂直布局。

【四】交互机制的设计:

说明: 一个线程一般只能有一个循环,由于我们的程序的主线程需要处理各种事件,如果在运行的时候阻塞线程等待用户输入,就无法处理窗口的事件,因此我们使用QThread类额外开一个工作线程,将用户的输入过程放在这个工作线程中实现。(这里暂且不考虑使用线程同步,而使用信号与槽的机制来进行工作线程和主线程的信息交流)

输入流程:用户输入内容-->敲击回车-->主线程发送用户输入的信号-->工作线程的槽函数接受到信号停止对工作线程的阻塞,并对输入的信息进行处理,传给对应的变量

输出流程:工作线程将要输出的变量转化成Qt字符串通过信号发出-->主线程的槽函数接收字符串-->主线程将字符串显示在文本浏览器控件(QTextBrowser)上

【五】代码实现

所有的文件:

Qt的UI设计文件:mainwindow.ui

实现输入输出的工作线程的类所在头文件:  ioThread.h

实现窗口设计和信号与槽设计的头文件:  mainwindow.h

实现输入输出的工作线程的类所在源文件:  ioThread.cpp

实现窗口设计和信号与槽设计和绑定的源文件:mainwindow.cpp

程序运行的入口源文件: main.cpp

类的设计:

mainwindow.h头文件中:

包含一个类的申明:

class mainwindow

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "ioThread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void keyPressEvent(QKeyEvent* event);   // 虚函数,键盘事件的重写,用于实现用户按下回车后的逻辑
    void closeEvent(QCloseEvent* event);    // 虚函数,对窗口关闭事件的重写,用于让工作线程安全退出
private:
    Ui::MainWindow *ui;
    IoThread aThread;   // 保存工作线程
signals:
    void inputMessage(QString msg); // 信号,用于向工作线程发送用户输入的字符串
public slots:
    void outputMessageEmit(QString msg);    // 槽,用于接收工作线程发送的需要输出信号
    void newlineEmit(); // 槽,用于接收工作线程的换行信号
#endif // MAINWINDOW_H

说明:该文件中是窗口类的申明,主体部分由Qt Creator自动生成,这里我们额外添加了一个信号和两个槽函数用于主线程与工作线程的信息交流。

在 ioThread.h文件中:

包含4个类的申明:

class MsgInStream                  定义输入流

class StreamLineEnd               定义换行对象,对应的是标准输入输出流中的endl

class MsgOutStream                定义输出流

class IoThread                          定义输入输出工作线程

#ifndef IOTHREAD_H
#define IOTHREAD_H
#include <QThread>
#include <QKeyEvent>
#include <QTextBrowser>
class MsgInStream:public QObject{
// 必须要继承QObject才可以使用信号与槽
// 输入流
    Q_OBJECT
private:
    QString inputMessage;   // 存储用户从行编辑器中输入的内容,类似控制台程序的缓存区
public:
    MsgInStream():ifFinishedInput(false){};
    ~MsgInStream(){};
    // 利用友元重载运算符
    friend MsgInStream& operator>>(MsgInStream& input,int& value);  // 读取整数
    friend MsgInStream& operator>>(MsgInStream& input,double& value);   // 读取双精度浮点数
    friend MsgInStream& operator>>(MsgInStream& input,QString& value);  // 读取Qt字符串
    friend MsgInStream& operator>>(MsgInStream& input,QChar& value);  // 读取一个字符
    bool ifFinishedInput;   // 写在operator>>重载友元函数中,如果未完成读入(按行)则进入死循环等待
public slots:
    void inputMsgGet(QString str);  // 槽函数用于获取主线程的lineEdit读取的字符串
class StreamLineEnd{
public:
    explicit StreamLineEnd(){};
    ~StreamLineEnd(){};
class MsgOutStream:public QObject{
// 输出流
    Q_OBJECT
public:
    MsgOutStream(){};
    ~MsgOutStream(){};
    friend MsgOutStream& operator<<(MsgOutStream& output,const int& value); // 输出整型
    friend MsgOutStream& operator<<(MsgOutStream& output,const double& value);  // 输出双精度
    friend MsgOutStream& operator<<(MsgOutStream& output,const QString& value); // 输出Qt字符串
    friend MsgOutStream& operator<<(MsgOutStream& output,const StreamLineEnd& endl);    // 换行
    friend MsgOutStream& operator<<(MsgOutStream& output,const QChar& endl); // 输出一个字符
signals:
    void outputMessage(QString msg);    // 通过信号与槽来实现输出
    void newline();
class IoThread: public QThread{
//Q_OBJECT
public:
    void threadStart(); // 用于启动线程
    void threadStop(); // 线程终止
    MsgInStream msgin;
    MsgOutStream msgout;
    StreamLineEnd endl;
    IoThread(){};
    ~IoThread(){
        wait();
        qDebug()<<"安全退出";
protected:
    void run() Q_DECL_OVERRIDE;    // 虚函数重写,当作控制台中的main函数
#endif // IOTHREAD_H

说明:为了能够使用信号与槽必须使用宏 Q_OBJECT,而要使用这个宏我们的类必须是继承了Qt基本类QObject,对于类StreamLineEnd只需要它作为占位使用,所以不需要添加类成员,构造和析构函数都为空即可,对于IoThread需要说明的是虚函数run可以当作控制台程序中的主函数使用,只需要将要实现的代码逻辑写在其中

在源文件mainwindow.cpp中

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    ui->setupUi(this);
    this->setWindowTitle("交互窗口输入输出测试");
    this->aThread.threadStart();    // 开启工作线程
    // 以下进行主线程和工作线程信息交流的信号与槽的连接
    connect(this,&MainWindow::inputMessage,&(this->aThread.msgin),&MsgInStream::inputMsgGet);
    connect(&(this->aThread.msgout),&MsgOutStream::outputMessage,this,&MainWindow::outputMessageEmit);
    connect(&(this->aThread.msgout),&MsgOutStream::newline,this,&MainWindow::newlineEmit);
    ui->lineEdit->setFocus();
MainWindow::~MainWindow()
    delete ui;
void MainWindow::keyPressEvent(QKeyEvent* event){
    if(event->key() == Qt::Key_Return){
        // 检查键盘事件是否是enter
        QString inputStr = ui->lineEdit->text();
        ui->lineEdit->clear();
        if(inputStr.length() != 0){
            // 如果用户有输入了就发射信号
            qDebug()<<inputStr;
            ui->textBrowser->insertPlainText("INPUT: "+inputStr);
            ui->textBrowser->append("");// 默认换行
            emit inputMessage(inputStr);
        }else{
            ui->textBrowser->append("<font color = yellow> INPUT: \"NONE\" </font>");
            ui->textBrowser->append("");    // 换行
void MainWindow::closeEvent(QCloseEvent* event){
    if(this->aThread.isRunning()){
        this->aThread.threadStop();
        this->aThread.wait();
    event->accept();
void MainWindow::outputMessageEmit(QString msg){
    ui->textBrowser->insertPlainText(msg);  // 在当行插入字符串
void MainWindow::newlineEmit(){
    ui->textBrowser->append("");    // 追加字符串并换行

在源文件ioThread.cpp中

# include "ioThread.h"
void MsgInStream::inputMsgGet(QString str){
    this->inputMessage = str;
    this->ifFinishedInput = true;
void IoThread::threadStart(){
    this->start();
void IoThread::threadStop(){
    this->terminate();  // 终止线程,强制终止,配合主窗口关闭事件的wait使用
MsgInStream& operator>>(MsgInStream& input,int& value){
    QString value_str;
    while(!input.ifFinishedInput){};    // 如果来自窗口的信息未被读取则再此阻塞工作线程
    // 如果出现字符'0'到'9'和'-','+'之外的字符则停止读入
    QString::iterator iter = input.inputMessage.begin();
    while(true){
        if((*iter)<'0'||(*iter)>'9'){
            // 非数字字符的识别和处理
            if(iter != input.inputMessage.begin()){
                // 包含的情况:非负号,或负号在后
                break;
        // 数字字符的识别和处理
        value_str += QString(*iter);
        ++iter;
    value = value_str.toInt();
    if(*iter == ' '){
        value_str += ' ';
    input.inputMessage.replace(value_str,"");
    if(value_str.length() == 0 || input.inputMessage.length() == 0){
        // 只要保留数据的字符串长度为0则默认后面的字符串无意义,直接停止读入
        input.ifFinishedInput = false;  // 数据读完后将标记更新
    return input;
MsgInStream& operator>>(MsgInStream& input,double& value){
    QString value_str;
    while(!input.ifFinishedInput){};    // 如果来自窗口的信息未被读取则再此阻塞工作线程
    // 如果出现字符'0'到'9'和'-','+'之外的字符则停止读入
    QString::iterator iter = input.inputMessage.begin();
    while(true){
        if(((*iter)<'0'||(*iter)>'9')&&(*iter)!='.'){
            // 非数字字符的识别和处理
            if(iter != input.inputMessage.begin()){
                // 包含的情况:非负号且非正号,或负号在后和正号在后
                break;
        // 数字字符的识别和处理
        value_str += QString(*iter);
        ++iter;
    value = value_str.toDouble();
    if(*iter == ' '){
        value_str += ' ';
    input.inputMessage.replace(value_str,"");
    if(value_str.length() == 0 || input.inputMessage.length() == 0){
        // 只要保留数据的字符串长度为0则默认后面的字符串无意义,直接停止读入
        input.ifFinishedInput = false;  // 数据读完后将标记更新
    return input;
MsgInStream& operator>>(MsgInStream& input,QString& value){
    while(!input.ifFinishedInput){};    // 如果来自窗口的信息未被读取则再此阻塞工作线程
    value = ""; // 首先要将引用value清空,否则value已有的值会影响后面的判断
    QString::iterator iter = input.inputMessage.begin();
    while(true){
        if(iter == input.inputMessage.end()){
            input.inputMessage.replace(value,"");
            break;
        if(*iter == ' '){
            input.inputMessage.replace(value+' ',"");
            break;
        }else{
            value += QString(*iter);
            ++iter;
    if(input.inputMessage.length() == 0){
        // 输入字符串为空,直接停止读入
        input.ifFinishedInput = false;  // 数据读完后将标记更新
    return input;
MsgInStream& operator>>(MsgInStream& input,QChar& value){
    while(!input.ifFinishedInput){};    // 如果来自窗口的信息未被读取则再此阻塞工作线程
    QString::iterator iter = input.inputMessage.begin();
    value = *iter;
    input.inputMessage.replace(0,1,""); // 使用replace的另一种重载版本,去除第一个字符
    if(input.inputMessage.length() == 0){
        // 输入字符串为空,直接停止读入
        input.ifFinishedInput = false;  // 数据读完后将标记更新
    return input;
MsgOutStream& operator<<(MsgOutStream& output,const int& value){
    QString value_str = QString::number(value);
    value_str = "OUTPUT: "+value_str;
    emit output.outputMessage(value_str);   // 发射输出信号
    return output;
MsgOutStream& operator<<(MsgOutStream& output,const double& value){
    QString value_str = QString::number(value);
    value_str = "OUTPUT: "+value_str;
    emit output.outputMessage(value_str);
    return output;
MsgOutStream& operator<<(MsgOutStream& output,const QString& value){
    QString value_str;
    value_str+=QString("OUTPUT: ")+value;
    emit output.outputMessage(value_str);
    return output;
MsgOutStream& operator<<(MsgOutStream& output,const StreamLineEnd& endl){
    Q_UNUSED(endl);
    emit output.newline();  // 发射换行信号
    return output;
MsgOutStream& operator<<(MsgOutStream& output,const QChar& value){
    QString value_str;
    value_str+=QString("OUTPUT: ")+value;
    emit output.outputMessage(value_str);
    return output;
void IoThread::run(){
    // 重载run函数
    int a,b;
    while(true){
        msgin>>a>>b;
        msgout<<a<<endl;
        msgout<<b<<endl;
   quit();

主函数源文件main.cpp

由Qt Creator生成

#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();

【六】运行效果

【七】总结

通过小项目的编写确实是一种检验自己知识点掌握和锻炼代码能力的一种有效途径,本人能力有限,程序中可能存在的问题欢迎大家指出,之后在这个栏目里我会继续分享一些有趣的小项目,让我们共同学习,共同进步。

import sys import qtawesome from PyQt5.QtCore import QObject, pyqtSignal, QEventLoop, QTimer, QThread, QTime from PyQt5.QtGui import QTextCursor from PyQt5.QtWidget
文章目录一、自定义控件的封装1.SpinBox 和 HorizontalSlider 结合使用1.1 目标效果2. 封装步骤2.1 在项目中添加新Qt设计师界面类文件2.2 进入设计控件的UI界面2.3 使用控件界面使用自定义控件2.4 在制作控件的文件中完善控件的功能二、常用的鼠标事件1.捕获鼠标进入或者离开Label区域2.捕获鼠标的其他事件3.在原有类上增加定时器事件3.1 在 Label 中让数字每隔1秒就进行 ++ 操作4.处理多个定时器三、定时器类1.创建定时器对象2.停止定时器 一、自定义控件
在使用QtCreator调试Qt程序时,有时我们希望输出一些信息到控制台上,其实设置很简单,步骤如下: 第一步:项目–运行 界面下勾选Run in terminal 第二步:在项目的pro文件中添加:CONFIG +=console第三步:添加头文件#include <QDebug>如此在程序的信息输出位置,可以用qDebug()进行输出,如qDebug()<<tr("hello world!
Qt环境下进行人机交互界面设计–工具条 Qt真是一个神奇的编译环境 不仅能绘图还能设计人机交互 (其实都是在计算机图形学课上学的) Markdown也是一个nice的编辑器 哈哈哈哈哈哈哈 不废话 正文开始 mainwindow.cpp 因为这里需要用到工具条 所以你需要添加头文件 #include"QMessageBox" #include"QToolBar" 由于你是对窗口进行了操作 所以...
class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(800, 600) self.centralwid
上篇:QT从入门到实战x篇_16_控件-按钮组(PushButton;ToolButton;RadioButton;CheckBox等按钮组控件的基础使用方法)介绍了按钮组的使用方法,接下来将会介绍几个比较重要的控件,本篇主要介绍List Widget。 Item Views( Model-Based):是基于一些模型的,基于数据库的输出,是按照数据库的模型将数据显示。 Item Widgets(Item-Based ) :是基于一些item输出 List Widget列表: (1)ui->
在VS2019中,Qt项目输出窗口中文全部显示乱码的问题可能是由于字符集不匹配或编码方式不正确导致的。解决这个问题的方法如下: 1. 确保项目的字符集设置正确。在VS2019中,可以通过右击项目 -> 属性 -> 配置属性 -> 常规 来设置字符集,一般推荐选择“使用多字节字符集”。 2. 检查Qt项目的编码方式是否正确。在Qt中,一般推荐使用UTF-8编码方式。可以通过在Qt项目的.pro文件或者源代码中设置编码方式,确保输出窗口中文能够正确显示。 3. 检查输出窗口的字体设置。有时候乱码问题可能是由于输出窗口的字体设置不正确导致的。可以尝试在VS2019中修改输出窗口的字体为支持中文的字体,如微软雅黑等。 4. 检查系统语言和区域设置。有时候乱码问题可能是由于系统语言和区域设置不匹配导致的。可以尝试在Windows系统中修改语言和区域设置,确保支持中文显示。 总之,解决VS2019中Qt项目输出窗口中文显示乱码的问题需要综合考虑项目字符集设置、编码方式、字体设置以及系统语言和区域设置等因素,找出具体原因并进行相应的设置调整。