求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
要资料
 
追随技术信仰

随时听讲座
每天看新闻
 
 
Qt 编程指南
第1章 Qt 开发环境
1.1 Qt 介绍
1.2 Qt 下载
1.3 Qt 在 Windows 下安装
1.4 Qt 在 Linux 下安装
1.5 认识开发工具
1.6 常见的名词术语
第2章 从Hello World开始
2.1 Hello World
2.2 Hello Qt
2.3 Hello Designer
2.4 Hello Creator
2.5 Qt程序调试
2.6 Qt帮助文档
第3章 字符串和字符编码
3.1 字符编码方式
3.2 Qt 程序字符编码
3.3 使用 QString
3.4 使用 QByteArray
第4章 信号和槽函数
4.1 元对象系统
4.2 使用原有的信号和槽
4.3 自定义信号和槽
4.4 系统属性
4.5 扩展阅读:ui_*.h代码
4.6 扩展阅读:moc_*.cpp代码
第5章 简单控件的使用
5.1 按钮类的控件
5.2 单行编辑控件
5.3 丰富文本编辑控件
 

 
目录
元对象系统
11 次浏览
1次  

Qt 程序里元对象系统无处不在,元对象系统最主要的一个功能就是实现信号和槽,窗体和控件对象之间的沟通一般都使用信号和槽,这是非常核心的东西,在学习了这些基础知识以后, 就可以根据 Qt 帮助文档自学成才。本节先简要介绍 Qt 元对象系统、信号和槽机制,基本是从 Qt 文档翻译过来的,然后通过按钮弹窗示例学习一下信号和槽的简单使用。

1. 元对象系统简介

在 Qt 助手的索引里面输入“The Meta-Object System”,就可以看到元对象系统的英文文档。现在将其主要的内容描述如下:

Qt 元对象系统实现了对象之间通信机制——信号和槽,并提供了运行时类型信息和动态属性系统。元对象系统是 Qt 类库独有的功能,是 Qt 对标准 C++ 的扩展,并且元对象系统本身也是由纯 C++ 语言写成的,所以学好 C++ 是必须的。

使用元对象系统的前提是需要三件事情:

  • ①直接或间接地以 QObject 为基类,这样才能利用元对象系统的功能,Qt 的窗体和控件最顶层的基类都是 QObject。
  • ②将 Q_OBJECT 放在类声明的私有段落,以启用元对象特性,如动态属性、信号和槽等。之前遇到的例子 Q_OBJECT 都是在类声明里的第一行,没有加 private 字样,因为类声明默认就是私有的。
  • ③元对象编译器(Meta-Object Compiler,moc)为每个 QObject 的子类提供必要的代码以实现元对象特性。

moc 工具读取 C++ 源码,找到一个或多个包含 Q_OBJECT 宏的类声明,然后生成额外的代码文件,如 moc_widget.cpp ,里面包含实现元对象系统的代码。生成的源码文件可以包含在类原有的源文件里,如在 widget.cpp 里包含:

#include "moc_widget.cpp"

这种包含方式看起来比较别扭,Linux 上的开发工具 KDevelop 自动生成的代码是这么用的。

第二种方式是编译链接时揉到一起,QtCreator 生成的代码就是通过编译链接时,把 moc_widget.o 与其他目标文件链接到一起,这种方式不用改源代码,相对而言比较顺眼。

除了提供信号和槽机制用于对象之间的通信(这是主要任务),元对象系统还提供了更多的特性:

  • QObject::metaObject() 函数返回当前类对象关联的元对象(meta-object)。
  • QMetaObject::className() 函数返回当前对象的类名称字符串,而不需要 C++ 编译器原生的运行时类型信息(run-time type information,RTTI)支持。
  • QObject::inherits() 函数判断当前对象是否从某个基类派生,判断某个基类是否位于从 QObject 到对象当前类的继承树上。
  • QObject::tr() 和 QObject::trUtf8() 函数负责翻译国际化字符串,因为 Qt5 规定源文件字符编码是 UTF-8,所以这两个函数现在功能是一样的。
  • QObject::setProperty() 和 QObject::property() 函数用于动态设置和获取属性,都通过属性名称字符串来 操作。
  • QMetaObject::newInstance() 构建一个当前类的新实例对象。

