Qt&C++ 技术分析4 - 流、d-pointer隐式共享以及容器迭代器

QT 中的流

下图展示 QT 中流的框架,含两个核心类 QTextStream 以及 QDataStream
在这里插入图片描述

QTextStream 对数据进行文本格式的输入/输出
QDataStream 对数据进行二进制格式的输入/输出
QFile ,负责文件的处理。
QTemporaryFile 创建并访问临时文件。
QBuffer 负责以 QIODevice 的接口访问一个 QByteArray 对象。
QProcess 负责以进程的形式启动一个外部程序,并和该进程进行通信。
QTcpSocket 与 QUdpSocket 负责使用 TCP、UDP 协议进行网络数据的发送和接收。

QTextCodec 负责 Unicode 与各种字符编码方式之间的转换。
QLocale 负责实现各种区域文化。


文件系统与底层文件操作

UNIX 系统中路径分割符为“/”,而 Windows 系统的为“\”

文件系统

Qt 使用类 QFileInfo 表示一个目录项的属性,类名中的“File”并不仅仅表示文件,而是泛指所有类型的目录项。

类 QDir 刻画一个目录的详细信息。
成员函数 entryInfoList(),返回该目录下子目录、文件以及链接的信息。


类 QFile

无论对于什么操作系统,子目录之间的分隔符都应该为“/”

//包含Qt框架中的QFile和QDebug库
#include <QFile>
#include <QDebug>

int main()
{
    
    
    //定义一个QFile对象f,并指定要打开的文件路径
    QFile f("data/test.txt");

    //以只读文本模式打开文件,如果打开失败,则返回-1,表示程序发生错误
    if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
        return -1;

    //将文件中的所有内容读取到一个QByteArray对象中
    QByteArray data = f.readAll();

    //输出QByteArray对象data的内容到控制台
    qDebug() << data;

    return 0;
}

QTextStream

QTextStream 在其内部使用两个字节长的 QChar 类型存放每个字符,使用 Unicode 编码方式

使用 QTextCodec 进行编码转换

扫描二维码关注公众号,回复: 15998636 查看本文章
#include <QFile> //包含Qt框架中的QFile库
#include <QTextStream> //包含Qt框架中的QTextStream库

int main(int argc, char *argv[])
{
    
    
    //定义一个QFile对象src_f,并指定要打开的文件路径
    QFile src_f("data/latin1.txt");

    //以只读模式打开文件,如果打开失败,则返回-1,表示程序发生错误
    if (!src_f.open(QIODevice::ReadOnly))
        return -1;

    //创建一个QTextStream对象in,并将其与QFile对象src_f关联
    QTextStream in(&src_f);

    //设置QTextStream对象in的编码格式为Latin1
    in.setCodec("latin1");

    //将文件中的所有内容读取到一个QString对象data中
    QString data = in.readAll();

    //定义一个QFile对象dest_f,并指定要打开的文件路径
    QFile dest_f("data/unicode.txt");

    //以只写模式打开文件,如果打开失败,则返回-1,表示程序发生错误
    if (!dest_f.open(QIODevice::WriteOnly))
        return -1;

    //创建一个QTextStream对象out,并将其与QFile对象dest_f关联
    QTextStream out(&dest_f);

    //设置QTextStream对象out的编码格式为UTF-16
    out.setCodec("UTF-16");

    //将QString对象data中的内容写入到文件中
    out << data;

    return 0;
}

QDataStream

QDataStream 可以处理自定义类型

用户 struct 定义新的结构体,并通过运算符重载的方式,搭配友元实现

struct ColorText{
    
    
    QString text;
    QColor  color;
};
QDataStream& operator << (QDataStream & stream, const ColorText & data)
{
    
    
    stream << data.text << data.color;return stream;
}
QDataStream& operator >> (QDataStream & stream, ColorText & data)
{
    
    
    stream >> data.text >> data.color;return stream;
}
int main()
{
    
    
    ColorText data;
    data.text  = "Red";  data.color = Qt::red;
    QFile file( "test.dat" );
    if( !file.open( QIODevice::ReadWrite) )  return -1;
    QDataStream stream( &file );
    stream << data;
    file.seek(0);   stream >> data;
    file.close();
    qDebug() << data.text << " " << data.color;
}

QLocale

QLocale 为每种区域文化定义了统一的名字,这个名字不会随着操作系统、编译器平台的变化而变化

