Qt入门教程(6) 局部变量元素加入成员变量QList后,局部变量出了作用域后,为什么QList仍然可以获取元素的值

  • 8
  • 9,308 views
  • A+
所属分类:Qt专栏 Qt经验集

问题

问题:现有一个成员变量QList,在函数中定义了一个局部变量的对象,并将该局部变量加入到QList中。当函数运行结束,局部变量也就出了作用域,这时,由于局部变量只是一个普通对象,而不是指针,所以,应该被销毁。但是,我们在外面仍然可以通过QList对象来访问之前加入进来的数据,这是为什么?

实验

#include <QApplication>
#include "mainwindow.h"
#include <QDebug>
#include <QLabel>
#include <QString>
 
struct tagTEST
{
    int a;
    QLabel *x;
};

QList testFunc()
{
    QList  testList;
    tagTEST tmp;
    qDebug()<<"before add list:";
    for(int i=0; i<10; i++)
    {
       tmp.a = i;
       tmp.x = new QLabel(QString::number(i));
       testList.append(tmp);
       qDebug()<<i<<"  a value:"<<tmp.a<<"  x value:"<<tmp.x;
    }
    qDebug()<<"before add list size:"<<testList.size();
    return testList;
}


int main(int argc,char *argv[])
{
    QFont f("ZYSong18030",10);
    QApplication::setFont(f);
    QApplication app(argc,argv);

    QList list = testFunc();
    int size = list.size();
    int i;
    qDebug()<<"after add list:";
    qDebug()<<"after add list size:"<<size;
    for(i=0; i<size; i++)
    {
        qDebug()<<i<<"  a value:"<<list[i].a<<"  x value:"<<list[i].x; } MainWindow *mainwindow = new MainWindow(); mainwindow->show();
    return app.exec();
}

运行结果

before add list: 
0   a value: 0   x value: QLabel(0x3cad0) 
1   a value: 1   x value: QLabel(0x3cd30) 
2   a value: 2   x value: QLabel(0x3cbf0) 
3   a value: 3   x value: QLabel(0x3cb90) 
4   a value: 4   x value: QLabel(0x3ccf0) 
5   a value: 5   x value: QLabel(0x3cd10) 
6   a value: 6   x value: QLabel(0x3cd90) 
7   a value: 7   x value: QLabel(0x3ca90) 
8   a value: 8   x value: QLabel(0x3cdd0) 
9   a value: 9   x value: QLabel(0x3cc90) 
before add list size: 10 
after add list: 
after add list size: 10 
0   a value: 0   x value: QLabel(0x3cad0) 
1   a value: 1   x value: QLabel(0x3cd30) 
2   a value: 2   x value: QLabel(0x3cbf0) 
3   a value: 3   x value: QLabel(0x3cb90) 
4   a value: 4   x value: QLabel(0x3ccf0) 
5   a value: 5   x value: QLabel(0x3cd10) 
6   a value: 6   x value: QLabel(0x3cd90) 
7   a value: 7   x value: QLabel(0x3ca90) 
8   a value: 8   x value: QLabel(0x3cdd0) 
9   a value: 9   x value: QLabel(0x3cc90)

得出结论

说明append函数在内部进行了数据的复制,并且这种复制只是浅拷贝,因为list中每个元素的x指针和tmp中的指针地址一样,说明只是进行了浅拷贝。

Qt入门教程(6) 局部变量元素加入成员变量QList后,局部变量出了作用域后,为什么QList仍然可以获取元素的值

图中,从刚开始,在函数中创建tmp元素时,分别在堆和栈上分别创建了数据,加入list之后,将原来tmp中的在栈上面的元素进行了浅拷贝,x指向的依旧是tmp中的那个内存,但是,元素添加进list之后,元素的作用域扩大了,因此,当函数运行结束后,tmp被销毁,但tmp所指向的内存空间依旧在,我们依然在外面通过list访问中的数据

 

发表评论

您必须登录才能发表评论!