元对象系统还提供了 qobject_cast() 函数,可以对基于 QObject 的类对象进行转换,qobject_cast() 函数功能类似标准 C++ 的 dynamic_cast()。当然 qobject_cast() 的优势在于不需要编译器支持 RTTI,而且跨动态链接库之间的转换也是可行的。简单地说,原本是派生类的对象指针,就可以转为基类对象指针来用(转换得到可用值),其他情况都会得到 NULL 指针。比如:

MyWidget 是 QWidget 的派生类,并且类声明带有 Q_OBJECT 宏,新建一个对象:

QWidget *widget = qobject_cast(obj);

虽然 obj 是一个 QObject *,但它本质是一个 MyWidget 对象指针,可以转成基类指针:

QWidget *widget = qobject_cast(obj);

但是如果将 MyWidget 对象指针转成其他无关的类对象指针,就会失败:

QLabel *label = qobject_cast(obj);

label的数值就是 NULL。

关于元对象系统介绍就是这些,实现元对象系统的内幕代码在最后一节讲解,本章主要是学会怎么用信号和槽机制。

2. 信号和槽

下面举叫外卖的例子来说明什么是信号和槽,比如:

  • ①比如到午饭时间了,某宅男饿了——由不饿到饿,是一个状态的变化,肚子饿了就相当于是一个信号。谁都会饿的,每个人都可以发这类信号。注意信号只是一个空想,没 东西吃是填不饱肚子的。饿了怎么办,准备叫外卖。
  • ②街上餐馆很多,都希望多做点生意,送外卖也是常事——做好饭送外卖就是槽函数。这个送外卖功能,餐馆一般都是有的,但谁来买送给谁,这个暂时定不了。如果餐馆饭 做得好,但没人吃那也是不行的。
  • ③食客饿了(信号),餐馆有送饭服务(槽函数),二者怎么沟通呢?通常我们都是打电话,Qt 把这个过程叫信号和槽的关联(connect)。虽然我们每次叫外卖都要拨一长串号码,但 Qt 关联比我们打电话方便,它只需要将信号关联具体某家餐馆外卖服务一次,以后都是自动拨号的。Qt 对象的信号和槽关联好之后,源头只需要发个信号,叫一声“我饿了”,connect 函数会自动拨号,餐馆立刻就送餐过来。

信号和槽函数在进行关联的时候,二者的参数需要一致,不能我叫西红柿鸡蛋的盖浇饭,餐馆给送兰州拉面,那是不行的。多个对象的信号和槽函数在参数匹配 的情况下,它 们之间的关联可以是一对一,一对多(某吃货可以同时叫多个餐馆的饭),多对一(多个人可以同时订某家餐馆的饭),所以关联是比较自由的。本节只看简单的一对一关 联,4.2 节再看复杂的。

3. 按钮弹窗示例

本小节示例效果是这样的:按一下窗体里的按钮,发个信号“我饿了”,然后自动弹出个消息框,显示“叮咚!外卖已送达”。下面我们打开 QtCreator,新建一个 Qt Widgets Application 项目,按步骤填:

①项目名称设置为 hungry,创建路径如 D:\QtProjects\ch04,点击“下一步”;

②Kit Selection 里面选中 Select all kits,点击“下一步”;

③基类选择 QWidget,然后其他名字就用自动填的,点击“下一步”;

④项目管理界面不修改,直接点“完成”。

然后看到 QtCreator 编辑模式里的 hungry 项目,我们打开 widget.ui,在 QtCreator 设计模式,拖一个“Push Button”到窗体中间位置,并修改该 pushButton 对象的 text 属性为:我饿了。看到类似如下图所示:

ui design

编辑好后按 Ctrl+S 保存,然后关闭 widget.ui 。窗体里的控件对象 pushButton 对象自己带有 clicked 信号,当我们用鼠标点击按钮时,clicked 信号自动触发,所以不需要我们定义新的信号。使用按钮自己的信号就够用了,现在信号就已经有了,我们完成了叫外卖的第 一步。

