关于Qt C++中connect的几种写法代码示例

前言

connect函数就像是编程世界里的“茴”字,千变万化,各有千秋。咱们程序员呢,就像是孔乙己那样,虽然有时候会觉得这些变化有些好笑,甚至有些令人头疼,但说到底,还不是得乖乖地学着、用着,毕竟这可是编程里的“必备技能”。connect的编写每个人都有自己的习惯,也有其特点,还是要深究一下的。

在Qt中,connect函数用于将信号(signal)与槽(slot)连接起来,以便在信号被发射时自动调用槽函数。Qt提供了几种不同的connect写法,以及定义槽函数的方式,它们各有特点和适用场景。

1. 传统的槽函数写法

在传统的Qt项目中,槽函数通常是在类的头文件中使用slots:关键字声明的特殊成员函数。例如:

class MainWindow : public QMainWindow {
  Q_OBJECT
 
public:
  MainWindow(QWidget *parent = nullptr);
  ~MainWindow();
 
private slots: // 使用slots关键字声明槽函数
  void onCalculateClicked();
 
  // ... 其他成员和槽函数 ...
};

然后在源文件中实现这个槽函数:

void MainWindow::onCalculateClicked() {
  // 槽函数的实现
}

使用这种写法时,conect函数通常这样调用:

connect(pbt, SIGNAL(clicked()), this, SLOT(onCalculateClicked()));

这里使用的是SIGNAL()SLOT()宏来将信号和槽转换为字符串形式的标识符。

2. 使用函数指针的connect写法(5.0)

在5.0及后续的Qt版本中,推荐使用函数指针的connect写法,因为它提供了更好的类型检查和编译时错误检测。这种写法不需要使用slots:关键字或SIGNAL()/SLOT()宏,而是直接使用函数指针。例如:

connect(pbt, &QPushButton::clicked, this, &MainWindow::onCalculateClicked);

在这种写法中,&QPushButton::clicked是信号clicked的函数指针,&MainWindow::onCalculateClicked是槽函数的函数指针。编译器会在编译时检查这些指针是否匹配。这种写法的优点如下:

  • 类型安全
    函数指针的connect写法提供了编译时的类型检查。这意味着如果信号和槽的函数签名不匹配,或者指定的函数不存在,编译器会立即给出错误提示。这大大减少了运行时错误的可能性,因为所有的连接都是在编译时验证的。

  • 易于阅读和维护
    使用函数指针使得代码更加直观和易于理解。你可以直接看到哪个信号被连接到了哪个槽,而不需要通过字符串来间接表示。这有助于代码的维护和调试,因为你可以更容易地跟踪信号和槽之间的连接关系。

  • 自动处理函数重载
    当存在多个重载的信号或槽函数时,使用函数指针可以自动选择正确的重载版本。传统的SIGNAL()SLOT()宏在处理重载函数时可能会遇到问题,因为它们依赖于函数名的字符串表示,无法区分重载的函数。而函数指针则包含了函数的完整签名,因此可以准确地选择所需的函数。

  • 提高性能
    虽然性能差异可能不是非常显著,但使用函数指针的connect写法可能比使用字符串的宏稍微快一些。这是因为函数指针可以直接在编译时解析和连接,而不需要在运行时进行字符串比较和查找。

  • 与现代C++标准兼容
    随着C++标准的不断发展,使用函数指针的connect写法更符合现代C++的编程风格。它利用了C++11及更高版本中的特性,如lambda表达式和智能指针,使得Qt的代码可以与这些现代特性更好地集成。

  • 减少宏的使用
    减少宏的使用可以降低代码的复杂性和潜在的错误。宏在C++中经常是导致难以调试的问题的源头,因为它们是在预处理阶段展开的,而不是在编译阶段进行类型检查的。使用函数指针可以避免这些问题。

3. Lambda表达式作为槽函数(C++11)

在C++11以后,除了传统的槽函数和函数指针,Qt还支持使用Lambda表达式作为槽函数。这种写法非常灵活,因为它允许你在connect调用中直接编写槽函数的代码,而不需要在类中定义额外的成员函数。例如:

connect(pbt, &QPushButton::clicked, this, [this]() {
  // Lambda表达式作为槽函数的实现
  bool isOK;
  double r = lEdit->text().toDouble(&isOK);
  if (isOK && r >= 0) {
    lab2->setText(QString::number(AreaCircal(r)));
  } else {
    lab2->setText("请输入合法的半径!");
  }
});

这种写法的优点如下:

  • 捕获或引用捕获的方式来控制这些变量的生命周期和作用域。

  • 灵活性和动态性
    Lambda表达式提供了一种灵活的方式来定义槽函数的行为。你可以在connect调用中直接编写Lambda表达式,而不需要事先在类中声明槽函数。这使得你可以根据需要在运行时动态地定义槽函数的行为。

  • 减少代码量
    使用Lambda表达式作为槽函数可以减少需要编写的代码量。你不需要为每个槽函数都声明一个成员函数,这不仅可以减少代码量,还可以使代码更加紧凑和易于管理。

  • 类型安全
    与传统的字符串宏相比,使用Lambda表达式作为槽函数提供了更好的类型安全。编译器会在编译时检查Lambda表达式的类型是否与信号的参数类型匹配,从而避免了运行时错误。

除了第一种传统写法外,现在较为流行的二、三种写法对比分析如下:

特性 Lambda表达式 函数指针
引入时间 C++11 C++早期版本、Qt5.0
定义方式 匿名函数,直接在代码中定义 需要显式声明和定义函数,然后通过函数指针指向该函数
语法简洁性 简洁,直接在调用处定义 相对繁琐,需要额外的函数声明和定义
类型推断 支持,编译器可根据上下文推断类型 不支持,需要显式指定函数返回类型和参数类型
捕获外部变量 可以捕获外部作用域中的变量 无法直接捕获外部变量,只能访问函数参数
执行环境 在新的栈帧中执行,具有独立的调用环境和栈空间 指向已存在的函数,不拥有独立的栈空间
灵活性 适用于简单的、单行表达式的场合,易于在需要的时间和地点实现功能闭包 适用于调用已经定义好的函数,支持动态绑定和回调函数
代码重用性 通常用于一次性或短暂的函数定义,代码重用性较低 可以通过函数指针在不同位置调用同一函数,代码重用性较高
类型安全性 类型安全,编译器检查函数签名 类型安全性较低,容易出现类型不匹配的问题
性能 现代编译器优化后性能损失可忽略,但在某些情况下可能带来轻微开销 通常性能开销较低,但间接调用可能带来一些额外开销
使用场景 适用于需要定义简单匿名函数的场景,如STL算法中的排序和过滤 适用于需要动态调用不同函数或实现回调函数的场景,如事件处理、插件系统等

上述三种方法的特点和区别总结:

  • 传统槽函数:易于阅读和维护,特别是在槽函数逻辑复杂或需要多次重用的情况下。但是,它们增加了类的复杂度,因为需要在头文件中声明槽函数。
  • 函数指针的connect:提供了更好的类型安全性,减少了运行时错误的可能性。它是现代Qt编程的推荐方式。
  • Lambda表达式:非常灵活,适用于简单的、一次性的槽函数逻辑。但是,如果Lambda表达式过于复杂,可能会降低代码的可读性。

4.使用QOverload选择重载信号的写法(Qt5.7)

使用QOverload选择重载信号的写法同样是在Qt5引入的,在Qt框架中具有显著的优点,并且适用于特定的场景。例如使用comboBox时,有时使用索引,有时使用字符串作为参数:

    QComboBox *comboBox = new QComboBox;
    QLabel *label = new QLabel;
    label->setText("初始文本");
 
    // 添加一些选项到组合框
    comboBox->addItem("选项 1");
    comboBox->addItem("选项 2");
    comboBox->addItem("选项 3");

    // 使用 QOverload 来连接重载的信号到不同的槽
    QObject::connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
                     [](int index){ qDebug() << "当前索引改变:" << index; });
 
    QObject::connect(comboBox, QOverload<const QString&>::of(&QComboBox::currentIndexChanged),
                     [](const QString &text){ qDebug() << "当前文本改变:" << text; });

