📜  c++中的字符串插值(1)

📅  最后修改于: 2023-12-03 14:59:51.349000             🧑  作者: Mango

C++中的字符串插值

字符串插值是C++中的一种字符串拼接方式,它能够将变量的值和字符串连接起来形成一个新的字符串。C++中的字符串插值使用的是std::format函数实现的。

格式化语法

字符串插值使用的格式化语法类似于python中的格式化字符串,使用大括号括起来的变量会被自动替换为相应的值。举个例子,假设我们有一个字符串"hello {}",我们可以通过插值的方式将其转换为"hello world"

std::string hello = "world";
std::string message = std::format("hello {}", hello);

上面的代码将"world"插入到了大括号内,输出结果为"hello world"

在大括号内还可以添加更多的格式化选项,例如:

  • {:d}:将值格式化为十进制整数
  • {:x}:将值格式化为十六进制整数
  • {:f}:将值格式化为浮点数
  • {:s}:将值格式化为字符串

例如,"{:d} + {:d} = {:d}"可以转换为"2 + 2 = 4"

使用位置参数

在现实应用中,我们可能有多个需要插入到字符串中的变量。我们可以使用位置参数来传递这些变量,位置参数用数字表示,从0开始。例如:

std::string hello = "world";
int n = 10;
std::string message = std::format("hello {0}, n={1}", hello, n);

上面的代码中,hellon会按照位置参数的顺序被插入到字符串中,输出结果为"hello world, n=10"

使用命名参数

使用位置参数的方式有时会让代码难以理解,特别是当有多个参数并且位置混乱时。因此,C++中支持使用命名参数的方式来传递参数。命名参数使用花括号括起来,并在花括号中使用name=value的格式来定义。例如:

std::string hello = "world";
int n = 10;
std::string message = std::format("hello {name}, n={num}", 
                                  std::arg("name", hello), 
                                  std::arg("num", n));

上面的代码中,hellon被分别放到了namenum中,输出结果与之前相同。

同时,我们也可以使用std::format_args结构来定义一组命名参数,并将其传递给std::format函数:

std::string hello = "world";
int n = 10;
auto args = std::format_args(std::arg("name", hello), 
                             std::arg("num", n));
std::string message = std::format("hello {name}, n={num}", args);
自定义格式化

当我们需要对特殊的类型进行特殊处理时,我们可以使用自定义格式化函数。自定义格式化函数有两种方式:重载std::format_tostd::formatter

重载std::format_to

重载std::format_to函数可以让我们自定义对某种类型的格式化方式。std::format_to的用法与std::format类似,只不过它将输出结果写入到指定的输出流中。例如:

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

void std::format_to(std::ostream& os, const Person& p) {
    os << "name=" << p.name << ", age=" << p.age;
}

int main() {
    Person p{"Tom", 18};
    std::cout << std::format("person: {}", p) << std::endl;
    return 0;
}

上面的代码中,我们为Person类型实现了自定义的格式化功能,输出结果为"person: name=Tom, age=18"

重载std::formatter

C++20中引入了std::formatter,它是一种更加强大的自定义格式化方式。std::format_to只能输出到流中,而std::formatter可以直接输出到字符串中。同时,std::formatter还支持更多的格式化选项,例如:

  • align:对齐方式
  • fill:填充字符
  • width:设置输出宽度
  • precision:设置浮点数输出精度

例如,我们可以使用std::formatter重写之前的Person类型的格式化:

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

template <typename Char>
struct my_formatter : std::formatter<Person, Char> {
    template <typename FormatContext>
    auto format(const Person& p, FormatContext& ctx) {
        return std::format_to(ctx.out(), "name={:{}}", p.name, ctx.width());
    }
};

template <typename Char>
struct my_formatter<std::tuple_element_t<0, typename FormatContext::type>> :
        std::formatter<Person, Char> {
    template <typename FormatContext>
    auto format(const Person& p, FormatContext& ctx) {
        auto& out = ctx.out();
        auto width = ctx.width();
        auto fill = ctx.fill();
        auto align = ctx.align();
        if (align == std::format_align::right) {
            out << std::setw(width) << std::setfill(fill) << p.name;
        } else if (align == std::format_align::left) {
            out << p.name << std::setw(width) << std::setfill(fill);
        } else {
            out << p.name;
        }
        return out;
    }
};

int main() {
    Person p{"Tom", 18};
    std::cout << std::format("person: {:{}}", my_formatter<char>{}, p, 10) << std::endl;
    std::cout << std::format("person: {:{<10}}", my_formatter<std::tuple<char, int>>{}, p) << std::endl;
    return 0;
}

上面的代码中,我们为Person类型实现了两个版本的自定义格式化器。第一个版本my_formatter<char>只提供了最基本的格式化功能,将name插入到format上下文中指定的位置,并使用宽度选项进行格式化。第二个版本my_formatter<std::tuple<char, int>>在第一个版本的基础上增加了更多的格式化选项,例如填充字符、对齐方式等。

总结

字符串插值是一种非常方便的C++字符串拼接方式,它使用std::format函数实现,语法类似于python中的格式化字符串,支持位置参数、命名参数以及自定义格式化器。掌握字符串插值对于C++开发者来说是非常有帮助的。