【问题标题】:C++ STL: Can arrays be used transparently with STL functions?C++ STL:数组可以透明地与 STL 函数一起使用吗?
【发布时间】:2010-10-17 07:58:04
【问题描述】:

在我看到这段代码之前,我一直认为 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 计划?此外,在这种用法中是否有任何需要程序员小心注意的警告或细节?

【问题讨论】:

  • 您最好使用 + 3 而不是 &a[3] :您可以引用该位置,但不能取消引用它。在 C 中,执行 &*(a + 3) 是合法的,但在 C++ 中,这是未定义的行为。在这种和类似的情况下,您可能会从编译器中的优化器中得到意外的惊喜
  • @litb:我不相信。我看到太多使用 &array[index] 的代码认为它是未定义的。据我所知,C++ 在这方面就像 C 中的普通旧数组一样,&array[index] 完全等于 &*(array+index) 或简单地 array+index。

标签: c++ arrays stl


【解决方案1】:

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

  • 输入迭代器:只能向前和单次,而且只能读取
  • 输出迭代器:只向前单传,只写

  • 转发迭代器:仅转发和读/写
  • 双向迭代器:前进后退,读/写
  • 随机访问迭代器:一口气进退任意步,读/写

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

通常,迭代器有多个 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&lt;T&gt;::iterator 也可以是T*。指针甚至是迭代器的一个很好的例子。因为它非常简单,但同时又如此强大,以至于它可以在一个范围内迭代。

【讨论】:

    【解决方案2】:

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

    事实上,std::vector&lt;T&gt;::iterator 的一种可能实现方式就是将其设为T*

    当然,对于数组,您将没有有用的 begin()end() 方法来查找有效的迭代器范围,但这就是 C 样式数组经常遇到的问题。

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

    【讨论】:

    • end() 不会只是 start() + size(),用指针实现吗?我不确定,但大多数情况下你会看到像“while(iter != vector.end())”这样的代码,它会起作用......
    • 您可以在一个简单的模板库中提供辅助函数,为数组提供开始/结束等效项。也就是只要数组没有通过函数/方法调用衰减成指针,并且数组不是动态的
    • @unwind: size() 不是 sizeof(array)。
    • 是的,end() == begin() + size()。我的意思是,对于数组,您需要手动跟踪大小。或者使用 sizeof-tricks,但(正如人们所说)并不总是有效。
    • boost::range 也有这样的数组开始/结束函数
    【解决方案3】:

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

    指针完成了成为随机访问迭代器所需的一切。因此,它是一个随机访问迭代器,可以在 STL 算法中使用。您可以查看向量实现;您很可能会发现 vector&lt;whatever&gt;::iteratorwhatever *

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

    【讨论】:

    • 我喜欢你的结束语:-)
    【解决方案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 数组的长度。
    • lengthof 的典型实现是:#define lengthof(x) sizeof(x)/sizeof(x[0]) 你也可以这样做:template int lengthof(const T[N] _) { 返回 N;但我不确定这是否适用于所有 C++ 编译器。
    • 模板 size_t lengthof( T(&)[N] ) { return N; }
    • 我在 Google 上搜索了 lengthof,想知道我是否错过了 C++09 创新。
    【解决方案5】:

    不要使用数组然后担心将它们传递给 STL 函数(人们可能称之为“前向兼容性”,因此很脆弱),IMO 你应该使用 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一起使用。你能解释一下如何实现你最后提到的向量到数组的转换吗?
    • 好吧,假设你有一个 c 函数: void foo(const int *pInts, int size) 其中 size 是 pInts 中元素的数量。你可以用这样的 vecotr 来调用它:vector v; ..用数据填充 v foo(&v[0],v.size());
    【解决方案6】:

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

    【讨论】:

    • 另见 std::array,或多或少 boost::array 但已标准化。
    【解决方案7】:

    只需对 Mykola 的answer 发表评论:

    数组不是指针,即使它们很容易衰减为指针。编译器对数组的信息比对容器的信息多:

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

    【讨论】:

    • "另一方面,如果将参数定义为 int a[10],则信息会丢失,但您将无法使用不同大小的数组调用函数。 " C++ 将完全忽略这 10 个 :)
    • 您必须添加“int N”作为函数大小、开始和结束的模板参数...
    • @Benoît:是的,我忘了。已更正。谢谢:)
    • 我不知道这种查找数组大小的方法!你能解释一下 T(&a)[N] 中的 & 在这种情况下是如何工作的吗?
    • Ash,已经完成了。耶! stackoverflow.com/questions/437150/…玩得开心!
    【解决方案8】:

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

    【讨论】:

      【解决方案9】:

      指针模型Trivial Iterator,以及来自数组模型的指针Random Access Iterator。所以是的,这是完全合法的。

      如果您对每个 S(T)L 算法的使用限制感兴趣,请熟悉iterator models

      【讨论】:

        【解决方案10】:

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

        标准 24.1 部分中指出了对迭代器的要求。指针与他们相遇。这是其中的一些

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

        就像一个普通的指向数组的指针 保证有一个指针 指向最后一个元素的值 数组的,所以对于任何迭代器类型 有一个迭代器值指向 过去 a 的最后一个元素 对应的容器。

        【讨论】:

        • 不,int a[10] 不是指针。它可以衰减为指针,但它完全是一种不同的类型,编译器对数组的信息比指针更多(大小:注意,这是编译器,而不是属性,并且仅当它尚未衰减为指针)
        【解决方案11】:

        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;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-10-10
          • 2012-03-16
          • 1970-01-01
          • 2011-01-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多