回到代码编辑模式,打开头文件 widget.h ,向其中添加槽函数声明,下面把该头文件内容完整贴出来:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent = 0);
~Widget();

//添加这一段代码
public slots: //槽函数声明标志
void FoodIsComing(); //槽函数

private:
Ui::Widget *ui;
};

#endif // WIDGET_H

Qt 的关键字 slots 就是槽函数声明段落的标志,槽函数声明段落可以是 private、protected 或者 public 类型的,这些访问权限和继承权限与普通成员函数是一样的,上面示范的是公有槽函数。除了声明段落标志不一样,槽函数与普通成员函数其他情况都是一样的,也可以作为普通成员 函数来调用,当然也必须有函数定义的实体代码,头文件里仅仅是声明。我们打开 widget.cpp 文件,添加头文件包含 和 槽函数定义代码:

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);

}

Widget::~Widget()
{
delete ui;
}

//槽函数定义代码,与普通成员函数类似
void Widget::FoodIsComing()
{
QMessageBox::information(this, tr("送餐"), tr("叮咚!外卖已送达"));
}

在槽函数定义代码部分,FoodIsComing 函数与普通成员函数没区别,只有在头文件才能看到它是不是在槽函数声明段落。新包含的头文件 <QMessageBox> 是声明了用于弹消息框的类。QMessageBox 类可以按照常规定义对象方式使用,如:

QMessageBox msgBox;
msgBox.setWindowTitle("Take out")
msgBox.setText("Food is coming.");
msgBox.exec();

setWindowTitle 函数是设置消息框标题,setText 是设置要显示的消息。exec 函数是指模态显示,消息框会弹出并显示在最上层,如果不关闭该消息框,就不会回到正常界面。

常规方式需要上面四句代码,而这类消息框内容格式相对单调,是可以做成预定义的消息框供程序员直接调用的。QMessageBox 类提供了静态公有函数,里面预定义好了现成的消息框,只需把参数传给它,就会自动弹窗。我们 FoodIsComing 函数里使用的就是静态函数 QMessageBox::information ,它的声明如下:

StandardButton QMessageBox::​information(QWidget * parent, const QString & title, const QString & text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton)

StandardButton 是 Qt 预定义的按钮类型枚举,比如 QMessageBox::Ok、QMessageBox::Cancel 等等,可以为消息框添加这些按钮,并且返回按钮枚举值。我们这里只用了头三个参数:父窗口指针、消息框标题、消息框内容。后面的 buttons 参数可以为消息框添加额外按钮,defaultButton 是指默认按钮,我们暂时用不着,先不管这些。返回值就是按钮类型的枚举值,可以获知用户是点击哪个按钮使消息框关闭的。

FoodIsComing 槽函数的声明和定义都按照上面写好之后,我们就完成了叫外卖的第二步。

下面第三步是将 pushButton 的信号 clicked (即“我饿了”)与 主窗口的槽函数 FoodIsComing 关联起来,实现自动拨号叫外卖。Qt 通过 QObject::​connect 函数完成信号和槽函数的关联,因为主窗口最顶层的基类是 QObject,所以我们下面代码不需要加 QObject:: 前缀。我们向 widget.cpp 文件构造函数里新增一句关联函数代码,完整的 widget.cpp 代码如下:

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);

//添加关联代码,必须放在 setupUi 函数之后
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(FoodIsComing()));
}

Widget::~Widget()
{
delete ui;
}

//槽函数定义代码,与普通成员函数类似
void Widget::FoodIsComing()
{
QMessageBox::information(this, tr("送餐"), tr("叮咚!外卖已送达"));
}

connect 函数第一个参数是发信号的源头对象指针,按钮对象的指针就是 ui->pushButton,ui 是为窗体构建界面的辅助类对象指针,我们在窗体设计界面拖的控件对象都存在这个 ui 指向的对象里。ui->pushButton 就指向我们之前拖的按钮对象。因为通过设计模式拖的控件全部是以指针类型访问的,所以以后说到窗体里的控件,一般都是说它的指针名字。

