Qt——容器类(译)
注:本文是我对Qt官方文档的翻译,错误之处还请指正。
原文链接:Container Classes
介绍
Qt库提供了一套通用的基于模板的容器类,可以用这些类存储指定类型的项。比如,你需要一个大小可变的QString的数组,则使用QVector<QString>。
这些容器类比STL(C++标准模板库)容器设计得更轻量、更安全并且更易于使用。如果对STL不熟悉,或者倾向于用“Qt的方式”,那么你可以使用这些类,而不去用STL的类。
这些容器类是隐式共享的(可参考我的一篇博文)、可重入的,并且对速度、内存消耗等进行了优化。除此之外,当它们作为只读的容器时是线程安全的,所有线程都可以使用它们。
你可以用两种方式遍历容器内存储的项:Java风格的迭代器和STL风格的迭代器。Java风格的迭代器更易于使用,并且提供了更高级的功能;STL风格的迭代器更高效,并且可以和Qt与STL的泛型算法一起使用。
Qt还提供了foreach关键字使我们方便地遍历容器中的项。
容器类
Qt提供了几个有序容器:QList、QLinkedList、QVector、QStack和QQueue。大多数时候,QList是最好的选择,虽然是用数组实现的,但在它的首尾添加元素都非常快。如果你需要一个链表(linked-list)就用QLinkedList;想要你的项在内存中连续存储,就使用QVector。QStack和QQueue(栈和队列)分别提供了后进先出(LIFO)和先进先出(FIFO)的机制。
Qt还有一些关联容器:QMap、QMultiMap、QHash、QMultiHash、QSet。“Multi”容器支持一个键对应多个值。“Hash”容器在有序集合上使用hash函数进行快速的查找,而没有用二叉搜索。
作为特殊的情况,QCache和QContiguousCache类在有限的缓存中提供对对象高效的哈希查找。
类 | 概述 |
QList<T> | 这是目前使用最频繁的容器类,它存储了指定类型(T)的一串值,可以通过索引来获得。本质上QList是用数组实现的,从而保证基于索引的访问非常快。可以通过QList::append()和QList::prepend在两端添加项,或者通过QList::insert()在中间插入项。QStringList是从QList<QString>得到的。 |
QLinkedList<T> | 类似于QList,但它使用迭代器而不是整数索引来获得项。当在一个很大的list中间插入项时,它提供了更好的性能,并且它有更好的迭代器机制。(只要那一项存在,指向那一项的迭代器依然保持有效。但插入或移除之后,QList中的迭代器可能会失效) |
QVector<T> | 在内存中相邻的位置存储一组值,在开头或中间插入会非常慢,因为它会导致内存中很多项移动一个位置。 |
QStack<T> | QVector的一个子类,提供后进先出的机制。在当前的QVector中增加了几个方法:push()、pos()、top()。 |
QQueue<T> | QList的一个子类,提供了先进先出的机制,在当前的QList中增加了几个方法:enqueue()、dequeue()、head()。 |
QSet<T> | 单值的数学集合,能够快速查找。 |
QMap<Key, T> | 提供了字典(关联数组)将类型Key的键对应类型T的值。通常一个键对应一个值,QMap以Key的顺序存储数据,如果顺序不重要,QHash是一个更快的选择。 |
QMultiMap<Key, T> | QMap的子类,提供了多值的接口,一个键对应多个值。 |
QHash<Key, T> | 和QMap几乎有着相同的接口,但查找起来更快。QHash存储数据没有什么顺序。 |
QMultiHash<Key, T> | QHash的子类,提供了多值的接口。 |
容器是可嵌套的。比如当键是QString类型、值是QList<int>类型时,用QMap<QString, QList<int> >是最好的选择,唯一的缺点是你必须在结尾的两个尖括号(>)之间插入一个空格,否则C++编译器可能会误将两个>当作右移运算符来解释,出现语法错误。
存储在容器中的值可以是任何可赋值的数据类型,为了达到这一点,一个类型必须有默认构造函数、拷贝构造函数还有一个赋值运算符。这个已经涵盖了大多数你可能想要存在容器中的类型,包括基本类型,比如int和double、指针类型,还有Qt中的类型,比如QString、QDate和QTime,但是它不包括QObject或者QObject的子类(QWidget, QDialog, QTimer等等)。如果你尝试使用QList<QWidget>,编译器可能会提示QWidget的拷贝构造函数和赋值操作符不可用。所以如果你想在容器中存储这些类型,把它们当做指针就行了,比如QList<QWidget *>。
这里有一个例子,达到可赋值的数据类型条件的一个普通数据类型:
class Employee
{
public:
Employee() {}
Employee(const Employee &other); Employee &operator=(const Employee &other); private:
QString myName;
QDate myDateOfBirth;
};
如果我们没有提供拷贝构函数或赋值运算符,C++会提供“一个值一个值地赋值”的默认实现。而且,如果你没有提供任何构造函数,C++将提供一个默认的构造函数,使用默认构造函数初始化它的成员。虽然没有任何显式的构造函数或赋值运算符,下面的数据类型可以被存在容器中:
struct Movie
{
int id;
QString title;
QDate releaseDate;
};
有些容器对它们能够存储的数据类型有特殊的要求,例如QMap<Key, T>键Key的必须提供<()运算符。在一些情况中,特定的函数有特殊的要求,达不到要求的话编译器将会报错。
Qt的容器提供运算符<<()和运算符>>(),这样一来它们很容易使用QDataStream来读写,这意味着容器中的数据类型也必须支持运算符<<()和>>()。我们可以对上面的Movie类做一些事:
QDataStream &operator<<(QDataStream &out, const Movie &movie)
{
out << (quint32)movie.id << movie.title
<< movie.releaseDate;
return out;
} QDataStream &operator>>(QDataStream &in, Movie &movie)
{
quint32 id;
QDate date; in >> id >> movie.title >> date;
movie.id = (int)id;
movie.releaseDate = date;
return in;
}
迭代器类
迭代器提供了获得容器中项的一套方法,Qt容器类有两种类型的迭代器:Java风格的以及STL风格的。当调用非const的成员函数将容器中的数据从隐式共享的拷贝中修改或分离时,两种迭代器都会失效。
Java风格的迭代器
Java风格的迭代器在Qt4中加入,比STL风格的迭代器更易于使用,但是以轻微的效率作为代价,它们的API以Java的迭代器类为模型。
对于每个容器类,都有两种Java风格的迭代器类型:一种是只读,另一种是可读写。
容器 | 只读迭代器 | 可读写迭代器 |
---|---|---|
QList<T>, QQueue<T> | QListIterator<T> | QMutableListIterator<T> |
QLinkedList<T> | QLinkedListIterator<T> | QMutableLinkedListIterator<T> |
QVector<T>, QStack<T> | QVectorIterator<T> | QMutableVectorIterator<T> |
QSet<T> | QSetIterator<T> | QMutableSetIterator<T> |
QMap<Key, T>, QMultiMap<Key, T> | QMapIterator<Key, T> | QMutableMapIterator<Key, T> |
QHash<Key, T>, QMultiHash<Key, T> | QHashIterator<Key, T> | QMutableHashIterator<Key, T> |
在这里,我们只关注QList和QMap。QLinkedList、QVector和QSet与QList的迭代器有同样的接口;QHash与QMap迭代器也有同样的接口。
与STL风格的迭代器不同,Java风格的迭代器指向项之间的位置,而不是直接指向项。由于这个原因,它们指向第一项之前,或者最后一项之后,或者两项之间。下面的图展示了包含4项的list的有效的迭代器位置,用红色箭头表示:
下面是一个典型的例子,迭代器按顺序循环遍历QList<QString>的所有元素,并把它们打印到控制台上:
QList<QString> list;
list << "A" << "B" << "C" << "D"; QListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
流程是这样的:将要遍历的Qlist被传到QListIterator的构造函数,这时迭代器定位在list的第一项之前("A"之前),接下来我们调用hasNext()来检测迭代器后面是否有一项,如果有,我们调用next()来跳过那一项,next()函数返回它跳过的那一项。对一个QList<QString>来说,那一项的类型是QString。
下面是如何在QList中倒序遍历:
QListIterator<QString> i(list);
i.toBack();
while (i.hasPrevious())
qDebug() << i.previous();
代码和正序遍历是对称的,我们调用toBack()将迭代器移到最后一项后面的位置。
下图描述了在一个迭代器上调用next()和previous()函数的效果:
下面的表概括了QListIterator的API:
函数 | 用途 |
---|---|
toFront() | 将迭代器移到list的最前面(在第一个项之前) |
toBack() | 将迭代器移到list的最后面 (最后一项之后) |
hasNext() | 如果迭代器没有到list的最后则返回true |
next() | 返回下一项,并将迭代器向前移动一个位置 |
peekNext() | 返回下一项,不会移动迭代器 |
hasPrevious() | 如果迭代器没有到list的最前面则返回true |
previous() | 返回上一项,并将迭代器移到上一个位置 |
peekPrevious() | 返回上一项,不会移动迭代器 |
QListIterator没有提供从list中插入或移除项的函数,想要实现插入和移除,你必须使用QMutableListIterator。下面举例说明使用QMutableListIterator从QList<int>中移除所有奇数。
QMutableListIterator<int> i(list);
while (i.hasNext()) {
if (i.next() % 2 != 0)
i.remove();
}
每次循环都会调用next(),它跳过list中的下一项,然后remove()函数移除我们刚刚从list中跳过的那一项,调用remove()不会使迭代器失效,所以它是安全的,我们可以继续使用它。在倒序遍历中同样有效:
QMutableListIterator<int> i(list);
i.toBack();
while (i.hasPrevious()) {
if (i.previous() % 2 != 0)
i.remove();
}
如果想修改某项的值,我们可以使用setValue(),下面的代码中,我们用128来替换所以大于128的值:
QMutableListIterator<int> i(list);
while (i.hasNext()) {
if (i.next() > 128)
i.setValue(128);
}
和remove()一样,setValue()操作我们刚刚跳过的那一项。如果是正序遍历,这一项在当前迭代器之前;如果是倒序遍历,这一项在当前迭代器之后。
next()函数返回list中这一项的非const引用,简单点,我们甚至连setValue()都不需要:
QMutableListIterator<int> i(list);
while (i.hasNext())
i.next() *= 2;
正如上面所说,QLinkedList、QVector还有QSet的迭代器类和QList的迭代器有着相同的API。现在,我们来看看QMapIterator,有点不同,因为他在键值对上遍历。
类似于QListIterator,QMapIterator提供了toFront()、toBack()、hasNext()、next()、peekNext()、hasPrevious()、previous()以及peekPrevious()。键和值的部分通过调用next()、peekNext()、previous()或peekPrevious()返回的对象的key()和value()来获得。
下面的例子中,移除所有首都名字以“City”结尾的一对(capital, country):
QMap<QString, QString> map;
map.insert("Paris", "France");
map.insert("Guatemala City", "Guatemala");
map.insert("Mexico City", "Mexico");
map.insert("Moscow", "Russia");
... QMutableMapIterator<QString, QString> i(map);
while (i.hasNext()) {
if (i.next().key().endsWith("City"))
i.remove();
}
QMapIterator还提供了直接在迭代器上操作的key()和value()函数,返回迭代器跳过的上一项的键和值。比如,下面的代码把QMap中的内容复制到QHash中:
QMap<int, QWidget *> map;
QHash<int, QWidget *> hash; QMapIterator<int, QWidget *> i(map);
while (i.hasNext()) {
i.next();
hash.insert(i.key(), i.value());
}
如果想要使用同一个值遍历所有项,我们使用findNext()或findPrevious()。下面例子中,我们移除所有带有某个特定值的项:
QMutableMapIterator<int, QWidget *> i(map);
while (i.findNext(widget))
i.remove();
STL风格的迭代器
自从Qt2.0发布就可以使用STL风格的迭代器了,它们适用于Qt和STL的泛型算法,并且对速度作了优化。
对于每个容器类,有两种STL风格的迭代器类型:只读的和可读写的。尽可能使用只读的迭代器,因为它们比可读写的迭代器要快。
容器 | 只读迭代器 | 可读写的迭代器 |
---|---|---|
QList<T>, QQueue<T> | QList<T>::const_iterator | QList<T>::iterator |
QLinkedList<T> | QLinkedList<T>::const_iterator | QLinkedList<T>::iterator |
QVector<T>, QStack<T> | QVector<T>::const_iterator | QVector<T>::iterator |
QSet<T> | QSet<T>::const_iterator | QSet<T>::iterator |
QMap<Key, T>, QMultiMap<Key, T> | QMap<Key, T>::const_iterator | QMap<Key, T>::iterator |
QHash<Key, T>, QMultiHash<Key, T> | QHash<Key, T>::const_iterator | QHash<Key, T>::iterator |
STL迭代器的API是以数组中的指针为模型的,比如++运算符将迭代器前移到下一项,*运算符返回迭代器所指的那一项。事实上,对于QVector和QStack,它们的项在内存中存储在相邻的位置,迭代器类型正是T *,const迭代器类型正是const T *。
在讨论中,我们重点放在QList和QMap,QLinkedList、QVector和QSet的迭代器类型与QList的迭代器有相同的接口;同样地,QHash的迭代器类型与QMap的迭代器有相同的接口。
下面是一个典型例子,按顺序循环遍历QList<QString>中的所有元素,并将它们转为小写:
QList<QString> list;
list << "A" << "B" << "C" << "D"; QList<QString>::iterator i;
for (i = list.begin(); i != list.end(); ++i)
*i = (*i).toLower();
不同于Java风格的迭代器,STL风格的迭代器直接指向每一项。begin()函数返回指向容器中第一项的迭代器。end()函数返回指向容器中最后一项后面一个位置的迭代器,end()标记着一个无效的位置,不可以被解引用,主要用在循环的break条件。如果list是空的,begin()等于end(),所以我们永远不会执行循环。
下图展示了一个包含4个元素的vector的所有有效迭代器位置,用红色箭头标出:
倒序遍历需要我们在获得项之前减少迭代器,这需要一个while循环:
QList<QString> list;
list << "A" << "B" << "C" << "D"; QList<QString>::iterator i = list.end();
while (i != list.begin()) {
--i;
*i = (*i).toLower();
}
在上面的代码中,我们使用一元运算符*来获得存储在某个迭代器位置的项,然后我们调用QString::toLower(),大多数C++编译器还允许我们使用i->toLower(),但有些不允许。
如果是只读的,你可以使用const_iterator、constBegin()和constEnd(),比如:
QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
qDebug() << *i;
下面的表概括了STL风格迭代器的API:
表达式 | 用途 |
---|---|
*i |
返回当前项 |
++i |
将迭代器指向下一项 |
i += n |
迭代器向前移动n项 |
--i |
将迭代器指向上一项 |
i -= n |
将迭代器你向后移动n项 |
i - j |
返回迭代器i和j之间项的数目 |
++和--运算符可以使用前缀(++i, --i)和后缀(i++, i--)的形式,前缀的版本修改迭代器并返回修改后迭代器的引用,后缀版本在修改之前先复制迭代器,然后返回它的拷贝。在不需要考虑返回值的情况下,我们推荐使用前缀运算符(++i, --i),因为它们稍微快一点。
对于非const的迭代器类型,一元运算符*可以被用在赋值运算符的左边。
对于QMap和QHash,*运算符返回项的值,如果你想要获得键,只需在迭代器上调用key()。为了对称,迭代器类型还提供了value()函数来获得值。举个例子,下面是如何将QMap中的所有项打印到控制台上:
QMap<int, int> map;
...
QMap<int, int>::const_iterator i;
for (i = map.constBegin(); i != map.constEnd(); ++i)
qDebug() << i.key() << ":" << i.value();
幸好有隐式共享,函数返回容器中的每个值效率很高。Qt的API包含很多返回QList或QStringList值的函数(比如QSplitter::sizes())。如果想要使用STL迭代器遍历它们,你应该存储一个拷贝,并在拷贝上进行遍历。比如:
// RIGHT
const QList<int> sizes = splitter->sizes();
QList<int>::const_iterator i;
for (i = sizes.begin(); i != sizes.end(); ++i)
... // WRONG
QList<int>::const_iterator i;
for (i = splitter->sizes().begin();
i != splitter->sizes().end(); ++i)
...
当函数返回容器的const或非const的引用,这个问题将不会发生。
foreach关键字
如果你想要按顺序遍历容器中的所有项,你可以使用Qt的foreach关键字。这个关键字是Qt特有的,与C++语言无关,并且使用了预处理器实现。
它的语法是:foreach (variable, container) statement。比如,下面是如何使用foreach遍历QLinkedList<QString>:
QLinkedList<QString> list;
...
QString str;
foreach (str, list)
qDebug() << str;
foreach代码明显比使用迭代器的代码少:
QLinkedList<QString> list;
...
QLinkedListIterator<QString> i(list);
while (i.hasNext())
qDebug() << i.next();
除非数据类型包含一个逗号(比如QPair<int, int>),用于遍历的变量可以在foreach语句中定义:
QLinkedList<QString> list;
...
foreach (const QString &str, list)
qDebug() << str;
和其它任何C++循环一样,你可以在foreach循环中把主体放在括号里,而且你可以使用break来结束循环:
QLinkedList<QString> list;
...
foreach (const QString &str, list) {
if (str.isEmpty())
break;
qDebug() << str;
}
在QMap和QHash中,foreach可以获得键值对中值的部分。如果你遍历既想获得键又想获得值,则可以使用迭代器(这样是最快的),或者你可以这样写:
QMap<QString, int> map;
...
foreach (const QString &str, map.keys())
qDebug() << str << ":" << map.value(str);
对于一个多值的(multi-valued)map:
QMultiMap<QString, int> map;
...
foreach (const QString &str, map.uniqueKeys()) {
foreach (int i, map.values(str))
qDebug() << str << ":" << i;
}
当进入foreach循环时Qt自动获得容器的一份拷贝,如果想修改你所遍历的容器,并不会影响循环。
foreach创建了容器的一份拷贝,使用变量的非const引用可以禁止你修改最初的容器,但它会影响拷贝,这也许是你不愿看到的。
其它类似容器的类
Qt提供了三个模板类,在一些方面与容器有点像。这些类不提供迭代器,而且不能使用foreach关键字。
- QVarLengthArray<T, Prealloc>提供一个低级的可变长度的数组,当速度特别重要的时候,它可以被用来替换QVector。
- QCache<Key, T>提供缓存,用来存储和Key类型键相关联的T类型的对象。
- QContiguousCache<T>提供了一种缓存可连续获得的数据的有效方式。
- QPair<T1, T2>存储一对元素。
其它类似于模板容器的非模板类型有QBitArray、QByteArray、QString和QStringList。
算法复杂度
算法复杂度关注当容器中项的数目增长时,函数有多快。例如,在QLinkedList中间插入一项是非常快的,无论其中存了多少项。另一方面,在QVector中项很多时,在中间插入一项是非常低效的,因为一半的项必须在内存中移动位置。
为了描述算法复杂度,我们使用下面的术语,基于“大O”标记法:
常量时间:O(1)。
指数时间:O(log n)。
线性时间:O(n)。
线性指数时间:O(nlog n)。
平方时间:O(n2)。
下面的表概括了Qt顺序容器的算法复杂度:
按索引查找 | 插入 | 在前面增加 | 在后面增加 | |
---|---|---|---|---|
QLinkedList<T> | O(n) | O(1) | O(1) | O(1) |
QList<T> | O(1) | O(n) | Amort. O(1) | Amort. O(1) |
QVector<T> | O(1) | O(n) | O(n) | Amort. O(1) |
在表中,“Amort”指的是“平摊行为”。比如,“Amort.O(1)”指的是如果你只调用函数1次,你可能得到O(n),但如果你多次调用,平均下来将是O(1)。
下面的表概括了Qt关联容器的算法复杂度:
关键字查找 | 插入 | |||
平均 | 最坏情况 | 平均 | 最坏情况 | |
QMap<Key, T> | O(log n) | O(log n) | O(log n) | O(log n) |
QMultiMap<Key, T> | O(log n) | O(log n) | O(log n) | O(log n) |
QHash<Key, T> | Amort. O(1) | O(n) | Amort. O(1) | O(n) |
QSet<Key> | Amort. O(1) | O(n) | Amort. O(1) | O(n) |
增长策略
QVector<T>、QString和 QByteArray在内存中连续存储它们的项;QList<T>维护一个指向每一项指针的数组,从而提供快速的基于索引的获得方法;QHash<Key, T>维护一个哈希表,它的大小与其中项的个数成比例。为了避免每次在容器末尾增加一项就分配一次内存,这些容器比实际需要的分配了更多的内存。
我们考虑下面的程序,根据一个QString来建立另一个QString:
QString onlyLetters(const QString &in)
{
QString out;
for (int j = 0; j < in.size(); ++j) {
if (in[j].isLetter())
out += in[j];
}
return out;
}
我们通过一次增加一个字符来动态地建立字符串。假设需要增加15000个字符,当字符串空间不够时,将会发生18次重新分配内存:4, 8, 12, 16, 20, 52, 116, 244, 500, 1012, 2036, 4084, 6132, 8180, 10228, 12276, 14324, 16372。最后,QString有16372个Unicode字符被分配,15000个被占用。
这些值可能看起来有点奇怪,下面是增长的规则:
- 1.QString一次分配4个字符,直到它增长到20。
- 2.从20到4084,每次增长一倍,更准确地说,增长到下一个2的次方,减去12。
- 3.从4084往后,每次增长2048个字符(4096字节)。这是因为当重新分配时,现代操作系统不会复制所有数据;物理内存被简单地重新排序,只有第一页和最后一页的数据需要被拷贝。
QByteArray和QList<T>使用了与QString差不多的算法。
QVector<T>对一些数据类型也使用同样的算法,这些数据类型可以使用memcpy()在内存中移动(包括基本的C++类型,指针类型以及Qt的共享类)。但是QVector<T>对只能调用构造和析构函数来移动的数据类型使用了不同的算法,这些情况下重新分配内存的代价更高,当空间不够时,QVector<T>通过内存加一倍来减少再分配的次数。
QHash<Key, T>是一个完全不同的情况。QHash的内部哈希表以2的次方增长,每次增长时,项被定为到新的存储块中,通过qHash(key) % QHash::capacity()(存储快的数目)计算。这个机制同样适用于QSet<T>和QCache<Key, T>。
QVector<T>、QHash<Key, T>、QSet<T>、QString和QByteArray提供了一些函数,让你能够检测和确定存储这些项用了多少内存:
- capacity()返回内存分配的项的数目(对QHash和QSet来说,是hash表中存储块的数目)。
- reserve(size)显式地为size个项预分配内存。
- squeeze()释放不需要用来存储项的内存。
如果你知道在容器中大约要存储多少项,可以调用reserve()开始,当你在容器中存储结束,可以调用squeeze()来释放额外的预分配的内存。
Qt——容器类(译)的更多相关文章
- Qt容器类之二:迭代器
一.介绍 遍历一个容器可以使用迭代器(iterators)来完成,迭代器提供了一个统一的方法来访问容器中的项目.Qt的容器类提供了两种类型的迭代器:Java风格迭代器和STL风格迭代器.如果只是想按顺 ...
- Qt容器类之一:Qt的容器类介绍
一.介绍 Qt库提供了一套通用的基于模板的容器类,可以用这些类存储指定类型的项.比如,你需要一个大小可变的QString的数组,则使用QVector<QString>. 这些容器类比STL ...
- Qt容器类汇总说明
版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt容器类汇总说明 本文地址:http://techieliang.com/2017/ ...
- Qt容器类——1. QList类、QLinkedList类和QVector类
在开发一个较高性能需求的应用程序时,程序员会比较关注这些容器类的运行效率,表2.1列出了QList.QLinkedList和QVector容器的时间复杂度比较. 1.QList类 QList<T ...
- Qt容器类的对象模型及应用(线性结构篇)(好多图,比较清楚)
用Qt做过项目开发的人,肯定使用过诸如QList.QVector.QLinkList这样的模板容器类,它们虽然名字长的不同,但使用方法都大致相同, 因为其使用方法都大体相同,很多人可能随便拿一个容器类 ...
- Qt容器类(总结)(新发现的QQueue和QStack,注意全都是泛型)
Introduction Qt库提供了一组基于模板的一般化的容器类.这些容器可以存储指定的类型的元素.例如,如果你需要一个可变大小的Qstring数组,可以用QVector<QString> ...
- Qt容器类之三:通用算法
在<QtAlgorithm>头文件中,Qt提供了一些全局的模板函数,这些函数是可以使用在容器上的十分常用的算法.我们可以在任何提供了STL风格迭代器的容器类上用这些算法,包括QList.Q ...
- Qt容器类的对象模型及应用(线性结构篇:对于QList来说,sharable默认是false的,但对于接下来讲的QVector来说,sharable默认是true)
用Qt做过项目开发的人,肯定使用过诸如QList.QVector.QLinkList这样的模板容器类,它们虽然名字长的不同,但使用方法都大致相同, 因为其使用方法都大体相同,很多人可能随便拿一个容器类 ...
- Qt浅译:JSON Support in Qt(JSON只有六种数据类型)
JSON Support in Qt Qt5之后开始提供对处理JSON数据的支持,JSON是一种Interter数据交换的数据格式. JSON 用于存储结构化的数据,JSON有6种基本数据类型 ...
随机推荐
- 用Metaclass实现一个精简的ORM框架
存档: # -*- coding: utf-8 -*- class Field(object): def __init__(self, name, column_type): self.name = ...
- docker 指令
杀死所有正在运行的容器docker kill $(docker ps -a -q) 删除所有已经停止的容器docker rm $(docker ps -a -q) 删除所有未打 dangling 标签 ...
- 基于Vue的简单通用分页组件
分页组件是每一个系统里必不可少的一个组件,分页组件分为两部分.第一部分是模版部分,用于显示当前分页组件的状态,例如正在获取数据.没有数据.没有下一页等等:第二部分是分页数据对象,用于封装一个分页组件的 ...
- Received non-all-whitespace CHARACTERS or CDATA event in nextTag(). ,无法整齐打印验证错误。 解析XML文档出现的问题
在启动keyCloak,想要在standAlone模式下切换数据库,修改standAlone.xml文件时. 在bin/目录下启动standAlone模式出现错误: 10:07:24,799 INFO ...
- Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第4节: recycler中获取对象
Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第四节: recycler中获取对象 这一小节剖析如何从对象回收站中获取对象: 我们回顾上一小节demo的ma ...
- java.io.tmpdir指定的路径在哪?
Java.io.tmpdir介绍 System.getproperty(“java.io.tmpdir”)是获取操作系统缓存的临时目录,不同操作系统的缓存临时目录不一样, 在Windows的缓存目录为 ...
- Hyperledger Fabric 账本结构解析
前言 现在很多人都在从事区块链方面的研究,作者也一直在基于Hyperledger Fabric做一些开发工作.为了方便后来人更快的入门,本着“开源”的精神,在本文中向大家讲解一下Hyperledger ...
- redis使用Jackson2JsonRedisSerializer序列化问题
一.spring boot 集成Redis方法 依赖 <!--redis--> <dependency> <groupId>org.springframework. ...
- 【转】git乱码解决方案汇总
git乱码解决方案汇 2012-11-04更新:官方的“终极”解决方案:msysGit1.7.10开始使用UTF-8编码保存文件名. 2011-10-24更新: 从一篇链接到本篇文章的文章(我对这篇文 ...
- 2017-2018-2 1723 『Java程序设计』课程 结对编程练习_四则运算
一.结对对象 姓名:侯泽洋 学号:20172308 担任角色:驾驶员(侯泽洋) 伙伴第一周博客地址 二.本周内容 1.程序需求 (1).自动生成题目 可独立使用(能实现自己编写测试类单独生成题目的功能 ...