为什么std :: begin()和std :: end()可用于固定数组,但不适用于动态数组?

时间:2019-09-06 20:01:41

标签: c++ c++11 iterator

我不明白为什么std::begin()函数在被赋予int * arr指针的情况下不起作用,但是可以与int arr[]数组一起工作。

此代码无效:

int *arr = new int[5]{ 1,2,3,4,5 };

if (find(begin(arr),end(arr),5)!=end(arr))
{
    cout << "found";
}

此代码确实有效:

int arr2[5] = { 1,2,3,4,5 };

if (find(begin(arr2),end(arr2),5)!=end(arr2))
{
    cout << "found";
}

3 个答案:

答案 0 :(得分:6)

它不起作用,因为int *arr不是数组,而是指针。它碰巧指向数组的第一个元素,但指向该元素,而不是数组。一旦将其转换为指针,长度信息就会丢失。

请注意:它不会完全丢失,因为它实际上是以特定于实现的方式隐藏的,以便分配器知道以后delete[]可以释放多少,但这并不是暴露于除分配器之外的任何东西,由于分配器对齐要求,它通常不等于请求的实际大小,因此在这里没有用。

答案 1 :(得分:1)

std::(c)begin()std:(c)end()被显式重载以使用固定大小的数组类型。关于数组大小的信息不会丢失,因为大小是数组类型本身的一部分。这些重载的实现可能看起来像这样:

template<typename T, size_t N>
T* begin(T (&arr)[N])
{
    return arr;
}

template<typename T, size_t N>
const T* cbegin(const T (&arr)[N])
{
    return arr;
}

template<typename T, size_t N>
T* end(T (&arr)[N])
{
    return arr + N;
}

template<typename T, size_t N>
const T* cend(const T (&arr)[N])
{
    return arr + N;
}

因此,std::begin(arr2)std::end(arr2)仅在arr2int[N]固定大小的数组类型时才完全有效。编译器可以根据传入的固定数组的类型推导TN模板参数的值,例如:

int arr2[5] = { 1,2,3,4,5 };

if (find(
    begin(arr2), // deduces T=int, N=5, thus calls std::begin<int,5>(arr2)
    end(arr2), // deduces T=int, N=5, thus calls std::end<int,5>(arr2)
    5)
    != end(arr2) // deduces T=int, N=5, thus calls std::end<int,5>(arr2)
    )
{
    cout << "found";
}

相反,因为int*指针根本无法提供类似的 1参数重载,因为没有有关所指向数组大小的信息(甚至std::end()是否指向数组)以将有效的迭代器返回到数组的末尾:

template<typename T>
T* begin(T *arr)
{
    return arr; // OK
}

template<typename T>
const T* cbegin(const T *arr)
{
    return arr; // OK
}

template<typename T>
T* end(T *arr)
{
    return arr + N; // NOT OK, WHAT IS N SUPPOSED TO BE?!?
}

template<typename T>
const T* cend(const T *arr)
{
    return arr + N; // NOT OK, WHAT IS N SUPPOSED TO BE?!?
}

但是,您可以提供自己的 2参数重载 1 ,因此您可以显式传递{{ 1}},例如:

N
namespace std
{
    template<typename T>
    T* begin(T *arr, size_t N)
    {
        return arr;
    }

    template<typename T>
    const T* cbegin(const T *arr, size_t N)
    {
        return arr;
    }

    template<typename T>
    T* end(T *arr, size_t N)
    {
        return arr + N;
    }

    template<typename T>
    const T* cend(const T *arr, size_t N)
    {
        return arr + N;
    }
}

Live Demo

1:不允许向int *arr = new int[5]{ 1,2,3,4,5 }; if (find( begin(arr,5), // deduces T=int, explicit N=5, thus calls std::begin<int>(arr2,5) end(arr2,5), // deduces T=int, explicit N=5, thus calls std::end<int>(arr2,5) 5) != end(arr2,5) // deduces T=int, explicit N=5, thus calls std::end<int>(arr2,5) ) { cout << "found"; } 名称空间添加新功能,但允许为现有标准功能添加自定义重载。 std是一个常见的例子,尽管通常使用ADL比扩展std::swap()命名空间更好。

答案 2 :(得分:0)

std::begin docs

template< class C >
auto begin( C& c ) -> decltype(c.begin());
     

将迭代器返回给定的容器c或数组的开头。这些模板依赖于C :: begin()具有合理的实现。

int *arr不是数组,而是指针,而int arr2[5] 数组。