三、Qt常用容器之QList

这篇具有很好参考价值的文章主要介绍了三、Qt常用容器之QList。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、QList介绍

介绍个锤子,有点累,摊牌了,这篇是水的,但是我觉得质量很高,因为我自己写不了这么详细,对,感谢某不知名网站编程指南,我直接白嫖,这篇实在是太多了,说句实话日常使用不久存个指针,然后遍历查找之类的,怎么能写这么多!

在开始讲解 QList 之前,我们先明确一下 Qt 数据容器能存储什么,不能存储什么。
Qt 提供的数据容器都是模板类,构造时要带数据类型,比如下面这句定义整型数列表:

QList<int> integerList;

Qt 数据容器有支持的数据类型,也有不支持的类型,不仅是 QList ,其他数据容器都有不支持的数据类型。
存储在 Qt 数据容器里面的数据,必须是可赋值类型!
比如 C++ 基本数据类型,int、double、指针等可以存储在 Qt 数据容器里;
Qt 的数据类型,比如 QString、QDate、QTime 等,我们在 Qt Assistant 帮助文档里面查询 QDataStream 支持的可串行化的数值类型,关键字索引为“Serializing Qt Data Types”,这些基本都是可以存到 Qt 数据容器里面的。

还有不能存储在数据容器里的:窗体 QWidget、对话框 QDialog、定时器 QTimer 等等, 凡是 QObject 类和所有 QObject 派生类的对象都不允许直接存储在数据容器里面 。如果代码里新建 QList 列表,那么编译器会报错,QWidget 的复制构造函数和赋值运算符 = 函数都是禁用的。因为窗口不算数据类型,窗口里有线程、指针、句柄、信号和槽函数等等非常复杂的东西,不能当普通数据类型使用。如果确实要存储窗口列表,那么只能存储指 针,比如 QList<QWidget >。
**
如果希望自定义数据类型能存储在 Qt 数据容器里面,那么自定义类型必须至少满足三个条件:
①定义默认构造函数,比如 MyData() ;
②定义复制构造函数,比如 MyData(const MyData &d);
③定义赋值运算符 = 函数,比如 MyData& operator=(const MyData &d)。
*
讲完可以存储和不能存储的类型之后,我们开始列表类 QList 的学习。

QList 就是存储 T 类型数据的列表,类似 T 的数组,可以通过数据下标访问各个元素, QList 针对数据的插入、列表头部添加元素、列表尾部添加元素等等做过优化,访问也非常高效。 QList 应用非常广泛, QList 可以模拟队列操作,比如入队 append() ,出队 takeFirst();也可以模拟栈工作,比如入栈 append() ,出栈 takeLast();模拟数组就更简单了,直接用用下标形式,如 aList[0] 。

2、构造函数

(1)构造函数

QList() //默认构造函数
QList(const QList & other) //复制构造函数
QList(QList && other) //移动构造函数
QList(std::initializer_list args) //初始列表构造函数
~QList() //析构函数

第一个是默认构造函数,不带任何参数,可以支持嵌套的列表或与其他数据容器嵌套。
第二个是复制构造函数,将 other 里面的元素均复制到新对象里面。
第三个是 C++11 新特性,配合 std::move 语句使用,将 other 里面的元素全部移动给新建对象,other 本身清空。
第四个也是 C++11 新特性,使得列表对象可以用大括号列举元素初始化,比如 {1, 3, 6, 8} 可以用于初始化整型数列表。
项目里启用 C++11 特性,需要在 *.pro 项目文件里添加一行配置代码:

CONFIG += c++11

3、插入函数

void append(const T & value)
void append(const QList<T> & value)
void push_back(const T & value) //同第一个 append(),STL风格添加到队尾

append() 是追加元素到列表的末尾,第一个追加函数添加一个元素到列表末尾,第二个追加函数将一个列表中所有元素追加到列表末尾。

void insert(int i, const T & value)

插入函数将 value 插入到序号为 i 的位置,如果 i 为 0,元素插到列表列头部,如果 i 为 size() 数值,那么元素添加到列表末尾。

