1.main函数介绍
//main函数为程序的入口,argc为命令行变量的数量,argv为命令行变量的数组
int main(int argc, char* argv[])
{
//定义QApplication对象,一个Qt应用程序有且仅有一个应用程序对象.
QApplication a;
//应用程序对象进入消息循环机制
return a.exec();
}
2.QT中常用快捷键
- 运行:ctrl + r;
- 编译:ctrl + b;
- 查找:ctrl + f;
- 帮助文档:第一种方法光标移到类名再按下F1;第二种将Qt安装包下的assistant.exe路径配置到系统环境变量中。
- F4进行同名之间的文件快速切换。比如说xxx.h到xxx.cpp就可以按下F4.
- 字体缩放:ctrl + 鼠标滚轮。
- 注释:ctrl + /
- 自动补齐格式:CTRL+i
- 将某行代码上移或者下移:CTRL +shift+PgUp/Ctrl+shift+PgDn
3.Qt中向控制台输出信息
#include <QDebug>
qDebug() << "Qt向控制台输出信息"; //qDebug会自动换行
4.Qt中的信号和槽
connect函数:
- 参数一:信号发送者
- 参数二:发送的信号(信号地址)
- 参数三:信号接收者
- 参数四:处理信号的槽函数(槽函数地址)
对于参数二和参数四:如果出现自定义信号和自定义槽函数重载时,传递信号的地址或者槽函数的地址作为实参是摸棱两可的。所以需要传递函数指针作为实参。
例如:connect(myButton,&QPushButton::clicked,this,&MyWidget::close)
Qt4版本以前的信号和槽的连接方式:
connect(myButton,SIGNAL(clicked()),this,SLOT(close()))
disconnect函数: 断开信号的连接
自定义类中自定义信号和槽
class Example:public QObject
{
//QT中的自定义信号必须声明在signals下面
//1.信号没有返回值,返回值类型为void
//2.信号可以有参数,可以重载
//3.信号只需要声明而不用实现。
signals:
void mySignal();
//QT中的自定义槽函数声明在public slots下面,但是在高版本中可以声明在全局区或者Public访问权限符下
//1.槽函数需要声明,必须定义
//2.槽函可以有参数,可以重载
//3.槽函数返回值类型为void
public slots:
void myslot();
}
emit:触发自定义信号的关键字。语法:
emit Example类对象->自定义信号名();
信号和槽的扩展:
- 信号是可以连接信号的
- 信号和槽可以通过disconnect函数断开的
- 一个信号可以连接多个槽函数
- 多个信号可以连接同一个槽函数
- 信号和槽函数的个数必须一一对应。(信号的个数可以大于槽函数的个数反之不可以)
Lambda表达式:
C++11的Lambda表达式用于定义并创建匿名的函数对象。Lambda表达式的基本构成:
[capture](parameters)mutable->return-type
{
statement
}
//[函数对象参数](操作符重载函数参数)mutable->返回值
//{
函数体
//}
函数对象参数有以下几种形式:
1.空:没有使用任何函数对象参数
2.=:函数体内可以使用Lambda所在作用范围内所有可见的局部变量。值传递方式
3.&:引用传递方式
4.this:函数体内可以使用Lambda所在类中的成员变量
5.a:将a按照值传递。按照值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况是const的.要修改传递进来的a的拷贝(不能修改值a本身),可以添加mutable修饰符。
6.&a:将a按照引用传递
使用示例:
int number = 100;
//因为mutable修饰符,在Lambda表达式函数体内可以修改number这个值的拷贝,但是不影响number值本身
connect(button,&QPushButton::clicked,this,[=]()mutable{number += 100;qDebug() << number;}); //200
qDebug() << number; //100
int ret = []()->int{return 1000;}();
qDebug() << ret; //1000
//利用Lambda表达式实现点击按钮关闭窗口
QPushButton * button = new QPushButton("关闭窗口",this);
connect(button,&QPushButton::clicked,this,[=](){
this->close();
});
5.菜单栏
如下图所示是一个window记事本中的一个菜单栏
如下图所示是菜单项
则代码片段示例如下:
在mainWindow.cpp中
#include "mainwindow.h"
#include <QMenuBar>
#include <QAction>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
this->setWindowTitle("记事本");
this->resize(600,600);
//创建菜单栏
QMenuBar* bar = new QMenuBar(this);
//将菜单栏添加到主窗口中
this->setMenuBar(bar);
//给菜单栏添加菜单
QMenu* menu1 = bar->addMenu("文件(F)");
QMenu* menu2 = bar->addMenu("编辑(E)");
QMenu* menu3 = bar->addMenu("格式(O)");
QMenu* menu4 = bar->addMenu("查看(V)");
QMenu* menu5 = bar->addMenu("帮助(H)");
//给菜单添加菜单项
menu1->addAction("新建(N) CTRL+N");
//添加分割副
menu1->addSeparator();
menu1->addAction("打开(O) CTRL+O");
//注:上述代码还未添加热键
}
MainWindow::~MainWindow()
{
}
运行结果如下:
6.工具栏
QToolBar * tool = new QToolBar(this);
//将工具栏添加进主窗口中
this->addToolBar(tool);
//设置工具栏停靠位置在左边
tool->setAllowedAreas(Qt::LeftToolBar);
//设置工具栏不可浮动
tool->setFloatable(false);
//固定住工具栏
tool->setMoveable(false);
//给工具栏添加菜单项
tool->addAction(action1);
//添加分隔符
tool->addSeparator();
//给工具栏添加菜单项
tool->addAction(action2);
7.状态栏
//一个主窗口中状态栏只能有一个
QStatusBar * bar = new QStusBar();
//将状态栏添加进主窗口中
this->setStatusBar(bar);
//状态栏中添加标签
QLabel* label = new QLabel("提示信息",this);
bar->addWidget(label);
8.铆接部件(浮动窗口可以有多个)
//创建铆接部件对象
QDockWidget* widget = new QDockWidge(this);
//将铆接部件添加进主窗口中
addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget);
//设置停靠范围(浮动窗口的停靠位置相对于中心部件而言的)
setallowedAreas(Qt::DockWidgetAreas areas);
9.核心部件(中心部件只能有一个)
QTextEdit* edit = new QTextEdit(this);
//将核心部件添加进主窗口中
setCentralWidget(QWidget *widget);
10.给Qt项目添加资源文件
- 将需要添加的资源放置项目下.
- 选中项目右击add new,然后添加Qt的Qt resource file.
- 右击新建的后缀名为.qrc的资源文件,然后open in editor。
- 先设置前缀,然后将你需要添加的资源文件(比如说图标)添加进来。
- 资源文件的使用:”:+你设置的前缀+文件名“
举例:给主窗口标题设置图标
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
//添加菜单栏
QMenuBar* mbar = new QMenuBar();
this->setMenuBar(mbar);
//添加菜单
QMenu* menu1 = mbar->addMenu("文件(F)");
QMenu* menu2 = mbar->addMenu("编辑(E)");
QMenu* menu3 = mbar->addMenu("格式(O)");
QMenu* menu4 = mbar->addMenu("查看(V)");
QMenu* menu5 = mbar->addMenu("帮助(H)");
//添加菜单项
menu1->addAction("新建(N) CTRL+N");
menu1->addAction("打开(O)... CTRL+O");
menu1->addAction("保存(S)... CTRL+S");
//添加文本编辑区(核心部件)
QTextEdit* edit = new QTextEdit();
this->setCentralWidget(edit);
//调整主窗口大小
this->resize(600,600);
//设置图标
this->setWindowIcon(QIcon(":/img/timg.jpg"));
//设置主窗口标题
this->setWindowTitle("无标题 -记事本");
}
运行如下:
当然了,你可以通过ui设置图标。
10.对话框
1.对话框的分类:
- 模态对话框:弹出一个对话框如果不对当前对话框进行相应的操作,是不能进行其他的操作的。
- 非模态对话框:弹出一个对话框如果不对当前对话框进行相应的操作,也可以进行其他的操作的。
示例创建一个模态对话框和一个非模态对话框:
// //当点击打开这个菜单项打开时,创建一个模态对话框
// connect(action2,&QAction::triggered,this,[=]()
// {
// //创建一个模态对话框
// QDialog dialog(this);
// dialog.resize(100,100);
//exec()函数有阻塞功能
// dialog.exec();
//当关闭模态对话框后才会打印Hello
// qDebug() << "Hello";
// });
connect(action2,&QAction::triggered,this,[=]
{
//创建非模态对话框时我们不使用栈上的对象,而使用堆上的对象
QDialog* dialog = new QDialog(this);
dialog->resize(100,100);
dialog->show();
//设置属性,是为了当关闭非模态对话框时立即释放堆上的资源。
dialog->setAttribute(Qt::WA_DeleteOnClose);
qDebug() << "Hello";
});
当然了通过ui文件也是可以创建模态对话框和非模态对话框的。
2.标准对话框。
Qt内置了一些标准对话框。
标准对话框QMessageBox(消息对话框)的使用:
//当点击“打开“这个菜单项弹出消息对话框
connect(action2,&QAction::triggered,this,[=]()
{
//错误对话框X
//QMessageBox::critical(this,"错误","error");
//信息对话框i
//QMessageBox::information(this,"OK","退出");
QMessageBox::information(this,"记事本","是否将更改保存到xxx.txt",QMessageBox::Save,
QMessageBox::No,
QMessageBox::Cancel);
//问题对话框?
//警告对话框!
});
3.颜色对话框QColorDialog的使用:
//创建颜色对话框
connect(action2,&QAction::triggered,this,[=]{
QColor color = QColorDialog::getColor(QColor(255,0,0));
//获取颜色对话框中的三种颜色值
qDebug() << color.red() << color.green() << color.blue();
});
4.标准文件对话框的使用
//创建文件对话框
connect(action2,&QAction::triggered,this,[=]
{
//参数四为过滤路径,返回值为选择的文件的路径
QString fileName = QFileDialog::getOpenFileName(this,
tr("打开"), "C:\\Users\\lenovo\\Desktop", tr("Image Files (*.png *.jpg *.bmp)"));
qDebug() << fileName;
});
5.选择字体对话框(QFontDialog)
//字体对话框
bool ok;
QFont font = QFontDialog::getFont(&ok, QFont("Times New Roman", 12), this,"字体对话框");
// font is set to the font the user selected
if (ok)
{
//获取用户选择的字体信息
qDebug() << "字体:" << font.family() << "字号:" << font.pointSize() <<
"是否加粗:" << font.bold() << "是否倾斜:" << font.italic();
// the user canceled the dialog; font is set to the initial
// value, in this case Times, 12.
}
else
{
qDebug() << font.family() << font.pointSize();
}
6.输入对话框(QInputDialog) 等等...
11.ui模拟实现一个登录窗口
12.常用控件按钮
- ToolButton一般用于显示图片用,PushButton一般不用于显示图片。
- GroupBox这个容器用于分组,一般和单选按钮RadioButton或者CheckBox复选按钮结合使用
13常用的item Widgets使用
1.List Widget控件的使用
//使用listWidget写一首小诗
//方法一
QListWidgetItem* item1 = new QListWidgetItem("静月思");
item1->setTextAlignment(Qt::AlignHCenter); //设置文本对齐方式为居中
ui->listWidget->addItem(item1);
QListWidgetItem* item2 = new QListWidgetItem("床前明月光,疑是地上霜");
item2->setTextAlignment(Qt::AlignHCenter); //设置文本对其
ui->listWidget->addItem(item2);
//方法二
//或者呢通过QStringList类,但是这种方式无法设置文本对齐方式
QStringList list;
list << "举杯望明月,低头思故乡";
ui->listWidget->addItems(list);
2.Tree Widget控件的使用
- 先设置头setHeaderLabels方法
- 添加顶层项目addTopLevelItem方法
- 添加子项目addChild方法
ui->setupUi(this);
//县设置头
ui->treeWidget->setHeaderLabels(QStringList() << "家庭成员" << "成员介绍");
//添加顶层项目
QTreeWidgetItem* item1 = new QTreeWidgetItem(QStringList() << "伯伯");
ui->treeWidget->addTopLevelItem(item1);
QTreeWidgetItem* item2 = new QTreeWidgetItem(QStringList() << "自己");
ui->treeWidget->addTopLevelItem(item2);
//添加子项目
QTreeWidgetItem* chItem1 = new QTreeWidgetItem(QStringList() << "儿子" << "崇明");
item1->addChild(chItem1);
QTreeWidgetItem* chItem2 = new QTreeWidgetItem(QStringList() << "女儿" << "傻子");
item1->addChild(chItem2);
QTreeWidgetItem* chItem3 = new QTreeWidgetItem(QStringList() << "儿子" << "崇明");
item2->addChild(chItem3);
QTreeWidgetItem* chItem4 = new QTreeWidgetItem(QStringList() << "女儿" << "傻子");
item2->addChild(chItem4);
运行结果如下:
3.Table Widget控件的使用
- 先设置表的列数通过setColumnCount方法
- 添加水平头通过setHorizontalHeaderLabel方法
- 设置行数setRowCount方法
- 设置正文(行数,列数,具体内容)
//TableWidget控件创建一个表格
//1.设置表的列数
ui->tableWidget->setColumnCount(3);
//2.设置水平表头
ui->tableWidget->setHorizontalHeaderLabels(QStringList() << "姓名" << "性别" << "年龄");
//3.设置行数
ui->tableWidget->setRowCount(3);
//4.设置正文
QStringList nameList;
nameList << "张三" << "李四" << "王五";
QList<QString>sexList;
sexList << "男" << "男" << "女";
for(int i = 0; i < 3; i++)
{
int column = 0;
ui->tableWidget->setItem(i,column++,new QTableWidgetItem(nameList[i]));
ui->tableWidget->setItem(i,column++,new QTableWidgetItem(sexList.at(i)));
//int转成QString
ui->tableWidget->setItem(i,column,new QTableWidgetItem(QString::number(i + 18)));
}
4.其他常用控件
- Scroll Area:滚动条的区域
2. Tool Box:分页
3. tab Widget:
4. Stacked Widget:
5. combo box:下拉框
6. line edit:单行编辑框,输入账号密码信息可以使用这个控件
注意:
1. 对于设置密码的显示模式,可以使用QLineEdit的setEchoMode
方法。
2. 设置QLineEdit单行编辑框的输入提示,可以使用setCompleter
方法。
比如说:
QStringList list;
list << "haha" << "hello" << "How are you";
QCompleter* completer = new QCompleter(list,this);
//设置匹配时不区分大小写
completer->setCaseSensitivity(Qt::CaseInsensitive);
//设置字符串部分匹配可以使用QCompleter类中的setFilterMode方法
completer->setFilterMode(Qt::MatchContains);
QLineEdit* line = new QLineEdit(this);
line->setCompleter(completer);
实现效果如图:
- span box
- Label:可以显示图片
//利用Lable显示图片
ui->label->setPixmap(QPixmap(":/img/t01de95bed9f4ebdc07.webp"));
运行结果如下:
利用Label还可以显示GIF动图
//Label显示GIF动图
QMovie* movie = new QMovie(":/image/1306351.gif");
ui->label->setMovie(movie);
//播放GIF
movie->start();
//让GIF动画自适应Label大小
ui->label->setScaledContents(true);
利用Label还可以显示互联网上的有效链接
//设置HTML语句
ui->label->setText("<h1><a href = \"https://www.baidu.com\">百度</a></h1>");
//setOpenEternalLinks()设置用户点击链接后自动打开。
ui->label->setOpenExternalLinks(true);
//让链接自适应Label大小
ui->label->setScaledContents(true);
5.自定义控件的封装:涉及到Qt设计师界面类的自定义创建和类的提升(使用自定义的控件)等
14.常用事件
QEvent类是所有事件类的基类
- 鼠标进入事件:enterEvent
- 鼠标离开事件:leaveEvent
- 鼠标按下,鼠标移动,鼠标释放:
mousePressEvent
mouseMoveEvent
mouseReleaseEvent
如果需要捕获鼠标按下或者移动或者释放的事件,需要重写event handler(就是对一些事件处理函数即虚函数重写)。比如说:用于鼠标按下的事件处理函数。
void MyLabel::mousePressEvent(QMouseEvent *event)
{
int x = event->x();
int y = event->y();
//<center>和<h1>均为HTML中的标签,分别表示文字居中和一级标题
QString text = QString("<center><h1>mouse press:(%1,%2)</h1></center>") .arg(x).arg(y);
this->setText(text);
}
获取鼠标释放时的坐标:
void MyLabel:: mouseReleaseEvent(QMouseEvent *event)
{
QString text = QString("<center><h1>mouse release:(%1,%2)</h1></center>") .arg(event->x()).arg(event->y());
this->setText(text);
}
4. 事件接受与忽略。accept函数与ignore函数
事件的接受与忽略一般用于closeEvent事件处理函数中。closeEvent事件处理函数的默认行为是接受形参event这个事件并且关闭窗口。应用示例如下:
void Widget::closeEvent(QCloseEvent* ev)
{
int ret = QMessageBox::question(this,"question","are you assure quit?");
if(ret == QMessageBox::Yes)
{
ev->accept();
}
else
{
ev->ignore();
}
}
运行如下:
- 定时器事件:
1.设置定时器方法1:
starttimer启动计时器,TimeEvent中的timerId()用于区分计时器。
2.设置定时器方法2:
//需要在ui文件中先拖动一个label.
ui->setupUi(this);
//设置计时器
QTimer* time = new QTimer(this);
//1000毫秒后启动计时器
time->start(1000);
connect(time,&QTimer::timeout,this,[=](){
static int num = 0;
ui->label->setText(QString::number(num++));
});
运行结果如下:
3.利用QTimer实现一个定时器:
//前提在ui文件中拖动一个label
ui->setupUi(this);
//设置计时器
QTimer* time = new QTimer(this);
//启动计时器
time->start(1000);
connect(time,&QTimer::timeout,this,[=](){
static int num = 0;
ui->label->setText(QString::number(num++));
});
connect(ui->pushButton,&QPushButton::clicked,this,[=](){
time->stop();
});
运行结果如下:
实现一个简易的定时器1:
ui->setupUi(this);
this->setWindowTitle("定时器");
this->setWindowIcon(QIcon(":/icon/Desert.jpg"));
this->setFixedSize(400,200);
//创建定时器对象
QTimer* time = new QTimer(this);
//每一秒钟加一
time->start(1000);
connect(time,&QTimer::timeout,this,[=](){
static int num = 0;
ui->label->setText(QString::number(num++));
});
connect(ui->pushButton,&QPushButton::clicked,this,[=](){
time->stop();
});
运行结果如下:
4.实现一个简易的定时器2:在类中重写timerEvent方法
void Widget::timerEvent(QTimerEvent *e)
{
static int num = 0;
ui->label->setText(QString("<center><h1>time out:%1</h1></center>").arg(num++));
}
然后启动计时器:
//timeId为定时器的唯一标识
int timeId = this->startTimer(1000);
5. event事件
主要功能进行事件的分发.event函数并不直接对事件进行处理,而是按照事件对象的类型分派给特定的事件处理函数。可以重写此函数定制此函数的行为。
函数:bool event(QEvent *);
返回值为true表示用户自己处理,不向下分发事件,时间停止传播下去。
返回值为false代表系统处理,但是最好抛给父类处理。
bool MyWidget::event(QEvent * e)
{
//如果是鼠标按下,在event事件分发中做拦截操作
if(e->type() == QEvent::MouseButtonPress)
{
}
//其他事件交给父类处理,默认处理
return QWidget::event(e);
}
6. 事件过滤器
在特定对象上安装事件过滤器,该过滤器仅过滤该对象收到的事件。然后再类中重写事件过滤函数。
步骤如下:
1.安装过滤器
ui->label->installEventFilter(this);
2.重写eventFiter事件过滤函数
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this); //给textEdit这个控件安装事件过滤器
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
//仅过滤textEdit这个对象收到的事件
if (obj == textEdit)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
}
else
{
return false;
}
}
else
{
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
15QPainter实现基本绘图
0.QT的绘画系统组要基于这三个类: QPainter, QPaintDevice, and QPaintEngine
1.画家使用画笔:
void Widget::paintEvent(QPaintEvent* event)
{
//创建画家对象,初始化一个painter对象
//this指定绘图设备
QPainter painter(this);
//创建画笔
QPen pen(QColor(255,0,0));
//画家拿起笔
painter.setPen(pen);
//利用画家画直线
painter.drawLine(QPoint(0,0),QPoint(100,100));
}
运行结果如下:
2.画家使用画刷:
//创建画刷
QBrush brush(Qt::black);
//画家使用画刷
painter.setBrush(brush);
//利用画家画圆
painter.drawEllipse(QPoint(50,50),30,30);
运行结果如下:
3.使用画家画图:
1.
painter.drawPixmap(10,10,QPixmap(":/img/Koala.jpg"));
- 画一个背景图
void Widget::paintEvent(QPaintEvent *event)
{
QPainter* painter = new QPainter(this);
painter->drawPixmap(0,0,this->width(),this->height(),QPixmap("C:\\Users\\zhihua\\Pictures\\example.jpg"));
}
16.QFile文件读写
- 首先页面布局:
对于上图的大致布局步骤如下:
- 选择widget容器
- 拖动line edit(单行输入框)和一个push_button到容器内,然后选择水平布局
- 拖动一个text edit到布局界面内
- 对整体进行垂直布局即可。
现在欲实现一个类似于windows记事本打开的功能
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
//当点击选择路径时,就弹出一个文件对话框
connect(ui->pushButton,&QPushButton::clicked,this,[=](){
QString name = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\zhihua\\Desktop",tr("Text files (*.txt);;Images (*.png *.xpm *.jpg)"));
if(name.isEmpty())
{
QMessageBox::warning(this,"警告","打开文件失败");
}
//将路径放到line edit中
ui->lineEdit->setText(name);
//读取文件内容,QT默认支持UTF—8文件
QFile file(name);
file.open(QIODevice::ReadOnly);
QByteArray array = file.readAll();
//将读取到的内容放入text edit中
ui->textEdit->append(array);
file.close();
});
}
运行结果如下:
上诉程序只支持UTF编码格式,并不支持GBK格式:
如果需要支持GBK格式,需要使用QTextCodec类进行选择指定编码格式的类。
ui->setupUi(this);
//当点击选择路径时,就弹出一个文件对话框
connect(ui->pushButton,&QPushButton::clicked,this,[=](){
QString name = QFileDialog::getOpenFileName(this,"打开文件","C:\\Users\\zhihua\\Desktop",tr("Text files (*.txt);;Images (*.png *.xpm *.jpg)"));
if(name.isEmpty())
{
QMessageBox::warning(this,"警告","打开文件失败");
}
//将路径放到line edit中
ui->lineEdit->setText(name);
//设置打开编码格式的文件
QTextCodec* code = QTextCodec::codecForName("gbk");
//读取文件内容,QT默认支持UTF—8文件
QFile file(name);
file.open(QIODevice::ReadOnly);
QByteArray array = file.readAll();
//将读取到的内容放入text edit中
//ui->textEdit->append(array);
//将字节array转成GBK
ui->textEdit->setText(code->toUnicode(array));
file.close();
});
17.QFileInfo类读取文件信息
18.使用文本流和数据流读写文件(主要涉及到两个类:QTextStream,QDataStream)
19.布局管理器布局
- 对于控件比如说QLabel和QLineEdit的布局,可以将它们放在QWidget容器中进行布局,这样就可以按照模块对控件进行布局。
2. 可以在布局对象属性中更改距离比如说左边距,上边距,右边距,下边距,间隙等。
20.对象树
当创建的对象在堆区时候,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中,一定程度简化了内存回收机制。