- 指针初始化
- lamada造成的崩溃
指针初始化
野指针和空指针:
声明一个指针变量并不会自动分配任何的内存,对指针执行间接访问前,指针必须初始化:或者使它指向现有的内存,或者为其分配动态内存。
NULL指针就是不指向任何东西的指针,它可以赋值给另一个指针,永远表示这个指针不指向任何值。对于NULL指针执行任何的间接访问操作的后果是不确定的,由编译器决定。
除了NULL指针之外,再没有任何内建的记法来表示指针常量,因为无法预知编译器会把变量放在什么位置。
需要注意一点,未初始化的指针在使用时会发生意想不到指针异常,可以看一段代码:
#include "TestPointer.h"
#include <QTimer>
#include <QDebug>
TestPointer::TestPointer(QObject *parent)
: QObject(parent)
{
if (m_timer == nullptr) {
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout,
this, &TestPointer::onTimeOut);
}
m_timer->start();
}
void TestPointer::onTimeOut()
{
qDebug() << "TestPointer::onTimeOut";
}
运行后,我们发现程序崩溃了, 通过调试定位,如图:
未初始化的指针指向一段随机的地址,所以,你即使在使用时判空,也会发生异常,并没有执行构造对象的代码,
所以在使用指针时要注意俩点:
- 初始化
- 指针判空
悬空指针:
使用qt窗体时,我们经常会有这样的使用
#include "MainWindow.h"
#include "ui_MainWindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_widPointer(nullptr)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
if (m_widPointer != nullptr) {
m_widPointer->deleteLater();
m_widPointer = nullptr;
}
delete ui;
}
void MainWindow::init()
{
if (m_widPointer == nullptr) {
m_widPointer = new QWidget(nullptr);
m_widPointer->setAttribute(Qt::WA_DeleteOnClose);
}
m_widPointer->show();
}
使用指针时,进行了初始化,也做了判空操作,但是依然崩溃,因为执行了这行代码:m_widPointer->setAttribute(Qt::WA_DeleteOnClose); 所以当我们在关闭m_widPointer窗体时,会自动释放这个指针所指向内存,但是我们没有把m_widPointer指针置空, 指针m_widPointer就变成悬空指针,再去释放他,或者使用它,就会发生指针访问异常。
所以在释放指针时,要先判空,再释放,释放了要及时置空。
类型转换:
再写代码时,可能大家会忽略类型转换的问题,但是类型转换可能会失败(子类基类,无关类型),比如:把QObject转换成QWidget,这些是明显的类型转换不合法,转换的结果就是空,代码如下:
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_widPointer(nullptr)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
if (m_widPointer != nullptr) {
m_widPointer->deleteLater();
m_widPointer = nullptr;
}
delete ui;
}
void MainWindow::init()
{
if (m_widPointer == nullptr) {
m_widPointer = new QWidget(nullptr);
m_widPointer->setAttribute(Qt::WA_DeleteOnClose);
}
QObject *prt = new QObject(this);
m_widPointer = dynamic_cast<QWidget*>(prt);
m_widPointer->show();
}
运行后发生崩溃,通过调试,我们发现:经过类型转换,结果是一个空指针,但是,如果使用强制类型转换,转换结果不是空,这是俩种情况在使用时,都会发生指针异常。
那么,多说一下c++类型转换:
- const_cast: 就是字面意思,去掉const属性或者volatile属性
- static_cast: 无需条件转换, 静态转换,
- 用于子类指针和基类指针之间的转换,但是把基类指针转换成子类指针时不安全的,推荐使用dynamic_cast
- 基本的数据类型转换,如enum, struct int char float等
- 不能进行无关类型的转换(非子类基类之间的转换)
- 把任何类型转换成void类型
- 不能去掉常性
- dynamic_cast:有条件转换,运行时类型安全检查,失败返回NULL,安全的子类和基类之间转换,必须要有虚函数
- reinterpret_cast: 仅仅重新解释类型,没有进行二进制的转换,
- 转换的类型必须是指针,引用,算术类型,或者成员指针,
- 可以把一个指针转换成一个整形,也可以把一个整数转换成一个指针,
- 但是不能把非32bit的实例转换成指针,主要用于函数指针的转换
迭代器失效:
当我们再使用容器时,很多情况下会使用使用迭代器,当我们从容器中拿出数据使用完,释放掉的后,再去访问迭代器时,迭代器已经失效了, 因为它所指向的内存已经释放了,再去使用就会发生指针异常。
所以,当我们使用迭代器时,要注意迭代器失效的问题,包括使用容器的时候,如果从容器中取出所需的数据,应该即使把这个数据从容器中pop出来,
lamada造成的崩溃
lambda表达式的基本构成:
lambda表达式表示了一个可调用的单元代码,与函数类似,具有一个返回类型,一个参数列表,一个函数体,不同之处在于,还有一个捕获参数列表。
[](int a, int b)->bool{return a > b; }
需要注意的是,返回类型必须使用尾置返回。参数列表和返回类型都是可以忽略的,但是捕获列表和函数体必须保留。个人猜想,省略了捕获列表和函数体可能会使得编译器无法判断类型。lambda既然是个可调用对象,那么它的调用方法也与一般的函数对象一致:
void MainWindow::init()
{
int i = 1, j = 1;
auto f = [](int& j) { ++j; };//等价于Increase
f(j);
}
捕获参数:
捕获可以说是lambda最难理解的部分。所谓捕获,指的是允许lambda使用它所在函数中的局部变量。对于函数外的变量以及函数内的static变量,无需进行捕获即可直接使用。
捕获有两种方式,一种是值捕获,另外一种是引用捕获。
static i=1;
int j=1;
auto f1 = [&j]()->int{ ++j; return j; };//引用捕获
auto f2 = [j](){return j; };//值捕获
auto f3 = [](){return i; }//静态变量无需捕获
引用捕获,实际上就是引用了一个局部变量。既然是引用,就要注意引用存在的问题。必须确保引用的变量在整个lambda执行的周期内都是存在的,并且为一个正确的值。
值捕获,相当于函数参数值传递的过程。因此,必须保证捕获的量是可拷贝的。此外,针对捕获的值为指针或者引用类型,也存在确保对象依然存在的问题。
备注:因此,应对尽量减少捕获,避免潜在的问题。可能的话,尽量避免捕获指针或者引用。
隐式捕获:隐式捕获是一种简略的方法,下面举个例子来说明一下:
void MainWindow::init()
{
int i = 1, j = 1;
auto f = [](int& j) { ++j; };//等价于Increase
f(j);
// auto f2 = [j]() {return j; };//error,因为这是个右值
auto f4 = [j]() mutable {return ++j; };//正确
}
注: 前面“=”或者“&”说明默认捕获的类型,如果有需要显示捕获的另外一种类型,在后面单独列出即可。
那么,我们再使用lamada时,如何产生的崩溃呢?
先看一段会发生崩溃的代码:
#include "TestPointer.h"
#include <QTimer>
#include <QDebug>
TestPointer::TestPointer(QObject *parent)
: QObject(parent)
, m_timer(nullptr)
{
QTimer::singleShot(3000, [&] () {
qDebug() << "TestPointer::TestPointer::timeout";
});
}
TestPointer::~TestPointer()
{
qDebug() << "TestPointer::~TestPointer";
}
void TestPointer::onTimeOut()
{
qDebug() << "TestPointer::onTimeOut";
}
运行后,可以看到一些奇怪的事情
对,先析构,然后定时器触发了。
如果把lamada表达式的使用编程这样,运行就没有问题了,
QTimer::singleShot(3000, this, [&] () {
qDebug() << "TestPointer::TestPointer::timeout";
});