使用C++/Qt编程的一些技巧

使用C++/Qt编程的一些技巧

来源 https://zhuanlan.zhihu.com/p/93292896

这里记录一下使用C++/Qt在日常编程中的一些小技巧和习惯和体会,个人觉得虽然不是高深的东西但肯定会是一些实用的东西。

  • 应该是用代码写界面还是使用拖拽控件来绘制界面呢

这个问题可以说是仁者见仁,智者见智的问题,这个问题可以引起代码派和拖拽派的争论甚至刀剑相向哦,就跟使用tab键还是空格键来缩进代码一样的。我个人觉得如果是简单的测试demo后者简单的小工具,可以使用拖拽,或者对于刚入门,对Qt界面不是很熟悉,但是公司又需要马上做东西出来,这时候拖拽可以解决我们的问题。但是,如果随着项目越来越大,界面越来越复杂,这时会发现维护拖拽界面(.ui文件)是一件不简单的事情,甚至是牵一发而动全身,而如果是用代码编写的界面,可以很好的将界面封装成小的组件和控件,达到复用的目的,并且也能在后期很好的修改和维护,结构清晰,最重要的是使用代码方便编写自定义控件,其实纵观java安卓、前端JS框架这些都是使用代码编写界面居多;还有就是当你对界面有一定的熟悉或者入门之后,我建议还是使用代码写界面,这样可以更好的了解Qt的机制,设计哲理,以及C++在Qt当中的使用,能够锻炼自己的C++编程能力,因为Qt本身其实就是一个庞大的C++项目,其中的实现和设计哲学对我们加深自己的C++编程能力非常有帮助,如果更有追求一点,可以适当的去阅读Qt的源码。

  • 关于样式表被忽视的一点

其实Qt的样式表是CSS2的子集,其中大部分CSS2的语法在qss中是支持的,但是在日常编程当中我发现被人用的很少(在CSS中被人们最常用的)的类选择器,在qss中类选择器相当于是属性选择器的一种特殊写法吧。

样式文件按照类选择器方式写:

.textLb{
    background-color:red; color:blue; }

其实这里的样式文件写成属性选择器个方式也可以:

QLabel[class="textLb"]{
    background-color:red;
    color:blue;
}
  • 关于一些临时性的数据缓存,因为这些数据比较随意或者比较杂,没有必要再在类中定义一套数据结构来存储

在Qt中,其实这可以很方便的解决了,只要是继承自QObject的派生类都会有QVariant property(const char *name) constbool setProperty(const char *name, const QVariant &value)

这两个方法分别是读取属性和设置属性,可以在需要的时候将值使用setProperty设置到实例中,然后在需要的时候使用property从实例中读取数据,这样像一些简单的临时数据,就不需要专门定义结构体或者成员变量去记录了,同时setProperty也是在样式设置中结合属性选择器使用的必备方法。其实像在Qt中不仅仅是property可以用来缓存数据,还有界面组件类,比如像一些列表组件或者树组件的item,比如QComboBox在添加选项的时候,可以给每个选项设置data,使用setItemData(int index, const QVariant &value, int role = Qt::UserRole),其中role是用来在获取数据的时候使用的,下面是一个简单例子:

// 假设下拉框中需要放置两个设备,显示设备名称,但是设备的唯一性信息用ip表示,绑定在数据itemdata中
QComboBox* cb = new QComboBox(this); cb->addItem("我是设备a", "192.168.1.10"); cb->setItemData(0, 9000, Qt::UserRole + 1); cb->insertItem(1, "我是设备b", "192.168.1.11"); cb->setItemData(1, 9001, Qt::UserRole + 1); // 读取选项数据 cb->currentData(Qt::UserRole).toString(); // 这里获取当前设备的ip cb->currentData(Qt::UserRole + 1).toInt(); // 这里获取当前设备的端口 
  • 关于连接信号槽connect的写法

在Qt5之前,connect一般都只能这么写connect(sender, SIGNAL(signal()), receiver, SLOT(receiveFunc())),就是说在connect的时候,必须把信号用宏SIGNAL包裹起来,把槽函数用宏SLOT包裹起来,这样才能被Qt的Moc机制识别,但是Qt5之后更加推荐"取地址的写法",举个例子:

QPushButton* btn = new QPushButton("我是按钮");
//! 这里假定TestClass有一个槽函数叫slotDoSomething()
TestClass* tc = new TestClass();
connect(btn, SIGNAL(clicked()), this, SLOT(onBtnClicked()));		// 方式1		
connect(btn, &QPushButton::clicked, this, &QssDemo::onBtnClicked);	// 方式2
connect(btn, &QPushButton::clicked, [this] {onBtnClicked(); });		// 方式3
connect(btn, &QPushButton::clicked, [this] {onBtnClicked(); });		// 方式3
connect(btn, &QPushButton::clicked, this, [this, tc] {
	onBtnClicked(); 
	tc->slotDoSomething() 
});		// 方式4

对于方式1,就是用“宏包裹的写法”,这种写法属于老式写法,弊端:在编译的时候即使信号或槽不存在也不会报错,但是在执行的时候无效,对于C++这种静态语言来说,这是不友好的,不利于调试;

对于方式2,就是“取地址写法”,采用这种写法,如果编译的时候信号或槽不存在是无法编译通过的,相当于编译时检查,不容易出错,还有就是槽的写法可以直接写在public控制域下,不一定非要写在public slots:控制域下;

对于方式3,采用了lambda表达式的写法,更加方便快捷。

三种方式都可以,个人比较喜欢用的是方式2和方式3,像一些简单逻辑直接用方式3来connect,对于一些复杂或者稍长一点的逻辑代码,用方式2来connect。

添加修改:

对于以上说法,方式3和方式4,不同之处在于一个写了接收者参数this,一个没写,如果方式3中this因为某种原因被删除销毁了,但是信号槽的连接关系依然存在,因为使用方式3,Qt只会关联信号的接收者是一个函数地址,所以如果外部触发了信号,就会触发槽函数,槽函数lambda里面调用了this成员,所以会导致访问无效指针,进而程序崩溃,这种情况就要使用方式4了,把connect的接收者this加上,这样在this被销毁,信号槽关系会断开。

方式3中lambda参数中的this跟方式4中lambda的参数tc是一个性质的,其本身跟信号槽的连接没有直接关系,只是在槽函数中调用了而已,当时方式4中的this因为被传给了connect的接收者参数,所以它的销毁跟信号槽的断开时有关系的。

再加一个小demo说明一下吧:

class Test : public QWidget {
	Q_OBJECT
public:
	Test(QWidget* parent = nullptr) :QWidget(parent) { 
            this->setAttribute(Qt::WA_DeleteOnClose, true);
              m_a_ = 9; 
         }
	~Test() {}

	void setTimer(QTimer* t) {
		connect(t, &QTimer::timeout,this, [this] {
			++m_a_;
			qDebug() << m_a_;
			emit aaa();
		});
	}
signals:
	void aaa();

private:
	int m_a_;
};

QTimer* timer = new QTimer();
Test* t = new Test();
t->resize(400, 400);

t->setTimer(timer);
timer->start(1000);
t->show();

在上述代码中,如果setTimer函数中的connect的第三个参数this不写,当你关闭test窗体的时候,程序就会挂掉。

综上,connect的连接断开只与信号的发送者和接收者有关。

================ End

猜你喜欢

转载自www.cnblogs.com/lsgxeva/p/12564472.html