void prepend(const T & value)
void push_front(const T & value) //STL风格,同 prepend()

prepend() 是将参数里的元素 value 添加到列表头部,等同于 insert(0, value) 。

4、删除和移动类函数

移除函数和删除函数调用之前都要判断列表是否非空,用 ! empty() 或 ! isEmpty() 判断,带有序号 i 的必须保证序号不越界。

T takeAt(int i) //移除序号为 i 元素并返回该元素
T takeFirst() //移除队头元素并返回该元素
T takeLast() //移除队尾元素并返回该元素

take**() 移除函数只是将元素从列表中卸载下来,并不会删除元素内存空间,卸下来的元素作为返回值返回。

void removeAt(int i) //删除序号为 i 的元素,释放该元素内存
void removeFirst() //删除队头元素,释放该元素内存
void pop_front() //同 removeFirst() ,STL风格
void removeLast() //删除队尾元素,释放该元素内存
void pop_back() //同 removeLast(),STL风格

remove**() 函数没有返回值,直接从列表删除元素,并释放该元素内存空间,删除的元素彻底消失。

int removeAll(const T & value) //删除列表中所有等于 value 值的元素,返回删除的数量
bool removeOne(const T & value) //删除列表中第一个等于 value 值的元素,如果列表有等于 value 值的元素,返回 true,否则返回 false

注意 removeAll( value ) 函数不是清空列表,而是删除列表中所有等于 value 值的元素,并返回删除的计数。
removeOne( value ) 函数第一个等于 value 值的元素,如果删除成功返回 true,即列表中存在等于 value 的元素,
如果删除失败返回 false,即列表中不存在等于 value 的元素。需要特别注意的是:
removeAll( value )、removeOne( value )这两个函数必 须要类型 T 存在 operator==() 等于号函数,T 类型能够进行相等判断!

void clear()

clear() 函数才是删除列表所有元素,并释放内存空间。

5、访问和查询函数

``int size() constint length() const //同 size()

获取列表大小,即存储的元素总数量。列表有 size() 函数,但是没有 resize() 函数,不能直接扩展列表大小。
列表只有为新元素提前保留空间的函数:

void reserve(int alloc)

reserve() 函数是在程序员能够提前预估元素总量的情况提前为 alloc 数量的元素申请内存空间,保留使用。
如果 alloc 数值不超过 size() 数值,该函数调用被忽略;如果 alloc 数值大于 size() 数值,那么提前分配空间,保证列表能够存储 alloc 数量的元素。注意 reserve() 函数不会改变列表 size() 数值大小,不会添加任何新元素,仅仅是调整内部存储空间,保留使用,实际并没有使用新分配的空间,列表内的元素个数不变。
列表有两个 count() 计数函数,用途不一样:

int count(const T & value) const
int count() const

第一个 count( value ) 函数统计列表中等于 value 值的元素个数,这个函数也需要类型 T 带有 operator==() 等于号函数。
第二个 count() 函数不带参数,等同于 size() 函数,是列表元素的总数量。

判断列表是否为空列表,使用如下函数:

bool empty() const //空列表返回 true,有元素就返回 false,STL风格
bool isEmpty() const //空列表返回 true,有元素就返回 false ,Qt 风格

注意 empty() /isEmpty() 、size()/length()/count() 函数要牢记经常使用,因 为列表的函数,凡是涉及到序号 i 的,绝大部分函数都不会判断序号 i 是否越界,需要程序员手动判断序号 i 是否越界,列表的很多函数并不安全!
列表函数为了优化访问效率,基本上没有为访问序号 i 的函数添加越界判断,所以一旦越界,程序很可能崩溃!

访问序号为 i 的元素,可以使用如下函数:

const T & at(int i) const

at( i ) 函数返回序号为 i 元素的只读引用,但是不 进行数组越界 判断,需要手动判断,该函数的好处是读取效率高。
比较安全的访问函数是下面两个:

T value(int i) const
T value(int i, const T & defaultValue) const

