Qt 学习之路 2(3):Hello, world!

想要学习 Qt 开发,首先要搭建 Qt 开发环境。好在现在搭建 Qt 开发环境还是比较简单的。我们可以到 Qt 官方网站找到最新版本的 Qt。在 Downloads 页面,可以看到有几个版本的 Qt:Qt SDK、Qt Library、Qt Creator 等等。它们分别是:

  • Qt SDK:包含了 Qt 库以及 Qt 的开发工具(IDE、i18n 等工具),是一套完整的开发环境。当然,这个的体积也是最大的(Windows 平台大约 1.7G,其它平台大约 780M)。如果仅仅为开发 Qt,建议选择这一项下载安装。安装方法很简单,同普通程序没有什么区别。所需注意的是,安装过程中可能能够提供选择是否安装源代码,是否安装 mingw 编译器(Windows),这个就按照需要进行选择即可。另外值得说明的是,Qt SDK 通常比单独的 Qt 库版本要旧一些。比如现在 Qt 正式版是 4.8.2,但是 Qt SDK 的最新版 1.2.1 中包含的 Qt 是 4.8.1。
  • Qt Library:仅包含 Qt 库。如果您已经安装了 Qt 开发环境,为了升级一下 SDK 中提供的 Qt 库版本,就可以安装这一个。安装过之后,应该需要在 IDE 中配置安装路径,以便找到最新版本的 Qt(如果不是覆盖安装的话)。
  • Qt Creator:基于 Qt 构建的一个轻量级 IDE,现在最新版是 2.5.2,还是比较好用的,建议使用 Qt Creator 进行开发。当然,如果你已经习惯了 VS2010 这样的工具,可以在页面最下方找到相应的 Addin。很多朋友希望阅读 Qt 代码以提高自己的开发水平。当然,Qt 的经典代码是 KDE,不过这个项目不大适合初学者阅读。此时,我们就可以选择阅读 Qt Creator 的代码,它的代码还是比较清晰的。

当我们安装完成 Qt 开发环境之后,就可以使用 Qt Creator 进行开发。在本系列中,豆子会一直使用这个 IDE 进行讲解。至于编译器,豆子一般会使用 mingw 或者 gcc。为了编译 Qt 5 的程序,你应该使用 gcc 4.5 以上的版本,这意味着,如果你是使用 Qt SDK 自带的 mingw,是不能编译 Qt 5 的程序的(因为这个自带的版本是 4.4),你应该升级 mingw 为 4.5 以上版本。

至此,我们已经有了 Qt 4 的完整开发环境。如果你想要开发 Qt 5,由于现在(2012 年 8 月) Qt 5 还处于测试阶段,并没有提供二进制库,所以我们需要使用 git 自己获取 Qt 5 的源代码自己编译(一般需要几个小时时间)。豆子非常不建议在 Windows 上编译 Qt 5,因为可能会出很多问题。如果你想尝试,可以参考这里。豆子提一句,在 Windows 上编译 Qt 5,需要安装 perl(并且要安装 GetOpt::Long 模块)、python 和 git,并且需要找到彼此路径。相比而言,Linux 上面就会简单很多。豆子建议,如果你想在 Windows 上尝试 Qt 5,可以考虑安装一个虚拟机,使用 Linux 平台;或者自己试着直接在 Windows 本地编译。豆子的环境是使用 openSUSE。openSUSE 的 Qt 5.0 Development Snapshots 已经提供了 Qt 5 二进制版本,免去了编译的过程。基于此,本文的 Qt 4 版本将在 Windows 平台上使用 mingw 进行测试;Qt 5 版本将在 openSUSE 上使用 gcc 4.6 进行测试。在未来官方推出 Qt 5 Windows 平台的二进制版本,也不排除在 Windows 上面测试 Qt 5 代码。

在 Qt Creator 中,我们可以在菜单栏的工具-选项-构建和运行的“Qt 版本”和“工具链”这两个选项卡中配置 Qt Creator 所使用的 Qt 版本和编译器。这或许是最重要的步骤,包括添加新的 Qt 版本以及以后的切换编译器或者 Qt 升级等。

下面尝试开发第一个 Qt 项目:HelloWorld。在 Qt Creator 中新建一个工程:

Qt Creator 新建工程

点击这个“新建文件或工程”,在左侧选择项目-Applications,中间选择 Qt Gui 应用,然后点击“选择…”:

Qt Creator GUI 工程

在弹出的对话框中填写名称、创建路径等信息:

Qt Creator 工程名字及位置

点击“下一步”,选择该工程的编译器。这里我们只选择 mingw 调试即可(在以后的项目中,根据自己的需要选择。)Shadow Build 的含义是“影子构建”,即将构建生成的文件不放在源代码文件夹下。这样可以最大地保持源代码文件夹的整洁。

Qt Creator 编译器选择

点击“下一步”,可以选择生成的主窗口文件。不过在我们的简单示例中是不需要这么复杂的窗口的,因此我们尽可能简单地选择,将“创建界面”的选择去除:

