提问人:24n8 提问时间:11/12/2018 最后编辑:JeJo24n8 更新时间:7/4/2022 访问量:4789
如何编写一个可以接受数组或向量的函数?
How to write a function that can take in an array or a vector?
问:
我想编写一个带有一个参数的 C++ 函数,以便可以传入以下任何一种类型:
std::vector<int>
std::array<int>
int array[numElements]
int *ptr = new int[numElements]
etc
模板化是实现这一目标的最佳方式吗?
答:
如果你期望只是能够做到,你就不能,因为我无法想到你的函数可以推断出动态分配的大小。func(v)
int[numElements]
一个好方法是采用一对正向迭代器,也就是说,如果你只需要一个接一个地迭代项目,因为随机访问在某些容器上非常糟糕,比如 .std::list
template<class FWIt>
void func(FWIt a, const FWIt b)
{
while (a != b)
{
std::cout << "Value: " << *a << '\n';
++a;
}
}
template<class T>
void func(const T& container)
{
using std::begin;
using std::end;
func(begin(container), end(container));
}
这将适用于以下内容:
int array[5] = {1, 2, 3, 4, 5};
func(array);
int* dynarray = new int[5]{1, 2, 3, 4, 5};
func(dynarray, dynarray + 5);
std::vector<int> vec{1, 2, 3, 4, 5};
func(vec);
func(vec.begin(), vec.end());
std::list<int> list{1, 2, 3, 4, 5};
func(list);
编辑:由于 @DanielH 的更改,这也可以通过直接传递原始数组而不是作为两个指针来工作(但仍然不适用于动态分配的数组)。
评论
func
for_each
auto v
template<class FWIt> void func(const FWIt a, const FWIt b) { while (a != b) { std::cout << *a; ++a; } }
std::list
func
using std::begin; using std::end; func(begin(container), end(container));
begin
end
std::begin
std::end
.begin
.end
for
循环来定义std::list
for
std::for_each
template<class InputIt, class UnaryFunction> constexpr UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f) { for (; first != last; ++first) { f(*first); } return f; }
std::pair
不能将所有列出的类型都获取到一个函数模板中。但是,您可以有函数模板重载,这将解决 和 的问题。std::vector<>
std::array<>
Type array[numElements]
template<typename Iter>
void funArray(const Iter begin, const Iter end)
{
std::cout << "Actual code here\n";
}
template<typename Container> void funArray(const Container& arr)
{
funArray(std::begin(arr), std::end(arr)); //std::vector or std::array
}
现在你可以写:
int main()
{
const std::size_t numElements = 5;
std::vector<int> vec;
std::array<int, numElements> arr;
int array[numElements];
int *ptr = new int[numElements];
funArray(vec);
funArray(arr);
funArray(array);
funArray(ptr, ptr+numElements);
return 0;
}
但是,对于动态分配的数组,您需要按照用户建议@Asu使用。
编辑:删除了冗余重载。
template<typename T, std::size_t N> void funArray(const T (&arr)[N]) {}
正如 @Daniel H 所指出的,上述 C 型数组的函数模板重载是徒劳的,因为它可以通过直接推导到 C 型数组来由第二个 overload() 处理。template<typename Container>
评论
funArray
T
using std::begin; using std::end
begin()
end()
begin()
end()
swap
funArray(array)
Container
int[5]
int*
std::end
main
模板化是实现这一目标的最佳方式吗?
这要视情况而定。如果你正在编写一个位于标头中的函数,因此可以在以后用于进一步的编译,那么 - 是的,可能:
template <typename IntContainer>
void f(Container& c);
或
template <typename IntContainer>
void f(const Container& c);
但是,如果实现只编译一次,则应考虑:
void f(gsl::span<int> sp);
或
void f(gsl::span<const int> sp);
它使用跨度。如果您还没有听说过它们,请阅读:
这个函数将能够按原样获取几乎所有的变量:、 和 普通(大小)数组可以在没有额外语法的情况下传递。但是,对于指针,您需要调用类似 .std::vector
std::array
f(gsl::make_span{ptr, numElements})
PS - 第三种选择,在标准库中非常常见,是将交互器而不是容器作为参数。这也需要模板化,因此它类似于第一个选项。
span
似乎就是你要找的。要么等待 C++20 :-)或使用 GSL 中的 span。请参阅什么是“跨度”以及何时应该使用跨度? 。示例如下。
你没有说你是否想要一个参数,该参数采用indata的可写或只读版本,因此该示例显示了两者。
#include <array>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>
#if defined(__has_include) && __has_include(<span>)
#include <span>
#endif
#ifdef __cpp_lib_span
using std::span;
#else
#include <gsl/gsl>
using gsl::span;
#endif
void funcDoSomething(span<int> data) {
int value{41};
for (auto &i: data) {
i += value;
++value;
}
}
void funcReadOnly(span<int const> data) {
for (auto &i: data) {
// ++i; won't compile since we have read-only span values
std::cout << i << ' ';
}
std::cout << '\n';
}
int main() {
std::vector<int> stdvec{10, 11, 12};
funcDoSomething(stdvec);
funcReadOnly(stdvec);
std::array<int, 3> stdarr{20, 21, 22};
funcDoSomething(stdarr);
funcReadOnly(stdarr);
int carr[3]{30, 31, 32};
funcDoSomething(carr);
funcReadOnly(carr);
auto ptr = std::unique_ptr<int[]>(new int[3]);
ptr[0] = 40;
ptr[1] = 41;
ptr[2] = 42;
funcDoSomething({ptr.get(), 3});
funcReadOnly({ptr.get(), 3});
return EXIT_SUCCESS;
}
如果它们都使用,那么您可以简单地接受开始和结束指针。您可以对它们使用标准算法,因为指针是迭代器。int
void my_func(int const* begin, int const* end)
{
std::for_each(begin, end, [](int i){
std::cout << i << '\n';
});
}
然后:
std::vector<int> v;
std::array<int> a;
int array[numElements];
int* ptr = new int[numElements];
my_func(v.data(), v.data() + v.size());
my_func(a.data(), a.data() + a.size());
my_func(std::begin(array), std::end(array));
my_func(ptr, ptr + numElements);
传递两个迭代器(泛型或其他迭代器)的好处是,您不必将整个容器传递给算法。
评论
std::span
etc
.“等”是什么意思?你能保证它只会留下来吗?或任何整数容器怎么样?按照建议,传递迭代器允许您指定满足算法的任何序列。这样可以防止您在需求发生变化或出现新的容器类型等时将自己逼入绝境。std::vector
std::array
int *
std::deque