• 新站建成,望大家多多关注!如有需要,可留言私聊!QQ群: 491728233 , 693233918
  • 投稿功能开通啦!大家可以去“联系我们”中进行投稿

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

Qt专栏 阿拉灯aladeng 5个月前 (09-29) 703次浏览 8个评论

问题

问题:现有一个成员变量 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 访问中的数据

 


更多文章敬请关注阿拉灯 Aladeng 可以加入 QQ 群或者微信群进一步交流相关话题
QQ 群 1:  491728233  Qt 入门教程(6) 局部变量元素加入成员变量 QList 后,局部变量出了作用域后,为什么 QList 仍然可以获取元素的值 || QQ 群 2: 693233918 Qt 入门教程(6) 局部变量元素加入成员变量 QList 后,局部变量出了作用域后,为什么 QList 仍然可以获取元素的值

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

(8)个小伙伴在吐槽
  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 程序异常结束。
    RenShen2018-10-11 23:11
    • 阿拉灯aladeng
      我学qt也不久,你改了代码,结果运行出错,是因为以下两点原因,并不能证明文中结论是错误的 1、tagTEST tmp只是一个局部变量,出了作用域之后,tmp会被浅拷贝出一个新对象加入到list中,tmp会被析构掉,调用它的析构函数,同时释放掉tmp中x所指向的内存空间,但因为是浅拷贝,所以,list中每个tagTEST元素中x所指向的内存空间其实是null,所以程序崩溃了 2、 QLabel继承自QObject,由Qt自动管理内存,不要随便自己手动释放QObject对象
      • 看来我说的不够明显,这里你的Qt知识中再一次出现了一点误区,Qt自动管理内存是基于父子关系的, 并不是说QObject对象不需要手动释放. 只是说,当对象B的父对象指定为A对象时,此时B对象的内存管理才会由A代为管理,当A析构时,遵循先子后父的顺序,A会将B先释放. 你的代码是出现了内存泄露,所以才能运行,并不是说QList 将tagTEST tmp局部变量的生命周期扩大了,只是你的X指针泄露了
        RenShen2018-10-11 23:41
        • 阿拉灯aladeng
          你说的确实有道理,qlabel必须加入到父容器中才能将其交由父容器代为管理,但问题来了,这并不能证明文中的结论是错的(文中“元素的作用域扩大了”说法是个生动的描述,并不是真的扩大了生命周期),因为确实是浅拷贝,才会出现你程序中的异常退出,如果真照你上面的写法,那qlist中会存在很多为null的内存……而且,这里其实也算不上内存泄漏,因为内存还在管控中,只是由原来的temp交由list来管理了 :mrgreen:
        • 阿拉灯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等几种基本智能指针,可以学习一下
            RenShen2018-10-12 00:27
          • Qt 智能指针学习 - 1+1=10 - CSDN博客 https://blog.csdn.net/dbzhang800/article/details/6403285
            RenShen2018-10-12 00:33
        • 阿拉灯aladeng
          我还是不大明白,你可以基于上面代码做个修改吗,既可以在外面正常访问list中的元素,又可以避免内存泄露 :grin: