美文网首页C++ 11
022 参数绑定

022 参数绑定

作者: 赵者也 | 来源:发表于2020-02-24 13:31 被阅读0次

对于那种只在一两个地方使用的简单操作, lambda 表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的 lambda 表达式。类似的,如果一个操作需要很多语句才能完成,通常使用函数更好。

如果 lambda 的捕获列表为空,通常可以用函数来代替它。例如我们既可以用一个 lambda,也可以用函数来实现将 vector 中的单词按长度排序。类似的,对于打印 vector 内容的 lambda,编写一个函数来替换它也是很容易的事情,这个函数只需接受一个 string 并在标准输出上打印它即可。

但是,对于捕获局部变量的 lambda,用函数来替换它就不是那么容易了。例如,我们用在 find_if 调用中的 lambda 比较一个 string 和一个给定大小。我们可以很容易地编写一个完成同样工作的函数:

bool check_size(const string &s,  string::size_type sz) {
    return s.size() >= sz;
}

但是,我们不能用这个函数作为 find_if 的一个参数。如前文所示,find_if 接受一个一元谓词,因此传递给 find_if 的可调用对象必须接受单一参数。biggies 传递给 find_if 的 lambda 使用捕获列表来保存sz。为了用 check_size 来代替此 lambda,必须解决如何向 sz 形参传递一个参数的问题。

标准库 bind 函数

我们可以解决向 check_size 传递一个长度参数的问题,方法是使用一个新的名为 bind 的标准库函数,它定义在头文件 functional 中。可以将 bind 函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式为:

auto  newCallable  = bind(callable,  args_list);

其中,newCallable 本身是一个可调用对象,args_list 是一个逗号分隔的参数列表,对应给定的 callable 的参数。即,当我们调用 newCallable 时,newCallable 会调用 callable,并传递给它 args_list 中的参数。

args_list 中的参数可能包含形如 _n 的名字,其中 n 是一个整数。这些参数是“占位符”,表示 newCallable 的参数,它们占据了传递给 newCallable 的参数的“位置”。数值 n 表示生成的可调用对象中参数的位置:_1 为 newCallable 的第一个参数,_2 为第二个参数,依此类推。

绑定 check_size 的 sz 参数

作为一个简单的例子,我们将使用 bind 生成一个调用 check_size 的对象,如下所示,它用一个定值作为其大小参数来调用 check_size:

// check6 是一个可调用对象,接受一个 string 类型的参数
// 并用此 string 和值 6 来调用 check
auto check6 = bind(check_size, _1, 6);

此 bind 调用只有一个占位符,表示 check6 只接受单一参数。占位符出现在 args_list 的第一个位置,表示 check6 的此参数对应 check_size 的第一个参数。此参数是一个 const string&。因此,调用 check6 必须传递给它一个 string 类型的参数,check6会将此参数传递给 check_size。

string s = "hello";
bool b1 = check6(s); // check6(s) 会调用 check_size(s, 6)

使用 bind,我们可以将原来基于 lambda 的 find_if 调用:

auto wc = find_if(words.begin(), words.end(),
               [sz](const  string  &a)

替换为如下使用 check_size 的版本:

auto wc = find_if(words.begin(), words.end(),
                bind(check_size, _1, sz));

此 bind 调用生成一个可调用对象,将 check_size 的第二个参数绑定到 sz 的值。当 find_if 对 words 中的 string 调用这个对象时,这些对象会调用 check_size,将给定的 string 和 sz 传递给它。因此,find_if 可以有效地对输入序列中每个 string 调用 check_size,实现 string 的大小与 sz 的比较。

使用 placeholders 名字

名字 _n 都定义在一个名为 placeholders 的命名空间中,而这个命名空间本身定义在 std 命名空间中。为了使用这些名字,两个命名空间都要写上。与我们的其他例子类似,对 bind 的调用代码假定之前己经恰当地使用了 using 声明。例如,_1 对应的 using 声明为:

using std::placeholders::_1;

此声明说明我们要使用的名字 _1 定义在命名空间 placeholders 中,而此命名空间又定义在命名空间 std 中。

对每个占位符名字,我们都必须提供一个单独的 using 声明。编写这样的声明很烦人,也很容易出错。可以使用另外一种不同形式的 using 语句,而不是分别声明每个占位符,如下所示:

using namespace namespace_name;

这种形式说明希望所有来自 namespace_name 的名字都可以在我们的程序中直接使用。例如:

using  namespace  std::placeholders;

使得由 placeholders 定义的所有名字都可用。与 bind 函数一样,placeholders 命名空间也定义在 functional 头文件中。

bind 的参数

如前文所述,我们可以用 bind 修正参数的值。更一般的,可以用 bind 绑定给定可调用对象中的参数或重新安排其顺序。例如,假定 f 是一个可调用对象,它有 5 个参数则下面对bind的调用:

// g 是一个有两个参数的可调用对象
auto g = bind(f,  a,  b, _2, c, _1);

生成一个新的可调用对象,它有两个参数,分别用占位符 _2 和 _1 表示。这个新的可调用对象将它自己的参数作为第三个和第五个参数传递给 f。f 的第一个、第二个和第四个参数分别被绑定到给定的值 a、b 和 c 上。

传递给 g 的参数按位置绑定到占位符。即,第一个参数绑定到 _1,第二个参数绑定到 _2。因此,当我们调用 g 时,其第一个参数将被传递给f作为最后一个参数,第二个参数将被传递给f作为第三个参数。实际上,这个 bind 调用会将

g(_1, _2)

映射为

f(a,  b, _2, c, _1);

即,对 g 的调用会调用 f,用 g 的参数代替占位符,再加上绑定的参数 a、b 和 c。例如调用 g(X, Y) 会调用 f(a, b, Y, c, X)

用 bind 重排参数顺序

下面是用 bind 重排参数顺序的一个具体例子,我们可以用 bind 颠倒 isShroter 的含义:

// 按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
// 按单词长度由长至短排序
sort(words.begin(), words.end(),  bind(isShorter, _2, _1));

在第一个调用中,当 sort 需要比较两个元素 A 和 B 时,它会调用 isShorter(A, B)。在第二个对 sort 的调用中,传递给 isShorter 的参数被交换过来了。因此,当 sort 比较两个元素时,就好像调用 isShorter(B, A) 一样。

绑定引用参数

默认情况下,bind 的那些不是占位符的参数被拷贝到 bind 返回的可调用对象中。但是,与 lambda 类似,有时对有些绑定的参数我们希望以引用方式传递,或是要绑定参数的类型无法拷贝。

例如,为了替换一个引用方式捕获 ostream 的 lambda:

// os 是一个局部变量,引用一个输出流
// c 是一个局部变量,类型为 char
for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c; });

可以很容易地编写一个函数,完成相同的工作:

ostream  &print(ostream &os, const string &s, char c) {
    return os << s << c;
}

但是,不能直接用 bind 来代替对 os 的捕获

// 错误:不能拷贝os
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));

原因在于 bind 拷贝其参数,而我们不能拷贝一个 ostream。如果我们希望传递给 bind 一个对象而又不拷贝它,就必须使用标准库\color{RED}{ref} 函数:

for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));

函数 ref 返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个 \color{RED}{cref} 函数,生成一个保存 const 引用的类。与 bind 一样,函数 ref 和 cref 也定义在头文件 functional 中。

向后兼容:参数绑定

旧版本 C++ 提供的绑定函数参数的语言特性限制更多,也更复杂。标准库定义了两个分
别名为 bind1st 和 bind2nd 的函数。类似 bind,这两个函数接受一个函数作为参数,
生成一个新的可调用对象,该对象调用给定函数,并将绑定的参数传递给它。但是,这
些函数分别只能绑定第一个或第二个参数。由于这些函数局限太强,在新标准中已被弃
用(deprecated)。所谓被弃用的特性就是在新版本中不再支持的特性。新的 C++ 程序应
该使用 bind。

相关文章

  • 022 参数绑定

    对于那种只在一两个地方使用的简单操作, lambda 表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常...

  • php pdo 基本操作

    不使用参数绑定: 使用参数绑定:

  • 【Hibernate】session.createQuery的参

    参数绑定: 都支持位置绑定和命名绑定如: Java代码 **基本的参数绑定: **setString();setI...

  • SpringMVC框架笔记02_参数绑定返回值文件上传异常处理器

    第1章 高级参数的绑定 1.1 参数的分类 默认参数绑定:request、response、session、mod...

  • spring cloud-feign使用遇到的坑

    参数绑定的问题: 含有多个参数传递的时候,必须使用@RequestMapping进行参数绑定,参数名称必须和con...

  • 参数绑定

    一 参数绑定过程 从客户端请求key/value数据,经过参数绑定,将key/value数据绑定到controll...

  • 参数绑定

    原始方法获取表单数据 参数绑定 post乱码解决方法 jsp页面中设置date的显示格式 日期格式转换 请求方法限定

  • 参数绑定

    默认支持的参数类型处理器形参中添加如下类型的参数处理适配器会默认识别并进行赋值。1.HttpServletRequ...

  • 参数绑定

    对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该...

  • jQuery事件

    一、基础事件 1、绑定事件 bind();参数一:要绑定事件函数的事件名。参数二:要绑定的事件函数(事件函数名),...

网友评论

    本文标题:022 参数绑定

    本文链接:https://www.haomeiwen.com/subject/nhyfoqtx.html