QLocale 的静态成员函数 system()返回这个区域文化。
Qt 应用程序本身会有一个默认的区域文化,构造函数 QLocale()返回的就是后者。起初,程序默认的区域文化被设

QTextStream out( stdout, QIODevice::WriteOnly);
int main( )
{
    
    
    double x = 123.456;
    out.setLocale( QLocale(QLocale::German) );       ①
    out << fixed << x << endl;
}

隐式共享与 d-pointer

隐式共享定义:一个类的多个对象所占用的内存是相互独立的。如果其中某些对象数据成员的取值完全相同,我们可以令它们共享一块内存以节省空间。只有当程序需要修改其中某个对象的数据成员时,我们再为该对象分配新的内存。

d-pointer:把与主类密切相关的数据成员抽离作为一个私类,主类中再定义一个指针指向该私类


隐式共享

QString 中的成员函数 toCaseFolded() 就用到了隐式共享技术
它使用引用计数的方式判断字符串是否相同,如果相同,则共享一块内存地址


d-pointer 在隐式共享中的应用

Qt 常在主类的名字后面加上后缀“Private”或者“Data”形成从类的名字

为了能够共享数据,我们必须将类中的数据分离出来,定义在一个单独的类中,再定义一个指针指向这个新类。
这个指针就被称为 d-pointer,这个模式就被称为 d-pointer 模式。
包含有 d-pointer 的那个类被称为主类,d-pointer 所指的那个类被称为从类。


二进制代码兼容

采用动态链接方式时,客户只需要更新 Qt 的动态链接库,不需要重新编译、部署 Qt 应用程序。
如果在这种情形下这些 Qt 应用程序仍然能够正常工作,我们称这个 动态链接库是二进制兼容的

程序员不可以添加、删除非静态数据成员,不可以更改非静态数据成员的定义顺序、类型

二进制兼容性通常包括以下三个方面

  1. ABI 兼容性:ABI(Application Binary Interface)指的是二进制接口,即不同编译器生成的二进制代码之间的接口规范。如果两个编译器使用相同的 ABI 规范,那么它们生成的二进制代码就可以在不同的平台上互相使用,这就是 ABI 兼容性。
  2. 数据类型兼容性:C++ 的数据类型在不同编译器、不同版本或不同操作系统上可能会有不同的大小和布局,如果不同的编译器使用相同的数据类型布局,就可以实现数据类型兼容性。
  3. 二进制格式兼容性:不同的操作系统和平台可能使用不同的二进制格式来存储可执行文件和库文件,如果不同的平台使用相同的二进制格式,就可以实现二进制格式兼容性。

d-pointer 模式实现

D-pointer 模式用于实现类的私有数据封装和二进制接口兼容性

实现 d-pointer 模式的主要步骤:

  1. 定义一个包含类的私有数据的结构体,并将其作为类的成员变量。
  2. 将类的所有公共成员函数的实现都移动到类的实现文件中,并在实现文件中定义一个指向私有数据结构体的指针。这个指针可以使用 new 运算符在堆上分配内存,也可以使用 std::unique_ptr 或 std::shared_ptr 等智能指针管理内存。
  3. 在类的构造函数中,为 D-指针分配内存,并将其指向私有数据结构体。在析构函数中,释放 D-指针的内存。
  4. 将类的公共接口重定向到私有数据结构体中的成员函数。这可以通过在公共接口中使用 D-指针来实现。
  5. 在类的头文件中,只声明类的公共接口,而不暴露私有数据结构体或 D-指针。

Qt 容器及迭代器

为便于跨平台,QT 研发出了 QTL(类似于 Cpp 的 STL),但是其运行速度较慢


QTL 概述

几种常见的迭代器及其对应类型

几种必备容器(单类型容器)

  • QVector<T> 将其所有元素存放在一块连续的内存中。随机访问的速度很快,但是插入/删除操作很慢。
  • QStack<T>QVector<T>的子类,实现栈的功能
  • QList<T> 在内部使用一个指针数组指向容器元素。能够快速随机访问每个元素。在容器首、尾添加元素的速度也较快。
  • QStringListQList<QString> 的子类,能高效地处理字符串列表。QQueue<T>QList<T>的子类,实现了队列的功能。
  • QLinkedList<T> 能够在很短而且固定的时间内完成元素的插入/删除操作,但是排序、查找操作却较慢。