目前评论:8   其中:访客  8   博主  0

    • avatar RenShen 1

      首先这个文章中的结论是错误的.
      其次文章代码有两处主要错诶,我发表个人意见指正一下.
      错误一:指针变量不初始化,造成程序崩溃隐患;
      错误二是c++ 的new 和 delete 的调用次数一定要对应,可是看上文的代码只有new 没有 delete,这是出现了内存泄露!
      可以适当修改代码如下:
      class tagTEST
      {
      public:
      int a = 0;
      QLabel *x = nullptr;
      tagTEST(){}
      ~tagTEST(){ if(x) delete x; }
      };

      再去运行代码看看,你会发现结论完全变了,你将得到这样的输出:
      before add list:
      0 a value: 0 x value: QLabel(0x3cad0)
      1 a value: 1 x value: QLabel(0x3cd30)
      2 a value: 2 x value: QLabel(0x3cbf0)
      3 a value: 3 x value: QLabel(0x3cb90)
      4 a value: 4 x value: QLabel(0x3ccf0)
      5 a value: 5 x value: QLabel(0x3cd10)
      6 a value: 6 x value: QLabel(0x3cd90)
      7 a value: 7 x value: QLabel(0x3ca90)
      8 a value: 8 x value: QLabel(0x3cdd0)
      9 a value: 9 x value: QLabel(0x3cc90)
      before add list size: 10
      after add list:
      after add list size: 10
      程序异常结束。

        • avatar 阿拉灯aladeng 4

          @RenShen 我学qt也不久,你改了代码,结果运行出错,是因为以下两点原因,并不能证明文中结论是错误的
          1、tagTEST tmp只是一个局部变量,出了作用域之后,tmp会被浅拷贝出一个新对象加入到list中,tmp会被析构掉,调用它的析构函数,同时释放掉tmp中x所指向的内存空间,但因为是浅拷贝,所以,list中每个tagTEST元素中x所指向的内存空间其实是null,所以程序崩溃了
          2、 QLabel继承自QObject,由Qt自动管理内存,不要随便自己手动释放QObject对象

            • avatar RenShen 1

              @阿拉灯aladeng 看来我说的不够明显,这里你的Qt知识中再一次出现了一点误区,Qt自动管理内存是基于父子关系的, 并不是说QObject对象不需要手动释放.
              只是说,当对象B的父对象指定为A对象时,此时B对象的内存管理才会由A代为管理,当A析构时,遵循先子后父的顺序,A会将B先释放.
              你的代码是出现了内存泄露,所以才能运行,并不是说QList 将tagTEST tmp局部变量的生命周期扩大了,只是你的X指针泄露了

                • avatar 阿拉灯aladeng 4

                  @RenShen 你说的确实有道理,qlabel必须加入到父容器中才能将其交由父容器代为管理,但问题来了,这并不能证明文中的结论是错的(文中“元素的作用域扩大了”说法是个生动的描述,并不是真的扩大了生命周期),因为确实是浅拷贝,才会出现你程序中的异常退出,如果真照你上面的写法,那qlist中会存在很多为null的内存……而且,这里其实也算不上内存泄漏,因为内存还在管控中,只是由原来的temp交由list来管理了 :mrgreen:

                  • avatar 阿拉灯aladeng 4

                    @RenShen 其实我也很想知道,如果我这样写不对,而你那样写又会崩溃,那应该怎么写呢

                      • avatar RenShen 1

                        @阿拉灯aladeng 回到这片文章的提问,可以的解释就是,tagTEST tmp局部变量中的数据,由QList中的另外一个tagTEST拷贝了,并且在tagTEST tmp局部变量退出作用域后,他的成员x指向的内存并没有随之释放,所以通过另外一个tagTEST对象仍然可以访问到x指向的内存区域.与QList并没有可以扩大作tagTEST tmp局部变量用域的能力.
                        这里为什么说发生了内存泄露,因为在退出main函数的作用域后,x指向的内存依旧没有被释放,要明白,main函数也是被其他系统接口调用的,程序层面上就造成了野指针.
                        总之就是你对这段代码能运行分析得到的结论有误,代码本来就是有错的,没改的必要.
                        但是问如何能避免此处 QLable对象的管理问题,除了指定父对象之外,对于没有父对象的QObject对象还能用Qt 或者 std 的智能指针管理堆变量.
                        Qt中提供了QPointer、QScopedPointer、QSharedPointer、QWeakPointer等几种基本智能指针,可以学习一下

                        • avatar RenShen 1

                          @阿拉灯aladeng Qt 智能指针学习 – 1+1=10 – CSDN博客
                          https://blog.csdn.net/dbzhang800/article/details/6403285

                        • avatar 阿拉灯aladeng 4

                          @RenShen 我还是不大明白,你可以基于上面代码做个修改吗,既可以在外面正常访问list中的元素,又可以避免内存泄露 :grin: