该bug的产生原因:字符集不对的原因
解决办法:记事本打开该文件 => 另存为 => 编码改为带有BOM 的 UTF-8 (更改该文件的编码格式)
乱码原因:QT
使用的utf-8
编码进行字符串处理,VS
使用的是GB2312
,二者编码格式不匹配,所以中文就会乱码
解决办法1:在字符串加上u8
QWidget w;
w.setWindowTitle(u8"windows 芒果");
w.show();
解决办法2:按照上面字符集不对产生的错误解决之后 => 项目=>命令行增加一个选项 /utf-8
解决办法3:安装拓展
作用:自动将文件编码格式保存为utf-8。而如果改为编写C语言文件的时候,需要禁用该拓展
方法4:在头文件当中增加
#pragma execution_character_set("utf-8")
.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件
QT += core gui Qt包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本以上 包含 widget模块
TARGET = 01_FirstProject //目标 生成的.exe程序的名称
TEMPLATE = app //模板 应用程序模板 Application
SOURCES += main.cpp\ //源文件
mywidget.cpp
HEADERS += mywidget.h //头文件
解释:
【1】.注释:从“#”开始,到这一行结束
【2】.模板变量:告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:TEMPLATE = app
【3】.指定生成的应用程序名:TARGET = QtDemo
【4】.工程中包含的头文件:HEADERS += include/painter.h
【5】.工程中包含的.ui设计文件:FORMS += forms/painter.ui
【6】.工程中包含的源文件:SOURCES += sources/main.cpp sources
【7】.工程中包含的资源文件:RESOURCES += qrc/painter.qrc
【8】.greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写
注释 ctrl + /
运行 ctrl + r
编译 ctrl + b
字体缩放 ctrl + 鼠标滚轮
查找 ctrl + f
整行移动 ctrl + shift + ↑ 或者↓
帮助文档 F1
自动对齐 ctrl + i;
同名之间的.h 和 .cpp切换 F4
帮助文档 第一种方式 F1 第二种 左侧按钮 第三种:QT安装路径下的bin目录,例如: C:\Qt\Qt5.6.0\5.6\mingw49_32\bin
main函数入口
//main程序入口 argc命令行变量的数量 argv命令行变量的数组
int main(int argc, char *argv[])
{
//a应用程序对象,在Qt中,应用程序对象 有且仅有一个
QApplication a(argc, argv);
Widget w; //窗口对象
w.show();//窗口对象 默认不会显示,必须要调用show方法显示窗口
//让应用程序对象进入消息循环 让代码阻塞到这行
return a.exec();
}
QApplication应用程序类:是Qt的整个后台管理的命脉它包含主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理
a . e x e c ( ) a.exec() a.exec():程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
在Qt程序中,最常用的控件之一就是按钮,一个按钮其实就是一个QPushButton类对象,其中有两种创建方式:
#include
//创建方式1:
QPushButton * btn = new QPushButton;
btn->setParent(this);//设置父亲 让btn对象依赖在当前Widget窗口中
btn->setText("德玛西亚");//设置文字
btn->move(100,100);//移动位置
//创建方式2:
QPushButton * btn2 = new QPushButton("孙悟空",this);
//下述的this都可以省略
this->resize(600,400);//重新指定窗口大小
this->setWindowTitle("第一个项目");//设置窗口标题
this->setFixedSize(600,400);//限制窗口大小 => 不可以拖拽去扩大/缩小大小
//重置窗口大小
resize(600,400); //this->resize(...)
对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize;
注意:如果是在Widget的构造函数当中:
QPushButton btnStack;
btnStack.setParent(this);
此时该按钮并不会显示在窗口当中,因为该对象是在栈上创建的,一旦Widget的构造函数执行完成之后,该对象就会被销毁,因此它不会显示在窗口中。要使 btnStack
对象显示在窗口中,你应该将它的生命周期延长,使其在窗口的整个生命周期内都存在 =>可以选择 将btnStack声明为类的成员变量
在Qt中创建对象的时候需要提供一个Parent对象指针,因为QObject是以对象树的形式组织起来的,当你创建一个对象时,会看到该对象的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
【1】.在创建对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表,当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类
【2】.QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件
【3】.我们也可以自己删除子对象,它们会自动从其父对象列表中删除
Qt 引入对象树的概念,在一定程度上解决了内存问题
{
QWidget window;
QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit 都是QObject的子类,此时这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数
错误代码
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
此时:1.父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了
2.在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了
结论:尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。当创建的对象在堆区时候,如果指定的父亲是QObject类或者父类是QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中,一定程度上简化了内存回收机制
以左上角为原点(0,0),x以右为正方向,y以下为正方向
如果是对于嵌套窗口,其坐标是相对于父窗口来说的
信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal),但是这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。
connect(sender, signal, receiver, slot);
信号槽的优点:松散耦合,信号的发送端和接收端本身没有任何关联,二者通过connect
函数进行关联,将两端耦合在一起
在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字
点击signals索引到系统自带的信号有如下几个:
槽函数的寻找方式和信号一样,只不过他的关键字是slot
testQt::testQt(QWidget* parent): QWidget(parent)
{
ui.setupUi(this);
QPushButton* btn = new QPushButton(u8"关闭窗口", this);
btn->move(100, 200);
resize(500, 500);
//参数1 信号的发送者 参数2 发送的信号(函数的地址) 参数3 信号的接受者 参数4 处理的槽函数
connect(btn, &QPushButton::clicked, this, &QWidget::close);
}
testQt.cpp
testQt::testQt(QWidget* parent): QWidget(parent)
{
ui.setupUi(this);
//1.创建老师和学生对象
this->teacher = new Teacher(this);
this->stu = new Student(this);
//点击按钮触发下课
QPushButton* btn = new QPushButton(u8"下课", this);
btn->move(200, 200);
btn->resize(100, 100);
//2.0:无参 信号和槽函数连接 =>假设没有重载版本时的调用情况=>只有treat()和hungry()
/*
connect(teacher, SIGNAL(hungry()), stu, SLOT(treat())); //QT4写法
connect(teacher, &Teacher::hungry, stu, &Student::treat);
*/
//2.1:无参 信号和槽函数连接 =>有重载版本时的调用情况
/*
void(Teacher:: * teacherSignal)() = &Teacher::hungry;
void(Student:: * studentSlot)() = &Student::treat;
connect(teacher, teacherSignal, stu, studentSlot);
connect(btn, &QPushButton::clicked, teacher, teacherSignal); //信号可以连接信号 => 当点击事件发生=>触发teacherSignal信号=>触发studentSlot槽函数
*/
//2.2:有参 信号和槽函数连接 =>有重载版本时的调用情况
/*
void(Teacher:: * teacherSignal)(QString) = &Teacher::hungry;
void(Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher, teacherSignal, stu, studentSlot);
//connect(btn, &QPushButton::clicked, teacher, teacherSignal); //err,原因:信号和槽的参数类型不匹配
//clicked的第一个参数为bool teacherSignal函数的参数为QString,信号和槽函数的参数 必须类型一一对应
*/
//3.调用下课函数
ClassOver();
}
void testQt::ClassOver()
{
//emit teacher->hungry();//触发老师饿了信号 ->无参版本
emit teacher->hungry(u8"宫保鸡丁"); //触发老师饿了信号 ->带参版本
}
QPushButton* btn = new QPushButton(u8"close Widget", this);
btn->move(200, 200);
btn->resize(100, 100);
connect(btn, &QPushButton::clicked,[=]() {
qDebug() << u8"关闭窗口";
close(); //this->close(); 关闭窗口
});
注意:如果第三个参数是this,第四个参数是lambda表达式,那么第三个参数可以省略
注意:
【1】.如果有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来明确指向函数地址, 然后再做连接
connect(teacher,&Teacher::hungury,student,&Student::treat); //void hungury()和void treat()连接
//自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽
//void hungury(QString name); 自定义信号
//void treat(QString name ); 自定义槽
void (Teacher:: *teacherSingal)(QString) = &Teacher::hungury;
void (Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
补充:关于函数指针指向的是类当中的成员函数:
class A
{
public:
void func()
{
cout << "A::func()" << endl;
}
};
void (A::*PFunc)() = &A::func;
int main()
{
A obj;
(obj.*PFunc)();//成员函数需要一个对象实例来调用
/*
obj:这是一个已经创建的类A的对象实例。成员函数需要一个对象实例来操作类的数据成员和执行操作。
.*:这是成员函数指针运算符。它用于将成员函数指针与对象实例关联在一起,以便调用成员函数。
PFunc:这是一个指向成员函数的指针,它保存了要调用的成员函数的地址。在这种情况下,它指向类A的func()成员函数。
():这是函数调用运算符,用于调用函数。在这里,我们使用它来调用与对象实例obj关联的成员函数PFunc指向的成员函数。
*/
return 0;
}
【2】发送者和接收者都需要是QObject的子类
【3】信号和槽函数返回值是 void,信号只需要声明,不需要实现,槽函数需要声明也需要实现。信号和槽函数都可以进行重载
$signals$
当中,自定义槽函数写到public slot
当中或者public
函数或者全局函数【4】槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响
【5】使用 emit 在恰当的位置发送信号,使用connect()函数连接信号和槽
【6】任何成员函数、static 函数、全局函数和 Lambda 表达式都可以作为槽函数
【7】信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果信号和槽的参数不一致,允许的情况是:槽函数的参数可以比信号的少,但是此时槽函数存在的那些参数的顺序也必须和信号的前面几个一致,因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)
【1】一个信号可以和多个槽相连
【2】多个信号可以连接到一个槽,只要任意一个信号发出,这个槽就会被调用
【3】一个信号可以连接到另外的一个信号
【4】信号槽可以被取消链接,可以disconnect
断开
disconnect(teacher,teacherSignal2,stu,studentSlot2); //断开信号 和 信号连接的格式一致
【5】l 使用Lambda 表达式
在使用QT5 的时候,能够支持QT5的编译器都是支持 Lambda 表达式的。在连接信号和槽的时候,槽函数可以使用Lambda表达式的方式进行处理
connect(teacher,teacherSingal,student,studentSlot); //QT5 teacherSingal和studentSlot是函数指针
connect(teacher,SIGNAL(hungry(QString)),student,SLOT(treat(QString))); //QT4版本的写法
//connect( 信号的发送者,发送的信号SIGNAL(信号),信号接受者,槽函数SLOT(槽函数))
优点:参数直观
缺点:编译器不会检测参数类型
使用了SIGNAL和SLOT这两个宏,作用是:将两个函数名转换成了字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的,而是在运行时给出错误。这无疑会增加程序的不稳定性