Qt 5.12实战
上QQ阅读APP看书,第一时间看更新

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()函数,该函数在原字符串开头插入另一个字符串。