指针初始化

野指针和空指针:

声明一个指针变量并不会自动分配任何的内存,对指针执行间接访问前,指针必须初始化:或者使它指向现有的内存,或者为其分配动态内存。
NULL指针就是不指向任何东西的指针,它可以赋值给另一个指针,永远表示这个指针不指向任何值。对于NULL指针执行任何的间接访问操作的后果是不确定的,由编译器决定。
除了NULL指针之外,再没有任何内建的记法来表示指针常量,因为无法预知编译器会把变量放在什么位置。
需要注意一点,未初始化的指针在使用时会发生意想不到指针异常,可以看一段代码:

  1. #include "TestPointer.h"
  2. #include <QTimer>
  3. #include <QDebug>
  4. TestPointer::TestPointer(QObject *parent)
  5. : QObject(parent)
  6. {
  7. if (m_timer == nullptr) {
  8. m_timer = new QTimer(this);
  9. connect(m_timer, &QTimer::timeout,
  10. this, &TestPointer::onTimeOut);
  11. }
  12. m_timer->start();
  13. }
  14. void TestPointer::onTimeOut()
  15. {
  16. qDebug() << "TestPointer::onTimeOut";
  17. }

运行后,我们发现程序崩溃了, 通过调试定位,如图:
image.png
未初始化的指针指向一段随机的地址,所以,你即使在使用时判空,也会发生异常,并没有执行构造对象的代码,
所以在使用指针时要注意俩点:

  • 初始化
  • 指针判空

是不是只要做到这一点,就不会发生指针崩溃了?答案是否。

悬空指针:

使用qt窗体时,我们经常会有这样的使用

  1. #include "MainWindow.h"
  2. #include "ui_MainWindow.h"
  3. MainWindow::MainWindow(QWidget *parent)
  4. : QMainWindow(parent)
  5. , ui(new Ui::MainWindow)
  6. , m_widPointer(nullptr)
  7. {
  8. ui->setupUi(this);
  9. }
  10. MainWindow::~MainWindow()
  11. {
  12. if (m_widPointer != nullptr) {
  13. m_widPointer->deleteLater();
  14. m_widPointer = nullptr;
  15. }
  16. delete ui;
  17. }
  18. void MainWindow::init()
  19. {
  20. if (m_widPointer == nullptr) {
  21. m_widPointer = new QWidget(nullptr);
  22. m_widPointer->setAttribute(Qt::WA_DeleteOnClose);
  23. }
  24. m_widPointer->show();
  25. }

使用指针时,进行了初始化,也做了判空操作,但是依然崩溃,因为执行了这行代码:m_widPointer->setAttribute(Qt::WA_DeleteOnClose); 所以当我们在关闭m_widPointer窗体时,会自动释放这个指针所指向内存,但是我们没有把m_widPointer指针置空, 指针m_widPointer就变成悬空指针,再去释放他,或者使用它,就会发生指针访问异常。
image.png
所以在释放指针时,要先判空,再释放,释放了要及时置空。

类型转换:

再写代码时,可能大家会忽略类型转换的问题,但是类型转换可能会失败(子类基类,无关类型),比如:把QObject转换成QWidget,这些是明显的类型转换不合法,转换的结果就是空,代码如下:

  1. #include "MainWindow.h"
  2. #include "ui_MainWindow.h"
  3. #include <QDebug>
  4. MainWindow::MainWindow(QWidget *parent)
  5. : QMainWindow(parent)
  6. , ui(new Ui::MainWindow)
  7. , m_widPointer(nullptr)
  8. {
  9. ui->setupUi(this);
  10. }
  11. MainWindow::~MainWindow()
  12. {
  13. if (m_widPointer != nullptr) {
  14. m_widPointer->deleteLater();
  15. m_widPointer = nullptr;
  16. }
  17. delete ui;
  18. }
  19. void MainWindow::init()
  20. {
  21. if (m_widPointer == nullptr) {
  22. m_widPointer = new QWidget(nullptr);
  23. m_widPointer->setAttribute(Qt::WA_DeleteOnClose);
  24. }
  25. QObject *prt = new QObject(this);
  26. m_widPointer = dynamic_cast<QWidget*>(prt);
  27. m_widPointer->show();
  28. }

运行后发生崩溃,通过调试,我们发现:经过类型转换,结果是一个空指针,但是,如果使用强制类型转换,转换结果不是空,这是俩种情况在使用时,都会发生指针异常。
image.png