Qt Creator MainWindow

终于到了最后一步。这里是在询问我们是否添加版本控制。对于我们的小项目当然是不需要的,所以选择“无”,然后点击“完成”即可:

Qt Creator 版本管理

可以看到,Qt Creator 帮助我们在 HelloWorld 项目文件夹下生成了四个文件:main.cpp,mainwindow.cpp,mainwindow.h 和 HelloWorld.pro。pro 文件就是 Qt 工程文件(project file),由 qmake 处理,生成 make 程序所需要的 makefile;main.cpp 里面就是一个main函数,作为应用程序的入口函数;其他两个文件就是先前我们曾经指定的文件名的文件。

我们将 main.cpp 修改如下:

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel label("Hello, world");
    label.show();

    return app.exec();
}

点击 Qt Creater 左侧下面的绿色三角按钮即可运行(这里一共有三个按钮,从上到下分别是“运行”、“调试”和“构建”)。如果没有错误的话,就会看到运行结果:

Qt HelloWorld 程序运行结果

这个程序有这么几行。我们解释一下。

前两行是 C++ 的 include 语句,这里我们引入的是QApplication以及QLabel这两个类。main()函数中第一句是创建一个QApplication类的实例。对于 Qt 程序来说,main()函数一般以创建 application 对象(GUI 程序是QApplication,非 GUI 程序是QCoreApplicationQApplication实际上是QCoreApplication的子类。)开始,后面才是实际业务的代码。这个对象用于管理 Qt 程序的生命周期,开启事件循环,这一切都是必不可少的。在我们创建了QApplication对象之后,直接创建一个QLabel对象,构造函数赋值“Hello, world”,当然就是能够在QLabel上面显示这行文本。最后调用QLabelshow()函数将其显示出来。main()函数最后,调用app.exec(),开启事件循环。我们现在可以简单地将事件循环理解成一段无限循环。正因为如此,我们在栈上构建了QLabel对象,却能够一直显示在那里(试想,如果不是无限循环,main()函数立刻会退出,QLabel对象当然也就直接析构了)。

示例程序我们已经讲解完毕。下面再说一点。我们可以将上面的程序改写成下面的代码吗?

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QLabel *label = new QLabel("Hello, world");
    label->show();

    return app.exec();
}

答案是,可以建议这样做!

首先,按照标准 C++ 来看这段程序。这里存在着内存泄露。当exec()退出时(也就是事件循环结束的时候。窗口关闭,事件循环就会结束),label 是没办法 delete 的。这就造成了内存泄露。当然,由于程序结束,操作系统会负责回收内存,所以这个问题不会很严重。即便你这样修改了代码再运行,也不会有任何错误。

早期版本的 Qt 可能会有问题(详见本文最后带有删除线的部分,不过豆子也没有测试,只是看到有文章这样介绍),不过在新版本的 Qt 基本不存在问题。在新版本的 Qt 中,app.exec()的实现机制确定,当最后一个可视组件关闭之后,主事件循环(也就是app.exec())才会退出,main()函数结束(此时会销毁app)。这意味着,所有可视元素已经都关闭了,也就不存在后文提到的,QPaintDevice没有QApplication实例这种情况。另外,如果你是显式关闭了QApplication实例,例如调用了qApp->quit()之类的函数,QApplication的最后一个动作将会是关闭所有窗口。所以,即便在这种情况下,也不会出现类这种问题。由于是在main()函数中,当main()函数结束时,操作系统会回收进程所占用的资源,相当于没有内存泄露。不过,这里有一个潜在的问题:操作系统只会粗暴地释放掉所占内存,并不会调用对象的析构函数(这与调用delete运算符是不同的),所以,很有可能有些资源占用不会被“正确”释放。事实上,在最新版的 Sailfish OS 上面就有这样的代码:

#include <QApplication>

int main(int argc, char *argv[])
{
    QScopedPointer<QApplication> app(new QApplication(argc, argv));
    QScopedPointer<QQuickView> view(new QQuickView);
    view->setSource("/path/to/main.qml");
    ...
    return app->exec();
}

这段代码不仅在堆上创建组件实例,更是把QApplication本身创建在了堆上。不过,注意,它使用了智能指针,因此我们不需要考虑操作系统直接释放内存导致的资源占用的问题。

当然,允许使用并不一定意味着我们建议这样使用。毕竟,这是种不好的用法(就像我们不推荐利用异常控制业务逻辑一样),因为存在内存泄露。而且对程序维护者也是不好的。所以,我们还是推荐在栈上创建组件。因为要靠人工管理newdelete的出错概率要远大于在栈上的自动控制。除此之外,在堆上和在栈上创建已经没有任何区别。

如果你必须在堆上创建对象,不妨增加一句:

label->setAttribute(Qt::WA_DeleteOnClose);

这点提示足够告诉程序维护者,你已经考虑到内存问题了。