第二个参数用 SIGNAL 宏包裹,里面是按钮对象的信号 clicked() ,信号的声明和成员函数类似,但必须放在 signals 声明段落。上面没看到 signals 声明段落是因为 QPushButton 类的对象自带这个信号,不需要我们来定义。

第三个参数是接收对象的指针,也就是服务提供方,是槽函数所在对象的指针,我们上面用的 this 指针就是主窗体自己。

第四个参数是接收对象里的槽函数,并用 SLOT 宏封装起来。

connect 函数意义是非常清晰的,将源头和源头的信号,关联到接收端和接收端的槽函数。注意源头和接收端必须是存在的实体对象指针,不能是野指针。connect 函数必须放在 ui->setupUi 之后,否则控件指针是未定义的野指针,那种关联必然失败,会导致程序崩溃。

编写 connect 函数代码的时候,对于第二个参数,我们敲好 “SIGNAL(” 字样的时候,编辑器会自动提示源头对象有哪些信号,这很方便:

ui design

也可以通过 Qt 帮助文档查询 QPushButton 的资料。

对于第四个槽函数宏,也是类似的提示效果:

ui design

FoodIsComing 槽函数就是我们自己写的,也在自动提示列表之内,其他的槽函数可以查 QWidget 类的帮助文档。编写 connect 函数的时候,需要注意括号嵌套的层数,因为括号比较多,如果末尾少了右括号,要注意补。

写好 connect 函数代码之后,叫外卖的第三步就完成了。我们点击 QtCreator 左下角运行按钮,查看运行效果,并点击窗体的按钮测试一下:

ui design

点击一下“我饿了”按钮,消息框自动就弹出来。整个流程就是按钮发一个 clicked 信号,connect 将该信号关联了主窗体的FoodIsComing 槽函数,这个槽函数实现弹窗。信号和槽机制往简单了说就是上面三板斧。信号本身是个空想,它自己不干活的,真正干活的是槽函数,槽函数完整功能并提供服务,信号和 槽通过 connect 关联,只需要关联一次,以后都会自动工作。

4. 按钮弹窗自动关联示例

这里的自动关联是指不需要手动编写 connect 函数,通过自动命名槽函数的方式来编写代码。自动关联的要求是槽函数根据源头的对象名(指针)和其信号名称来命名,元对象系统可以实现剩下的自动 connect 功能。这对窗体设计是一种便利,如果我们窗体里拖了 10 个按钮,手动编写 connect 函数的话,就需要编 10 个 connect 函数调用,比较麻烦的。通过自动关联方式,这些 connect 函数代码全可以省了。我们只需要关注如何实现槽函数的功能即可。下面我们新建一个自动关联的项目,我们重新打开 QtCreator,点击菜单“文件”--> “新建文件或项目”,建立一个 Qt Widgets Application,按步骤:

①项目名称填写 autoconn,创建路径 D:\QtProjects\ch04,点击“下一步”;

②Kit Selection 界面选中 Select all kits,点击“下一步”;

③类信息界面,基类选择 QWidget,其他的用自动命名的,点击“下一步”;

④项目管理界面不修改,点击“完成”。

在编辑模式,项目视图里打开界面文件 widget.ui ,进入图形界面设计模式,类似地拖一个按钮放到窗体中间,这次我们修改按钮的两个属性,将按钮对象的 objectName 设置为 hungryButton,将 text 属性设置为:我饿了。如图所示:

ui design

objectName 就是对象名称属性,设置为 hungryButton 之后,实际代码里对应的就是 ui->hungryButton 按钮指针。

编辑好属性之后,我们开始使用自动关联槽函数的方法,右击 hungryButton 按钮,点击右键菜单里的“转到槽 ...”,

ui design

会出现根据信号来定义槽函数的界面:

ui design

信号列表里面,第一列是信号的名称,第二列是该对象所属的类或基类名称,QPushButton 不仅有自己的信号,还拥有它的基类 QAbstractButton、再上层基类 QWidget、顶层基类 QObject 等定义的信号。(注意信号可以有各种参数,但不能有返回值,也就是说必 须返回 void 类型。)

选择第一个 clicked() 信号,然后点击 OK 。

这样就自动添加了槽函数的声明和它的定义代码底板,QtCreator 会自动跳转到 widget.cpp 的 void Widget::on_hungryButton_clicked() 函数定义处:

ui design

QtCreator 会自动添加槽函数,并且跳转到槽函数定义位置,方便程序员编辑。由于是自动关联的,这个槽函数名称也是自动生成的,这时候不要修改这个函数的名 称,也不要改按钮的对象名称,这样才能保证自动关联有效。

然后就可以在该槽函数里面添加我们需要的代码,修改之后,widget.cpp 完整内容如下:

#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}

Widget::~Widget()
{
delete ui;
}

void Widget::on_hungryButton_clicked()
{
QMessageBox::information(this, tr("送餐"), tr("叮咚!外卖已送达"));
}

Widget 构造函数里没有 connect 函数调用,因为不需要了,就是这么简单。

widget.h 里面有 on_hungryButton_clicked 函数的声明,也是 QtCreator 自动添加的,不需要修改,这里只是把 widget.h 代码贴出来给大家看看:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
Q_OBJECT

public:
explicit Widget(QWidget *parent = 0);
~Widget();

private slots:
void on_hungryButton_clicked();

private:
Ui::Widget *ui;
};

