[英]C++ primer 6th Exercise 10.8
您可以描述一个简单的列表,如下所示:
如您所见,该列表确实很简单。 例如,它不允许插入或删除。 设计一个List类来表示这种抽象类型。您应该提供带有类声明的list.h头文件和带有类方法实现的list.cpp文件,还应创建一个利用您的设计的简短程序。
保持列表规范简单的主要原因是为了简化此编程工作。您可以将列表实现为数组,或者如果您熟悉数据类型,则可以实现为链接列表。 但是公共接口不应取决于您的选择,即公共接口不应具有数组索引,指向节点的指针等。 应该用创建列表,在列表中添加项目等一般概念来表达。处理访问每个项目并执行操作的通常方法是使用以函数指针作为参数的函数:
void visit(void (*pf)(Item &));
此处pf指向引用Item参数的函数(不是成员函数),其中Item是列表中项目的类型。visit()函数将此函数应用于列表中的每个项目。堆栈类作为一般指南。
我想知道的是为什么我应该使用指向函数的指针? 使用普通成员函数和使用以函数指针为参数的函数有什么区别? (在这种情况下,使用void visit(void (*pf)(Item &))
)?
功能指针
假设您有一个函数,该函数需要一个数字并将其平方并返回。 并且您有一个列表,要对其平方的每个成员。 你是怎样做的?
两者执行相同的任务。 您可能会认为前一种情况更容易实现。 毕竟,您不必处理函数指针。
但是,如果您说20个函数,它们可以将一个参数加倍,三次,三次,平方等,那么将一个参数传递给它们。 如果遵循第一种方法,则必须编写20个不同的函数(可能使用不同的名称)。 但是现在,后者变得有意义。 您只需声明各个功能。 并通过指针传递数组和20个函数中的任何一个来调用转换器函数,以完成您的任务。
一个示例是C ++ STL中的std::transform
。
工作内容:
#include <iostream>
#include <vector>
typedef double (*function)(double);
void transformer(std::vector<double>& to_transform, function f)
{
for(auto it = to_transform.begin(); it != to_transform.end(); ++it)
*it = f(*it);
}
void print(const std::vector<double>& v)
{
std::cout << "[ ";
for(double val : v)
std::cout << val << " ";
std::cout << "]" ;
}
double f1(double a) { return a*2; }
double f2(double a) { return a*3; }
double f3(double a) { return a/2; }
double f4(double a) { return a*a*a; }
int main() {
std::vector<double> array = { 2.3, 5.6, 4.5, 7.8, 2.3 };
std::vector<function> function_ptrs = { &f1, &f2, &f3, &f4 };
std::size_t val ;
std::cout << "The original : " ;
print(array);
std::cout << "\nChoose a function (1-4) : ";
std::cin >> val;
std::cout << "The array after applying function " << val << " is : ";
transformer(array, function_ptrs[(val - 1) % function_ptrs.size()]);
print(array);
return 0;
}
我假设您有一个符合C ++ 11的编译器。 上面的代码有4个函数,它们具有双重功能并以某种方式进行转换。 transformer
函数将这样的函数应用于double的向量。 函数指针也存储在向量中-是一个函数指针数组。 当通过索引访问普通元素时,可以调用这些函数。 选择一个选项时,适当的函数由transformer
元素明智地调用并在double的向量上执行。
您可以使用模板(而不是固定的双精度)并使用STL中的std::transform
进一步改进它。
C ++ 11 Lambda表达式
同样,对于C ++ 11,您应该更喜欢lambda而不是函数指针。 Lambdas写为
[ ... capture list ... ] ( params ) -> return_type (optional) { body }
使用lambda的解决方案将是这样的:
#include <iostream>
#include <algorithm>
#include <vector>
template <typename T>
void print(const std::vector<T>& v)
{
std::cout << "[ ";
for(T val : v)
std::cout << val << " ";
std::cout << "]" ;
}
int main() {
std::vector<double> array = { 2.3, 5.6, 4.5, 7.8, 2.3 };
std::cout << "The original : " ;
print(array);
std::cout << "\nThe array after transforming : " ;
std::transform(array.begin(), array.end(), array.begin(),
[](double x) { return x * x; });
print(array);
return 0;
}
功能对象
您可以声明自己的类,该类只会重载()
运算符(使对象可调用),并且与函数指针(可以传递给函数并被调用)执行相同的工作,即在这种情况下,该类如下所示:
class double_the_value
{
double operator()(double val) const { return val * 2.0 ; }
};
double_the_value f ;
std::cout << f(3.0) ; // outputs 6.0
实际用法是std::unordered_map
容器,如果您将自己的类类型用于键,则需要提供一个键哈希器-它可以是一个函数对象。 此答案对此进行了详细说明。
当您创建一个真正的抽象列表时,您不知道需要在对象上调用哪些函数,因此您通常的成员函数还不够。 您不能全部写。
此模式的一种常见且更简单的替代方法是返回完整集合的副本,或将迭代器公开给第一个和最后一个元素。 但是,这可能会导致性能问题并带来风险-副本可能会很昂贵,并且通常是不必要的,并且如果您使用迭代器,则当集合在您下面更改时,它们可能变得无效。 访客模式通过提供更好的封装来隐藏所有这些内容,并将迭代循环保留在它通常所属的类内。
这是附加的抽象层。 如果您有一个项目集合,则通常需要对该集合中的某些(或全部)项目进行操作。 访问者模式使您可以检查每个项目,然后执行某些操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.