C++ STL:数组可以透明地与 STL 函数一起使用吗?

发布于 2024-07-16 11:20:43 字数 574 浏览 5 评论 0原文

我一直假设 STL 函数只能与 STL 数据容器(如 vector)一起使用,直到我看到这段代码:

#include <functional>
#include <iostream>
#include <numeric>
using namespace std;

int main()
{
    int a[] = {9, 8, 7};
    cerr << "Sum: " << accumulate(&a[0], &a[3], 0, plus<int>()) << endl;
    return 0;
}

它使用 g++ 编译和运行,没有任何警告或错误,给出了正确的结果输出总和为 24。C

++/STL 标准是否允许使用带有 STL 函数的数组? 如果是,那么像数组这样的古老结构如何适应模板化迭代器、容器和函数的宏伟 STL 计划? 另外,在这种用法中是否有任何程序员应该小心的注意事项或细节?

I was under the assumption that STL functions could be used only with STL data containers (like vector) until I saw this piece of code:

#include <functional>
#include <iostream>
#include <numeric>
using namespace std;

int main()
{
    int a[] = {9, 8, 7};
    cerr << "Sum: " << accumulate(&a[0], &a[3], 0, plus<int>()) << endl;
    return 0;
}

It compiles and runs without any warnings or errors with g++, giving the correct output sum of 24.

Is such usage of arrays with STL functions allowed by the C++/STL standard? If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions? Also, are there any caveats or details in such usage that the programmer should be careful about?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

一杯敬自由 2024-07-23 11:20:43

好吧,你问的是数组。 您可以轻松获得指向其元素的指针,因此基本上可以归结为指针是否可以与 STL 函数透明地使用的问题。 指针实际上是最强大的迭代器。 有不同的种类

  • 输入迭代器:仅前向和单遍,仅读取
  • 输出迭代器:仅前向和单遍,仅写入

  • 前向迭代器< /strong>:仅向前,可读写
  • 双向迭代器:向前和向后,可读写
  • 随机访问迭代器:一口气向前和向后任意步,且读/写

现在第二组中的每个迭代器都支持之前提到的所有迭代器的所有功能。 指针模拟了最后一种迭代器——随机访问迭代器。 您可以添加/减去任意整数,并且可以读取和写入。 除了输出迭代器之外,所有迭代器都有一个operator->,可用于访问我们迭代的元素类型的成员。