严重的是,label 是建立在堆上的,app 是建立在栈上的。这意味着,label 会在 app 之后析构。也就是说,label 的生命周期长于 app 的生命周期。这可是 Qt 编程的大忌。因为在 Qt 中,所有的QPaintDevice必须要在有QApplication实例的情况下创建和使用。大家好奇的话,可以提一句,QLabel继承自QWidgetQWidget则是QPaintDevice的子类。之所以上面的代码不会有问题,是因为 app 退出时,label 已经关闭,这样的话,label 的所有QPaintDevice一般都不会被访问到了。但是,如果我们的程序,在 app 退出时,组件却没有关闭,这就会造成程序崩溃。(如果你想知道,怎样做才能让 app 退出时,组件却不退出,那么豆子可以告诉你,当你的程序在打开了一个网页的下拉框时关闭窗口,你的程序就会崩溃了!)

109 Comments

  1. nihao 2012年8月23日
  2. Anonymous 2012年8月23日
    • DevBean 2012年8月24日
  3. 大米 2012年9月1日
    • DevBean 2012年9月2日
      • 大米 2012年9月2日
  4. anonymous 2012年9月24日
    • anonymous 2012年9月24日
      • DevBean 2012年9月24日
        • anonymous 2012年9月24日
          • DevBean 2012年9月24日
  5. Michael_BJFU 2012年10月2日
    • DevBean 2012年10月2日
  6. great 2012年10月12日
    • DevBean 2012年10月13日
      • great 2013年1月19日
        • 豆子 2013年1月21日
  7. langziyang 2012年11月23日
    • DevBean 2012年11月23日
  8. BobYang 2012年11月24日
  9. BobYang 2012年11月24日
    • DevBean 2012年11月25日
      • BobYang 2012年11月25日
      • BobYang 2012年11月25日
        • DevBean 2012年11月25日
  10. zhupp 2013年1月6日
    • 豆子 2013年1月6日
  11. hwb 2013年1月13日
    • 豆子 2013年1月14日
      • hwb 2013年1月14日
  12. andmoe 2013年1月15日
    • 豆子 2013年1月16日
  13. why_not 2013年1月31日
    • 豆子 2013年2月5日
  14. realfan 2013年2月7日
    • 豆子 2013年2月7日
      • realfan 2013年2月7日
      • xiaohui_hubei 2014年1月10日
  15. Kiwee 2013年4月15日
    • 豆子 2013年4月16日
      • nighcate 2015年1月16日
        • 豆子 2015年1月17日
  16. hailong 2013年5月9日
    • 豆子 2013年5月9日
  17. 信相一号 2013年5月11日
    • 豆子 2013年5月12日
      • 信相一号 2013年5月13日
        • 豆子 2013年5月13日
          • 杨超 2014年6月8日
          • 豆子 2014年6月12日
  18. zeroten 2013年6月10日
    • 豆子 2013年6月14日
  19. hilbet 2013年6月15日
    • 豆子 2013年6月17日
  20. twyok 2013年6月24日
    • 豆子 2013年6月24日
  21. twyok 2013年6月24日
    • 豆子 2013年6月25日
      • twyok 2013年6月25日
  22. ustckun 2013年7月22日
    • 豆子 2013年7月22日
      • ustckun 2013年7月22日
      • 张子帅 2014年4月20日
        • 豆子 2014年4月21日
  23. rewriter 2013年11月19日
    • 豆子 2013年11月19日
  24. LSH 2013年12月2日
    • 豆子 2013年12月2日
  25. worldsing 2013年12月31日
    • 豆子 2014年1月3日
  26. xiaohui_hubei 2014年1月10日
    • 豆子 2014年1月11日
  27. Jimmy 2014年2月18日
    • 豆子 2014年2月18日
  28. robberjohn 2014年3月17日
    • 豆子 2014年3月18日
  29. 呵呵 2014年4月9日
    • 呵呵 2014年4月9日
  30. 壹号 2014年4月14日
    • 豆子 2014年4月15日
  31. 今朝有酒今朝醉 2014年7月5日
    • 豆子 2014年7月6日
  32. d 2014年11月23日
    • d 2014年11月23日
  33. Qter 2014年12月19日
    • 豆子 2014年12月21日
  34. xtbliss158 2015年3月4日
    • 豆子 2015年3月9日
  35. 肉松 2015年3月5日
  36. cssin 2015年4月28日
    • 豆子 2015年6月3日
  37. Bill 2015年7月30日
  38. Shana 2015年9月20日
    • 豆子 2015年9月21日
  39. 我是安娜 2016年5月13日
    • 豆子 2016年5月20日
  40. iamhuskar 2016年5月23日
    • 豆子 2016年5月23日
  41. 天丝 2016年5月23日
    • 豆子 2016年5月23日
      • 天丝 2016年7月8日
  42. Ollog 2016年7月5日
    • 豆子 2016年7月8日
  43. 胡马依北风 2016年10月17日
  44. 胡马依北风 2016年10月17日
    • 豆子 2016年10月17日
  45. 张仙 2017年3月7日
  46. HandsomePot 2018年9月7日
    • 豆子 2018年9月8日

Leave a Reply