QString 存储16位QChar的字符串,其中每个QChar对应一个 UTF-16代码单元。QString 使用(写入时复制copy-on-write)来减少内存使用并避免不必要的数据复制。这也有助于减少存储16位字符而不是8位字符的固有开销。
除了QString之外,Qt还提供了存储原始字节和传统8位“\0”结尾字符串的QByteArray类。在大多数情况下,使用QString,Unicode支持使得应用程序将很容易翻译。使用QByteArray类时合适的两种主要情况是需要存储原始二进制数据时,以及内存保护至关重要时(如在嵌入式系统中)。
typedef QStringData Data;
typedef QTypedArrayData<ushort> QStringData;
class Q_CORE_EXPORT QString
{
public:
typedef QStringData Data;
//构造函数、拷贝函数、赋值运算符...........
//字符串属性函数、字符串操作函数,修改、比较、查找..........
private:
Data *d;
};
QString内部除了定义的函数之外,成员变量只有Data类型的指针,指向字符数据地址。内部采用共享机制,在QString对象赋值时只是类似智能指针执行浅拷贝,简单复制指针d的值,增加引用计数。同时QString采用写时复制机制,只在修改数据时执行深拷贝,复制字符数据并进行写操作。
QString在构造时调用QTypedArray的allocate函数,将返回的地址赋值给指针d。以QChar为参数的构造函数,size设置为1,data返回地址开始存储字符数据,末尾存储结束字符。
QString::QString(QChar ch)
{
d = Data::allocate(2);
Q_CHECK_PTR(d);
d->size = 1;
d->data()[0] = ch.unicode();
d->data()[1] = '\0';
}
以QString的const &为参数的拷贝构造函数只是复制指针,ref()增加计数值。
inline QString::QString(const QString &other) noexcept : d(other.d)
{ Q_ASSERT(&other != this); d->ref.ref(); }
析构函数中,当引用计数为零时才会调用deallocate函数释放内存。
inline QString::~QString() { if (!d->ref.deref()) Data::deallocate(d); }
在使用left()、mid()等字符串操作时会修改字符串,此时会创建新的QString对象并在构造函数使用memcpy()进行深拷贝。
QString QString::left(int n) const
{
if (uint(n) >= uint(d->size))
return *this;
return QString((const QChar*) d->data(), n);
}
QString的数据存放在指针d指向的对象中,对象类型为QTypedArrayData,继承自QArrayData。
QTypedArrayData重写的data()、allocate()、reallocateUnaligned()、deallocate()、sharedNull()都是直接调用了父结构体的函数,此外还基于data()封装了一些迭代器操作。
在QString中,类模板QTypedArrayData的模板实参T为ushort,所以是以两字节的方式存储字符数据的。data函数的返回值为ushort类型的指针,后续在QString中通过data()返回指针的[]运算符函数对字符数据进行操作。
template <class T>
struct QTypedArrayData
: QArrayData
{
typedef T* iterator;
typedef const T* const_iterator;
T *data() { return static_cast<T *>(QArrayData::data()); }
const T *data() const { return static_cast<const T *>(QArrayData::data()); }
iterator begin(iterator = iterator()) { return data(); }
iterator end(iterator = iterator()) { return data() + size; }
const_iterator begin(const_iterator = const_iterator()) const { return data(); }
const_iterator end(const_iterator = const_iterator()) const { return data() + size; }
const_iterator constBegin(const_iterator = const_iterator()) const { return data(); }
const_iterator constEnd(const_iterator = const_iterator()) const { return data() + size; }
class AlignmentDummy { QArrayData header; T data; };
Q_REQUIRED_RESULT static QTypedArrayData *allocate(size_t capacity,
AllocationOptions options = Default)
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return static_cast<QTypedArrayData *>(QArrayData::allocate(sizeof(T),
Q_ALIGNOF(AlignmentDummy), capacity, options));
}
static QTypedArrayData *reallocateUnaligned(QTypedArrayData *data, size_t capacity,
AllocationOptions options = Default)
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return static_cast<QTypedArrayData *>(QArrayData::reallocateUnaligned(data, sizeof(T),
capacity, options));
}
static void deallocate(QArrayData *data)
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
QArrayData::deallocate(data, sizeof(T), Q_ALIGNOF(AlignmentDummy));
}
static QTypedArrayData *fromRawData(const T *data, size_t n,
AllocationOptions options = Default) { //.............}
static QTypedArrayData *sharedNull() noexcept
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return static_cast<QTypedArrayData *>(QArrayData::sharedNull());
}
static QTypedArrayData *sharedEmpty()
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return allocate(/* capacity */ 0);
}
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
static QTypedArrayData *unsharableEmpty()
{
Q_STATIC_ASSERT(sizeof(QTypedArrayData) == sizeof(QArrayData));
return allocate(/* capacity */ 0, Unsharable);
}
#endif
};
QTypedArrayData重写的data()、allocate()、reallocateUnaligned()、deallocate()、sharedNull()都是直接调用了父结构体QArrayData的函数,所以字符数据和基础的操作都定义在QArrayData中,QArrayData即数据容器。
[ref|size|alloc|reserve|offset|--the real data--]
struct Q_CORE_EXPORT QArrayData
{
QtPrivate::RefCount ref;
int size;
uint alloc : 31;
uint capacityReserved : 1;
qptrdiff offset; //从标头开头开始的字节数
void *data()
{
Q_ASSERT(size == 0 || offset < 0 || size_t(offset) >= sizeof(QArrayData));
return reinterpret_cast<char *>(this) + offset;
}
const void *data() const {//.......}
//这是指数组数据的可变性,而不是 QArrayData 中数据成员表示的“标头数据”。
//共享数据(数组和标头)仍必须遵循 COW 原则。
bool isMutable() const { return alloc != 0; }
enum AllocationOption {//内存分配方式
CapacityReserved = 0x1,//调整内存大小或克隆时至少保留原始容量大小。
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
Unsharable = 0x2,//非共享的,在克隆时必须重新分配内存
#endif
RawData = 0x4,//如果设置了RawData,分配内存时就会忽略填充而使数据紧贴在QArrayData头之后。
Grow = 0x8,//会分配超出用户要求的内存,达到比用户要求数量更大的下一个2次幂
Default = 0//
};
Q_DECLARE_FLAGS(AllocationOptions, AllocationOption)
size_t detachCapacity(size_t newSize) const {//................}
AllocationOptions detachFlags() const {//................}
AllocationOptions cloneFlags() const {//................}
Q_REQUIRED_RESULT static QArrayData *allocate(size_t objectSize, size_t alignment,
size_t capacity, AllocationOptions options = Default) noexcept;
Q_REQUIRED_RESULT static QArrayData *reallocateUnaligned(QArrayData *data, size_t objectSize,
size_t newCapacity, AllocationOptions newOptions = Default) noexcept;
static void deallocate(QArrayData *data, size_t objectSize,
size_t alignment) noexcept;
static const QArrayData shared_null[2];
static QArrayData *sharedNull() noexcept { return const_cast<QArrayData*>(shared_null); }
};
通过QChar或者char实例化一个QString对象时调用allocate函数,内部调用了malloc分配内存,并将QArrayData指针对象header指向该内存,初始化标头数据,并返回header赋值给指针d。
QArrayData *QArrayData::allocate(size_t objectSize, size_t alignment,
size_t capacity, AllocationOptions options) noexcept
{
//..............
QArrayData *header = static_cast<QArrayData *>(::malloc(allocSize));
if (header) {
quintptr data = (quintptr(header) + sizeof(QArrayData) + alignment - 1)
& ~(alignment - 1);
#if !defined(QT_NO_UNSHARABLE_CONTAINERS)
header->ref.atomic.storeRelaxed(bool(!(options & Unsharable)));
#else
header->ref.atomic.storeRelaxed(1);
#endif
header->size = 0;
header->alloc = capacity;
header->capacityReserved = bool(options & CapacityReserved);
header->offset = data - quintptr(header);
}
return header;
}
如果没有定义QT_NO_CAST_FROM_ASCII
和QT_RESTRICTED_CAST_FROM_ASCII
两个宏,在QString中会定义一系列以const char*
为参数的函数,比如拷贝构造函数和拷贝赋值运算符,支持QString和字符串常量的转换和比较。而如果定义了该宏,类似 QString dd = "example"
和dd == "example"
的操作是不能通过编译的,因为相关函数将失效,并且定义QT_NO_CAST_FROM_ASCII
后将几个函数定义成私有的。
Qt提供了QLatin1String类来更高效的利用const char*的类型,dd == "example"
转换成dd == QLatin1String("example")
虽然在代码输入的时候有点长,但是两者效率差不多,同时也比使用QString::fromLatin1()
转换更快。
class QLatin1String
{
public:
Q_DECL_CONSTEXPR inline QLatin1String() noexcept : m_size(0), m_data(nullptr) {}
Q_DECL_CONSTEXPR inline explicit QLatin1String(const char *s) noexcept : m_size(s ? int(strlen(s)) : 0), m_data(s) {}
Q_DECL_CONSTEXPR explicit QLatin1String(const char *f, const char *l)
: QLatin1String(f, int(l - f)) {}
Q_DECL_CONSTEXPR inline explicit QLatin1String(const char *s, int sz) noexcept : m_size(sz), m_data(s) {}
inline explicit QLatin1String(const QByteArray &s) noexcept : m_size(int(qstrnlen(s.constData(), s.size()))), m_data(s.constData()) {}
Q_DECL_CONSTEXPR const char *latin1() const noexcept { return m_data; }
Q_DECL_CONSTEXPR int size() const noexcept { return m_size; }
Q_DECL_CONSTEXPR const char *data() const noexcept { return m_data; }
//字符串操作
//类型别名
private:
int m_size;
const char *m_data;
};
在编译时构造QString对象,适用于只能接受QString参数的场景,转换后的字符串数据存储在编译后文件的只读数据段中,使用时读取。接受const char *或QLatin1String直接使用比该宏更高效。
const char *字符默认UTF-8编码,QString采用UTF-16编码。根据源文件相应格式解码后,Windows环境下可执行文件中的字符串是本地编码格式GBK编码,运行时以UTF-8解码,再进行UTF-16编码就会出现乱码。
解决方法:
1、解码:QStringLiteral()
宏或者QString::fromLocal8Bit()
封装字符串
2、编码:编译器采用UTF-8编码生成可执行文件
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
QStringRef 提供 API 的只读子集。此类旨在提高操作从现有实例获取的子字符串时子字符串处理的性能。QStringRef 通过简单地引用原始字符串的一部分来避免标准的内存分配和引用计数开销(修改字符串操作如mid、left、right等,会创建一个新的字符串,申请空间并拷贝数据)。这在低级代码(例如解析器中使用的代码)中可能被证明是有利的,但代价是可能更复杂的代码。
对于大多数用户来说,使用 QStringRef 而不是 QStringRef 没有语义上的好处,因为 QStringRef 需要注意内存管理问题,这可能会使代码的编写和维护更加复杂。
class Q_CORE_EXPORT QStringRef {
const QString *m_string;
int m_position;
int m_size;
public:
inline QStringRef(const QString *string, int position, int size);
//............
};