那么,多说一下c++类型转换:

  • const_cast: 就是字面意思,去掉const属性或者volatile属性
  • static_cast: 无需条件转换, 静态转换,
    • 用于子类指针和基类指针之间的转换,但是把基类指针转换成子类指针时不安全的,推荐使用dynamic_cast
    • 基本的数据类型转换,如enum, struct int char float等
    • 不能进行无关类型的转换(非子类基类之间的转换)
    • 把任何类型转换成void类型
    • 不能去掉常性
  • dynamic_cast:有条件转换,运行时类型安全检查,失败返回NULL,安全的子类和基类之间转换,必须要有虚函数
  • reinterpret_cast: 仅仅重新解释类型,没有进行二进制的转换,
    • 转换的类型必须是指针,引用,算术类型,或者成员指针,
    • 可以把一个指针转换成一个整形,也可以把一个整数转换成一个指针,
    • 但是不能把非32bit的实例转换成指针,主要用于函数指针的转换

通过一段代码来了解一下:
image.png

迭代器失效:

当我们再使用容器时,很多情况下会使用使用迭代器,当我们从容器中拿出数据使用完,释放掉的后,再去访问迭代器时,迭代器已经失效了, 因为它所指向的内存已经释放了,再去使用就会发生指针异常。
image.png
所以,当我们使用迭代器时,要注意迭代器失效的问题,包括使用容器的时候,如果从容器中取出所需的数据,应该即使把这个数据从容器中pop出来,

lamada造成的崩溃

lambda表达式的基本构成:

lambda表达式表示了一个可调用的单元代码,与函数类似,具有一个返回类型,一个参数列表,一个函数体,不同之处在于,还有一个捕获参数列表。

  1. [](int a, int b)->bool{return a > b; }

需要注意的是,返回类型必须使用尾置返回。参数列表和返回类型都是可以忽略的,但是捕获列表和函数体必须保留。个人猜想,省略了捕获列表和函数体可能会使得编译器无法判断类型。lambda既然是个可调用对象,那么它的调用方法也与一般的函数对象一致:

  1. void MainWindow::init()
  2. {
  3. int i = 1, j = 1;
  4. auto f = [](int& j) { ++j; };//等价于Increase
  5. f(j);
  6. }

运行结果:
image.png

捕获参数:

捕获可以说是lambda最难理解的部分。所谓捕获,指的是允许lambda使用它所在函数中的局部变量。对于函数外的变量以及函数内的static变量,无需进行捕获即可直接使用。
捕获有两种方式,一种是值捕获,另外一种是引用捕获。

  1. static i=1;
  2. int j=1;
  3. auto f1 = [&j]()->int{ ++j; return j; };//引用捕获
  4. auto f2 = [j](){return j; };//值捕获
  5. auto f3 = [](){return i; }//静态变量无需捕获

引用捕获,实际上就是引用了一个局部变量。既然是引用,就要注意引用存在的问题。必须确保引用的变量在整个lambda执行的周期内都是存在的,并且为一个正确的值。

值捕获,相当于函数参数值传递的过程。因此,必须保证捕获的量是可拷贝的。此外,针对捕获的值为指针或者引用类型,也存在确保对象依然存在的问题。

备注:因此,应对尽量减少捕获,避免潜在的问题。可能的话,尽量避免捕获指针或者引用。

隐式捕获:隐式捕获是一种简略的方法,下面举个例子来说明一下:

  1. void MainWindow::init()
  2. {
  3. int i = 1, j = 1;
  4. auto f = [](int& j) { ++j; };//等价于Increase
  5. f(j);
  6. // auto f2 = [j]() {return j; };//error,因为这是个右值
  7. auto f4 = [j]() mutable {return ++j; };//正确
  8. }

注: 前面“=”或者“&”说明默认捕获的类型,如果有需要显示捕获的另外一种类型,在后面单独列出即可。

那么,我们再使用lamada时,如何产生的崩溃呢?

先看一段会发生崩溃的代码:

  1. #include "TestPointer.h"
  2. #include <QTimer>
  3. #include <QDebug>
  4. TestPointer::TestPointer(QObject *parent)
  5. : QObject(parent)
  6. , m_timer(nullptr)
  7. {
  8. QTimer::singleShot(3000, [&] () {
  9. qDebug() << "TestPointer::TestPointer::timeout";
  10. });
  11. }
  12. TestPointer::~TestPointer()
  13. {
  14. qDebug() << "TestPointer::~TestPointer";
  15. }
  16. void TestPointer::onTimeOut()
  17. {
  18. qDebug() << "TestPointer::onTimeOut";
  19. }

运行后,可以看到一些奇怪的事情
image.png
对,先析构,然后定时器触发了。
如果把lamada表达式的使用编程这样,运行就没有问题了,

  1. QTimer::singleShot(3000, this, [&] () {
  2. qDebug() << "TestPointer::TestPointer::timeout";
  3. });

image.png
image.png