字符串
我们将从 QString开始。每一个图形用户界面(GUI) 程序都会用到字符串,不仅仅是为用户界面,更多的是为数据结构所用。 C++ 本身提供两种宇符串:传统的 C语言型的,以" \0",结尾的字符数组和 std::string类。与这两种字符串不同, QString 支持 16 位 Unicode 值。Unicode码以 ASCII 码和 Latin-1码为子集,具有它们常用的数字值。但由于 QString是 16位的;它可以表示数千种其他字符以表达世界上绝大多数的语言。
当使用 QString时,我们不必担心那些诸如如何分配足够的内存空间或确保数据以"\0"结尾等晦涩难懂的细节。从概念上说,可以将 QString看成 QChar 向量。 QString可以嵌入"\0"字符。 length()函数会返回包括被嵌入"\0"字符的整个字符串的大小。
QString为连接两个字符串提供了一个二进制+操作符,还为在字符串后追加字符串提供了+=操作符。因为 QString在行数据的末端自动预分配内存空间,因此通过重复添加字符来建立字符串就相当快捷了。下面是一个结合使用+和+=的例子:
QString str = "User: ";
str += userName + "\n";
以下的 QString::append() 函数与+=操作符的功能相同:
str = "User";
str.append(userName);
str.append("\n");
使用 QString的 sprintf()函数则是连接字符串的另一种完全不同的方法:
str.sprintf("%s %.lf%%", "perfect competition", 100.0);
这个函数支持与 C++库sprintf()函数相同的格式说明符。在之前的例子中, str 被赋值为"perfect competition 100.0%"。
从其他字符串或者数组来建立一个字符串的另一种方法是使用arg():
st r '" QSt ring ("%1 %2 (%3s-%4s)")
.arg( "permissive") .arg("society") .arg(1950) .arg(1970);
在这个例子中,“permissive” 会取代" %1",“society” 会取代"%2",“1950” 会取代"%3",而 “1970"会取代”%4"。相应的结果为"permissive society (1950s-1970s) “。arg()的重载可以处理各种数据类型。对于控制宇段长度、数值基数或者浮点精度等,一些重载有额外的参数。相较于 sprintf()而言, arg()通常是一个更好的解决方案,因为是它类型安全的,完全支持 Unicode 编码,并且允许译码器对”%n"参数进行重新排序。
通过使用 QString::number()静态函数, QString 可以将数字转换为字符串:
str = QString::number(59.6);
或者也可以使用 setNum() 函数:
str.setNum(59.6);
还可以使用 tolnt()、toLongLong()、toDouble()等来完成从字符串到数字的逆转换。例如:
bool ok;
double d = str.toDouble(&ok);
这些函数接受一个任选的指向 bool 变量的指针,并且根据转换成功与否来将变量值设为 true或false 如果转换没有完成,这些函数将返回0。
一旦有了字符串,我们通常都希望从字符串中提取出所需的部分。 mid() 函数返回在给定位置(第一个参数)开始且达到给定长度(第二个参数)的子串。例如,下面的代码在控制台上打印出"pays":
QString str = "polluter pays principle";
qDebug() << str.mid(9, 4);
如果省略第二个参数, mid() 函数返回在给定位置开始到字符串末端结束的子串。例如,下面的代码在控制台上打印出"pays principle" :
QString str = "polluter pays principle";
qDebug() << str.mid(9);
也可以使用 left()和 right() 函数来完成相似的任务。它们俩都可以接收字符的数量 并且返回字符串的前 n个字符或最后 n个字符。例如,下面的代码在控制台上打印出 “polluter principle”:
QStrirg str = "polluter pays principle";
qDebug() << str.left(8) << " "<< str.right(9);
如果想查明一个字符串是否包含一个特定的字符、子串或者正则表达式,可以使用QString中indexOf()函数:
QString str = "the middle bit";
int i = str.indexOf("middle");
这会将i设置为4。在失败时,indexOf()函数返回-1,并且接收一个可以的开始位置和区分大小写的标记。
如果仅仅想要检查字符串是否以某种字符(串)开始或者结束,则可以使用startsWith()和endsWith()函数:
if (url.startsWith("http:") && url.endsWith(".png"))
...
上面的代码就比下面的代码更简单、快捷:
if (url.left(5) == "http:" && url. right(4) == ".png")
利用==操作符进行字符串比较是区分大小写的。如果要比较那种用户可见的字符串, localeAwareCompare()函数通常是一个不错的选择;如果比较并不区分大小写,则可以使用 toUpper()或toLower()函数,例如:
if (fileName.toLower() == "readme.txt")
...
如果想使用一个字符串来代替另一个字符串中的某一部分,可以使用 replace():
QString str = "a cloudy day";
str.replace(2, 6, "sunny");
字符串的结果是"a smy day" 。上面的代码可以用 remove()和 insert()重新写为:
str.remove(2, 6);
str. insert(2, "sunny");
首先,我们去掉从第二个位值开始的 6个宇符,字符串因此变为"a day"气(含有两个空格 ),然后在第二个位置插人"sunny"。
replace()函数的重载版本让第二个参数代替所有第一个参数出现的地方。例如,下面是使用"∓"代替字符串中所有的"&":
str.replace("&", "&");
我们经常需要删除一个字符串中空白处的空格(比如空格符、制表符、换行符等 )。 QString有一个可以从字符串的两端删除空白处的空格的函数:
QString str =" BOB \t THE \nDOG \n";
qDebug() << str.trimmed();
字符串 str 描述如下:
由trimmed() 返回的字符串为:
当处理用户输人数据时,除了从字符串的两端删除空白处的空格特外,通常还想用简单的空格符代替字符串内部每一连续空白处的空格:
QString str =" BOB \t THE \nDOG \n";
qDebug() <<str.simplified();
simplified()返回的字符串为:
使用 QString::split(),可以把一个字符串分成一些QStringList 子串:
QString str = "polluter pays principle";
QStringList words = str.split(" ");
在上面的例子中,我们把宇符串 "polluter pays principle"分成了三个子串, “polluter”、“pays"和"principle”。
使用 join()函数, QStringList 中的项可以连接起来形成一个单一的字符串。在每一对被连接的字符串之间都要插人 join()的参数。例如,下面是如何创建一个由 QStringList 包含的所有字符串组成的、按字母表顺序排列且由换行符分隔的简单字符串:
words.sort();
str = words.join("\n");
当对字符串进行操作时,通常需要判断字符串是否为空。这可以通过调用 isEmpty()或者检查length()是否为 0来完成。
大多数情况下,从 const char *
字符串到 QString的转换是自动完成的,例如:
str +=" (1878)";
这里,我们采用无格式形式为 QString 加上一个 const char *
。为了明确地将 QString转换为const char* ,可以只使用一个 QString强制转换,或者调用 fromAscii()或fromLatin1()。
字节数组
要将 QString 转换为 const char*,可以使用 toAscii()或 toLatin1()函数,它们返回 QByteArray,而利用 QByteArray::data()或 QByteArray::constData(),可以将 QByteArray 转换为constData()。例如:
printf("User: %s\n" , str.toAscii().data());
为了方便起见, Qt 提供了 qPrintable()宏用来执行与序列 toAscii().constData():相同的功能:
printf("User: %s\n", qPrintabte(str));
当在 QByteArray 上调用 data()或 constData()时,返回的字符串属于 QByteArray 对象。这就意味着不必为内存泄漏而担心了, Qt 将为我们重新收回内存。另一方面,必须注意不要太长时间地使用指针。如果 QByteArray 没有存储在一个变量中,那么它将在声明的末端自动删除。
QByteArray 类有一个与 QString很相似的应用编程接口。诸如 left()、right()、mid()、toLower()、toUpper()、trimmed()和 simplified()等函数,在 QByteArray中的语义形式与在 QString中的相同。
QByteArray对于存储原始的二进制数据以及 8 位编码的文本字符串非常有用。一般说来,我们推荐使用 QString而不是 QByteArray来存储文本,因为 QString支持 Unicode 编码。
为方便起见, QByteArray 自动保证"最后一个项之后的项"总为"\0",这使得利用 const char*可以很容易地将 QByteArray 传递给一个函数。 QByteArray 还支持嵌入的"\0"宇符,以允许我们存储任意的二进制数据。
在某些情况下,我们需要在同一个变量中存储不同类型的数据。一种方法是像 QByteArray或QString一样,对数据进行编码。例如,字符串可以支持文本值或者以字符串形式支持数字值。这些方法很灵活,但是它抛弃了 C++的一些优点,尤其是类型的安全性和效率。Qt提供了一个更加灵巧的方法,也就是 QVariant,来处理那些能够支持不同数据类型的变量。
变量
QVariant 类可以支持许多种 Qt 类型的值,除了基本的 C++ 数字类型(如 double int) ,还包括QBrush、QColor、QCursor、 QDateTime、QFont、QKeySequence、QPalette、QPen、Pixrnap、QPoint、QRect、QRegion、QSize和 QString。 QVariant类也支持容器类,如 QMap <QString,QVariant> QStringList和QList<QVariant>
在项视图类、数据库模块和 Settings 中广泛使用了变量,变量允许我们读写项数据、数据库数据以及任意与 QVariant 兼容的用户首选参数。
通过容器类的嵌套值,可以利用 QVariant创建任意复杂的数据结构:
QMap<QString,QVariant> pearMap;
pearMap["Standard"] = 1.95;
pearMap["Organic"] = 2.25;
QMap<QString, QVariant> fruitMap;
fruitMap["Orange"] = 2.10;
fruitMap["Pineapple"] = 3.85;
frtlitMap["Pear"] = pearMap;
这里,我们利用字符串键(产品名)和浮点数(价格)或者映射的值创建一个映射。*映射包括三个键:“Orange”、“Pear”、“Pineapple”。与"Pear"键相关联的值是一个包含两个键(“Standard"和"Organic”)的映射。当遍历一个支持变量值的映射时,需要使用type()来检查变量保持所支持的类型,以便做出适当的反应。
创建这样的数据结构是非常吸引人的,因为能够以任意方式组织数据。但是QVariant的便利性是以降低效率及可读性为代价的。通常,定义一个适当的C++类来存储随时可能的数据,是值得的。
Qt的元对象系统使用QVariant,因此它也是QtCore模块的一部分。但是,当与QtGui模块相连时,QVariant可以存储与图形用户界面相关的类型,例如QColor、QFont、QIcon、QImage和QPixmap:
QIcon icon("open.png");
QVariant variant = icon;
为了从QVariant中获得与图形用户界面相关的值,可以使用如下的QVariant::value<T>()模板成员函数:
QIcon icon = variant.value<QIcon>();
value<T>()函数也可以用非图形用户界面数据类型和QVariant之间进行转换,但实际上对于非图形界面类型,通常使用to…()作为非图形用户界面数据类型的转换函数[例如toString()]。
如果自定义数据类型提供了默认的构造函数和副本构造函数的话,QVariant也可以用来存储它们。为此,必须首先使用Q_DECLARE_METATYPE()宏注册数据类型,尤其是在类定义下的头文件中:
Q_DECLARE_METATYPE(BusinessCard);
这使我们可以使用如下的方式来编写代码:
BusinessCard businessCard;
QVariant variant = QVariant::fromValue(businessCard);
...
if(variant.canConvert<BusinessCard>()){
BusinessCard card = variant.value<BusinessCard>();
...
}
由于编译器的局限性,在MSVC 6中,这些模板成员函数并不可用。如果需要用到这个编译器,可以用qVariantFromValue()、qVariantValue<T>()和qVariantCanConvert<T>()全局函数来代替。
如果自定义数据类型采用<<和>>操作符来完成从QDataStream的读写,就可以使用qRegisterMetaTypeStreamOperators<T>()来注册这些自定义数据类型。这就可以利用QSettings在其他类型之中存储自定义数据类型的首选参数。例如:
qRegisterMetaTypeStreamOperator<BusinessCard>("BusinessCard");