一 回调函数
前言
在Qt中传数据,我们使用了一个信号和槽系统,但这并不意味着不能使用旧的经过验证的方法,即使用 CallBack回调函数功能。
事实上使用 CallBack 功能比信号和槽要快一些。并且当发送信号的对象在程序中被销毁并且不再使用时,就信号理想地从槽中分离而言,回调可以更容易使用。
如何使用CallBack工作
假设A类包含B类的对象,B类有动作时想要通知到A类,B类应该有个设置回调函数的接口,A类应该定义相应的回调函数,将函数指针传递给B。
还是直接举例吧:
例如,将使用一个类,在图形场景中绘制一个正方形,并由W,A,S,D键控制。移动时,正方形必须将其坐标的数据发送到创建它的类。也就是说,它应该把这个类的函数作为它的 CallBack 函数。要做的程序效果如下,通过WASD控制方块移动,主窗口接收正方形的位置信息,并将位置信息填入QLineEdit:
square.h中部分代码:
class Square : public QObject, public QGraphicsItem
{
Q_OBJECT
public:
explicit Square(QObject *parent = 0);
~Square();
// 设置回调函数的函数
void setCallbackFunc(void(*func) (QPointF point));
protected:
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
private:
QTimer * m_timer;
void(*m_callbackFunc)(QPointF point);
private slots:
void slotTimer();
};
square.cpp中部分代码:
Square::Square(QObject *parent)
: QObject(parent), QGraphicsItem()
{
m_timer = new QTimer();
connect(m_timer, &QTimer::timeout, this, &Square::slotTimer);
m_timer->start(1000 / 33);
}
Square::~Square()
{
}
void Square::setCallbackFunc(void(*func) (QPointF point))
{
m_callbackFunc = func;
}
QRectF Square::boundingRect() const
{
return QRectF(-15, -15, 30, 30);
}
void Square::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->setPen(Qt::black);
painter->setBrush(Qt::green);
painter->drawRect(-15, -15, 30, 30);
Q_UNUSED(option);
Q_UNUSED(widget);
}
void Square::slotTimer()
{
// 根据按钮触发情况移动正方形
if (GetAsyncKeyState('A'))
{
this->setX(this->x() - 2);
}
if (GetAsyncKeyState('D'))
{
this->setX(this->x() + 2);
}
if (GetAsyncKeyState('W'))
{
this->setY(this->y() - 2);
}
if (GetAsyncKeyState('S'))
{
this->setY(this->y() + 2);
}
// 调用回调函数传递正方形位置 类似于发信号给mainwindow 由mainwindow执行相应槽函数
m_callbackFunc(this->pos());
}
mainwindow.h部分代码
class MainWindow : public QWidget
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = Q_NULLPTR);
~MainWindow();
private:
Ui::MainWindow* ui;
QGraphicsScene* m_scene;
Square *m_square; // 声明正方形 传输回调
static QLineEdit *line1; // 声明一个静态QLineEdit, 执行回调
static QLineEdit *line2; // 声明一个静态QLineEdit, 执行回调
private:
// 声明一个回调函数
static void getPosition(QPointF point);
};
mainwindow.cpp部分代码
QLineEdit * MainWindow::line1;
QLineEdit * MainWindow::line2;
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
ui = new Ui::MainWindow();
ui->setupUi(this);
// 初始化QLineEdit
line1 = new QLineEdit();
line2 = new QLineEdit();
// 把两个line 放进gridLayout
ui->gridLayout->addWidget(line1, 0, 1);
ui->gridLayout->addWidget(line2, 0, 2);
// 初始化图形场景
m_scene = new QGraphicsScene();
// 设置场景到 graphicsView
ui->graphicsView->setScene(m_scene);
m_scene->setSceneRect(0, 0, 300, 300);
m_square = new Square();
// 将getPosition设置回调 接收m_square传入的数据
m_square->setCallbackFunc(getPosition);
m_square->setPos(100, 100);
m_scene->addItem(m_square);
}
MainWindow::~MainWindow()
{
delete ui;
}
/**
* @fn MainWindow::getPosition
* @brief 回调函数接收正方形位置 写入两个lineEdit
* @param QPointF point
* @return void
*/
void MainWindow::getPosition(QPointF point)
{
line1->setText(QString::number(point.x()));
line2->setText(QString::number(point.y()));
}
类内成员函数作为回调函数为什么需要加static?
在类中封装回调函数,回调函数必须要加上static关键字,这样this指针就不能用了,类中静态函数只能访问类的静态成员,不能访问类中的非静态成员,静态函数属于这个类,而不再仅仅属于具体的对象,类的静态函数中没有默认的this指针。但是为什么要这要做呢?
//1.例如一个回调函数被要求声明为以下形式(不属于具体的对象):
void CALLBACK function();
//2.如果这个函数在类ObjClass里面,编译器会为其添加一个this指针,
//用于指向调用该函数的对象。所以编译出来的代码是这种形式:
void CALLBACK ObjClass::function(ObjClass* this)
函数参数列表与被要求声明的形式不一致。就比如说,主窗口中回调函数中有this指针,但是子部件调用的时候可没有,导致参数不匹配。因此我们应该废弃this,而我们也恰巧需要使用主窗口类中的控件,我们可以把对象实例的指针或引用做为参数,和回调函数地址一并传给子部件。
备注:回调函数也可以声明在类外,全局的传类对象指针给回调函数即可使用类内成员,但是回调函数是全局的,所以会影响类的封装性
二 this指针
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。
使用示例
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。根据以下程序来说明this指针?
#include<iostream>
using namespace std;
class Point
{
private:
int x,y;
public:
Point(int a,int b)
{
x=a;
y=b;
}
void MovePoint(int a,int b)
{
x+=a;
y+=b;
}
void print()
{
cout<<"x="<<x<<"y="<<y<<endl;
}
};
int main()
{
Point point1(10,10);
point1.MovePoint(2,2);
point1.print();
return 0;
}
当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);
第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}
即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。
应用参考
在前面曾经提到过: 每个对象中的数据成员都分别占有存储空间,如果对同一个类定义了n个对象,则有n组同样大小的空间以存放n个对象中的数据成员。
但是,不同对象都调用同一个函数代码段。那么,当不同对象的成员函数引用数据成员时,怎么能保证引用的是所指定的对象的数据成员呢?
假如,对于例9.6程序中定义的Box类,定义了3个同类对象a,b,c。如果有a.volume( ) ,应该是引用对象a中的height,width和length,计算出长方体a的体积。
如果有b.volume( ) ,应该是引用对象b中的height,width和length,计算出长方体b的体积。而现今都用同一个函数段,系统怎样使它分别引用a或b中的数据成员呢?在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称为this指针。
它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。例如,当调用成员函数a.volume时,编译系统就把对象a的起始地址赋给this指针,于是在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员。
例如volume函数要计算height*width*length的值,实际上是执行:(this->height)*(this->width)*(this->length)由于当前this指向a,因此相当于执行:(a.height)*(a.width)*( a.length)这就计算出长方体a的体积。同样如果有b.volume( ) ,编译系统就把对象b的起始地址赋给成员函数volume的this指针,显然计算出来的是长方体b的体积。
this指针是隐式使用的,它是作为参数被传递给成员函数的。本来,成员函数volume的定义如下:int Box::volume( ){return (height*width*length);}C++把它处理为int Box::volume(Box *this){return (this->height * this->width * this->length);}即在成员函数的形参表列中增加一个this指针。在调用该成员函数时,实际上是用以下方式调用的:a.volume(&a);将对象a的地址传给形参this指针。然后按this的指向去引用其他成员。
需要说明: 这些都是编译系统自动实现的,编程序者不必人为地在形参中增加this指针,也不必将对象a的地址传给this指针。在需要时也可以显式地使用this指针。
例如在Box类的volume函数中,下面两种表示方法都是合法的、相互等价的。return (height * width * length); //隐含使用this指针return (this->height * this->width * this->length); //显式使用this指针可以用*this表示被调用的成员函数所在的对象,*this就是this所指向的对象,即当前的对象。例如在成员函数a.volume( )的函数体中,如果出现*this,它就是本对象a。
上面的return语句也可写成return((*this).height * (*this).width * (*this).length);注意*this两侧的括号不能省略,不能写成*this.height。所谓“调用对象a的成员函数f”,实际上是在调用成员函数f时使this指针指向对象a,从而访问对象a的成员。在使用“调用对象a的成员函数f”时,应当对它的含义有正确的理解。
原文地址:
在Qt中使用回调函数替代信号槽_qt信号槽和回调函数_lesliefish的博客-CSDN博客