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

24

我之前认为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的迭代器、容器和函数的大计划?此外,在使用这种方法时,程序员需要注意哪些警告或细节?


你最好使用a + 3而不是&a[3]:你可以引用该位置,但不能对其进行解引用。在C中,执行&*(a + 3)是合法的,但在C++中这是未定义的行为。在这种和类似的情况下,编译器优化器可能会给你带来意想不到的问题。 - Johannes Schaub - litb
2
@litb:我不相信这个。我看到太多使用&array[index]的代码,认为它是未定义的。据我所知,对于普通数组,C++在这方面就像C一样,&array[index]与&*(array+index)或简单地array+index完全相等。 - Zan Lynx
11个回答

42

你问到了一个数组。你可以轻松地获取其元素的指针,因此问题基本上就归结为指针是否可以与STL函数透明地使用。指针实际上是最强大的迭代器类型之一。存在不同种类的迭代器:

  • 输入迭代器:只能向前遍历一次,并且只能读取。
  • 输出迭代器:只能向前遍历一次,并且只能写入。

  • 前向迭代器:只能向前遍历,并且可读写。
  • 双向迭代器:可以向前和向后遍历,并且可读写。
  • 随机访问迭代器:可以任意向前或向后跳,可读写。

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

通常,迭代器具有几个typedefs作为成员:

  • value_type - 迭代器遍历的内容类型(int,bool,string等)
  • reference - value_type的引用
  • pointer - 指向value_type的指针
  • difference_type - 两个迭代器之间距离的类型(由std::distance返回)
  • iterator_category - 这是一个标签类型:它被typedef为表示迭代器类型的类型。可以是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*。指针甚至是一个很好的迭代器示例,因为它非常简单但同时又非常强大,可以遍历一个范围。

18
标准设计迭代器的目的是让它们尽可能地像指针一样感觉和行为。此外,由于迭代器基于模板,唯一相关的事情就是迭代器类型是否定义了正确的运算符。结果是指针会像随机访问迭代器一样开箱即用。
实际上,std::vector<T>::iterator 的一个可能实现方法就是将其作为 T*
当然,对于数组,您将无法使用有用的 begin()end() 方法来查找有效的迭代器范围,但这是使用 C 风格数组时始终存在的问题。
编辑:实际上,正如在评论和其他答案中提到的那样,如果数组不是动态的并且没有衰变为指针,则可以为数组实现这些函数。但我的基本观点是,与使用标准容器时相比,您必须更加小心。

end()不就是start()+size()吗,使用指针实现?我不确定,但大多数情况下你会看到像“while(iter != vector.end())”这样的代码,那应该可以工作... - unwind
你可以在一个简单的模板库中提供辅助函数,为数组提供begin/end等价物。也就是说,只要数组没有通过函数/方法调用衰变为指针,并且数组不是动态的。 - David Rodríguez - dribeas
是的,end() == begin() + size()。我所说的是对于数组,您需要手动跟踪其大小。或者使用sizeof技巧,但是(正如人们所说),这并不总是有效的。 - CAdaker
你可以编写一个模板辅助函数,它返回数组的大小,并在使用指针时给出编译器错误。这有助于避免 sizeof 错误。另一个答案的评论中有一个示例。 - jalf
1
boost::range也为数组提供了类似的begin/end函数。 - Johannes Schaub - litb
显示剩余2条评论

7
简短回答:STL算法通常定义为使用各种迭代器工作。一个迭代器的定义取决于它的行为:它必须是可解引用的,可以通过++递增,并且还有其他一些也定义了它属于哪种迭代器的内容(最普遍的是随机访问)。请记住,STL算法是模板,因此问题在于语法。同样,具有定义operator()的类实例在语法上就像函数一样,因此它们可以互换使用。
指针可以执行任何需要成为随机访问迭代器的操作。因此,它是随机访问迭代器,并且可以在STL算法中使用。您可以查看向量实现;您很可能会发现vector<whatever>::iteratorwhatever *
这并不使数组成为有效的STL容器,但确实使指针成为有效的STL迭代器。

2
我喜欢你的结尾句子 :-) - Ashwin Nanjappa

4

使用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;

我不确定是否可以编写自己的函数来返回C数组的长度。 - Mykola Golubyev
一个typical implementation of lengthof会是: #define lengthof(x) sizeof(x)/sizeof(x[0])你也可以尝试这样做: template <class T, int N> int lengthof(const T[N] _) { return N; } 但我不确定它是否适用于所有的C++编译器。 - Niki
模板 <typename T,size_t N> size_t lengthof(T(&)[N]) { 返回 N; } - bayda
我在谷歌上搜索了lengthof,想知道是否错过了C++09的创新。 - Thomas L Holaday

2

关于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...
}

1
如果你将参数定义为int a[10],那么信息就会丢失,但你将无法使用不同大小的数组调用该函数。C++将完全忽略这个10 :) - Johannes Schaub - litb
你必须为你的函数 size、begin 和 end 添加一个模板参数 "int N"。 - Benoît
@Benoît:是的,我忘了。已更正。谢谢 :) - David Rodríguez - dribeas
我对这种查找数组大小的方法毫不知情!您能解释一下在 T(&a)[N] 中 & 是如何起作用的吗? - Ashwin Nanjappa
Ash,已经完成了。耶!https://dev59.com/2HRC5IYBdhLWcg3wAcXU#437178 玩得开心! - Johannes Schaub - litb
我不知道用什么关键词搜索那个。非常感谢! :-) - Ashwin Nanjappa

2

不要使用数组,然后担心将它们传递给STL函数(这可能被称为“前向兼容性”,因此很容易出错),我认为你应该使用std::vector,并使用其(稳定可靠的)向后兼容性,以便在需要时使用接受数组的函数。

所以你的代码变成了:

#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,由于向量与数组具有二进制兼容性,因此可以这样做。


我知道向量的使用,只是我不知道数组可以与STL一起使用。您能解释一下如何实现您在结尾提到的向量到数组的转换吗? - Ashwin Nanjappa
假设你有一个C函数:void foo(const int *pInts, int size),其中size是pInts中元素的数量。你可以这样使用vector调用它:vector<int> v; ..用数据填充v foo(&v[0],v.size()); - PaulJWilliams

2
这段文字的翻译如下:

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


另请参阅std :: array,更多或更少的boost :: array但标准化。 - joeforker

1

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


1

0

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

标准24.1节中有指向迭代器的要求。而指针符合这些要求。以下是其中一些要求:

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

就像指向数组的常规指针保证存在指向数组最后一个元素之后的指针值一样,对于任何迭代器类型,都存在一个迭代器值,该值指向相应容器的最后一个元素之后。


不,int a[10] 不是指针。它可以衰减为指针,但它是完全不同的类型,并且编译器使用数组比指针具有更多信息(大小:请注意,这是编译器,而不是属性,仅在它尚未衰减为指针时适用)。 - David Rodríguez - dribeas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接