我的C++规范 - 未知参数处理
你应该先看看 main.cpp 文件中,两种替换字符串的使用,在使用 replace_str_ref 函数时,你需要替换几次就需要调用这个函数几次,但使用 replace_args 类是,它总是可以通过一行代码就完成数次替换,而且它是可选的,你想替换几次就替换几次。当你有了递归函数,有了 Targ…需要注意的是,递归执行虽然每一次都执行的同一个函数,但实际上每个函数都是被单独复制出来的,每个函数的
未知参数处理
可变参数
main.cpp
#include <iostream>
#include "mclog.h"
#include "replace_args.h"
int main(int argc, char **argv)
{
// 使用 const 保持数据不变
const std::string name = "JudyHopps";
const std::string introduce = "我是兔朱迪和尼克狐尼克组成了动物城最强搭档";
const std::string content = "\n这里是兔子胡萝卜节表演大会\n请 {1} 进行自我介绍\n{2}\n";
MCLOG("使用 replace_str_ref 函数");
std::string tm_replace_str_ref = content;
replace_str_ref(tm_replace_str_ref, "{1}", name);
replace_str_ref(tm_replace_str_ref, "{2}", introduce);
MCLOG($(tm_replace_str_ref));
MCLOG("\n使用 replace_args 对象,多次替换");
std::string tm_replace_args = content;
std::string ret_replace_args = replace_args(tm_replace_args)("{1}", name, "{2}", introduce);
MCLOG($(ret_replace_args));
MCLOG("\n使用 replace_args 对象,只替换一次");
std::string tm_replace_args_1 = content;
std::string ret_replace_args_1 = replace_args(tm_replace_args_1)("{1}", name);
MCLOG($(ret_replace_args_1));
return 0;
}
replace_args.h
#ifndef REPLACE_ARGS_H
#define REPLACE_ARGS_H
#include <iostream>
#include "format.h"
// 可以接收可变数据的字符串替换格式化类
struct replace_args
{
// 通过构造哈数,赋值一份字符串到 _org 变量
replace_args(const std::string &org) : _org(org) {}
// replace 无参数版本
// 处理完成之后,最终的数据在 _org 中,返回结果
// 这个函数只调用一次
std::string replace()
{
return _org;
}
// replace 有参数版本
// 可变参数模板函数 Targ 代表数量未知的
// str_old str_new 这两个参数会从 arg 这个未知数量上拿走两个数据 arg 的数量 -2
// 这个函数可能调用多次
template <typename... Targ>
std::string replace(const std::string &str_old, const std::string &str_new, Targ... arg)
{
// 调用 format.h replace_str_ref 来处理数据
replace_str_ref(_org, str_old, str_new);
// 递归调用
// 如果 arg 的数量大于0,调用 replace 有参数版本
// arg 如果这一次数量为0,调用 replace 无参数版本
return replace(arg...);
}
// 重载 () 括号操作符,自定义返回 std::string 类型
// 并调用 replace 函数,根据 arg 的数量选择不同版本
// 这个函数只调用一次
template <typename... Targ>
std::string operator()(Targ... arg)
{
return replace(arg...);
}
// 储存传入数据的复制版本
std::string _org;
};
#endif // REPLACE_ARGS_H
打印结果
使用 replace_str_ref 函数 [/home/red/open/github/mcpp/example/10/main.cpp:13]
[tm_replace_str_ref:
这里是兔子胡萝卜节表演大会
请 JudyHopps 进行自我介绍
我是兔朱迪和尼克狐尼克组成了动物城最强搭档
] [/home/red/open/github/mcpp/example/10/main.cpp:17]
使用 replace_args 对象,多次替换 [/home/red/open/github/mcpp/example/10/main.cpp:19]
[ret_replace_args:
这里是兔子胡萝卜节表演大会
请 JudyHopps 进行自我介绍
我是兔朱迪和尼克狐尼克组成了动物城最强搭档
] [/home/red/open/github/mcpp/example/10/main.cpp:22]
使用 replace_args 对象,只替换一次 [/home/red/open/github/mcpp/example/10/main.cpp:24]
[ret_replace_args_1:
这里是兔子胡萝卜节表演大会
请 JudyHopps 进行自我介绍
{2}
] [/home/red/open/github/mcpp/example/10/main.cpp:27]
当了解上一篇文章的面向对象之后,你已经算入门了代码编程,或许你应该需要开始了解一些编程语言的小技巧
今天的带来的技巧是模板可变参数,这是一个很有意思的东西,这个技术本质上是一种可有可无的东西,但是这个技术却可以让你少写很多代码,可以在开发者简化代码的同时让调用者更方便的使用
多次替换
你应该先看看 main.cpp 文件中,两种替换字符串的使用,在使用 replace_str_ref 函数时,你需要替换几次就需要调用这个函数几次,但使用 replace_args 类是,它总是可以通过一行代码就完成数次替换,而且它是可选的,你想替换几次就替换几次
replace_args 实现这种功能的原因是因为它可以接收多个参数,而且参数的数量是可以不确定的,只要符合规则即可
可变参数声明
// 终止版本
std::string replace()
{
return "END";
}
// 运行版本
template <typename... Targ>
std::string replace(const std::string &one, const std::string &two, Targ... args)
{
return replace(args...);
}
上面代码判断 replace 函数就是一个可接收多个参数的可变参数声明例子,replace 需要存在至少两个版本,一个运行版本,一个终止版本,replace 函数总是递归调用的
当满足这些要求,你还差一步就是声明 Targ… 类型,获取到 args 变量,请注意 typename… Targ 与 Targ… 和 args… 这三个 … 的顺序和位置,这是必须的,你问我为什么怎么乱我也不知道,反正规定就这样,毕竟C++这种糟糕的规定也不是一天两天了
当你有了递归函数,有了 Targ… 可变数量类型,有了 replace 的运行和终止版本这三个前置条件,那现在就可以获取可变数量的参数和类型了,然后你只需要在 replace 函数的运行版本上指定你需要取什么数据就可以
注意这个 replace 运行版本的写法,Targ… args 必须放在最后,如何我要取2个变量,就定义 one two
,三个呢 one two three 你随意就好,这只是表明你希望一次性从 args 上获取几个数据,但是 args 一定是参数数量的整倍数才行,否则会报错
值得注意的是,这里的例子 replace 使用的是两个参数,one two 都刚好是 std::string 实际上他们可以是任何类型,整数也好字符也罢,当然名字也是可以变的,只要符合后续运行的代码即可
这意为着可变参数一旦满足三个前置条件,你可以定制任何数据数量参数,定制参数顺序的类型,定制每个变量的名称,定制多个运行版本,反正可变参数模板的编写真的很随意,只要符合规则就可以运行
如果你想更改参数类型,在代码的写法上可以参考 replace_args 类,然后剔除实际执行代码,编译成功之后在加上实际运行代码即可
递归调用
replace_args 类的代码中提到递归调用,其实递归代码非常好理解,一个函数反复的执行自身,直到遇到终止条件,如果没有终于则直接栈溢出报错
需要注意的是,递归执行虽然每一次都执行的同一个函数,但实际上每个函数都是被单独复制出来的,每个函数的内部变量等数据会被放到函数栈中,然后以层层递进的方式执行函数,每一个递归函数的状态都是独立且全新的
虽然在执行递归函数时,看起来同一个函数被执行了很多次,但是在调用者看来就执行了一次,也就是外部调用只看第一次递归函数的执行结果,不关心其他深层递归在干嘛,只不过第一次递归函数的执行结果往往由最后一次执行决定而已
相关递归栈与递归执行返回需要自行学习
重载符号
// 重载符号写法
struct replace_args
{
// 构造函数
replace_args(const std::string &org) : _org(org) {}
// 重载符号
template <typename... Targ>
std::string operator()(Targ... arg)
{
return replace(arg...);
}
};
// 临时对象调用
std::string ret_replace_args = replace_args(tm_replace_args)("{1}", name, "{2}", introduce);
// 声明变量的等价调用
replace_args obj(tm_replace_args);
std::string ret_replace_args = obj("{1}", name, "{2}", introduce)
可以看出 replace_args 类对象的调用很奇怪,居然是使用两个小括号来传递参数和调用的,而且一行代码就完成了参数传递和结果返回
这里的 replace_args 语法第一个小括号是 构造函数调用 第二个是 operator() 小括号重载调用
使用小括号重载之后,就可以通过小括号接收传递参数,可以更方便的使用 replace_args 的运行,而已可以从 声明变量的等价调用 看出
创建出来的 obj 对象也可以使用小括号调用,是因为实际上 临时对象调用 replace_args(tm_replace_args) 这个写法是创建了一个临时对象,临时对象可以和正常对象一样去使用类的函数
要记住临时对象是没有名字的,下一行代码就无法引用,所以临时对象只有一行代码的作用域,下一行代码就自动销毁了
使用建议
其实可以从例子中看出可变参数模板也不是非用不可,但是使用它确实可以减少一部分代码的编写,让调用者更方便,但是同时也增加了代码复杂度
所以我建议新手不要轻易使用这类可以简化代码的小技巧,即使使用也要在代码复杂度和调用者便捷之间取得平衡
当然你已经足够熟练,我建议可以多使用类似的技巧让一部分代码看上去更优雅一些
如果你是新手,要注意 replace_str_ref 这个函数是非常低效的,在其他非教学类代码上,请不要使用这个函数
如果你没有 replace_str_ref 函数的实际代码,它出现在 今天是星期几 这篇文章
项目路径
https://github.com/HellowAmy/mcpp.git
更多推荐
所有评论(0)