键值对类型容器

  • QMultiMap<Key, T>
    它是 QMap 的子类,其 insert 函数允许新元素的 key 和已有元素的 key 相同。
    它不支持运算符“[]”,取而代之的是函数 values(),该函数返回所有具有指定 key 值的元素,并将它们存放在一个 QList 对象中。
  • QHash<Key, T>
    使用哈希表存取 key,因而能够快速地依据 key 定位某个元素
    元素并没有按照 key 的取值排序,降低了搜索的性能
  • QSet<T>
    内部使用 QHash 实现集合的功能
    能够快速完成集合的插入、元素定位操作。
    unite()合并两个集合,intersect()求取两个集合的交集,substract()求取两个集合之差,contains()判断一个集合是否含有某个元素。
  • QCache<Key,T>
    依然也为键值对的存储形式
    QCache 所能存放的元素数量被有意地限定
    当有新元素需要被插入到容器中时,最近使用频率最低的那些元素会被删除。

QTL 容器对应迭代器

foreach 遍历容器的时候,接收的第一个参数表示 foreach 得到的元素,这里必须使用 typedef 预定义类型,然后直接使用

就如下方代码的 foreach( pair_type element, list),你不可以把 pair_type 更换为 QPair<string,double>

typedef QPair<string,double> pair_type;
QList< pair_type > list;
list << pair_type("pi",3.14) << pair_type("e", 2.718);foreach( pair_type element, list)②
    cout << element.first  << " " << element.second << endl;

通用算法

只要一个容器内部的迭代器支持这些基本操作,该函数模板就可以操作这个容器。这样的函数模板被称为 通用算法(algorithm)

通用算法 qSort 使用快速排序算法,将一个元素序列排成升序
对于有序容器,通用算法 qBinaryFind 使用二分搜索算法
qUpperBound 在一个元素序列中寻找最后一个大于指定值的元素,返回指向该元素的一个迭代器
无论容器是否有序,qFind 在容器中逐个搜索与某个指定值相等的元素,并返回一个指向该元素的迭代器。

qCount 计算某个值在容器中出现的次数。
qDeleteAll 调用 C++运算符 delete,析构容器中的元素。
qEqual 比较两个元素序列是否相等。
qSwap 调换两个元素的值。


函子(谓词)

QTL 提供了类似于 CPP 中的“谓词”操作

即类似于 CPP 中的 sort 函数,最后一个参数为控制排序方式的匿名回调函数

下图即使用了 QT 自己提供的成型谓词 qLess 来进行降序排列

QList<int> ql;
ql << 4 << 3 << 2 << 1 << 0;
qSort(ql.begin(),  ql.end(),  qLess<int>() );

QTL 容器与 QDataStream

一段代码,自己体会,不去解释,学学就懂

/**
 * @brief 保存词典到文件中
 *
 * @param dict 词典对象的引用
 * @param fname 要保存的文件名
 * @return int 如果保存成功返回 0,否则返回 -1
 */
int save_dict_map(dict_type& dict, char* fname)
{
    
    
    QFile dictf(fname);   // 创建一个 QFile 对象,并指定文件名
    if (!dictf.open(QIODevice::WriteOnly)) return -1;  // 尝试以只写方式打开文件,如果失败则返回 -1
    QDataStream ds(&dictf);  // 创建一个 QDataStream 对象,并将其与 QFile 关联
    ds << dict;  // 将词典对象保存到 QDataStream 中
    cout << fname << " saved\n";  // 输出保存成功的消息
}

/**
 * @brief 从文件中加载词典
 *
 * @param dict 词典对象的引用
 * @param fname 要加载的文件名
 * @return int 如果加载成功返回 0,否则返回 -1
 */
int load_dict_map(dict_type& dict, char* fname)
{
    
    
    QFile dictf(fname);  // 创建一个 QFile 对象,并指定文件名
    if (!dictf.open(QIODevice::ReadOnly)) return -1;  // 尝试以只读方式打开文件,如果失败则返回 -1
    QDataStream ds(&dictf);  // 创建一个 QDataStream 对象,并将其与 QFile 关联
    ds >> dict;  // 从 QDataStream 中加载词典对象
    cout << fname << " loaded\n";  // 输出加载成功的消息
}

类型分类计数在 QList 中的应用

容器元素的类型被表示为 QList 的模板参数 T
如果 T 占用的内存较多,QList 将每个容器元素存放在堆中,再维护一个指针数组,指向这些元素

对 QList 的插入或者删除操作会比单向链表的要稍慢,因为需要使用标准函数 memmove() 来移动数组中的指针


猜你喜欢

转载自blog.csdn.net/delete_you/article/details/132003117
今日推荐