QT聊天项目开发DAY02

发布于:2025-04-14 ⋅ 阅读:(32) ⋅ 点赞:(0)

1.添加输入密码的保密性

LoginWidget::LoginWidget(QDialog*parent)
	: QDialog(parent)
{
	ui.setupUi(this);

	ui.PassWord_Edit->setEchoMode(QLineEdit::Password);

	BindSlots();
}

2.添加密码的验证提示

3.修复内存泄漏,并嵌套UI子窗口到主窗口里面

之前并没有设置窗口的父类对象,而选择在mainWindow的析构函数释放函数导致的一系列问题

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);
    _LoginWidget = new LoginWidget(this);
    _RegisterWidget = new RegisterWidget(this);

    setCentralWidget(_LoginWidget);

    _LoginWidget->setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
    _RegisterWidget->setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
    _RegisterWidget->hide();

    // 绑定槽函数
    BindSlots();
}

4.设置错误提示的文本颜色

修改.qss文件

#Tip_Label[state='normal']
{
	color: green;
}

#Tip_Label[state='err']
{
	color: red;
}

5.创建全局头文件,来管理状态

刷新当前UI窗口的状态,去qss文件中查询当前的窗口的样式

#ifndef GLOBAL_H
#define GLOBAL_H
#include <QWidget>
#include <functional>
#include "QStyle"

// extern 声明此变量是在其他文件中定义的全局变量
extern std::function<void(QWidget*)> repolish;

#endif // GLOBAL_H

#include "Global.h"

// 初始化声明的全局变量
std::function<void(QWidget*)> repolish = [](QWidget* Widget)
{
	Widget->style()->unpolish(Widget);
	Widget->style()->polish(Widget);
};

6.刷新窗口的样式

#include "Global.h"

RegisterWidget::RegisterWidget(QWidget *parent)
	: QDialog(parent)
{
	ui.setupUi(this);

	ui.Tip_Label->setProperty("state", "normal");
	repolish(ui.Tip_Label);
}

7.正则表达式解析验证码

void RegisterWidget::ShowTipLabel(const QString& tip, QString State)
{
	ui.Tip_Label->setText(tip);
	ui.Tip_Label->setProperty("state", State);
	repolish(ui.Tip_Label);
}


void RegisterWidget::OnGetCodeButtonClicked()
{
	QString Email = ui.Email_Edit->text();

	// 设置正则表达式
	QRegularExpression Regex(R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)");
	bool Match = Regex.match(Email).hasMatch();
	if (Match)
	{
		// 发送验证码
	}
	else
	{
		ShowTipLabel(QString::fromLocal8Bit("邮箱格式不正确"), "error");
	}
}

8.创建模板单例类

#ifndef SINGLETON_H
#define SINGLETON_H
#include "Global.h"

template<typename T>
class Singleton
{
protected:
	Singleton<T>() = default;
	Singleton(const Singleton<T>& ) = delete;
	Singleton<T>& operator=(const Singleton<T>& ) = delete;

	static std::shared_ptr<T> m_pInstance;

public:
	static std::shared_ptr<T> Instance()
	{
		static std::once_flag flag;
		std::call_once(flag, []() {
			try {
				m_pInstance = std::shared_ptr<T>(new T());
			}
			catch (...) {
				// 处理异常,这里可以记录日志等
				std::cerr << "Failed to create singleton instance." << std::endl;
			}
			});
		return m_pInstance;
	}

	void PrintAddress()
	{
		cout << "Singleton<T> address: " << m_pInstance.get() << endl;
	}

	~Singleton<T>()
	{
		std::cout << "Singleton<T> is destroyed" << std::endl;
	}
};

template<typename T>
std::shared_ptr<T> Singleton<T>::m_pInstance = nullptr;


#endif // SINGLETON_H

9.创建全局唯一的模板网络管理类

头文件报错,模块没有添加,添加模块

#ifndef HTTPMANAGER_H
#define HTTPMANAGER_H

