【现代C++】使代码简洁的两种解包方式

(点击上方公众号,可快速关注)

前言

C++没有模式匹配,在提取某个数据类型的元素或成员时会显得不太方便。假设t是一个包含3个元素的元组,类型为std::tuple<int,double,bool>,取每个元素的代码如下:

auto i = std::get<0>(t);
auto d = std::get<1>(t);
auto b = std::get<2>(t);

需要三行语句,每行语句的功能基本相同,除了索引不一样,显得冗余;相信很多同学的做法是:将第一行代码复制两份,在此基础上修改为后两句,这样出错就容易多了,万一忘了改索引呢。

上面的例子是关于元组的,std::pair<>是元组的特列,也能通过通用的std::get方式获取元素。除此之外,还提供了firstsecond成员变量,较之元组简单了不少,但名字太“抽象”,完全体现不出数据的含义,可读性不高。

我们看下支持模式匹配的Haskell是怎么做的:

let (i, d, b) = t

只用一行代码,是不是特简洁;而且可以给元素命名,可读性也很高。很幸运,现代C++提供了两种工具,能实现与之类似的效果(当然还远不如Haskell强大),下面将分别介绍。

std::tie

C++11通过标准库的方式提供,用于std::tuplestd::pair类型的数据解包。函数签名为:

#include <tuple>
template< class... Types >
tuple<Types&...> tie( Types&... args ) noexcept;

从参数和返回值类型上能大体猜到这个函数的工作原理:它实际上将绑定变量通过引用方式传入tie函数,tie函数将这些参数组装成一个tuple,等号右侧的tuple的整体赋值给这个tuple,从而实现tuple每个位置的值都绑定到相应的变量上。

使用方式也比较简单,先声明绑定变量,然后将这些变量传入tie函数:

int i;
double d;
bool b;
std::tie(i, d, b) = t;

代码已经有点Haskell的味道了,虽然解包的代码少了,但绑定的变量需要事先声明,显得代码不少。

在Haskell中,如果不想捕获某个值,可以使用wildcard。如,程序可能用不到第2个值,可以这么写:

let (i, _, b) = t

std::tie也提供了类似的功能,使用std::ignore代替变量即可:

int i;
bool b;
std::tie(i, std::ignore, b) = t;

特别说明下,tie函数也可以对std::pair解包,因为std::tuple重载了赋值操作符,所以赋值操作符右侧为pair的情况也能处理。

template< class U1, class U2 >
constexpr tuple& operator=( const pair<U1,U2>& p );

template< class U1, class U2 >
constexpr tuple& operator=( pair<U1,U2>&& p );

tie函数使用起来比较简单,但也没多大亮点。仅仅采用标准库实现的,限制还是比较多的:

  • 绑定变量必须先定义

    某些类型的变量可能不太容易声明,这样就很难使用tie获取元素。

  • 适用范围窄

    仅用于tuple和pair

  • 只有拷贝语义

    解包时,右侧的元素值只能复制给左侧的值,如果对象较大,复制成本会比较高。

Structured Bindings

C++17中引入的一个语言特性,叫做Structured Binding,有了它,就不必用std::tie了。这一部分主要介绍这个新的语言特性的用法,通过示例代码给出。

这个语法支持三种类型的结构的绑定,下面一一说明:

绑定数组的元素

C++主要有两种数组类型:

  • 继承自C语言的原始数组

  • C++11标准库引入的std::array类型

都支持结构绑定:

int rawArray[] = {1, 2};
std::array array {3, 4};

auto [x, y] = rawArray; // x=1, y=1
auto [a, b] = array;    // a=3, b=4

结构绑定配合auto关键字使用,方括号内的是元素被绑定的变量名,与数组的元素个数一致。上例中的绑定相当于将数组元素直接拷贝到对应的变量里,语义跟普通的赋值操作是一样的。普通的变量能支持constvolatile等修饰符,而且支持引用(&&&),结构绑定同样支持。

std::array array {std::string("a"), std::string("b")};

const auto& [a, b] = array;    // a="a", b="b"

上例中,ab是常量字符串的引用类型。注意const&auto三者之间的位置,可以根据实际需要省略const&,语义跟普通的变量是一致的,在后续的示例中就不突出这些用法了。

绑定tuple-like类型的元素

tuple-like类型是指行为跟tuple一致的类型,怎么才算行为一致,这里留个坑,后续填。现在只需知道标准库的std::tuplestd::pair肯定是tuple-like类型即可。

用法示例如下:

auto p = std::make_pair(1, 2);
auto t = std::make_tuple(3, 4);

auto [x, y] = p;  // x=1, y=1
auto [a, b] = t;  // a=3, b=4
绑定类的数据成员

还能绑定结构体数据成员,需要保证被绑定的数据成员是public的。

struct Person {
    std::string name;
    int age;
};

Person p {"name", 66};
auto [n, a] = p;  // n="name", a=66

是不是很简单。

小结

std::tieStructured Binding之间的选择问题:如果编译器支持C++17,当然推荐后者。在std::tie一节末尾列出的3个限制,Structured Binding都比较完美地解决了,而且代码简洁清晰。保持代码简洁、可读性好就是提高生产力。

喜欢我的文章,请关注我的公众号。转载请标明出处。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
解包pak文件的代码可以使用Python语言来实现。以下是一个示例代码: ``` import os def extract_pak_file(pak_file_path, output_dir): with open(pak_file_path, 'rb') as pak_file: pak_file_header = pak_file.read(4) if pak_file_header != b'PAK ': print("Invalid PAK file format.") return num_files = int.from_bytes(pak_file.read(4), byteorder='little') for _ in range(num_files): file_name_length = int.from_bytes(pak_file.read(4), byteorder='little') file_name = pak_file.read(file_name_length).decode('utf-8') file_data_offset = int.from_bytes(pak_file.read(4), byteorder='little') file_data_size = int.from_bytes(pak_file.read(4), byteorder='little') file_data = pak_file.read(file_data_size) output_file_path = os.path.join(output_dir, file_name) os.makedirs(os.path.dirname(output_file_path), exist_ok=True) with open(output_file_path, 'wb') as output_file: output_file.write(file_data) print("Extracted:", output_file_path) pak_file_path = "example.pak" output_dir = "extracted_files" extract_pak_file(pak_file_path, output_dir) ``` 以上代码首先会打开pak文件,读取文件头部并检查文件格式是否为PAK。然后读取包含在文件中的文件数目。对于每个文件,代码会读取文件名长度和文件名,文件数据偏移量和文件数据大小。然后将文件数据写入到对应的输出文件中。最后,使用调用该函数时传入的输出目录和文件名来构建输出文件路径,并在输出前创建目录(如果目录不存在)。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值