#endif // WIDGET_H

QtCreator 自动添加的槽函数声明是 private slots,这个私有槽函数也是可以正常运行的,主要是访问权限和继承权限与 public 类型不一样,其他的功能是相同的。

有 QtCreator 自动生成的槽函数,有了自动关联,我们实际只遍了两行代码,就是开头的 #include <QMessageBox>和槽函数里的一句弹消息框。自动关联的方式大大简化了我们需要做的工作。现在我们只需要点击 QtCreator 左下角的运行按钮就够了:

ui design

这里有一个疑问,没有手动关联的 connect 函数,信号和槽它们怎么工作的呢?

诀窍有两条,第一个是槽函数命名非常严格,必须按照如下规则来写:

void on_object name_signal name_signal parameters

必须以 on_ 打头,接下来是对象名,对应例子的 hungryButton,再接一个下划线,最后是信号名和信号可能的参数。

上面示例的槽函数自动命名就是:

void on_hungryButton_clicked();

按照规则命名是第一步。

第二步是由 uic 和 moc 等工具自动完成的,在 D:\QtProjects\ch04\build-autoconn-Desktop_Qt_5_4_0_MinGW_32bit-Debug 文件夹里可以找到 ui_widget.h,最关键的就是 setupUi 函数末尾一句:

void setupUi(QWidget *Widget)
{
if (Widget->objectName().isEmpty())
Widget->setObjectName(QStringLiteral("Widget"));
Widget->resize(400, 300);
hungryButton = new QPushButton(Widget);
hungryButton->setObjectName(QStringLiteral("hungryButton"));
hungryButton->setGeometry(QRect(160, 100, 75, 23));

retranslateUi(Widget);

QMetaObject::connectSlotsByName(Widget);
} // setupUi

connectSlotsByName 就是完成自动关联的函数,这是元对象系统包含的功能,根据对象名、信号名与 on_<object name>_<signal name>(<signal parameters>) 槽函数进行自动匹配关联,可以给程序员提供便利,省了许多 connect 函数调用的代码。后面 4.5 节还会再详细讲这些代码,本节学会用自动关联大法就够了。

5. 关联函数的语法格式

虽然 Qt 有比较好用的自动关联大法,但自动关联不是万能的,尤其是涉及到多个窗体的时候,比如 A 窗体私有按钮控件与 B 窗体私有消息框函数,这个因为权限限制,不是想自动关联就可以自动关联的。自动关联一般用于一个窗体之内的控件关联,其他很多情况都是需要手动编写 connect 函数的,所以学习 connect 函数的语法句式是必须的。

上面展示的 connect 关联是传统语法句式,如本节第一个例子:

connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(FoodIsComing()));