#include "singletion.h"
#include <QString>
#include <QUrl>
#include <QObject>
#include <QNetworkAccessManager>
#include <QJsonObject>
#include <QJsonDocument>

class HttpManager : public QObject, public Singleton<HttpManager>
{
	Q_OBJECT

//public:
//	~HttpManager();
//
private:
	HttpManager();
};
#endif // HTTPMANAGER_H

在别的地方获取该模板类的指针编译,报错误

报错是编辑器还没有反应过来,已经没有问题了,然而在模板类中会析构自己创建的智能指针,而这个智能指针又会去调用HttpManager的析构函数,所以必须要将析构函数的权限设置为公有

如果设置为私有智能指针就找不到这个析构函数就会编译报错

设置成公有就没有任何问题

10.封装发送HTTP请求的接口

/* 发送Http请求 */
void PostHttpReq(QUrl url, QJsonObject jsonObj, ReqId reqId, Modules module);

创建全局的枚举

signals:
	void sig_http_finished(ReqID reqId, QString res, ErrorCodes errCode, Modules module);

11.Shared_Ptr的特点

每一个Shared_ptr 维护一个控制块记录当前指向资源的引用计数(即有多少个shared_ptr 共享该资源)

行为:

构造/拷贝:                 引用计数++

析构/重置(reset()) :      引用计数--

计数归零时自动释放该资源

#include <iostream>
#include <memory>

class Widget {
public:
    Widget() {
        std::cout << "Widget constructor run" << std::endl;
    }
    ~Widget() {
        std::cout << "Widget destructor run" << std::endl;
    }

    std::shared_ptr<Widget> GetSharedObject() {
        return std::shared_ptr<Widget>(this);
    }
};

int main()
{
    std::shared_ptr<Widget> p(new Widget());
    std::shared_ptr<Widget> q = p;

    std::cout << p.use_count() << std::endl;
    std::cout << q.use_count() << std::endl;

    return 0;
}

智能指针拷贝或者赋值时,引用计数会++;

std::shared_ptr<Widget> p(new Widget());
std::shared_ptr<Widget> q = p->GetSharedObject();
int main()
{
    Widget* w = new Widget();
    std::shared_ptr<Widget> p(w);
    std::shared_ptr<Widget> p2(w);
    //std::shared_ptr<Widget> q = p->GetSharedObject();

    std::cout << p.use_count() << std::endl;
    std::cout << p2.use_count() << std::endl;

    return 0;
}

这两段代码其实是等价的,这就为同一个对象创建了第二个控制块,导致他们的引用计数都为1,造成了二次析构

std::enable_shared_from_this正是为了解决这个问题而存在

12.std::enable_shared_from_this

使用条件极为苛刻,只有当该对象被Shared_Ptr管理时,才能够正常的使用,否则会报错

#include <iostream>
#include <memory>

class Widget : public std::enable_shared_from_this<Widget> {
public:
    Widget() {
        std::cout << "Widget constructor run" << std::endl;
    }
    ~Widget() {
        std::cout << "Widget destructor run" << std::endl;
    }

    std::shared_ptr<Widget> GetSharedObject() {
        return shared_from_this();
    }
};

int main()
{
    Widget* w = new Widget();
    //std::shared_ptr<Widget> p(w);
    std::shared_ptr<Widget> q = w->GetSharedObject();

    //std::cout << p.use_count() << std::endl;
    std::cout << q.use_count() << std::endl;

    return 0;
}

  1. shared_from_this()

    • 返回与已有 shared_ptr 共享控制块 的新 shared_ptr
    • 若对象未被 shared_ptr 管理,调用它会抛出 std::bad_weak_ptr 异常。

总结

  • 永远不要直接通过 this 构造 shared_ptr,这会导致多个独立的控制块
  • 需要对象内部返回 shared_ptr 时,必须使用 std::enable_shared_from_this 和 shared_from_this()
  • 创建对象时,优先使用 std::make_shared 或确保对象始终由 shared_ptr 管理。

13.继续完善发送HTTP请求的接口