在这个例子中,currentIndexChanged信号被重载了两次:一次接受一个整型参数(表示当前选中项的索引),另一次接受一个字符串参数(表示当前选中项的文本)。我们使用QOverload模板类来指定我们想要连接的是哪一个重载版本的信号。然后,我们使用lambda表达式来定义当信号发出时执行的代码。QOverload是Qt 5.7及更高版本中引入的一个模板类,它用于在连接重载信号时提供类型安全。在Qt 5.7之前的版本中,您可能需要使用static_cast或函数指针来实现相同的功能,但这样做通常不太安全且容易出错。

具体优点:

  • 类型安全
    • 使用QOverload可以确保在连接信号和槽时,信号和槽的参数类型完全匹配。这有助于在编译时捕获潜在的错误,提高代码的稳定性。
  • 清晰明确
    • 通过显式地指定要连接的重载信号版本,代码更加清晰易懂。其他开发者可以一目了然地看到信号和槽之间的对应关系,减少误解和错误。
  • 灵活性
    • QOverload允许开发者在信号存在多个重载版本的情况下,选择最适合当前需求的信号版本进行连接。这增加了代码的灵活性,使其能够适应不同的场景和需求。
  • 避免歧义
    • 在没有QOverload之前,如果信号存在多个重载版本,开发者需要通过其他方式(如函数指针转换)来指定要连接的信号版本,这可能会引入歧义和错误。而QOverload提供了一种直接且明确的方式来避免这些问题。

这种写法用于当信号存在多个重载版本时,通过QOverload模板类明确指定要连接的是哪一个重载版本的信号。比如comboBox::currentIndexChanged信号可能有多个重载版本,而通过使用QOverload<int>或QOverload<const QString&>明确指定,然后进行处理。适应场景:

  • 信号存在多个重载版本
    • 当类中的信号存在多个重载版本时,使用QOverload可以明确指定要连接的重载信号版本,从而避免连接错误或歧义。
  • 需要类型安全的信号槽连接
    • 在对类型安全要求较高的项目中,使用QOverload可以确保信号和槽的参数类型完全匹配,减少运行时错误的可能性。
  • 复杂的GUI应用程序
    • 在复杂的GUI应用程序中,信号和槽的连接可能变得非常复杂。使用QOverload可以使代码更加清晰易懂,方便维护和管理。
  • 需要灵活处理不同参数类型的场景
    • 在某些场景下,可能需要根据不同的参数类型来处理信号。使用QOverload可以方便地选择不同参数类型的信号版本进行连接,从而满足这些需求。

写到这里,感觉有点与孔乙己说的茴字的几种写法行为相仿,这里Qt里的connect函数,简直就是编程界的“茴”字啊!孔乙己要是穿越到编程世界,看到connect的种种写法,怕是要笑得胡子都抖掉了,心里还得嘀咕:“这信号槽的connect,怎地也有如此多般变化,真是绝了!”

想象一下,咱们这connect函数,简直就是编程里的“百搭小能手”,既能跟老式的函数指针眉来眼去,又能跟新潮的Lambda表达式暗送秋波,还能跟那信号映射机制搞点小暧昧。这不,就像孔乙己研究“茴”字的几种写法一样,咱们程序员也得琢磨琢磨connect的几种姿势-_-||b

在选择哪种写法时,应根据具体的需求和代码风格来决定。对于复杂的槽函数逻辑,传统的槽函数可能更合适;对于简单的逻辑或临时的连接,Lambda表达式可能更方便;而函数指针的connect则通常是一个折中的选择,既安全又易于使用。

总结

到此这篇关于关于Qt C++中connect的几种写法的文章就介绍到这了,更多相关Qt C++中connect写法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源链接:https://www.jb51.net/program/331069o54.htm

© 版权声明
THE END
支持一下吧
点赞15 分享
评论 抢沙发
头像
请文明发言!
提交
头像

昵称

取消
昵称表情代码快捷回复

    暂无评论内容