头两个参数是源头对象和信号,后两个参数接收对象和槽函数,这个其实是 Qt4 和之前版本一直存在的句式,在 Qt5 中也是好使的,这种句式可读性很好,信号和槽的标识也很清晰。这种关联方式其实是旧式语法,它的函数声明为:

QMetaObject::Connection QObject::​connect(const QObject * sender, const char * signal, const QObject * receiver, const char * method, Qt::ConnectionType type = Qt::AutoConnection)

connect 函数返回类型是 QMetaObject::Connection ,返回值就是代表信号和槽连接关系的对象,可以用于运行时判断关联是否正确,或者用于解除关联。每次调用 connect 函数都会生成新的连接对象,注意不要对同样的信号和槽 重复 调用 connect 函数,那样会生成多个不同的连接对象,导致一次信号会触发多次同样的槽函数。
注意到 connect 函数参数里的 signal 和 method(槽函数)都是 char * 字符串类型,所以旧式语法的 connect 函数是根据信号和槽函数的字符串名称来关联的,不具备编译时类型检查,大家都是字符串,参数类型在编译时都不知道。关联出错只有在运行时才会体现。

最后的关联类型参数一般我们都使用默认值 Qt::AutoConnection,这在多线程编程的时候才会有讲究。对于单线程的,关联一般用直连类型(Qt::DirectConnection),信号一触发, 对应槽函数立即就被调用执行;对于多线程程序,跨线程的关联一般用入队关联(Qt::QueuedConnection),信号触发后,跨线程的槽函数被加入事件 处理队列里面执行,避免干扰接收线程里的执行流程。Qt::AutoConnection 会自动根据源头对象和接收对象所属的线程来处理,默认都用这种类型的关联,对于多线程程序这种关联也是安全的。

Qt5 为了能够尽早检查关联类型和参数的匹配情况,引入了新的函数指针关联语法,这样在程序编译时就能发现关联正确与否。新式语法格式如下:

QMetaObject::Connection QObject::​connect(const QObject * sender, PointerToMemberFunction signal, const QObject * receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)

与旧语法句式区别就在于 signal 和 method (槽函数),新句式用的是 PointerToMemberFunction ,这个类型名称是不存在的,只是在文档里面显示,方便程序员看的,实际使用的是模板函数。具体模板函数定义比较复杂,比较关键的就是两个函数参数类型需要一致,或者信号声 明时的参数更多更广。因为信号本身是不干活的,它多点参数无所谓,但必须保证槽函数运行时需要的参数是一定有的。

新的语法句式应用到第一个例子就是如下面这样:

connect(ui->pushButton, &QPushButton::clicked, this, &Widget::FoodIsComing);

这里第二个位置是一个函数指针形式,第四个位置也是一个函数指针形式,信号里声明的参数和槽函数声明是一致的,所以关联是匹配的。

使用 connect 函数时,如果接收端是 this 指针代表的对象,那么语法可以简化,省掉 this 指针的参数:

connect(ui->pushButton, SIGNAL(clicked()), SLOT(FoodIsComing())); //旧式语法

新式语法也可以省掉 this 指针参数:

connect(ui->pushButton, &QPushButton::clicked, &Widget::FoodIsComing); //新式语法

最后提醒一下,不管使用旧句式,还是新句式,关联的源头和接收端一定是实际存在的对象,ui->pushButton 这个按钮对象成功创建之后,上面的关联才能正常执行。虽然新句式可以用 &QPushButton::clicked 这个函数指针形式,但注意这是把源头对象关联到接收端对象,而不是把类关联到类!如果没有定义对象实体,关联函数就没意义。

tip 练习

① 将第一个示例中的 connect 函数修改为:

connect(ui->pushButton, SIGNAL(clicked456()), this, SLOT(FoodIsComing()));

编译运行例子看看效果,运行时注意看输出面板里打印的信息。

② 将第一个示例中的旧式关联替换成新式关联:

connect(ui->pushButton, &QPushButton::clicked, this, &Widget::FoodIsComing);

编译运行例子查看效果。

③ 将新式语法句子里的 clicked 修改成 clicked456,看看还能否编译成功。


您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码: 验证码,看不清楚?请点击刷新验证码 必填



11 次浏览
1次