QString and QVariant
Strings are used by every GUI program, not only for the user interface, but often also as data structures.C++ natively provides two kinds of strings: traditional C-style '\0'-terminated character arrays and the string class. Qt's QString class is more powerful than either of them. The QString class holds 16-bit Unicode values. Unicode contains ASCII and Latin-1 as a subset, with their usual numeric values. But since QString is 16-bit, it can represent thousands of other characters for writing most of the world's languages. See Chapter 15 for more information about Unicode.QString provides a binary + operator to concatenate two strings and a += operator to append one string to another. Here's an example that combines both:
QString str = "User: ";
str += userName + "\n";
There is also a QString::append() function that does the same thing as the += operator:
str = "User: ";
str.append(userName);
str.append("\n");
A completely different way of combining strings is to use QString's sprintf() function:
str.sprintf("%s %.1f%%", "perfect competition", 100.0);
This function supports the same format specifiers as the C++ library's sprintf() function. In the example above, str is assigned "perfect competition 100.0%".Yet another way of building a string from other strings or from numbers is to use arg():
str = QString("%1 %2 (%3s-%4s)")
.arg("permissive").arg("society").arg(1950).arg(1970);
In this example, "%1" is replaced by "permissive", "%2" is replaced by "society", "%3" is replaced by "1950", and "%4" is replaced by "1970". The result is "permissive society (1950s-1970s)". There are arg() overloads to handle various data types. Some overloads have extra parameters for controlling the field width, the numerical base, or the floating-point precision. In general, arg() is a much better solution than sprintf(), because it is type-safe, fully supports Unicode, and allows translators to change the order of the "%n" parameters.QString can convert numbers into strings using the QString::number() static function:
str = QString::number(59.6);
Or using the setNum() function:
str.setNum(59.6);
The reverse conversion, from a string to a number, is achieved using toInt(), toLongLong(), toDouble(), and so on. For example:
bool ok;
double d = str.toDouble(&ok);
These functions also accept an optional pointer to a bool and set the bool to true or false depending on the success of the conversion. When the conversion fails, these functions always return 0.Once we have a string, we often want to extract parts of it. The mid() function returns the substring starting at a given position and of a given length. For example, the following code prints "pays" to the console:
QString str = "polluter pays principle";
cerr << str.mid(9, 4).ascii() << endl;
If we omit the second argument (or pass 1), mid() returns the substring starting at a given position and ending at the end of the string. For example, the following code prints "pays principle" to the console:
QString str = "polluter pays principle";
cerr << str.mid(9).ascii() << endl;
There are also left() and right() functions that perform a similar job. Both accept a number of characters, n, and return the first or last n characters of the string. For example, the following code prints "polluter principle" to the console:
QString str = "polluter pays principle";
cerr << str.left(8).ascii() << " " << str.right(9).ascii()
<< endl;
If we want to check if a string starts or ends with something, we can use the startsWith() and endsWith() functions:
if (uri.startsWith("http:") && uri.endsWith(".png"))
...
This is both simpler and faster than this:
if (uri.left(5) == "http:" && uri.right(4) == ".png")
...
String comparison with the == operator is case sensitive. For case insensitive comparisons, we can use upper() or lower(). For example:
if (fileName.lower() == "readme.txt")
...
If we want to replace a certain part of a string by another string, we can use replace():
QString str = "a sunny day";
str.replace(2, 5, "cloudy");
The result is "a cloudy day". The code can be rewritten to use remove() and insert():
str.remove(2, 5);
str.insert(2, "cloudy");
First, we remove five characters starting at position 2, resulting in the string "a day" (with two spaces), then we insert "cloudy" at position 2.There are overloaded versions of replace() that replace all occurrences of their first argument with their second argument. For example, here's how to replace all occurrences of "&" with "&" in a string:
str.replace("&", "&");
One very frequent need is to strip the whitespace (such as spaces, tabs, and newlines) from a string. QString has a function that strips whitespace from both ends of a string:
QString str = " BOB \t THE \nDOG \n";
cerr << str.stripWhiteSpace().ascii() << endl;
String str can be depicted as


QString str = " BOB \t THE \nDOG \n";
cerr << str.simplifyWhiteSpace().ascii() << endl;
The string returned by simplifyWhiteSpace() is

