我们可以在std :: array中使用常规的指针算法吗?

时间:2018-08-31 17:31:23

标签: c++ arrays c++11 c++-standard-library

我想弄清楚如何在指向std :: array类元素的指针上使用旧风格的指针算法。以下代码(也许不足为奇)不会编译:

int main(int argc, char *argv[])
{
    double* data1 = new double[(int)std::pow(2,20)];
    std::cout << *data1 << " " << *(data1 +1) << std::endl;
    delete data1;
    data1 = NULL;

    double* data2 = new std::array<double, (int)std::pow(2,20)>;
    std::cout << *data2 << " " << *(data2 +1) << std::endl;
    delete data2;
    data2 = NULL;

    return 0;
}

作为练习,我想使用所有常规的指针算法,但是我不想指向旧式的double数组,而是希望它指向std :: array的元素。我对这一行的看法:

    double* data2 = new std::array<double, (int)std::pow(2,20)>;

指示编译器data2是指向分配的std::array<double,(int)std::pow(2,20)>堆的第一个元素的指针。

我被告知double* name = new double[size];的含义如下: 完全 :«堆栈为指向一个double的指针分配内存并命名该指针{{1 }},然后堆分配一个大小为name的双精度数组,然后将指针设置为指向该数组的第一个元素。»由于上述代码未编译,因此我必须从中学到了一些不正确的知识,因为相同的语法不适用于std :: arrays。

这引起了两个问题:

  1. 语句size actual 是什么意思?
  2. 如何使用std :: array实现我想要的?
  3. 最后,如何使用std :: unqiue_ptr和std :: make_unique实现相同的目的?

6 个答案:

答案 0 :(得分:7)

  

我被告知double* name = new double[size];的含义恰好是:«堆栈为一个指向一个double的指针分配内存,并命名该指针名称,然后堆分配一个size大小为double的数组,然后设置指针指向数组的第一个元素。»由于上述代码无法编译,因此我必须被教导一些不正确的东西,因为相同的语法不适用于std :: arrays。

您对该声明是正确的,但请记住,此工作方式是new[]new是不同的运算符。动态分配std::array时,您正在调用单个对象new,返回的指针指向std::array对象本身。

您可以对std::array的内容进行指针算术运算。例如,data2.data() + 1是指向data2[1]的指针。请注意,您必须调用.data()以获得指向基础数组的指针。

无论如何,不​​要动态分配std::array对象。尽可能避免动态分配,但是如果需要,请使用std::vector

答案 1 :(得分:4)

  

我们可以对std::array使用常规指针算法吗?

是的,请确保可以-但不能在数组本身(即对象)上。而是使用数组中数据的地址,该地址是通过std::array的{​​{3}}获得的,就像这样:

std::array<double, 2>  data2 { 12.3, 45.6 };
double* raw_data2 = data2.data(); // or &(*data2.begin());
std::cout << *raw_data2 << " " << *(raw_data2 + 1) << std::endl;

和这个data() method。但是您可能实际上并不需要使用指针算术,而可以利用std::array的更好抽象来编写不同的代码。

PS-避免对newdelete使用显式的内存分配(有关此问题,请参见compiles and runs fine)。就您而言,您根本不需要堆分配-就像您不需要常规数组那样。

答案 2 :(得分:3)

您可以使用std::array成员函数访问data()的“原始指针”视图。但是,std::array的要点是您不必这样做:

int main(int argc, char *argv[])
{
    std::array<double, 2> myArray;
    double* data = myArray.data();
    // Note that the builtin a[b] operator is exactly the same as
    // doing *(a+b).
    // But you can just use the overloaded operator[] of std::array.
    // All of these thus print the same thing:
    std::cout << *(data) << " " << *(data+1) << std::endl;
    std::cout << data[0] << " " << data[1] << std::endl;
    std::cout << myArray[0] << " " << myArray[1] << std::endl;

    return 0;
}

答案 3 :(得分:1)

广义的含义:

type* name = new othertype[size];

最后是“我需要一个指向name的变量type,并使用{{1}用size的{​​{1}}个实例的othertype个实例的连续分配来对其进行初始化}”。请注意,这涉及强制转换,可能甚至无法工作,因为new[]othertype可能不支持该操作。 type中的std::array不等于指向double的指针。它是指向double句点的指针,但是如果您要假装为std::array,并且您不介意程序由于未定义的行为而崩溃,则可以继续。您的编译器应在此处警告您,如果没有警告您,则警告不够严格。

标准库容器都是关于迭代器的,而不是指针,尤其是指针算法。迭代器比指针具有更大的灵活性和功能,它们可以处理奇异的数据结构,例如链表,树等,而不会给调用者带来很多开销。

某些容器,例如doublestd::vector支持“随机访问迭代器”,这是对它们的内容进行直接指针式访问的形式:std::array,依此类推。仔细阅读任何给定容器的文档,因为有些容器允许这样做,而许多容器则不允许这样做。

请记住,“变量”和“分配在堆栈上”不一定是同一件事。一个优化的编译器可以并将该指针放置在所需的任何位置,包括寄存器而不是内存,或者如果它认为可以在不破坏代码所表现出的行为的情况下将其放到任何地方,则什么也没有。

如果您要使用a[1],实际上作为标准库容器,它总是比C样式的数组好:

std::array

如果您需要共享此结构,则需要考虑使用std::array<double, 2> data2; 的花费是否值得。这个东西的内存占用将很小,而复制它的内容将变得微不足道,因此使用相对昂贵的内存管理功能毫无意义。

如果要遍历较大的结构,请考虑改用引用,并将该结构定位在您拥有的最中央的数据结构中,这样就不必按设计进行复制。

答案 4 :(得分:1)

当然,这些都是合法的:

template<class T, std::size_t N>
T* alloc_array_as_ptr() {
  auto* arr = new std::array<T,N>;
  if (!arr) return nullptr;
  return arr->data();
}
template<class T, std::size_t N>
T* placement_array_as_ptr( void* ptr ) {
  auto* arr = ::new(ptr) std::array<T,N>;
  return arr->data();
}
template<std::size_t N, class T>
std::array<T, N>* ptr_as_array( T* in ) {
  if (!in) return nullptr;
  return reinterpret_cast<std::array<T,N>*>(in); // legal if created with either above 2 functions!
}
// does not delete!
template<std::size_t N, class T>
void destroy_array_as_ptr( T* t ) {
  if (!t) return;
  ptr_as_array<N>(t)->~std::array<T,N>();
}
// deletes
template<std::size_t N, class T>
void delete_array_as_ptr(T* t) {
  delete ptr_as_array<N>(t);
}
令人震惊的是,如果使用得当,以上内容实际上是合法的。指向数组第一个元素的指针可以与整个std :: array相互转换。

您必须自己跟踪数组大小。

我不建议这样做。

答案 5 :(得分:1)

std::array毕竟是STL容器!

auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;