引出
QT开发积累——浮点类型的大小比较-----qFuzzyCompare 的作用
qt中浮点类型的大小比较-----qFuzzyCompare 的作用
qFuzzyCompare
是 Qt 提供的一个函数,用于比较两个浮点数是否相等。由于浮点数在计算机中的表示存在精度问题,直接使用 ==
运算符比较两个浮点数可能会因为微小的舍入误差而导致不准确的结果。qFuzzyCompare
函数通过引入一个小的容差范围来解决这个问题,使得在一定精度范围内相等的浮点数被认为是相等的。
qFuzzyCompare
的方法
qFuzzyCompare
有两个重载版本:
bool qFuzzyCompare(double p1, double p2)
bool qFuzzyCompare(float p1, float p2)
使用示例
#include <QDebug>
#include <cmath>
int main() {
double a = 0.1 + 0.2;
double b = 0.3;
// 直接比较可能会因为精度问题导致不准确的结果
qDebug() << (a == b); // 输出: false
// 使用 qFuzzyCompare 进行比较
qDebug() << qFuzzyCompare(a, b); // 输出: true
return 0;
}
注意事项
- 精度问题:
qFuzzyCompare
函数在比较时会考虑一个小的容差范围,但这个范围是有限的。对于非常大或非常小的浮点数,仍然可能出现比较不准确的情况。 - 零值比较:
qFuzzyCompare
函数在比较零值时需要特别注意。Qt 还提供了qFuzzyIsNull
函数来专门处理零值的比较。
qFuzzyIsNull
函数
qFuzzyIsNull
用于检查一个浮点数是否接近于零:
bool qFuzzyIsNull(double d)
bool qFuzzyIsNull(float f)
使用示例:
#include <QDebug>
int main() {
double a = 1e-10;
qDebug() << (a == 0.0); // 输出: false
qDebug() << qFuzzyIsNull(a); // 输出: true
return 0;
}
总结
qFuzzyCompare
和 qFuzzyIsNull
是 Qt 提供的用于处理浮点数比较的实用函数,可以有效避免由于浮点数精度问题导致的比较错误。在需要比较浮点数的场景中,推荐使用这些函数来提高代码的健壮性和准确性。
以下关于误差的控制
qFuzzyCompare
是 Qt 提供的一个用于比较浮点数是否相等的函数,考虑到浮点数计算中的精度问题。它内部使用了一定的误差范围来判定两个浮点数是否“足够接近”,以避免由于微小的舍入误差导致的比较不准确。
默认实现
qFuzzyCompare
的默认实现是基于两个浮点数相对差值的比较。以下是 qFuzzyCompare
的默认实现:
inline bool qFuzzyCompare(double p1, double p2) {
return (qAbs(p1 - p2) <= 0.000000000001 * qMin(qAbs(p1), qAbs(p2)));
}
自定义精度
如果你希望使用自定义的比较精度,你需要编写自己的比较函数,而不是直接使用 qFuzzyCompare
。下面是一个示例,展示如何编写一个自定义的浮点数比较函数,允许你指定比较精度:
#include <cmath> // for std::abs
#include <QtGlobal> // for qMin and qAbs
#include <QDebug>
bool customFuzzyCompare(double p1, double p2, double epsilon) {
return (qAbs(p1 - p2) <= epsilon * qMin(qAbs(p1), qAbs(p2)));
}
int main() {
double num1 = 0.30000000000000004; // Due to floating-point precision
double num2 = 0.3;
double epsilon = 1e-12; // Custom precision
if (customFuzzyCompare(num1, num2, epsilon)) {
qDebug() << "Numbers are equal within the custom precision.";
} else {
qDebug() << "Numbers are not equal within the custom precision.";
}
return 0;
}
解释
customFuzzyCompare
函数:这个函数接受三个参数:p1
和p2
是要比较的两个浮点数,epsilon
是允许的相对误差。- 使用
qAbs
和qMin
:函数内部使用qAbs
计算绝对值,并使用qMin
获取较小的绝对值,以避免过小的相对误差范围。 - 自定义精度的比较:在
main
函数中,设置了一个自定义的精度epsilon
,并调用customFuzzyCompare
来比较两个浮点数。
推荐使用场景
- 科学计算:在科学计算中,浮点数的精度要求较高,自定义比较精度可以提高计算结果的可靠性。
- 图形计算:在图形计算中,浮点数误差会影响渲染结果,通过自定义比较精度可以提高图形渲染的准确性。
通过自定义的比较函数,你可以灵活地调整浮点数比较的精度,以满足不同应用场景的需求。
总结
QT开发积累——浮点类型的大小比较-----qFuzzyCompare 的作用
日积月累,开发集锦
方法参数加const和不加const的区别
在Qt和C++中,方法参数是否加const
关键字主要影响该参数在方法内部的修改权限。下面是加const
和不加const
的区别:
不加const
- 可修改性:如果方法参数没有声明为
const
,那么在该方法内部,你可以修改这个参数的值。 - 适用场景:当你需要在方法内部修改传入的参数时,不应该使用
const
。
加const
- 不可修改性:如果方法参数声明为
const
,那么在该方法内部,你不能修改这个参数的值。任何尝试修改const
参数的行为都会导致编译错误。 - 适用场景:当你希望确保方法不会修改传入的参数时,应该使用
const
。这有助于提高代码的可读性和可维护性,因为它清楚地表明了方法的意图。 - 性能优势:在某些情况下,编译器可能会对
const
参数进行优化,例如通过避免不必要的复制来提高性能。 - 安全性:使用
const
可以减少意外修改参数的风险,从而提高代码的健壮性。
示例
假设有一个Qt类MyClass
,它有一个方法process
,该方法接受一个字符串参数:
class MyClass : public QObject {
Q_OBJECT
public:
void process(QString& text) {
// 可以修改text
text.append(" (modified)");
}
void print(const QString& text) const {
// 不能修改text
// text.append(" (modified)"); // 这将导致编译错误
qDebug() << text;
}
};
- 在
process
方法中,参数text
没有声明为const
,因此可以在方法内部修改它。 - 在
print
方法中,参数text
声明为const
,因此不能在方法内部修改它。
总结来说,方法参数加const
主要是为了确保该参数在方法内部不被修改,从而提供更好的代码清晰度和安全性。如果不加const
,则意味着参数可以在方法内部被修改。根据方法的实际需求和设计意图,选择合适的const
使用方式。
方法加static和不加static的区别
在Qt类中,方法(成员函数)是否声明为static
主要影响该方法的访问方式和作用域。下面是static
方法和非static
方法之间的主要区别:
非static
方法(实例方法)
- 访问方式:非
static
方法必须通过类的实例(对象)来调用。这意味着你需要先创建一个类的对象,然后才能调用其方法。 - 访问成员变量:非
static
方法可以访问类的所有成员变量,包括private
和protected
成员。 - 作用域:每个对象都有自己的一组非
static
成员变量的副本,因此非static
方法操作的是调用它的对象的成员变量。 - 生命周期:非
static
方法的生命周期与对象的生命周期相关联。
static
方法(类方法)
- 访问方式:
static
方法可以直接通过类名来调用,无需创建类的实例。例如,ClassName::staticMethod()
。 - 访问成员变量:
static
方法只能访问类的static
成员变量。它不能直接访问非static
成员变量,因为static
方法不与任何特定的对象实例关联。 - 作用域:
static
方法属于类本身,而不是类的任何特定实例。因此,它操作的是类的static
成员变量,这些变量在所有实例之间共享。 - 生命周期:
static
方法的生命周期与程序的生命周期相同,不依赖于对象的创建或销毁。
示例
假设有一个Qt类MyClass
,它有一个非static
方法nonStaticMethod()
和一个static
方法staticMethod()
:
class MyClass : public QObject {
Q_OBJECT
public:
MyClass() {}
void nonStaticMethod() {
// 可以访问非static成员变量
// 必须通过对象调用
}
static void staticMethod() {
// 只能访问static成员变量
// 可以直接通过类名调用
}
private:
int nonStaticVar;
static int staticVar;
};
int MyClass::staticVar = 0;
- 调用
nonStaticMethod()
:
MyClass obj;
obj.nonStaticMethod();
调用staticMethod()
:
MyClass::staticMethod();
总结来说,static
方法和非static
方法的主要区别在于它们如何与类的实例和成员变量交互,以及如何被调用。static
方法更像是全局函数,但它们属于类,并且只能访问类的static
成员。而非static
方法则与类的实例紧密相关,可以访问所有类型的成员变量。
Qt遍历list提高效率
在Qt中遍历QList
或其他容器时提高运算效率主要依赖于以下几点:
- 使用迭代器而非索引访问:对于
QList
和其他Qt容器类,使用迭代器进行遍历通常比使用索引访问元素更高效,特别是在非随机访问容器(如QList
对于大型对象)中。这是因为迭代器直接引用容器中的元素,而不是通过索引重新计算位置。
QList<int>::iterator it;
for (it = list.begin(); it != list.end(); ++it) {
// 使用 *it 访问元素
}
或者使用C++11的范围基于的for循环:
for (int &element : list) {
// 使用 element
}
避免不必要的对象复制:如果你的QList
包含的是自定义类型或大型对象,请确保在遍历时使用引用(或常量引用,如果你不需要修改它们),这样可以避免遍历过程中对象的不必要拷贝。
for (CustomType &item : list) {
// 使用 item
}
如果不需要修改元素,使用常量引用:
for (const CustomType &item : list) {
// 只读访问 item
}
考虑使用更合适的容器:QList
是一个非常通用的容器,但并不总是最高效的选择。如果你需要频繁地在序列中间插入或删除元素,可能会更倾向于使用QLinkedList
。如果元素经常被访问(特别是随机访问),则QVector
可能是更好的选择。选择最适合你需求的容器类型可以显著提高性能。
减少遍历次数:有时可以通过算法优化来减少对容器的遍历次数。例如,如果可能,尝试将多个操作合并到单个遍历中完成,而不是对同一个列表进行多次遍历。
使用Qt Concurrent进行并行处理:对于大型数据集和可以并行处理的任务,考虑使用Qt Concurrent模块中的函数,如QtConcurrent::map
,它可以自动地将任务分配到多个线程中执行,从而利用多核处理器的优势。
#include <QtConcurrent>
void processItem(int &item) {
// 对 item 进行处理
}
QList<int> list = ...;
QtConcurrent::map(list, processItem);
遵循这些指导原则可以帮助你在Qt应用程序中提高遍历QList
(以及其他容器)的运算效率。然而,请记住,性能优化应该是基于实际需要和通过分析确定的瓶颈来进行的。在许多情况下,代码的清晰性和可维护性比微小的性能提升更为重要。
显示函数的调用
使用&
与不使用&
在C++中,使用&
与不使用&
在for循环中会产生不同的行为:
- 使用
&
: 当在范围for循环(range-based for loop)中使用引用符号&
时,你将获得对容器内元素的直接引用。这意味着在循环内部对这些元素进行的任何修改都会反映到原始容器中的元素上。例如:
for (CircleData &ct : cList) {
ct.someProperty = newValue; // 这将修改cList中元素的属性
}
在这个例子中,任何对ct
的修改都会直接修改cList
中的对应CircleData
对象。
不使用 &
: 如果不使用引用符号,范围for循环将创建容器内每个元素的副本。在这种情况下,循环内部对副本所做的任何修改都不会影响原始容器中的元素。例如:
for (CircleData ct : cList) {
ct.someProperty = newValue; // 这只会修改副本的属性,并不会影响cList中的元素
}
在这个例子中,ct
是CircleData
对象的一个临时副本,对它的修改不会影响cList
中的原始对象。
总之,如果你打算修改循环中的元素,应当使用引用(&
),这将避免不必要的对象副本并确保修改作用于容器中的实际元素。如果你不需要修改元素或者只是想读取元素的数据,那么可以省略&
来避免潜在的副作用。然而,即使在只读情况下,为了避免不必要的拷贝操作,通常也会使用常量引用(const &
),如下:
for (const CircleData &ct : cList) {
// 只读取ct的数据,不进行修改
}
这样可以提高效率,特别是在处理大型对象时。
qt方法的参数中使用&
与不使用&
在 Qt 和 C++ 中,方法参数中使用 &
和不使用 &
的区别主要涉及到参数传递的方式,即值传递和引用传递。这两种方式在性能、副作用和语义上有所不同。
值传递
当方法参数不使用 &
时,参数是通过值传递的。这意味着在调用方法时,会创建参数的一个副本,并在方法内部使用这个副本。
void modifyValue(int value) {
value = 10;
}
int main() {
int x = 5;
modifyValue(x);
qDebug() << x; // 输出: 5
return 0;
}
在这个例子中,modifyValue
方法接收一个 int
类型的参数 value
,并修改其值为 10。但由于 value
是通过值传递的,原始变量 x
的值不会被修改。
引用传递
当方法参数使用 &
时,参数是通过引用传递的。这意味着在调用方法时,传递的是原始变量的引用,而不是副本。因此,在方法内部对参数的任何修改都会直接反映到原始变量上。
void modifyValue(int &value) {
value = 10;
}
int main() {
int x = 5;
modifyValue(x);
qDebug() << x; // 输出: 10
return 0;
}
在这个例子中,modifyValue
方法接收一个 int
类型的引用参数 value
,并修改其值为 10。由于 value
是通过引用传递的,原始变量 x
的值被修改为 10。
常量引用传递
在某些情况下,你可能希望避免参数的副本创建,但又不想修改原始变量。这时可以使用常量引用传递。
void printValue(const int &value) {
qDebug() << value;
}
int main() {
int x = 5;
printValue(x); // 输出: 5
return 0;
}
在这个例子中,printValue
方法接收一个 const int &
类型的参数 value
,并打印其值。由于 value
是常量引用,方法内部不能修改其值,但可以避免创建副本,提高性能。
总结
- 值传递:创建参数的副本,方法内部对参数的修改不会影响原始变量。
- 引用传递:传递原始变量的引用,方法内部对参数的修改会直接影响原始变量。
- 常量引用传递:避免创建副本,同时保证参数不被修改。
在 Qt 和 C++ 编程中,选择合适的参数传递方式可以提高代码的性能和可读性。通常,对于大型对象或需要在方法内部修改参数值的情况,使用引用传递或常量引用传递是更好的选择。
除法的一个坑
在C++(包括Qt)中,如果你执行整数除法(例如 1 / 2
),结果将是一个整数,因为操作数都是整数。这意味着结果会被截断,而不是四舍五入。因此,1 / 2
的结果将是 0
,而不是 0.5
。
如果你想得到 0.5
这个结果,你需要确保至少有一个操作数是浮点数。这可以通过将其中一个操作数显式转换为浮点数来实现,或者直接使用浮点数进行运算。
以下是在Qt中如何将 1 / 2
的结果用浮点数接收的示例:
#include <QDebug>
int main() {
// 方法1: 将其中一个操作数转换为浮点数
float result1 = static_cast<float>(1) / 2;
qDebug() << "Result 1:" << result1; // 输出: Result 1: 0.5
// 方法2: 直接使用浮点数进行运算
float result2 = 1.0f / 2.0f;
qDebug() << "Result 2:" << result2; // 输出: Result 2: 0.5
return 0;
}
在这两个示例中,我们都确保了除法运算至少有一个浮点数参与,这样结果就会是浮点数,而不是整数。这样,当你将结果赋值给一个浮点数变量时,你将得到正确的浮点数结果 0.5
。
项目创建相关
新建一个项目
其他
规范和帮助文档
//类名首字母大写,单词和单词之间首字母大写
//函数名变量名称首字母小写,单词和单词之间首字母大写
//快捷键
//注释ctrl +/
// 运行 ctrl +r
// 编译 ctrl b
//字体缩放ctr1+ 鼠标滚轮
//查找 ctrl+f
//整行移动 ctrl+shift+↑或者,
//帮助文档F1
//自动对齐 ctrl i;
//同名之间的.h和.cpp切换 F4
//帮助文档第一种方式F1第二种左侧按钮 1
//D:\MyPrograme\QT\5.14.2\mingw73_32\bin
创建第一个Qt程序
2.1点击创建项目后,选择项目路径到以及给项目起名称
2.2名称·不能有中文不能有空格
2.3路径·不能有中文路径
2.4默认创建有窗口类,yWidget,基类有三种选择:QWidget、QMainWindowQDialog
2.5 main函数
2.5.1 QApplication a应用程序对象,有且仅有一个
2.5.2 mywidget w;实例化窗口对象
2.5.3 w.show0调用show函数显示窗口
2.5.4 return a.exec(让应用程序对家进入消总循环机,制中,代码阻塞到当前行
#include "mywidget.h"
#include <QApplication> // 应用程序头文件
#include <QLabel>
// main程序入口 argc 命令行变量数量,argv命令行变量数组
int main(int argc, char *argv[])
{
// 应用程序对象,qt中有且只有一个
QApplication a(argc, argv);
// 窗口默认不显示,需要调用show方法
myWidget w;
w.show();
// 进入消息循环,死循环
return a.exec();
}
3按钮控件常用API
3.1 创建QPushButton*btn=new QPushButton
3.2 设置父亲setParent(this
3.3 设置文本 setText(文字")
3.4 设置位置move宽,高W
3.5 重新指定商口大小 resize
3.6 设置窗口标题 setwindowTitle
3.7 设置窗口固定大小setFixedsize
#include "mywidget.h"
#include <QLabel>
#include <QPushButton>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
{
QLabel *label = new QLabel("hello qt");
label->setParent(this);
// 创建按钮
QPushButton *btn = new QPushButton("第二个按钮",this);
btn->move(100,100);
// 充值窗口大小
resize(600,400);
setWindowTitle("第一个窗口");
}
myWidget::~myWidget()
{
}
对象树概念
4对像树
4.1 当创建的对象在堆区时候,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中。
4.2 一定程度上简化了内存回收机制
信号signal槽slot
信号的发送者,发送的具体信号,信号的接受者,信号的处理(槽)
信号槽的优点,松散耦合,信号发送端和接受端本身是没有关联的,通过 connect连接将两端耦合在一起
6信号和槽
6.1连接函数:connect
6.2参数
6.2.1参数1 信号的发送者
6.2.2参数2 发送的信号(函数地址)
6.2.3参数3 信号的接受者
6.2.4参数4 处理的槽函数(函数的地址)
6.3松散羯合
自定义信号和槽
1.自定义信号
写到signals下
返回void
需要声明,不需要实现
可以有参数,可以重戟
2.自定义槽
返回void
需要声明,也需要实现
可以有参数,可以重载
写到public slot下或者public或者全局函数
3.建立连接
// 老师饿了,学生请吃饭
connect(te,&Teacher::hungry,st,&Student::treat);
4.进行触发
自定义信号重载
当自定义信号和槽出现重载
8.1 需要利用还数指针明确指向函数的地址·
8.2void(Teacher::tsignal )QString )=&Teacher::hungry;
8.3 QString转成char *
8.3.1.ToUtf80转为QByteArray
8.3.2.Data0转为Char *
8.4信号可以连接信号
8.5断开信号disconnect
带参数的
void (Teacher::*teacherSignal)(QString) = &Teacher::hungry;
void (Student::*StudentSlot)(QString) = &Student::treat;
void Student::treat(QString foodName){
// QString -> char * 先转成QByteArray(.toUtf8()) 再转成Char* ()
qDebug() << "请老师吃。。。" << foodName.toUtf8().data();
}
按钮触发
// 用一个按钮调用下课
QPushButton *btn = new QPushButton("下课了",this);
// 重置窗口daxiao
this->resize(600,400);
connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);
信号触发信号
// 无参的信号和槽连接
void (Teacher::*teacherSignal2)(void) = &Teacher::hungry;
void (Student::*StudentSlot2)(void) = &Student::treat;
connect(te,teacherSignal2,st,StudentSlot2);
// 信号连接信号
connect(btn,&QPushButton::clicked,te,teacherSignal2);
断开信号disconnect
拓展
1、信号是可以连接信号
2、一个信号可以连接多个槽函数
3、多个信号可以连接同一个糟函数
4、信号和槽函数的参数必须类型一一对应
5、信号和槽的参数个数是不是要一致?信号的参数个数可以多余槽函数的参数个数
connect(信号的发送者,发送的信号signal信号),信号接受者,槽函数SLOT)
优点:参数直观
缺点:编译器不会检测爸数类型:
lambda表达式
[=](){
btn->setText("aaa");
}();
返回值
int ret = []()->int{return 1000;}();
qDebug() << "ret = " << ret;
mutable修饰
QPushButton *myBtn1 = new QPushButton(this);
QPushButton *myBtn2 = new QPushButton(this);
myBtn1->move(100,100);
int m = 10;
connect(myBtn1,&QPushButton::clicked,this,
[m]()mutable {m=100+10;qDebug()<< m;});
connect(myBtn2,&QPushButton::clicked,this,
[=](){qDebug()<<m;});
qDebug() << m;
案例
QPushButton * btnClose = new QPushButton;
btnClose->setText("close");
btnClose->move(100,0);
btnClose->setParent(this);
connect(btnClose,&QPushButton::clicked,this,
[=](){
btnClose->setText("关闭");
emit te->hungry("娃哈哈");
// this->close();
});
打开关闭窗口案例
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 窗口
QWidget *myWidget = new QWidget;
myWidget->resize(200,200);
QPushButton *btnOpen = new QPushButton("打开",this);
this->resize(600,400);
connect(btnOpen,&QPushButton::clicked,
this,[=](){
myWidget->show();
});
QPushButton *btnclose = new QPushButton("关闭",this);
btnclose->move(200,0);
connect(btnclose,&QPushButton::clicked,
this,[=](){
myWidget->close();
});
// 一个按钮控制两个窗口
QWidget *myWidget1 = new QWidget;
myWidget1->resize(300,100);
QPushButton *myBtn = new QPushButton("open",this);
myBtn->move(50,50);
connect(myBtn,&QPushButton::clicked,this,
[=](){
if(myBtn->text()=="open"){
myBtn->setText("close");
myWidget1->show();
}
else {
myBtn->setText("open");
myWidget1->close();
}
});
}
Widget::~Widget()
{
delete ui;
}