通常,迭代器有多个 typedef 作为成员

  • value_type - 迭代器迭代的内容 (int, bool, string, ...)
  • reference - 对 value_type 的
  • 引用 指针 - 指向 value_type 的指针
  • Difference_type - 两个迭代器之间的距离是什么类型(返回通过std::distance)。
  • iterator_category - 这是一个标记类型:它被定义为表示迭代器类型的类型。 std::input_iterator_tag、...、std::random_access_iterator_tag。 算法可以使用它来重载不同类型的迭代器(例如 std::distance 对于随机访问迭代器来说更快,因为它只能返回 a - b

现在,指针当然没有那些成员。 C++ 有一个 iterator_traits 模板,专门用于指针。 因此,如果您想获取任何迭代器的值类型,无论

iterator_traits<T>::value_type

它是指针还是其他迭代器,它都会为您提供该迭代​​器的 value_type。

所以 - 是的,指针可以很好地与 STL 算法一起使用。 正如其他人提到的,即使 std::vector::iterator 也可以是 T*。 指针甚至是迭代器的一个很好的例子。 因为它非常简单,但同时又非常强大,可以在一定范围内进行迭代。

Well, you ask about an array. You can just easily get a pointer to its elements, so it basically boils down to the question whether pointers can be used transparently with STL functions. A pointer actually is the most powerful kind of an iterator. There are different kinds

  • Input iterator: Only forward and one-pass, and only read
  • Output iterator: Only forward and one-pass, and only write

  • Forward iterator: Only forward, and read/write
  • Bidirectional iterator: Forward and backward, and read/write
  • Random access iterator: Arbitrary steps forward and backward in one breath, and read/write

Now each iterator in the second group supports all the things of all iterators mentioned before it. A pointer models the last kind of iterators - a random access iterator. You may add/subtract an arbitrary integer and you may read and write. And all except the output iterator has a operator-> that can be used to access a member of the element type we iterate over.

Normally, iterators have several typedefs as members

  • value_type - what the iterator iterates over (int, bool, string, ...)
  • reference - reference to the value_type
  • pointer - pointer to the value_type
  • difference_type - what type the distance between two iterators has (returned by std::distance).
  • iterator_category - this is a tag-type: it is typedefed to a type that represents the kind of the iterator. either std::input_iterator_tag, ..., std::random_access_iterator_tag. Algorithms can use it to overload on different kinds of iterators (like std::distance is faster for random access iterators, because it can just return a - b)

Now, a pointer of course does not have those members. C++ has an iterator_traits template and specializes it for pointers. So if you want to get the value type of any iterator, you do

iterator_traits<T>::value_type

And whether it is a pointer or some other iterator, it will give you the value_type of that iterator.

So - yes, a pointer can very well be used with STL algorithms. As someone else mentioned, even std::vector<T>::iterator can be a T*. A pointer is a very good example of an iterator even. Because it is so exceedingly simple but at the same time so powerful that it can iterate over a range.

南城追梦 2024-07-23 11:20:43

该标准设计的迭代器的感觉和行为尽可能类似于指针。 此外,由于迭代器基于模板,唯一相关的是迭代器类型定义了正确的运算符。 结果是指针的开箱即用行为就像随机访问迭代器一样。

事实上,std::vector::iterator 的一种可能实现就是将其设为 T*

当然,对于数组,您不会使用有用的 begin()end() 方法来查找有效的迭代器范围,但这就是您始终遇到的问题C 风格数组。

编辑:实际上,正如评论和其他答案中所提到的,如果数组不是动态的并且没有衰减为指针,则可以为数组实现这些函数。 但我的基本观点是,您必须比使用标准容器时更加小心。

The standard has designed iterators to feel and behave as much like pointers as possible. Also, since iterators are based on templates, the only relevant thing is that the iterator type has the proper operators defined. The result is that pointers will out-of-the-box behave just like random access iterators.

In fact, a possible implementation of std::vector<T>::iterator is to just make it a T*.

Of course, for an array you won't have the useful begin() and end() methods to find the valid iterator range, but that's the problem you always have with C style arrays.

Edit: Actually, as has been mentioned in the comments and other answers, you can implement those functions for arrays if the array is not dynamic and has not decayed into a pointer. But my basic point was that you have to be more careful than when using the standard containers.

故人如初 2024-07-23 11:20:43

简短回答:STL 算法通常被定义为与各种类型的迭代器一起使用。 迭代器是由它的行为定义的:它必须可以用 * 来取消引用,它必须可以用 ++ 来递增,以及其他各种也定义了它是什么类型的迭代器的东西(最常见的是随机访问)。 请记住,STL 算法是模板,因此问题是语法问题之一。 类似地,定义了operator()的类实例在语法上就像函数一样,因此它们可以互换使用。

指针可以完成随机访问迭代器所需的一切。 因此,它是一个随机访问迭代器,并且可以在 STL 算法中使用。 你可以看看向量的实现; 你很可能会发现 vector::iterator 是一个 whatever *

这不会使数组成为有效的 STL 容器,但它确实使指针成为有效的 STL 迭代器。

Short answer: STL algorithms are generally defined to work with iterators of various sorts. An iterator is defined by its behavior: it must be dereferenceable with *, it must be incrementable with ++, and various other things that also define what sort of iterator it is (the most general is random access). Remember that STL algorithms are templates, so the question is one of syntax. Similarly, a class instance with operator() defined works syntactically just like a function, so they can be used interchangeably.

A pointer does everything needed to be a random-access iterator. Therefore, it is a random-access iterator, and can be used as such in STL algorithms. You could look at vector implementations; you're very likely to find that vector<whatever>::iterator is a whatever *.

This doesn't make an array a valid STL container, but it does make pointers valid STL iterators.

潦草背影 2024-07-23 11:20:43

标准是否允许将数组与 STL 函数一起使用?

是的

如果是,那么像数组这样的古老结构如何适应模板化迭代器、容器和函数的宏伟 STL 计划?

迭代器的设计语义与指针类似。

此外,在这种用法中是否有程序员应该注意的警告或细节?

我更喜欢下一个用法:

int a[] = {9, 8, 7};
const size_t a_size = lengthof( a );
cerr << "Sum: " << accumulate( a, a + a_size , 0, plus<int>()) << endl;

或者使用 boost::array 更好更安全:

boost::array< int, 3 > a = { 9, 8, 7 };
cerr << "Sum: " << accumulate( a.begin(), a.end(), 0, plus<int>()) << endl;

Is such usage of arrays with STL functions allowed by the standard?

yes

If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions?

Iterators was designed with similar semantic as pointers.

Also, are there any caveats or details in such usage that the programmer should be careful about?

I prefer next usage:

int a[] = {9, 8, 7};
const size_t a_size = lengthof( a );
cerr << "Sum: " << accumulate( a, a + a_size , 0, plus<int>()) << endl;

Or it much better and safer to use boost::array:

boost::array< int, 3 > a = { 9, 8, 7 };
cerr << "Sum: " << accumulate( a.begin(), a.end(), 0, plus<int>()) << endl;
真心难拥有 2024-07-23 11:20:43

在我看来,您应该使用 std::vector 并使用其(稳定且可靠的)向后兼容性,而不是使用数组,然后担心将它们传递给 STL 函数(人们可能称之为“向前兼容性”,因此很脆弱)获取数组,如果您需要使用它们。

因此,您的代码将变为:

#include <functional>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

int main()
{
    vector<int> a(3);
    a[0] = 9;
    a[1] = 8;
    a[2] = 7;
    cerr << "Sum: " << accumulate(a.begin(), a.end(), 0, plus<int>()) << endl;
    return 0;
}

如果您需要将“a”传递给 C API,您可以这样做,这要归功于向量与数组的二进制兼容性。

Instead of using arrays and then worrying about passing them to STL functions (what one might call 'forwards compatibility', and is therefore fragile), IMO you should use std::vector and use its (stable and dependable) backwards compatibility with functions that take arrays, should you ever need to use them.

So your code becomes:

#include <functional>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

int main()
{
    vector<int> a(3);
    a[0] = 9;
    a[1] = 8;
    a[2] = 7;
    cerr << "Sum: " << accumulate(a.begin(), a.end(), 0, plus<int>()) << endl;
    return 0;
}

And if you ever need to pass 'a' to a C API you can do so, thanks to vectors binary compatibility with arrays.

江南月 2024-07-23 11:20:43

boost::array 简介 (传统数组的简单模板化包装器,也定义 STL 兼容的迭代器类型和 begin()/end() 等)包含一些关于它们兼容性程度的有趣讨论与STL。

The introduction to boost::array (a simple templated wrapper for conventional arrays, which also defines STL-compatible iterator types and begin()/end() etc) contains some interesting discussion of their degree of compatability with STL.

待"谢繁草 2024-07-23 11:20:43

只是对 Mykola 的答案发表评论:

数组不是指针,即使它们很容易退化为指针。 编译器拥有的关于数组的信息比关于容器的信息更多:

namespace array {
   template <typename T, int N>
   size_t size( T (&a)[N] ) {
      return N;
   }
   template <typename T, int N>
   T* begin( T (&a)[N] ) {
      return &a[0];
   }
   template <typename T, int N>
   T* end( T (&a)[N] ) {
      return &a[N];
   }
}
int main()
{
   int theArray[] = { 1, 2, 3, 4 };
   std::cout << array::size( theArray ) << std::endl; // will print 4
   std::cout 
      << std::accumulate( array::begin( theArray ), array::end( theArray ), 0, std::plus<int>() )
      << std::endl; // will print 10
}

虽然您无法询问数组的大小,但编译器将在调用给定模板时解析它。

现在,如果您调用一个接受 int a[] 的函数(请注意,没有大小),这类似于定义一个 int* 参数,并且大小信息在途中丢失。 编译器将无法确定函数内数组的大小:数组已退化为指针。

另一方面,如果将参数定义为int a[10],则信息会丢失,但您将无法调用该函数具有不同大小的数组。 这与C版本完全不同,至少在C99之前没有检查过[*]。 在 C 中,编译器将忽略参数中的数字,签名将等同于以前的版本。

@litb:你是对的。 我进行了这个测试,但它是对数组的引用,而不是数组。 感谢您指出。

dribeas@golden:array_size$ cat test.cpp 
void f( int (&x)[10] ) {}
int main()
{
    int array[20];
    f( array ); // invalid initialization of reference of type 'int (&)[10]' from...
}

Just a comment on Mykola's answer:

Arrays are not pointers, even if they tend to decay into pointers really easily. The compiler has more info on an array than on a container:

namespace array {
   template <typename T, int N>
   size_t size( T (&a)[N] ) {
      return N;
   }
   template <typename T, int N>
   T* begin( T (&a)[N] ) {
      return &a[0];
   }
   template <typename T, int N>
   T* end( T (&a)[N] ) {
      return &a[N];
   }
}
int main()
{
   int theArray[] = { 1, 2, 3, 4 };
   std::cout << array::size( theArray ) << std::endl; // will print 4
   std::cout 
      << std::accumulate( array::begin( theArray ), array::end( theArray ), 0, std::plus<int>() )
      << std::endl; // will print 10
}

While you cannot ask about the size of the array, the compiler will resolve it when calling the given templates.

Now, if you call a function that takes a int a[] (note that there is no size), that is similar to defining an int* parameter, and the size info is lost in the way. The compiler will not be able to determine the size of the array inside the function: the array has decayed into a pointer.

If , on the other hand, you define the parameter as int a[10] then the information is lost, but you will not be able to call the function with an array of a different size. This is completely different than the C version, at least prior to C99 have not checked lately[*]. In C the compiler will ignore the number in the argument and the signature will be equivalent to the previous version.

@litb: You are right. I had this test around, but it is with a reference to an array, not with an array. Thanks for pointing it out.

dribeas@golden:array_size$ cat test.cpp 
void f( int (&x)[10] ) {}
int main()
{
    int array[20];
    f( array ); // invalid initialization of reference of type 'int (&)[10]' from...
}
救赎№ 2024-07-23 11:20:43

是的,这是故意的。 迭代器可以实现为指针,因此您可以将指针用作迭代器。

Yes and this is on purpose. Iterators can be implemented as pointers and therefore you can use pointers as iterators.

梦里的微风 2024-07-23 11:20:43

指针模型 Trivial Iterator,以及来自数组模型的指针 随机访问迭代器。 所以是的,这是完全合法的。

如果您对每种 S(T)L 算法的使用限制感兴趣,请熟悉 迭代器模型

Pointers model Trivial Iterator, and pointer from arrays model Random Access Iterator. So yes, it's perfectly legal.

If you are interested on the usage constraints of each S(T)L algorithm, familiarize yourself the iterator models.

过期以后 2024-07-23 11:20:43

正如 int a[] 可以被视为一个指针。 在 C++ 中,指针可以递增并指向下一个元素。 由于指针可以进行​​比较,因此指针可以用作迭代器。

标准 24.1 节中指出了对迭代器的要求。 指针满足了它们。 这是其中一些

所有迭代器 i 支持表达式*i

就像指向数组的常规指针一样
保证有一个指针
指向最后一个元素之后的值
数组的,因此对于任何迭代器类型
有一个迭代器值指向
超过 a 的最后一个元素
相应的容器。

As int a[] can be treated as a pointer. And in C++ pointers can be incremented and point after that to the next element. And as pointers can be compared then pointers can be used as iterators.

There are requirements for iterators pointed in the standard 24.1 section. And pointers meet them. Here is some of them

All iterators i support the expression*i

Just as a regular pointer to an array
guarantees that there is a pointer
value pointing past the last element
of the array, so for any iterator type
there is an iterator value that points
past the last element of a
corresponding container.

聽兲甴掵 2024-07-23 11:20:43

STL 有隐藏的东西。 大部分工作都归功于迭代器,请考虑以下代码:

std::vector<int> a = {0,1,2,3,4,5,6,7,8,9};
// this will work in C++0x use -std=c++0x with gcc
// otherwise use push_back()

// the STL will let us create an array from this no problem
int * array = new int[a.size()];
// normally you could 'iterate' over the size creating
// an array from the vector, thanks to iterators you
// can perform the assignment in one call
array = &(*a.begin());

// I will note that this may not be considered an array
// to some. because it's only a pointer to the vector.
// However it comes in handy when interfacing with C
// Instead of copying your vector to a native array
// to pass to a C function that accepts an int * or
// anytype for that matter, you can just pass the
// vector's iterators .begin().

// consider this C function
extern "C" passint(int *stuff) { ... }

passint(&(*a.begin())); // this is how you would pass your data.

// lets not forget to delete our allocated data
delete[] a;

the STL has it hidden stuff. Most of this works thanks to iterators, consider this code:

std::vector<int> a = {0,1,2,3,4,5,6,7,8,9};
// this will work in C++0x use -std=c++0x with gcc
// otherwise use push_back()

// the STL will let us create an array from this no problem
int * array = new int[a.size()];
// normally you could 'iterate' over the size creating
// an array from the vector, thanks to iterators you
// can perform the assignment in one call
array = &(*a.begin());

// I will note that this may not be considered an array
// to some. because it's only a pointer to the vector.
// However it comes in handy when interfacing with C
// Instead of copying your vector to a native array
// to pass to a C function that accepts an int * or
// anytype for that matter, you can just pass the
// vector's iterators .begin().

// consider this C function
extern "C" passint(int *stuff) { ... }

passint(&(*a.begin())); // this is how you would pass your data.

// lets not forget to delete our allocated data
delete[] a;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文