简体   繁体   English

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

[英]C++ STL: Can arrays be used transparently with STL functions?

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: 我假设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;
}

It compiles and runs without any warnings or errors with g++, giving the correct output sum of 24. 它编译并运行时没有任何警告或g ++错误,给出正确的输出和24。

Is such usage of arrays with STL functions allowed by the C++/STL standard ? 是否允许 C ++ / STL 标准 允许使用具有STL功能的数组? If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions? 如果是,那么像数组这样的古老结构如何适应模板化迭代器,容器和函数的宏STL计划? Also, are there any caveats or details in such usage that the programmer should be careful about? 此外,程序员应该注意这些用法中是否有任何警告或细节?

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. 你可以很容易地得到一个指向它的元素的指针,所以它基本上归结为指针是否可以透明地与STL函数一起使用的问题。 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. 除输出迭代器之外的所有迭代器都有一个operator-> ,可用于访问我们迭代的元素类型的成员。

Normally, iterators have several typedefs as members 通常,迭代器有几个typedef作为成员

  • value_type - what the iterator iterates over (int, bool, string, ...) value_type - 迭代器迭代的内容(int,bool,string,...)
  • reference - reference to the value_type reference - 对value_type的引用
  • pointer - pointer to the value_type 指针 - 指向value_type的指针
  • difference_type - what type the distance between two iterators has (returned by std::distance ). difference_type - 两个迭代器之间的距离是什么类型(由std::distance返回)。
  • iterator_category - this is a tag-type: it is typedefed to a type that represents the kind of the iterator. iterator_category - 这是一个标记类型:它被typedefed为一个表示迭代器类型的类型。 either std::input_iterator_tag , ..., std::random_access_iterator_tag . 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 ) 算法可以用它来重载不同类型的迭代器(比如std::distance对于随机访问迭代器更快,因为它只能返回a - b

Now, a pointer of course does not have those members. 现在,指针当然没有那些成员。 C++ has an iterator_traits template and specializes it for pointers. C ++有一个iterator_traits模板,专门用于指针。 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. 无论是指针还是其他迭代器,它都会为您提供该迭代​​器的value_type。

So - yes, a pointer can very well be used with STL algorithms. 所以 - 是的,指针可以很好地与STL算法一起使用。 As someone else mentioned, even std::vector<T>::iterator can be a T* . 正如其他人提到的,即使是std::vector<T>::iterator也可以是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. 因为它非常简单但同时又如此强大以至于它可以在一个范围内迭代。

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* . 实际上, std::vector<T>::iterator一个可能实现就是使它成为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. 当然,对于数组,您将没有有用的begin()end()方法来查找有效的迭代器范围,但这是C样式数组始终存在的问题。

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. 但我的基本观点是,你必须比使用标准容器时更加小心。

Short answer: STL algorithms are generally defined to work with iterators of various sorts. 简短回答:STL算法通常被定义为与各种迭代器一起使用。 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. 请记住,STL算法是模板,因此问题是语法之一。 Similarly, a class instance with operator() defined works syntactically just like a function, so they can be used interchangeably. 类似地,使用operator()定义的类实例在语法上与函数一样,因此它们可以互换使用。

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. 因此,它是一个随机访问迭代器,可以在STL算法中使用。 You could look at vector implementations; 你可以看看矢量实现; you're very likely to find that vector<whatever>::iterator is a whatever * . 你很可能会发现vector<whatever>::iterator是一个whatever *

This doesn't make an array a valid STL container, but it does make pointers valid STL iterators. 这不会使数组成为有效的STL容器,但它确实使指针成为有效的STL迭代器。

Is such usage of arrays with STL functions allowed by the standard? 是否允许标准允许使用具有STL功能的数组?

yes

If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions? 如果是,那么像数组这样的古老结构如何适应模板化迭代器,容器和函数的宏STL计划?

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更好更安全:

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

Just a comment on Mykola's answer : 只是评论Mykola的答案

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. 现在,如果你调用一个带有int a[]的函数(注意没有大小),这类似于定义一个int*参数,并且大小信息会丢失。 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. 另一方面, 如果将参数定义为int a[10]则信息将丢失 ,但您将无法使用不同大小的数组调用该函数。 This is completely different than the C version, at least prior to C99 have not checked lately[*]. 这与C版完全不同,至少在C99之前没有检查过[*]。 In C the compiler will ignore the number in the argument and the signature will be equivalent to the previous version. 在C中,编译器将忽略参数中的数字,签名将等同于先前的版本。

@litb: You are right. @litb:你是对的。 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...
}

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. 而不是使用数组然后担心将它们传递给STL函数(人们可能称之为'前向兼容性',因此是脆弱的),IMO你应该使用std :: vector并使用它(稳定和可靠)向后兼容的函数如果您需要使用它们,请使用数组。

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. 如果您需要将'a'传递给C API,您可以这样做,这要归功于向量与数组的二进制兼容性。

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

Yes and this is on purpose. 是的,这是故意的。 Iterators can be implemented as pointers and therefore you can use pointers as iterators. 迭代器可以实现为指针,因此您可以使用指针作为迭代器。

Pointers model Trivial Iterator , and pointer from arrays model Random Access Iterator . 指针模型为Trivial Iterator ,指针来自数组模型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 . 如果您对每个S(T)L算法的使用限制感兴趣,请熟悉迭代器模型

As int a[] can be treated as a pointer. 作为int,[]可以被视为指针。 And in C++ pointers can be incremented and point after that to the next element. 在C ++中,指针可以递增,然后指向下一个元素。 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. 标准24.1部分中指出了迭代器的要求。 And pointers meet them. 指针符合他们。 Here is some of them 这是其中一些

All iterators i support the expression*i 所有迭代器我都支持表达式* 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. 正如指向数组的常规指针一样,它保证指针值指向数组的最后一个元素,因此对于任何迭代器类型,都有一个迭代器值指向相应容器的最后一个元素。

the STL has it hidden stuff. STL有隐藏的东西。 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM