• 使用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

  • 相关阅读:
    kafka报错:Invalid message size: 0
    转载:elastic5.x部署常见问题总结
    hadoop集群zookeeper迁移
    生产环境轻量级dns服务器dnsmasq搭建文档
    (3)安装elastic6.1.3及插件kibana,x-pack,essql,head,bigdesk,cerebro,ik
    (2)安装elastic6.1.3及插件kibana,x-pack,essql,head,bigdesk,cerebro,ik
    (1)安装elastic6.1.3及插件kibana,x-pack,essql,head,bigdesk,cerebro,ik
    换种思路解决日志占用磁盘空间问题
    更改hadoop集群yarn的webui中的开始时间和结束时间为本地时间
    两种虚拟机扩容方式扩容后在线生效的方法
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/12564472.html
Copyright © 2020-2023  润新知