第一个 value( i ) 函数返回 序号为 i 的元素(数值复制,不是引用),如果 i 越界,那么返回 T 类型默认构造函数生成的数值,比如 int、double、指针 都返回 0(Qt 容器为基本数据类型做了初始化)。
第二个 value( i, value ) 原理是类似的, i 不越界就返回该序号元素值,越界就返回参数里指定的 value 值。
两个 value() 函数因为每次调用都进行数组越界判断,所以访问效率不如 at() 函数高,在知道不越界的情况下使用 at() 更好。

查询数组里是否包含某个数值元素,使用如下函数:

bool contains(const T & value) const

如果包含等于 value 值的元素返回 true,否则返回 false。要统计等于 value 值的元素个数,使用前面的 count( value ) 函数。
如果希望查询等于 value 值的元素的序号,

int indexOf(const T & value, int from = 0) const //从前向后 查找等于 value 值的元素序号
int lastIndexOf(const T & value, int from = -1) const //从后向前查找等于 value 值的元素序号

indexOf( value, from ) 是从前向后查找元素,第一个参数是要匹配的数值,第二个是查询的起始最小序号,默认从 0 序号开始查找。
lastIndexOf( value, from ) 是从后向前查找元素,第一个参数是要匹配的数值,第二个是查询的起始最大序号,默认从队尾开始查找。
这两个查询函数,如果没找到匹配元素就返回 -1,如果找到了就返回值正确的序号。
注意 contains( value )、count( value ) 、indexOf( value, from )、lastIndexOf( value, from ) 函数都要求 T 类型支持 operator==() 等于号函数。

判断队头、队尾元素是否为 value 的函数如下:

bool startsWith(const T & value) const //检查队头是否等于 value
bool endsWith(const T & value) const //检查队尾是否等于 value

startsWith( value ) 检查队头元素,如果等于 value 就返回 true,如果列表为空或队头不等于 value 返回 false。
endsWith( value ) 检查队尾元素,如果等于 value 就返回 true,如果列表为空或末尾不等于 value 返回 false。

获取列表头部、尾部元素引用的函数如下:

T & first() //队头读写引用,可以修改队头数值
const T & first() const //队头只读引用
T & front() //队头读写引用,可以修改队头数值,STL风格
const T & front() const //队头只读引用,STL风格
T & last() //队尾读写引用,可以修改队尾数值
const T & last() const //队尾只读引用
T & back() //队尾读写引用,可以修改队尾数值,STL风格
const T & back() const //队尾只读引用,STL风格

注意区分只读引用和读写引用,只读引用不会改变元素的数值,而读写引用可以修改队头或队尾的数值。
上面获取队头、队尾引用的 8 个函数本身没有进行列表数组非空判断,在调用它们之前,
必须手动用 ! empty() 或 ! isEmpty() 判断列表非空之后才能调用上面 8 个函数。

获取列表的子列表,使用如下函数:

QList<T> mid(int pos, int length = -1) const

mid() 函数新建一个子列表,将本列表从序号 pos 开始位置,复制长度为 length 数量的元素到子列表中并返回。
如果 length 为 -1(或大于后面剩余的元素数量),就返回从 pos 开始的所有元素列表。返回的子列表是独立的新列表,与本列表没有内存共享。

6、替换、移动和交换函数

替换函数就是赋值修改:

void replace(int i, const T & value) // 等同于 list[i] = value;

将 序号为 i 的元素数值修改为新的 value。注意序号 i 不能越界,必须满足 0 <= i < size() 。

void move(int from, int to)

move(from, to) 移动函数是将序号 from 的元素移动到序号为 to 的位置,就是先卸载 from 序号元素,然后插入到 to 序号位置。
两个序号必须都位于 0 到 size() 之间,序号必须合法。

void swap(QList<T> & other)

这是大 swap() 函数,将本列表所有元素与参数 other 列表内所有元素互换,这个函数不会出错,并且互换的效率非常高。

void swap(int i, int j)

第二个是小 swap() 函数,将序号 i 的元素和序号 j 的元素数值互换,序号 i、j 不能越界,必须合法。

7、运算符函数

我们设置三个简单整数列表,在表格中举例说明各个运算符函数用途。三个列表如下:

