QStringLiteral 是Qt5中新引入的一个用来从“字符串常量”创建QString对象的宏(字符串常量指在源码中由”"包含的字符串)。在这篇博客我讲解释它的的内部实现和工作原理。

提要

让我们从它的使用环境开始说起: 假设你想要在Qt5中从字符串常量初始化一个QString对象,你应该这样:

  • 大多数情况:使用QStringLiteral(“某字符串”) --如果它最终转会换成QString的话
  • 使用QLatin1String(“某字符串”) --如果使用的函数有支持QLatin1String的重载(比如operator==, operator+, startWith, replace等)的话

我把这段话放在最开始是为了那些不怎么想了解其具体技术细节的人着想。

继续阅读你将了解QStringLiteral是如何工作的。

回顾QString的工作方式

QString,和Qt中的其他类一样,是一个”隐式共享类“。它唯一的数据成员就是一个指向其“私有”数据的指针。 QStringData由 malloc函数分配空间,并且在其后(同一块内存块)分配了足够的空间来存放实际的字符数据。

// 为了此博客的目标做了简化
struct  QStringData  {QtPrivate::RefCount ref;  // 对QAtomicInt进行封装int  size;  // 字符串的大小uint  alloc :  31  ;  // 该字符串数据之后预留的内存数uint  capacityReserved :  1  ;  // reserve()使用到的内部细节qptrdiff  offset;  // 数据的偏移量 (通常是 sizeof(QStringData))inline  ushort  *data(){  return  reinterpret_cast  <  ushort  *>(  reinterpret_cast  <  char  *>(  this  ) + offset); }
};
// ...
class  QString  {QStringData  *d;
public  :// ... 公共 API ...
};

offset是指向QStringData相对数据的指针。在Qt4中它是一个实际的指针。稍后我们会讲到为什么这个指针发生了变化。

在字符串中保存的实际数据是UTF-16编码的,这意味着每一个字符都占用了两个字节。

CSDN QT技术栈大纲:Qt开发必备技术栈学习路线和资料

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓

文字与转换

字符串常量是指直接在源码中用引号包起来的字符串。
这有一些例子。(假设action,string和filename都是QString类型)

o->setObjectName(  "MyObject"  );
if  (action ==  "rename"  )string.replace(  "%FileName%"  , filename);

第一行我们调用了 QObject::setObjectName(const QString&)函数。 这里有一个通过构造函数产生的从const char*到QString的隐式转换。一个新的QStringData获取了足够保存 "MyObject"字符串的空间,接着这个字符串 从 UTF-8转码为UTF-16并拷贝到Data内 。
在最后一行调用QString::replace(const QString &, const QString &)函数的时候也发生了相同的操作,一个新的QStringData获取了保存 "%FileName%"的空间。

有办法避免QStringData的内存分配和字符串的复制操作吗?

当然有,创建临时的QString对象耗费甚巨,解决这个问题的一个方法是重载一个 const char*作为参数的通用方法。 于是 我们有了下面的这几个赋值运算符重载:

bool  operator==(  const  QString  &,  const  QString  &);
bool  operator==(  const  QString  &,  const  char  *);
bool  operator==(  const  char  *,  const  QString  &)

这些重载运算可以直接操作原始char*,不必为了我们的字符串常量去创建临时QString对象。

编码与 QLatin1String

在Qt5中,我们把char* 字符串的默认编码 改成了UTF-8。但是相对纯ASCII或者latin1而言,很多算法处理UTF-8编码数据的时候会慢很多。

因此你可以使用QLatin1String,它是在确定编码的情况下对char*进行的轻量级封装。一些接收QLatin1String为参数的重载函数能够直接对纯latin1数据进行处理,不必进行编码转换。

所以我们的第一个例子现在看起来是这样了:

o->setObjectName(  QLatin1String  (  "MyObject"  ));
if  (action ==  QLatin1String  (  "rename"  ))string.replace(  QLatin1String  (  "%FileName%"  ), filename);

好消息是QString::replace与operator==操作有了针对QLatin1String的重载函数,所以现在快很多。

在对s etObjectName的调用中,我们避免了从UTF-8的编码转换,但是我们仍然需要进行一次从QLatin1String到QString的(隐性)转换, 所以不得不堆中分配QStringData的空间。

介绍 QStringLiteral

有没有可能在调用setObjectName的时候同时阻止分配空间与复制字符串常量呢?当然,这就是QStringLiteral所做的。

这个宏会在编译时尝试生成QStringData,并初始化其全部字段。它甚至是存放在 .rodata内存段 中所以可以在不同的进程中共享。

为了实现这个目标我们需要两个C++语言的特性:

  1. 在编译的时候生成UTF-16格式字符串的可能性
    Win环境下我们可以使用宽字符 L"String"。 Unix环境下我们使用新的C++11 Unicode字符串: u"String"。( GCC 4.4和clang支持。)
  2. 从表达式中创建静态数据的能力
    我们希望能把QStringLiteral放在代码的任何地方。一种实现方法就是把一个静态的QStringData放入一个C++11 lambda 表达式。(MSVC 2010和GCC 4.5支持) (我们同样用到了GCC __extension__ ({ }) )

实现

我们需要一个同时包含了QStringData和实际字符串的POD结构。这个结构取决于我们生成的UTF-16时使用的实现方法。

/* 定义QT_UNICODE_LITERAL_II并且声明基于编译器的qunicodechar   */
#if defined(Q_COMPILER_UNICODE_STRINGS)// C++11 unicode 字符串#define QT_UNICODE_LITERAL_II(str) u"" strtypedef  char16_t qunicodechar;
#elif __SIZEOF_WCHAR_T__ == 2// wchar_t是两个字节  (这里条件被适当简化)#define QT_UNICODE_LITERAL_II(str) L##strtypedef  wchar_t  qunicodechar;
#elsetypedef  ushort  qunicodechar;  //fallback
#endif
// 会包含字符串的结构体
// N是字符串大小
template  <  int  N>
struct  QStaticStringData
{QStringData  str;qunicodechar data[N +  1  ];
};
// 包裹了指针的辅助类使得我们可以将其传递给QString的构造函数
struct  QStringDataPtr
{  QStringData  *ptr; };
#if defined(QT_UNICODE_LITERAL_II)
// QT_UNICODE_LITERAL needed because of macro expension rules
# define QT_UNICODE_LITERAL(str) QT_UNICODE_LITERAL_II(str)
# if defined(Q_COMPILER_LAMBDA)
#  define QStringLiteral(str) \([]() ->   QString    { \enum    { Size =   sizeof  (  QT_UNICODE_LITERAL  (str))/  2    -   1    }; \static    const    QStaticStringData  <Size> qstring_literal = { \Q_STATIC_STRING_DATA_HEADER_INITIALIZER(Size), \QT_UNICODE_LITERAL  (str) }; \QStringDataPtr    holder = { &qstring_literal.str }; \const    QString    s(holder); \return    s; \}()) \
# elif defined(Q_CC_GNU)
// 使用GCC的 __extension__ ({ }) 技巧代替lambda
// ... <skiped> ...
# endif
#endif
#ifndef QStringLiteral
// 不支持lambdas, 不是GCC,或者GCC为C++98模式,使用4字节wchar_t
// fallback, 返回一个临时的QString
// 默认认为源码为utf-8编码
# define QStringLiteral(str) QString::fromUtf8(str, sizeof(str) - 1)
#endif

让我们稍微简化一下这个宏,然后看看这个宏是如何展开的

o->setObjectName(  QStringLiteral  (  "MyObject"  ));
// 将展开为:
o->setObjectName(([]() {// 我们在一个返回QStaticString的lambda表达式中// 使用sizeof计算大小(去掉末尾的零结束符)enum  { Size =  sizeof  (u  "MyObject"  )/  2  -  1  };// 初始化(静态数据在编译时初始化)static  const  QStaticStringData  <Size> qstring_literal ={ {  /* ref = */  -  1  ,/* size = */  Size,/* alloc = */  0  ,/* capacityReserved = */  0  ,/* offset = */  sizeof  (  QStringData  ) },u  "MyObject"  };QStringDataPtr  holder = { &qstring_literal.str };QString  s(holder);  // 调用QString(QStringDataPtr&)构造函数return  s;}())  // 调用lambda);

引用计数器初始化为-1。由于这是只读数据所以这个负数永远不会发生增减。

可以看到,我们使用一个偏移量(qptrdiff)而不是向Qt4中那样使用一个指向字符串的指针是多么重要。把一个指针放在一个只读的部分里面是完全不可能的,因为指针很可能会在加载时 重新分配 。这意味着每次启动或者调用程序、库文件的时候操作系统都不得不用重分配表重写全部的指针地址。

数据结果

为了好玩,我们来看一段从一个非常简单的对QStringLiteral的调用后生成的汇编代码。 可以看到下面几乎没有什么代码,还有.rodata段的数据分布。

QString  returnAString() {return  QStringLiteral  (  "Hello"  );
}

在x84_64用g++ -O2 -S -std=c++0x (GCC 4.7)编译后

      .  text.  globl    _Z13returnAStringv.  type     _Z13returnAStringv, @function
_Z13returnAStringv:; load the address of the QStringData into %rdxleaq      _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal(%rip), %rdxmovq      %rdi, %rax; copy the QStringData from %rdx to the QString return object; allocated by the caller.  (the QString constructor has been inlined)movq      %rdx, (%rdi)ret.  size     _Z13returnAStringv, .-_Z13returnAStringv.  section      .rodata.  align  32.  type     _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal, @object.  size     _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal,  40
_ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal:.  long     -  1     ; ref.  long     5      ; size.  long     0      ; alloc + capacityReserved.  zero     4      ; padding.  quad     24     ; offset.  string  "H"    ; the data. Each .string add a terminal ''.  string  "e".  string  "l".  string  "l".  string  "o".  string  "".  string  "".  zero     4

结论

我希望读完这篇博客的现在,你们能更好的理解什么时候用和不用QStringLiteral。
还有一个宏叫做QByteArrayLiteral,工作原理和QStringLiteral几乎一模一样但是创建的是QByteArray。

本文福利,费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓

Qt QStringLiteral相关推荐

  1. Qt之 QStringLiteral

    介绍 QStringLiteral是一个宏,他可以把编译期间代码中的字符串string直接构造为QString类型的对象,通过这个方法可以在与西宁过程中节约一定的构造上的开销.它更大的作用在于他可以它 ...

  2. qt中使用QStringLiteral宏来实现带参数的输出

    叙述 在QStringLiteral宏中输入字符串,但是字符串中有些值想用变量来输出,直接上代码: qDebug()<<QStringLiteral("已经重连%1次未果,请尝试 ...

  3. qt mysql now()_Qt + mysql 運用 (項目一)

    自己整合了一些資料方便以后查看,另外參考了一些資料嘗試做了個學生管理系統 以下資料若有錯誤或有侵權的地方,請前輩們指正,謝謝! 在Qt項目中右鍵執行qmake,之后在運行. 另外一種方法是在MVS20 ...

  4. Qt之QFutureWatcher

    简述 QFuture 表示异步计算的结果,QFutureWatcher 则允许使用信号和槽监视 QFuture,也就是说,QFutureWatcher 是为 QFuture 而生的. 简述 详细描述 ...

  5. 7.QML Qt Quick——基于Qt Quick Controls 2实现图片浏览器

    Qt Quick Controls 2提供了一组UI控件,例如按钮,标签,复选框,滑块等(用之查之即可).用于在Qt Quick中创建用户界面.UI控件很多,这里通过一个图片浏览器的实现来逐步讲解 图 ...

  6. qt 实现的电视遥控系统,如何让qt响应来自遥控器的按键信息?

    结帖率 60% 如题: 目前在做一个项目,使用qt实现一个类似于机顶盒的遥控系统,那么关键的问题来了,如何让qt响应遥控器的按键信息呢? 应该分两步吧: 1.搭载qt的终端接收来自遥控器的按键信息,并 ...

  7. qt中对任务繁忙时QProgressDialog的使用

    在qt中对于后台数据处理比较耗时情况下,如果放在UI主线程中来处理,给人一种卡顿的感觉,可以将耗时的部分放在次线程中来处理,主线程来显示处理进度. 在次线程中处理时,需要将处理进度信息传给UI线程,只 ...

  8. qt系统托盘显示、无主窗体

    系统图盘是应用程序经常用到的一个控件,当应用程序需要长时间存在的时候,这个控件会变得非常有用,比如,窗口隐藏,显示,关于.关闭等接口都可以放在图盘中处理,今天与到一个问题,需求是这样的:只需要显示图盘 ...

  9. 把Qt的界面文件(.ui文件)生成源文件(.h或.cpp)封装成链接库

    前言 在用Qt做开发时,为了方便快速,一般都使用Qt设计师界面类来做界面相关的布局,这个类在当前工程中是没有.cpp或.h文件的,但主类又有引入了这个头文件,点开转到定义或声明时,是打不开的,如下图: ...

最新文章

  1. 任正非内部重磅发言:华为不可能简单学阿里、亚马逊
  2. 年度国家科学技术奖:高文周志华王海峰唐杰等上榜,两位“30后”院士获最高奖...
  3. vue-router 在项目中的使用
  4. matlab基于ssd的角点匹配_基于关键点的目标检测
  5. 准确率 召回率_机器学习中F值(F-Measure)、准确率(Precision)、召回率(Recall)
  6. 中运用_钢琴教学中指法的安排与运用
  7. 三菱FX3U与三菱变频器 modbus RTU通讯案例 采用485方式,modbus RTU协议。 与变频器通讯,控制启停,频率,加减速时间设定,频率
  8. 儒豹公布09年7月手机搜索热门关键词排行榜
  9. 简述冯诺依曼工作原理_冯诺依曼提出的计算机的基本工作原理是什么?
  10. Probabilistic Matrix Factorization(概率矩阵分解)
  11. 三级分销系统产品设计原理
  12. 5990. 找出数组中的所有孤独数字
  13. 用HTML+CSS做一个漂亮简单大学生校园班级网页(web前端期末大作业)
  14. 英伟达辟谣 RTX 3060 被破解传闻
  15. JS 生成永不重复的随机序列号
  16. C#winform中OpenFileDialog的用法
  17. TypeScript泛型工具
  18. 社交媒体中有哪些有趣的数据?能挖掘出哪些价值?
  19. 使用crow创建一个c++的web服务
  20. 论文阅读:FACIAL: Synthesizing Dynamic Talking Face with Implicit Attribute Learning

热门文章

  1. 要么做,要么滚!没有试试看这一说
  2. linux中搜索文件内容关键字
  3. 【智能制造】智能制造与智能工厂的主要特征
  4. python win32com批量导出.ppt/.pptx文件所有图片
  5. 将PPT导出图片分辨率提高的方法
  6. 使用 Screen 创建并管理多个 shell
  7. 开篮球馆需要什么_开一个篮球馆怎么样?开篮球馆需要办理什么手续?
  8. 微信小程序加水印(含代码效果图)
  9. vb标准(一):用户界面的设计
  10. 【C++】由于找不到xxx.dll,无法继续执行代码,重新安装程序可能会解决此问题。(解决办法)