QString str = "polluter pays principle";
QStringList words = QStringList::split(" ", str);
In the example above, we split the string "polluter pays principle" into three substrings: "polluter", "pays", and "principle". The split() function has an optional bool third argument that specifies whether empty substrings should be ignored (the default) or not.The elements in a QStringList can be joined to form a single string using join(). The argument to join() is inserted between each pair of joined strings. For example, here's how to create a single string that is composed of all the strings contained in a QStringList sorted into alphabetical order and separated by newlines:
words.sort();
str = words.join("\n");
When dealing with strings, we often need to determine whether a string is empty or not. One way of testing this is to call isEmpty(); another way is to check whether length() is 0.QString distinguishes between null strings and empty strings. This distinction has its roots in the C language, which differentiates between 0 (a null pointer) and " (an empty string). To test whether a string is null, we can call isNull(). For most applications, what matters is whether or not a string contains any characters. The isEmpty() function provides this information, returning true if a string has no characters (is null or empty), and false otherwise.The conversions between const char * strings and QString is automatic in most cases, for example:
str += " (1870)";
Here we add a const char * to a QString without formality.In some situations, it is necessary to explicitly convert between const char * and QString. To convert a QString to a const char *, use ascii() or latin1(). To convert the other way, use a QString cast.Chapter 4 to hold the value of a cell, which could be either a QString, a double, or an invalid value.One common use of variants is in a map that uses strings as keys and variants as values. Configuration data is normally saved and retrieved using QSettings, but some applications may handle this data directly, perhaps storing it in a database. QMap<QString, QVariant> is ideal for such situations:
QMap<QString, QVariant> config;
config["Width"] = 890;
config["Height"] = 645;
config["ForegroundColor"] = black;
config["BackgroundColor"] = lightGray;
config["SavedDate"] = QDateTime::currentDateTime();
QStringList files;
files << "2003-05.dat" << "2003-06.dat" << "2003-07.dat";
config["RecentFiles"] = files;
How Implicit Sharing WorksImplicit sharing works automatically behind the scenes, so when we use classes that are implicitly shared, we don't have to do anything in our code to make this optimization happen. But since it's nice to know how things work, we will study an example and see what happens under the hood.
We set str1 to "Humpty" and str2 to be equal to str1. At this point, both QStrings point to the same data structure in memory (of type QStringData). Along with the character data, the data structure holds a reference count that indicates how many QStrings point to the same data structure. Since both str1 and str2 point to the same data, the reference count is 2.
When we modify str2, it first makes a deep copy of the data, to ensure that str1 and str2 point to different data structures, and it then applies the change to its own copy of the data. The reference count of str1's data ("Humpty") becomes 1, and the reference count of str2's data ("Dumpty") is set to 1.A reference count of 1 means that the data isn't shared.
If we modify str2 again, no copying takes place because the reference count of str2's data is 1. The truncate() function operates directly on str2's data, resulting in the string "Dump". The reference count stays at 1.
When we assign str2 to str1, the reference count for str1's data goes down to 0, which means that no QString is using the "Humpty" data anymore. The data is then freed from memory. Both QStrings now point to "Dump", which now has a reference count of 2.Writing implicitly shared classes isn't very difficult. The Qt Quarterly article "Data Sharing with Class", available online at http://doc.trolltech.com/qq/qq02-data-sharing-with-classl, explains how to do it. |
QMap<QString, QVariant>::const_iterator it = config.begin();
while (it != config.end()) {
QString str;
if (it.data().type() == QVariant::StringList)
str = it.data().toStringList().join(", ");
else
str = it.data().toString();
cerr << it.key().ascii() << ": " << str.ascii() << endl;
++it;
}
It is possible to create arbitrarily complex data structures using QVariant by holding values of container types:
QMap<QString, QVariant> price;
price["Orange"] = 2.10;
price["Pear"].asMap()["Standard"] = 1.95;
price["Pear"].asMap()["Organic"] = 2.25;
price["Pineapple"] = 3.85;
Here we have created a map with string keys (product names) and values that are either floating-point numbers (prices) or maps. The top level map contains three keys: "Orange", "Pear", and "Pineapple". The value associated with the "Pear" key is a map that contains two keys ("Standard" and "Organic").Creating data structures like this can be very seductive since we can structure the data in any way we like. But the convenience of QVariant comes at a price. For the sake of readability, it is usually worth defining a proper C++ class to store our data. A custom class provides type safety and will also be more speed- and memory-efficient than using QVariant.