注:本文是我对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——容器类(译)的更多相关文章

  1. Qt容器类之二:迭代器

    一.介绍 遍历一个容器可以使用迭代器(iterators)来完成,迭代器提供了一个统一的方法来访问容器中的项目.Qt的容器类提供了两种类型的迭代器:Java风格迭代器和STL风格迭代器.如果只是想按顺 ...

  2. Qt容器类之一:Qt的容器类介绍

    一.介绍 Qt库提供了一套通用的基于模板的容器类,可以用这些类存储指定类型的项.比如,你需要一个大小可变的QString的数组,则使用QVector<QString>. 这些容器类比STL ...

  3. Qt容器类汇总说明

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:Qt容器类汇总说明     本文地址:http://techieliang.com/2017/ ...

  4. Qt容器类——1. QList类、QLinkedList类和QVector类

    在开发一个较高性能需求的应用程序时,程序员会比较关注这些容器类的运行效率,表2.1列出了QList.QLinkedList和QVector容器的时间复杂度比较. 1.QList类 QList<T ...

  5. Qt容器类的对象模型及应用(线性结构篇)(好多图,比较清楚)

    用Qt做过项目开发的人,肯定使用过诸如QList.QVector.QLinkList这样的模板容器类,它们虽然名字长的不同,但使用方法都大致相同, 因为其使用方法都大体相同,很多人可能随便拿一个容器类 ...

  6. Qt容器类(总结)(新发现的QQueue和QStack,注意全都是泛型)

    Introduction Qt库提供了一组基于模板的一般化的容器类.这些容器可以存储指定的类型的元素.例如,如果你需要一个可变大小的Qstring数组,可以用QVector<QString> ...

  7. Qt容器类之三:通用算法

    在<QtAlgorithm>头文件中,Qt提供了一些全局的模板函数,这些函数是可以使用在容器上的十分常用的算法.我们可以在任何提供了STL风格迭代器的容器类上用这些算法,包括QList.Q ...

  8. Qt容器类的对象模型及应用(线性结构篇:对于QList来说,sharable默认是false的,但对于接下来讲的QVector来说,sharable默认是true)

    用Qt做过项目开发的人,肯定使用过诸如QList.QVector.QLinkList这样的模板容器类,它们虽然名字长的不同,但使用方法都大致相同, 因为其使用方法都大体相同,很多人可能随便拿一个容器类 ...

  9. Qt浅译:JSON Support in Qt(JSON只有六种数据类型)

    JSON Support in Qt   Qt5之后开始提供对处理JSON数据的支持,JSON是一种Interter数据交换的数据格式.   JSON 用于存储结构化的数据,JSON有6种基本数据类型 ...

随机推荐

  1. 应用.NET控制台应用程序开发批量导入程序。

    一.最近一直在调整去年以及维护去年开发的项目,好久没有在进行个人的博客了.每天抽了一定的时间在研究一些开源的框架,Drapper 以及NHibernate以及当前比较流行的SqlSuper框架 并进行 ...

  2. 动态加载与插件系统的初步实现(三):WinForm示例

    代码文件在此Download,本文章围绕前文所述默认AppDomain.插件容器AppDomain两个域及IPlugin.PluginProvider.PluginProxy3个类的使用与变化进行. ...

  3. C# 代码备份数据库 ,不需要 其他 DLL

    protected void Button1_Click(object sender, EventArgs e)    {        ///        ///备份方法        ///  ...

  4. Spring入门学习笔记(3)——事件处理类

    目录 Spring中的事件处理 Spring内建事件 监听Context事件 Example 自定义Spring事件 Spring中的事件处理 ApplicationContext 是Spring的核 ...

  5. hadoop 集群HA高可用搭建以及问题解决方案

    hadoop 集群HA高可用搭建 目录大纲 1. hadoop HA原理 2. hadoop HA特点 3. Zookeeper 配置 4. 安装Hadoop集群 5. Hadoop HA配置 搭建环 ...

  6. Django_QueryDict

    介绍 class QueryDict(MultiValueDict): """ A specialized MultiValueDict which represents ...

  7. Daily Scrum (2015/11/7)

    今晚谢金洛同学的UI工作完成,我们进行了UI和后端的拼接,准备开始规范化地进行系统测试. 成员 今日任务及成果 时间 明日任务 符美潇 1.把之前PM分配的编码任务及其说明准备好发给PM 1h 待定 ...

  8. Scrum Meeting 11.07

    成员 今日任务 明日计划 用时 徐越       赵庶宏       薄霖       卞忠昊 JOSN数据解析 WebView和JavaScript交互基础  3h  武鑫 设计界面:独立完成一些简 ...

  9. java实验1实验报告(20135232王玥)

    实验一 Java开发环境的熟悉 一.实验内容 1. 使用JDK编译.运行简单的Java程序 2.使用Eclipse 编辑.编译.运行.调试Java程序 二.实验要求 1.没有Linux基础的同学建议先 ...

  10. 20145214《网络对抗》MAL_后门原理与实践

    20145214<网络对抗>MAL_后门原理与实践 基础问题回答 (1)例举你能想到的一个后门进入到你系统中的可能方式? 网页上查找资料时有时会不小心点到弹出来的广告,如果这个广告是个钓鱼 ...