3.2 字节数组类QByteArray
字节数组类QByteArray提供一个字节数组,用于存储原始字节。使用QByteArray类比使用char *更方便。该类在串口通信中经常被使用,因为串口通信数据都是一个一个的8位字节流。
3.2.1 初始化
通常有两种方法可以初始化QByteArray类的对象。
第一种方法是通过const char *将其传递给构造函数。例如,以下代码创建一个大小为5个字节的字节数组,数据为“Hello”:
QByteArray ba("Hello");
虽然我们定义了5个字节长度的字节数组对象,索引范围从0到4,但是系统自动会在字节数组对象结尾添加一个'\0'字符,这是为了某些场合使用方便。所以,我们在索引5的位置可以得到字符数据'\0',比如:
第二种方法是使用resize()设置数组的大小,并初始化每个数组元素。
QByteArray ba; ba.resize(6); ba[0] = 0x3c; ba[1] = 0xb8; ba[2] = 0x64; ba[3] = 0x18; ba[4] = 0xca;
QByteArray类使用从0开始的索引值,就像C++数组一样。在调用resize()后,新分配的字节具有未定义的值。要将所有字节设置为特定值,可以调用fill()函数,该函数的原型声明如下:
QByteArray &QByteArray::fill(char ch, int size = -1)
其中,参数ch是要给字节数组设置的字符;size如果不是-1,就表示重新要为字节数组开辟的空间大小。比如:
第一次调用fill()函数后,ba所有空间的内容都是字符o了;第二次调用fill()函数后,因为fill()函数的第二个参数size是2,所以会重新调整ba的空间大小,变为2个字节,而且内容重新设置为"XX"。
3.2.2 访问某个元素
访问QByteArray类对象中的某个元素主要有4种方式,分别为[]、at()、data[]和constData[]。其中,[]和data[]方式为可读可写,at()和constData[]方式仅为可读。如果只是进行读操作,则通过at()和constData[]方式的访问速度最快,因为避免了复制处理。
at()可以比operator []()更快,就是因为前者不会发生深层复制。
【例3.2】 访问QByteArray类对象中的单个数据
(1)启动Qt Creator 4.8.2,新建一个控制台项目,项目名为test。
(2)在main.cpp中输入如下代码:
qDebug()会输出ba[2]对应的字符,ASCII码为0x64的字符是'd'。
(3)按Ctrl+R快捷键运行项目,结果如图3-2所示。
图3-2
3.2.3 截取子字符串
要一次提取多个字节,可使用函数left()、right()或mid()。
(1)函数left()返回从索引0位置开始、长度为len的子字节数组,该函数的原型声明如下:
QByteArray left(int len)
其中,参数len表示从数组左边开始要截取的字节数组的长度,如果len大于原来整个字节数组的长度,则返回整个字节数组。下列代码演示了函数left()函数的使用:
QByteArray x("Pineapple"); QByteArray y = x.left(4); // y == "Pine"
(2)函数right()用来获取从字节数组最后一个字节数据开始,向前面截取len个字节并返回截取的子字节数组。该函数的原型声明如下:
QByteArray right(int len)
其中,参数len表示从右边开始要截取的子字节数组的长度,如果len大于原来整个字节数组的长度,则返回整个字节数组。下列代码演示了函数right()函数的使用:
QByteArray x("Pineapple"); QByteArray y = x.right(5); // y == "apple"
(3)函数mid()返回从指定索引位置开始,向右边(即后面)长度为len的子字节数组。该函数的原型声明如下:
QByteArray mid(int pos, int len = -1)
其中,参数pos表示开始截取的索引,索引值从0开始;len表示要截取的子字节数组的长度,如果len为-1(默认值)或pos+len大于原字节数组的长度,则返回从pos开始一直到右边剩下的全部字节数组。下列代码演示了函数mid()函数的使用:
3.2.4 获取字节数组的大小
可以用成员函数size、length和count来获取字节数组的大小。除了名字不同,这3个函数是等同的,函数的原型声明如下:
int size(); int length(); int count();
这3个函数返回字节数组中的字节数。Size()函数的用法如下:
QByteArray ba("Hello"); int n = ba.size(); // n == 5
执行后,n等于5。可见,size()并不包含字符串末尾自动添加的'\0'。另外,如果以字符串形式初始化,中间有'\0',则size()不会统计'\0'及其后面的字符。
QByteArray ba2("He\0llo"); int n = ba2.size(); // n == 2
执行后,n等于2。通过resize分配空间,然后通过逐个赋值来进行初始化的话,中间某个字节数据是'\0',并不会被size()函数截断。比如:
3.2.5 数据转换与处理
从串口读取到的QByteArray数据一般需要进行提取和解析,此时就需要将QByteArray数据转换为各种类型的数据。常用的转换包括:
(1)转为Hex,用于显示十六进制,这点在调试时特别有用,因为大多HEX码是没有字符显示的,如0x00、0x20等。
(2)转为不同进制数值并显示,如二进制、八进制、十进制和十六进制等数值。
(3)转为整数类型、浮点类型等的数据类型。
(4)字母大小写进行转换。
(5)转为字符串类型。
1. Hex转换(十六进制转换)
QByteArray类的公有静态函数QByteArray::fromHex可以把十六进制编码的数据转换为字符(char)类型的数据,并存储到QByteArray类对象中。该函数的原型声明如下:
QByteArray fromHex(const QByteArray &hexEncoded)
其中,参数hexEncoded是十六进制编码的字节数组。由于该函数并不检查参数的有效性,因此遇到非十六进制数据则直接略过,然后继续处理剩余的数据。下列代码演示了fromHex()函数的使用:
字符'5'和'1'为一组,转为十六进制数据0x51,0x51对应的十进制数据是81,ASCII码为81的字符是'Q'。
与fromHex()相逆的函数是toHex(),该函数将字节数组中十六进制的数值编码转化为字符,它的原型声明如下:
QByteArray toHex()
下列代码演示了toHex()函数的使用:
索引为0的字节数据为0x30,直接转为两个字符'3'和'0'。
2. 数值转换与输出
尽管QByteArray类是一个集合,但也可以作为一个特殊形式的数值来用,其灵活的转换格式可大大方便各种格式数据转换与显示的需求,如显示二进制和十六进制、显示科学记数和指定小数位的数值。QByteArray类的公有静态函数number可以完成这些功能。该函数可以将某个整数转为某种进制的字符数组,函数number的原型声明如下:
QByteArray number(int n, int base = 10)
其中,参数n是要转变的整数;base是要进行转换的进制,进制取值范围为2到36,即从二进制到三十六进制。该函数返回整数n对应的base进制的字符数组。下列代码演示了number()函数的使用:
与此公有静态函数功能类似的公有函数是setNum(),该函数也是将某个整数转为某种进制的字符数组,函数的原型声明如下:
QByteArray & setNum(int n, int base = 10)
其中,参数n是要转变的整数;base是要进行转换的进制,进制取值范围为2到36,即从二进制到三十六进制。该函数返回整数n对应的base进制的字符数组。下列代码演示了setNum()函数的使用:
QByteArray ba; int n = 63; ba.setNum(n); // ba == "63" ba.setNum(n, 16); // ba == "3f"
因为不是静态函数,所以要用对象来调用。此外,根据setNum()函数第一个参数的类型,setNum()函数可以有多种版本,比如:
QByteArray &QByteArray::setNum(ushort n, int base = 10) QByteArray &QByteArray::setNum(short n, int base = 10) QByteArray &QByteArray::setNum(uint n, int base = 10) QByteArray &QByteArray::setNum(qlonglong n, int base = 10)
用法类似,只是n的取值范围不同。
除了整数之外,还能把数值按指定格式和小数位转换输出,所调用的函数依旧是number(),只不过参数形式变了:
QByteArray number(double n, char f = 'g', int prec = 6)
其中,参数n是要进行转换的实数;f表示转换格式,取值如下:
· e:采用指数法表示实数,此时实数的格式如[-]9.9e[+|-]999。
· E:格式同e,不过E要大写。
· f:普通小数表示法,此时格式如[-]9.9。
· g:使用e或f格式,第三个参数表示有效数字位的个数。
· G:使用E或f格式,第三个参数表示有效数字位的个数。
当参数f为'e'、'E'或'f '时,prec表示十进制小数点后小数部分的位数;当f为'g'或'G'时,prec表示有效数字位数的最大数目。注意,小数位要四舍五入。
【例3.3】 实数转为字节数组
(1)启动Qt Creator 4.8.2,新建一个控制台项目,项目名为test。
(2)在main.cpp中输入如下代码:
我们分别使用了5种格式将实数12345.6转换为字节数组,最后输出结果。
(3)按Ctrl+R快捷键运行项目,结果如图3-3所示。
图3-3
3.2.6 字母大小写的转换
QByteArray类对象若为带大小写字母的字符串,可调用函数toUpper()和toLower()实现字母大小写的转换。函数toUpper()的原型声明如下:
QByteArray toUpper()
函数很简单,没有参数,直接返回转换成大写字母后的字节数组。在转换过程中,碰到已经是大写的字母就忽略。用法举例如下:
QByteArray x("Qt by THE QT COMPANY"); QByteArray y = x.toUpper(); // y == "QT BY THE QT COMPANY"
函数toLower()也很简单,它的原型声明如下:
QByteArray toLower()
返回转换成小写字母后的字节数组。在转换过程中,碰到已经是小写的字母就忽略。用法举例如下:
QByteArray x("Qt by THE QT COMPANY"); QByteArray y = x.toLower(); // y == "qt by the qt company"
除了字母大小写的转换,QByteArray类还提供了判断是大写字母还是小写字母的成员函数isUpper和isLower。其中,isLower()函数的原型声明如下:
bool isLower()
如果字节数组中只包含小写字母则返回true,否则返回false。
3.2.7 字符串数值转为各类数值
QByteArray类对象的字符若都为数值,则可通过to**函数(也称为方法)转为各种类型的数据,示例如下:
3.2.8 QByteArray与char*互转
成员函数data可以返回指向字节数组中存储数据的指针。该函数的原型声明如下:
char *data();
该指针可用于访问和修改组成数组的元素。可以指定具体访问字节数组中的某一个,比如ba.data()[0]表示访问第0个。
如果要把char*转为QString,可以直接作为参数传入QByteArray类的构造函数中:
char* pt; QByteArray byte(str);
我们来看一个小例子。
【例3.4】 返回char*并打印内容。
(1)启动Qt Creator 4.8.2,新建一个控制台项目,项目名为test。
(2)在test.cpp中输入如下代码:
(3)按Ctrl+R快捷键运行项目,结果如图3-4所示。
图3-4
3.2.9 QByteArray与std::string互转
string是C++标准库中的字符串类型。QByteArray类提供的成员函数toStdString()可以将字节数组转为string。该函数的原型声明如下:
std::string toStdString();
与该函数相反的函数是静态成员函数fromStdString(),它将string数据转为字节数组,该函数的原型声明如下:
[static] QByteArray QByteArray::fromStdString(const std::string &str);
其中,参数str是要转换的string字符串。函数返回转换后的字节数组。注意,转换的是str的一份备份,转换过程并不会影响str本身的内容。
3.2.10 与字符串QString互转
QString是Qt的字符串类,QByteArray是byte的数组。它们之间也可以互转。
QByteArray与QString互转极为简单,二者在本质上是类似的,都是连续存储的,区别是前者可以存储无法显示的字符,后者只存储可显示的字符。如QByteArray类对象可以存储0x00-0x19,而QString类对象只能存储如0x30等可显示字符(0x20-0x7E)。有关可显示字符,可参见ASCII表,相信大家在学习C语言时都了解过了。
String转QByteArray的代码如下:
QString str=QString("hello world!"); QByteArray arr = str.toLatin1();
QByteArray转QString的代码如下:
QByteArray arr("hello world!"); QString str = arr;
下面再看一下QByteArray转为QString示例:
QByteArray ba("abc123"); QString str = ba; //或str.prepend(ba); qDebug()<<str ; //输出:"abc123"
QString转为QByteArray示例:
QString str("abc123"); QByteArray ba = str.toLatin1(); qDebug()<<ba; //输出:"abc123"
3.2.11 QByteArray与自定义结构体之间的转化
在Socket网络编程中,网络数据一般是uchar类型(最好是用uchar来传输,避免莫名其妙的错误,另外用char类型也可以),在Qt中则可以使用QByteArray类。QByteArray类在QSocket共享库中,根据C++中char*数据与结构体之间的映射可以实现结构体与QByteArray的转化。下面来看一段代码:
上面这段程序的运行结果如下:
3.2.12 判断是否为空
可以使用函数isEmpty()来判断字节数组是否为空,即size是否为0。函数isEmpty()的原型声明如下:
bool isEmpty();
如果字节数组的size为0,则返回true,否则返回false。
下列代码演示isEmpty()函数的使用:
QByteArray().isEmpty(); // returns true QByteArray("").isEmpty(); // returns true QByteArray("abc").isEmpty(); // returns false
3.2.13 向前搜索和向后搜索
函数indexOf()返回该字节数组中第一次出现字节数组ba的索引位置,从索引位置向前搜索。该函数的原型声明如下:
int indexOf(const QByteArray &ba, int from = 0);
其中,参数ba为要查找的目标字节数组ba,找到ba就返回索引值;from表示开始搜索位置对应的索引值,默认从索引值为0的位置开始搜索。如果找到ba,则返回第一次出现ba所在位置对应的索引值,如果没有找到,则返回-1。注意,所谓向前搜索,就是朝着索引值增大的方向搜索,即在数组中从左到右搜索。
下列代码演示了这个函数的使用方法:
indexOf()还可以搜索char*和QString类型的数据,函数的原型声明如下:
int indexOf(const char *str, int from = 0); int indexOf(const QString &str, int from = 0);
此外,还有以某个字符为搜索对象的函数声明形式:
int indexOf(char ch, int from = 0);
使用示例如下:
indexOf()函数是向前搜索,另外还有一个函数lastIndexOf()是向后搜索,该函数的原型声明如下:
int lastIndexOf(const QByteArray &ba, int from = -1);
3.2.14 插入
函数insert()可以在某个索引位置上插入字节数组,该函数的原型声明如下:
QByteArray & insert(int i, const QByteArray &ba);
其中,i为要插入的索引位置;ba为要插进去的字节数组对象。使用示例如下:
QByteArray ba("Meal"); ba.insert(1, QByteArray("ontr")); // ba == "Montreal"
此外,也可以在某个位置插入一个或多个字符,有两个函数,这两个函数的原型声明如下:
QByteArray & QByteArray::insert(int i, char ch); QByteArray & insert(int i, int count, char ch);
其中,i为要插入的索引位置;count是要插入的字符个数,其实就是count个ch;ch为要插入的字符。
另外,还有一种重载形式,就是插入char*类型的数据,有两种函数的原型声明形式:
QByteArray & insert(int i, const char *str); QByteArray & QByteArray::insert(int i, const char *str, int len);
第一种形式不带长度,插入全部str;第二种形式带长度len,len表示str中的len个字节。
此外,Qt还提供了prepend()函数,该函数在原字符串开头插入另一个字符串。