QList<int> aList = {1, 3, 5};
QList<int> bList = {2, 4, 6};
QList<int> cList;

上面使用 C++11 初始列表构造了列表 aList 和 bList ,cList 是空列表。各个运算符函数举例如下表:

运算符函数 举 例 描述
bool operator!=(const QList<T> & other) const aList != bList ; aList 和 bList 两个列表有元素不同,结果为 true。
QList<T> operator+(const QList<T> & other) const cList = aList + bList; aList 和 bList 复制拼接后生成新列表,赋值给 cList。
QList<T> & operator+=(const QList<T> & other) aList += bList ; 复制 bList 所有元素追加到 aList 末尾。
QList<T> & operator+=(const T & value) aList += 100 ; 添加一个元素 100 到 aList 末尾。
QList<T> & operator<<(const QList<T> & other) aList<<bList; 复制 bList 所有元素追加到 aList 末尾。
QList<T> & operator<<(const T & value) aList<<100; 添加一个元素 100 到 aList 末尾。
QList<T> & operator=(const QList<T> & other) cList = aList; aList 所有元素都复制一份给 cList,aList本身不变。二者相等。
QList & operator=(QList<T> && other) //移动赋值 cList = std::move(aList) ; aList 所有元素都移动给 cList,aList本身被清空。
bool operator==(const QList<T> & other) const aList == bList ; aList 和 bList 有元素不同,结果为 false。 只有两个列表所有元素相等并且顺序一样,它们才能算相等。
T & operator[](int i) aList[0] = 100; 获取序号为 i 的元素的读写引用,可修改列表元素。
const T & operator[](int i) const qDebug()<<aList[0] ; 获取序号为 i 的元素的只读引用。

这里说明一下:operator==() 函数需要左右两个列表的长度、每个序号对应元素全部都相同才返回 true,两个列表的元素次序也都要求一样。列表的等于号函数和不等于号函数都要求元素类型 T 必须有 operator==() 判断各个元素是否相等。
移动赋值与移动构造类似,都是 C++11 的新特性,需要使用 std::move 语句实现,移动后右边列表会被清空,元素只存在左边列表里。
中括号函数(数组下标访问函数)要求序号 i 必须合法, 0 <= i < size() ,如果序号越界,程序会崩溃。

8、迭代器函数

QList 内嵌了 STL 风格的只读迭代器和读写迭代器:

QList::const_iterator //只读迭代器类,STL风格
QList::iterator //读写迭代器类,STL风格
QList::ConstIterator //只读迭代器,Qt命名风格
QList::Iterator //读写迭代器,Qt命名风格

迭代器就像指向元素的指针,可以枚举列表中所有元素,迭代器本身支持各种操作符函数,
比如 ++ 是找寻下一个元素,-- 是倒退一个元素, (* it) 是获取元素。Qt 帮助文档中示范了迭代器的使用:

QList<QString> list;
list.append("January");
list.append("February");
...
list.append("December");

QList<QString>::const_iterator i;
for (i = list.constBegin(); i != list.constEnd(); ++i)
cout << *i << endl;

上述代码定义了一个字符串列表,为字符串列表添加多个字符串,然后定义字符串列表的迭代器 i;
i 从列表头部迭代器开始,逐个遍历列表元素,打印每个字符串,直到迭代器末尾结束。
list.constBegin() 是指向队头元素的指针,但是注意 list.constEnd() 是指向队尾后面假想元素的指 针,
list.constEnd() 指向的东西根本不存在,仅用于越界判断。

获取指向队头、队尾假想元素的只读迭代器函数如下:

const_iterator begin() const //指向队头迭代 器,STL风格
const_iterator cbegin() const //指向队头迭代器,STL风格
const_iterator constBegin() const //指向队头迭代器,Qt命名风格
const_iterator end() const //指向队尾假想元素迭代器,STL风格
const_iterator cend() const //指向队尾假想元素迭代器,STL风格
const_iterator constEnd() const //指向队尾假想元素迭代器,Qt命名风格

获取指向队头、队尾假想元素的读写迭代器函数如下:

iterator begin() //指向队头迭代器,STL风格
iterator end() //指向队尾假想元素迭代器,STL风格

利用迭代器也可以添加元素或删除元素,通过迭代器插入元素的函数如下:

iterator insert(iterator before, const T & value) //在 before 指向的元素前面插入元素 value

这个 insert() 函数需要注意两点:第一是返回值的迭代器指向新增元素 value ;
第二是执行插入元素操作后,参数里的迭代器 before 失效,不能再使用,只能利用返回值的迭代器进行遍历。
通过迭代器删除一个元素或多个元素的函数如下:

iterator erase(iterator pos) //删除 pos 指向的元素,返回指向下一个元素的迭代器或者 list.end()
iterator erase(iterator begin, iterator end) //删除从 begin 到 end 指向的元素,注意 end 指向的元素不删除

第一个 erase() 函数删除单个元素,它的返回值可能为指向下一个元素或者 list.end() ,要注意判断是否为指向队尾假想元素的迭代器。
第二个 erase() 函数删除多个元素,从 begin 删除到 end,但是 end 指向的元素不删除,这个函数总是返回参数里的 end 迭代器。
由于 QList 自身带有非常多的功能函数,并且支持数组下标形式的访问,实际中几乎不需要使用迭代器操作 QList。因为用迭代器不如用数组下标来操作简单快捷。

9、容器类型转换函数

列表支持将自身对象转换为其他容器类型,比如集合、标准库列表、向量:

QSet<T> toSet() const //转为集合
std::list<T> toStdList() const //转为标准库的列表
QVector<T> toVector() const //转为向量

QList 能够转出,也能使用列表的静态成员函数,把其他三种容器转换为新的列表对象:

QList<T> fromSet(const QSet<T> & set) //静态函数,将集合转为列表
QList<T> fromStdList(const std::list<T> & list) //静态函数,将标准库列表转为 Qt 列表
QList<T> fromVector(const QVector<T> & vector) //静态函数,将向量转为列表

静态成员函数的语法类似下面这样:

QVector v = {1, 2, 3};QList<int> cList = QList<int>::fromVector(v);

(9)其他内容
QList 附带了友元函数 operator<<() 和 operator>>(),用于支持数据流输入和输出:

QDataStream & operator<<(QDataStream & out, const QList<T> & list)
QDataStream & operator>>(QDataStream & in, QList<T> & list)

这些流操作符函数正常运行的前提是 类型 T 也能支持流的输入输出,对于 C++ 基本类型 int、double 等都没问题;
Qt 的数据类型如 QColor、QPoint 一般也都附带了友元函数,用于支持流输入输出。
如果使用自定义类型,希望存储在列表中并支持自动的流输入输出,那么要为自定义类型添加友元函数 operator<<() 和 operator>>() 。

使用 QList 时,需要注意 QList 仅支持存储 值类型、指针类型,不能存储变量的引用。
如果定义列表时类型 T 设置为引用,如 QList<int &> ,那么程序无法编译!

Qt 带有全局函数,可以支持容器类对象的排序:

void qSort(Container & container) //排序
void qStableSort(Container & container) //稳定排序

排序函数要求容器的元素类型 T 必须支持 operator<() 小于号函数,用于比较元素大小。
Qt 调用的小于号函数原型是两个参数的全局 operator<() 函数,不是成员函数,应该在类外面声明并定义下面的小于号函数:

bool operator< ( const T &t1, const T &t2 )

一般要将该函数声明为 T 类型的友元函数,方便访问私有变量。

最后我们梳理一下,如果自定义类型希望能够完美地和 QList 配合使用,那么需求如下:
① 必须是可赋值类型,需要默认构造函数、复制构造函数、赋值函数 operator=() ;
② 如果希望支持查询函数,需要双等号函数 operator==();
③ 如果希望支持排序函数,需要全局小于号函数 operator< ( const T &t1, const T &t2 ) ;
④ 如果希望支持 QDataStream 数据流输入输出,那么添加友元函数 operator<<() 和 operator>>() 。
第一条是必须实现的函数,后面三条是建议实现的函数。文章来源地址https://www.toymoban.com/news/detail-438329.html

