QString
QString类提供了一个Unicode(Unicode是一种支持大部分文字系统的国际字符编码标准)字符串。其实在第一个Hello World程序就用到了它,而几乎所有的程序中都会使用到它。
QString存储了一串QChar,而QChar提供了一个16位的Unicode 4.0字符。在后台,QString使用隐式共享(implicit sharing)来减少内存使用和避免不必要的数据拷贝,这也有助于减少存储16位字符的固有开销。
隐式共享
隐式共享(Implicit Sharing)又称为写时复制(copy-on-write)。Qt中很多C++类使用隐式数据共享来尽可能的提高资源使用率和尽可能的减少复制操作。使用隐式共享类作为参数传递是既安全又有效的,因为只有一个指向该数据的指针被传递了,只有当函数向它写入时才会复制该数据。
共享的好处是程序不需要进行不必要的数据复制,这样可以减少数据的拷贝和使用更少的内存,对象也可以很容易地被分配,或者作为参数被传递,或者从函数被返回。隐式共享在后台进行,在实际编程中我们不必去关注它。Qt中主要的隐式共享类有:QByteArray、QCursor、QFont、QPixmap、QString、QUrl、QVariant和所有的容器类等等 。
QPixmap p1, p2;
p1.load("image.bmp");
p2 = p1; // p1与p2共享数据
QPainter paint;
paint.begin(&p2); // p2被修改
paint.drawText(0,50, "Hi");
paint.end();
一个共享类由一个指向一个共享数据块的指针和数据组成,在共享数据块中包含了一个引用计数。当一个共享对象被建立时,会设置引用计数为1,例如这里QPixmap类是一个隐式共享类,开始时p1和p2的引用计数都为1。每当有新的对象引用了共享数据时引用计数都会递增,而当有对象不再引用这个共享数据时引用计数就会递减,当引用计数为0时,这个共享数据就会被销毁掉。
例如这里执行了“p2 = p1;”语句后,p2便与p1共享同一个数据,这时p1的引用计数为2,而p2的引用计数为0,所以p2以前指向的数据结构将会被销毁掉。
当处理共享对象时,有两种复制对象的方法:深拷贝(deep copy)和浅拷贝(shallow copy)。
深拷贝意味着复制一个对象,而浅拷贝则是复制一个引用(仅仅是一个指向共享数据块的指针)。一个深拷贝是非常昂贵的,需要消耗很多的内存和CPU资源;而浅拷贝则非常快速,因为它只需要设置一个指针和增加引用计数的值。
当隐式共享类使用“=”操作符时就是使用浅拷贝,如上面的“p2 = p1;”语句。但是当一个对象被修改时,就必须进行一次深拷贝,比如上面程序中“paint.begin(&p2);”语句要对p2进行修改,这时就要对数据进行深拷贝,使p2和p1指向不同的数据结构,然后将p1的引用计数设为1,p2的引用计数也设为1。
编辑操作
在QString中提供了多个方便的函数来操作字符串,例如:
- append()和prepend()分别实现了在字符串后面和前面添加字符串或者字符;
- replace()替换指定位置的多个字符;
- insert()在指定位置添加字符串或者字符;
- remove()在指定位置移除多个字符;
- trimmed()除去字符串两端的空白字符,这包括‘\t’、‘\n’、‘\v’、‘\f’、‘\r’和‘ ’;
- simplified()不仅除去字符串两端的空白字符,还将字符串中间的空白字符序列替换为一个空格;
- split()可以将一个字符串分割为多个子字符串的列表等等。
对于一个字符串,也可以使用“[ ]”操作符来获取或者修改其中的一个字符,还可以使用“+”操作符来组合两个字符串。在QString类中一个null字符串和一个空字符串并不是完全一样的。一个null字符串是使用QString的默认构造函数或者在构造函数中传递了0来初始化的字符串;而一个空字符串是指大小为0的字符串。一般null字符串都是空字符串,但一个空字符串不一定是一个null字符串,在实际编程中一般使用isEmpty()来判断一个字符串是否为空。
示例:
QString str = "hello";
qDebug() << QObject::tr("字符串大小:") << str.size(); // 大小为5
str[0] = QChar('H'); // 将第一个字符换为‘H'
qDebug() << QObject::tr("第一个字符:") << str[0]; // 结果为‘H'
str.append(" Qt"); // 向字符串后添加"Qt"
str.replace(1,4,"i"); // 将第1个字符开始的后面4个字符替换为字符串"i"
str.insert(2," my"); // 在第2个字符后插入" my"
qDebug() << QObject::tr("str为:") << str; // 结果为Hi my Qt
str = str + "!!!"; // 将两个字符串组合
qDebug() << QObject::tr(“str为:”) << str; // 结果为Hi my Qt!!!
str = " hi\r\n Qt!\n ";
qDebug() << QObject::tr("str为:") << str;
QString str1 = str.trimmed(); // 除去字符串两端的空白字符
qDebug() << QObject::tr("str1为:") << str1;
QString str2 = str.simplified(); // 除去字符串两端和中间多余的空白字符
qDebug() << QObject::tr("str2为:") << str2; //结果为hi Qt!
查询操作
- 在QString中提供了right()、left()和mid()函数分别来提取一个字符串的最右面,最左面和中间的含有多个字符的子字符串;
- 使用indexOf()函数来获取一个字符或者子字符串在该字符串中的位置;
- 使用at()函数可以获取一个指定位置的字符,它比“[ ]”操作符要快很多,因为它不会引起深拷贝;
- 可以使用contains()函数来判断该字符串是否包含一个指定的字符或者字符串;
- 可以使用count()来获得字符串中一个字符或者子字符串出现的次数;
- 而使用startsWith()和endsWidth()函数可以用来判断该字符串是否是以一个字符或者字符串开始或者结束的;
- 对于两个字符串的比较,可以使用“>”和“<=”等操作符,也可以使用compare()函数。
示例:
qDebug() << endl << QObject::tr("以下是在字符串中进行查询的操作:") <<endl;
QString str = "yafeilinux";
qDebug() << QObject::tr("字符串为:") << str;
// 执行下面一行代码后,结果为linux
qDebug() << QObject::tr("包含右侧5个字符的子字符串:") << str.right(5);
// 执行下面一行代码后,结果为fei
qDebug() << QObject::tr("包含第2个字符以后3个字符的子字符串:") << str.mid(2,3);
qDebug() << QObject::tr("'fei'的位置:") <<str.indexOf("fei"); //结果为2
qDebug() << QObject::tr("str的第0个字符:") << str.at(0); //结果为y
qDebug() << QObject::tr("str中'i'字符的个数:") << str.count('i'); //结果为2
// 执行下面一行代码后,结果为true
qDebug() << QObject::tr("str是否以”ya“开始?") << str.startsWith("ya");
转换操作
QString中的toInt()、toDouble()等函数可以很方便的将字符串转换为整型或者double型数据,当转换成功后,它们的第一个bool型参数会为true;
使用静态函数number()可以将数值转换为字符串,这里还可以指定要转换为哪种进制;
使用toLower()和toUpper()函数可以分别返回字符串小写和大写形式的副本。
实例:
qDebug() << endl << QObject::tr("以下是字符串的转换操作:") << endl;
str = "100";
qDebug() << QObject::tr("字符串转换为整数:") << str.toInt(); // 结果为100
int num = 45;
qDebug() << QObject::tr("整数转换为字符串:") << QString::number(num);//结果为“45”
str = "FF";
bool ok;
int hex = str.toInt(&ok,16);
qDebug() << "ok: "<< ok << QObject::tr("转换为十六进制:") << hex; // 结果为ok:true 255
num = 26;
qDebug() << QObject::tr("使用十六进制将整数转换为字符串:")
<< QString::number(num,16);//结果为1a
str = "123.456";
qDebug() << QObject::tr("字符串转换为浮点型:") << str.toFloat();//结果为123.456
str = "abc";qDebug() << QObject::tr("转换为大写:") << str.toUpper();// 结果为ABC
str = "ABC";qDebug() << QObject::tr("转换为小写:") <<str.toLower();// 结果为abc
arg( )函数
arg()函数中的参数可以取代字符串中相应的“%1”等标记,在字符串中可以使用的标记在1到99之间,arg()函数会从最小的数字开始对应,比如QString(“%5,%2,%7”).arg(“a”).arg(“b”),那么“a”会代替“%2”,“b”会代替“%5”,而“%7”会直接显示。
arg()的一种重载形式是:
arg ( const QString & a1, const QString & a2 );
它与使用str.arg(a1).arg(a2)是相同的,不过当参数a1中含有“%1”等标记时,两者的效果是不同的,这个可以在后面的程序中看到。
该函数的另一种重载形式为:
arg ( const QString & a, int fieldWidth = 0, const QChar & fillChar = QLatin1Char( ‘ ’ ) );
这里可以设定字段宽度,如果第一个参数a的宽度小于fieldWidth的值,那么就可以使用第三个参数设置的字符来进行填充。这里的fieldWidth如果为正值,那么文本是右对齐的,比如前面程序中的结果为“ni***hi”。而如果为负值,那么文本是左对齐的,例如将前面的程序中的fieldWidth改为-5,那么结果就应该是“nihi***”。
arg()还有一种重载形式:
arg ( double a, int fieldWidth = 0, char format = ‘g’, int precision = -1,
const QChar & fillChar = QLatin1Char( ‘ ’ ) );
它的第一个参数是double类型的,后面的format和precision分别可以指定其类型和精度。
int age = 25;
QString name = "yafei";
// name代替%1,age代替%2
str = QString("name is %1, age is %2").arg(name).arg(age);
// 结果为name is yafei,age is 25
qDebug() << QObject::tr("更改后的str为:") << str;
str = "%1 %2";
qDebug() << str.arg("%1f","hello"); // 结果为%1f hello
qDebug() << str.arg("%1f").arg("hello"); // 结果为hellof %2
str = QString("ni%1").arg("hi",5,'*');
qDebug() << QObject::tr("设置字段宽度为5,使用'*'填充:") << str;//结果为ni***hi
qreal value = 123.456;
str = QString("number: %1").arg(value,0,'f',2);
qDebug() << QObject::tr("设置小数点位数为两位:") << str; //结果为"number:123.45
QByteArray
QByteArray类提供了一个字节数组,它可以用来存储原始字节(包括‘\0’)和传统的以‘\0’结尾的8位字符串。
使用QByteArray比使用const char * 要方便很多,在后台,它总是保证数据以一个‘\0’结尾,而且使用隐式共享来减少内存的使用和避免不必要的数据拷贝。
但是除了当需要存储原始二进制数据或者对内存保护要求很高(如在嵌入式Linux上)时,一般都推荐使用QString,因为QString是存储16位的Unicode字符,使得在应用程序中更容易存储非ASCII和非Latin-1字符,而且QString全部使用的是Qt的API。 QByteArray类拥有和QString类相似的接口函数,除了arg()以外,在QByteArray中都有相同的用法。
QByteArray与QString之间的转换:
QByteArray cstr("abcd");
QString s = cstr;
QByteArray cstr("abcd");
QString s;
s.prepend(cstr);
QString s("hello");
QByteArray cstr = s.toLatin1();
QVariant
QVariant类像是最常见的Qt的数据类型的一个共用体(union),一个QVariant对象在一个时间只保存一个单一类型的一个单一的值(有些类型可能是多值的,比如字符串列表)。
可以使用toT()(T代表一种数据类型)函数来将QVariant对象转换为T类型,并且获取它的值。这里toT()函数会复制以前的QVariant对象,然后对其进行转换,所以以前的QVariant对象并不会改变。
QVariant是Qt中一个很重要的类
QObject::property()返回的就是QVariant类型的对象。
QVariant类的toInt()函数返回int类型的值,toFloat()函数返回float类型的值。
因为QVariant是QtCore库的一部分,所以它没有提供对QtGui中定义的数据类型(例如QColor、QImage等)进行转换的函数,也就是说,这里没有toColor()这样的函数。
不过,我们可以使用QVariant::value()函数或者qVariantValue()模板函数来完成这样的转换,例如下面程序中对QColor类型的转换。 对于一个类型是否可以转换为一个特殊的类型,可以使用canConvert()函数来判断,如果可以转换,则该函数返回true。
也可以使用convert()函数来将一个类型转换为其他不同的类型,如果转换成功则返回true,如果无法进行转换,variant对象将会被清空,并且返回false。
需要说明,对于同一种转换,canConvert()和convert()函数并不一定返回同样的结果,这通常是因为canConvert()只报告QVariant进行两个类型之间转换的能力。也就是说,如果在提供了合适的数据时,这两个类型间可以进行转换,但是,如果提供的数据不合适,那么转换就会失败,这样convert()的返回值就与canConvert()不同了。例如下面程序中的QString类型的字符串str,当str中只有数字字符时,它可以转换为int类型,比如str = “123”,因为它有这个能力,所以canConvert()返回为true。但是,现在str中包含了非数字字符,真正进行转换时会失败,所以convert()返回为false。
示例:
QVariant v1(15);
qDebug() << v1.toInt(); // 结果为15
QVariant v2(12.3);
qDebug() << v2.toFloat(); // 结果为12.3
QVariant v3("nihao");
qDebug() << v3.toString(); // 结果为"nihao“
QColor color = QColor(Qt::red);
QVariant v4 = color;
qDebug() << v4.type(); // 结果为QVariant::QColor
qDebug() << v4.value<QColor>(); // 结果为QColor(ARGB 1,1,0,0)
QString str = "hello";
QVariant v5 = str;
qDebug() << v5.canConvert(QVariant::Int); // 结果为true
qDebug() << v5.toString(); // 结果为"hello"
qDebug() << v5.convert(QVariant::Int); // 结果为false
qDebug() << v5.toString(); // 转换失败,v5被清空,结果为"0"