/* 发送HTTP请求 */
void HttpManager::PostHttpReq(QUrl url, QJsonObject jsonObj, ReqID reqId, Modules module)
{
	QByteArray data = QJsonDocument(jsonObj).toJson();
	QNetworkRequest request(url);
	request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
	request.setHeader(QNetworkRequest::ContentLengthHeader, data.length());

	std::shared_ptr<HttpManager> self = shared_from_this();

	QNetworkReply* reply = m_pNetworkAccessManager->post(request, data);
	connect(reply, &QNetworkReply::finished, [self, reply, reqId, module]()
		{
			if (reply->error() == QNetworkReply::NoError)
			{
				QString strReply = QString(reply->readAll());
				emit self->sig_http_finished(reqId, strReply, ErrorCodes::SUCCESS, module);
				reply->deleteLater();
				return;
			}
			else
			{
				qDebug() << "HttpManager::PostHttpReq error: " << reply->error();
				emit self->sig_http_finished(reqId, "", ErrorCodes::ERROR_NETWORK, module);
				reply->deleteLater();
				return;
			}
		});
}

14.创建http请求完成的槽函数

public slots:
	void slot_http_finished(ReqID reqId, QString res, ErrorCodes errCode, Modules module);
/* 处理http请求发送成功的槽函数 */
void HttpManager::slot_http_finished(ReqID reqId, QString res, ErrorCodes errCode, Modules module)
{
	/* 转发给注册模块 */
	if (module == Modules::REGISTERMOD)
	{
		emit sig_reg_mod_finished(reqId, res, errCode);
	}
}

15.实现注册模块的槽函数

/* 当注册成功时 */
void slot_reg_mod_finish(ReqID reqId, QString res, ErrorCodes errCode);
void RegisterWidget::BindSlots()
{
	connect(ui.Get_Code_Btn, &QPushButton::clicked, this, &RegisterWidget::OnGetCodeButtonClicked);
	/* 当注册请求完成时 */
	connect(HttpManager::Instance().get(), &HttpManager::sig_reg_mod_finished, this, &RegisterWidget::slot_reg_mod_finish);
}
/* 注册模块回调 */
void RegisterWidget::slot_reg_mod_finish(ReqID reqId, QString res, ErrorCodes errCode)
{
	if (errCode != ErrorCodes::SUCCESS)
	{
		ShowTipLabel(QString::fromLocal8Bit("注册失败"), "error");
		return;
	}
}

16.解析Json字符串(反序列化)

// 解析Json
void AnalyzingJsonData(ReqID reqId, QByteArray data);


/* 解析Json数据 */
void RegisterWidget::AnalyzingJsonData(ReqID reqId, QByteArray data)
{
	QJsonDocument jsonDoc = QJsonDocument::fromJson(data);								// .json文件
	if (jsonDoc.isNull())
	{
		ShowTipLabel(QString::fromLocal8Bit("json 解析失败"), "error");
		return;
	}

	// 解析json数据
	if (!jsonDoc.isObject())
	{
		ShowTipLabel(QString::fromLocal8Bit("json 格式不正确"), "error");
		return;
	}

	// 回调函数
	_handlers[reqId](jsonDoc.object());
}									
void RegisterWidget::InitHttpHandlers()
{
	// 注册获取验证码回包的逻辑
	_handlers.insert(ReqID::ID_GET_VARIFY_CODE, [this](const QJsonObject& jsonObj)
		{
			int error = jsonObj["error"].toInt();
			if (error != ErrorCodes::SUCCESS)
			{
				ShowTipLabel(QString::fromLocal8Bit("参数错误"), "error");
				return;
			}

			auto email = jsonObj["email"].toString();
			ShowTipLabel(QString::fromLocal8Bit("验证码已发送至邮箱,请注意查找"), "normal");
			qDebug() << QString::fromLocal8Bit("验证码已发送至邮箱") << email;
		});
}

注册函数对象,来动态绑定回调


网站公告

今日签到

点亮在社区的每一天
去签到