到了这里,关于三、Qt常用容器之QList的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • [QT编程系列-21]:基本框架 - QT常见数据结构:QString、QList、QVector、QMap、QHash、QSet、QPair详解

    目录 1 QString 2 QList 3 QVector 4 QMap 5 QHash 6 QSet 7 QPair QString是Qt中用于存储和操作字符串的类。它提供了丰富的字符串处理方法和功能。 以下是QString的一些主要特点和常用操作: 创建QString对象: 获取字符串内容和长度: 字符串比较和搜索: 字符串分割和连接: 字符串格式

    2024年02月16日
    浏览(41)
  • Qt 容器介绍

    Qt容器对应STL容器,都是分为序列容器(顺序)容器、关联容器、散列(哈希)容器。 序列容器(顺序)容器 :QVector、QList、QLinkedList、QStack、QQueue 关联容器 :QMap、QMultiMap 散列容器 :QSet、QHash、QMultiHash QListT:T 不能QObject或者任何其子类。T必须是一个可赋值的类型,即提供一

    2024年02月11日
    浏览(27)
  • Docker容器命令(有点详细)

    Docker 容器是 Docker 平台中的一个基本概念,它是 Docker 技术的核心组成部分。Docker 容器是一个独立、可执行的运行单元,它包含了应用程序及其所有依赖项,可以在任何支持 Docker 的环境中运行。下面是 Docker 容器的详细介绍: 轻量和独立 : Docker 容器是轻量级的,它们与宿

    2024年02月15日
    浏览(44)
  • 鸿蒙常用容器组件介绍

    本文不介绍Text,Image这种单独的视图控件,主要还是过一下在构成一个复杂页面时所需要的外层的容器组件。免得在实际开发的时候要构建外层组件时不知道要用什么比较好 本文虽然也会贴一些测试代码,但是参考还是以鸿蒙的API参考为主,最好在上手这些视图的同时,学习

    2024年01月22日
    浏览(41)
  • 「Qt」常用事件介绍

            🔔 在开始本文的学习之前,笔者希望读者已经阅读过《「Qt」事件概念》这篇文章了。本文会在上篇文章的基础上,进一步介绍 Qt 中一些比较常用的事件。         当我们想要让控件收到某个事件时做一些操作,通常都需要 重写 相应的事件处理函数。这些事件处

    2024年02月14日
    浏览(34)
  • QT---day1(QT的介绍、常用类及组件)

      思维导图:  

    2024年02月15日
    浏览(43)
  • 【微信小程序 | 实战开发】常用的视图容器类组件介绍和使用(1)

    个人名片: 🐼 作者简介:一名大二在校生,喜欢编程🎋 🐻‍❄️ 个人主页🥇: 小新爱学习. 🐼 个人WeChat:hmmwx53 🕊️ 系列专栏:🖼️ 零基础学Java——小白入门必备 重识C语言——复习回顾

    2024年02月02日
    浏览(57)
  • C++初阶:容器适配器介绍、stack和queue常用接口详解及模拟实现

    介绍完了list类的相关内容后:C++初阶:适合新手的手撕list(模拟实现list) 接下来进入新的篇章,stack和queue的介绍以及模拟: stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 stack是作为容器适配器

    2024年02月19日
    浏览(44)
  • 【微信小程序】-- 常用视图容器类组件介绍 -- view、scroll-view和swiper(六)

    💌 所属专栏:【微信小程序开发教程】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大家:这里是CSDN,我总结知识的地方,喜欢的话请三连,有问题请私信 😘 😘 😘   大家好,又见面了,

    2024年01月18日
    浏览(61)
  • 摊牌了,我已经不知道摆烂了多少天了!

    是的,你没有看错! 我觉得用这个词来形容,我还是很合适的吧,怎么说呢, 可能接下来的行文逻辑,会有写无厘头,甚至说毫无章法可言,或是题不对文? but,这已经不重要了,嘘别说话,感觉来了! 人很难做到心无旁骛,毫无波澜,常会被这样那样的人和事所打乱计划

    2024年01月22日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包