我想弄清楚如何在指向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;
}
作为练习,我想使用所有传统的指针算法,但不是指向旧式双数组,我希望它指向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的指针分配内存并命名指针name
,然后堆分配一个大小为size
的双精度数组,然后将指针设置为指向第一个元素由于上面的代码没有编译,我必须被教导一些不正确的东西,因为相同的语法对std :: arrays不起作用。
这提出了几个问题:
type* name = new othertype[size];
声明的实际含义是什么?我被告知
double* name = new double[size];
的含义完全如下:«Stack为指向ONE double的指针分配内存并命名指针名称,然后堆分配一个大小为double的数组,然后将指针设置为指向第一个元素由于上面的代码没有编译,我必须被教导一些不正确的东西,因为相同的语法对std :: arrays不起作用。
你对这个陈述是正确的,但请记住,这样做的方式是new[]
是与new
不同的运算符。当您动态分配std::array
时,您将调用单个对象new
,并且返回的指针指向std::array
对象本身。
你可以对std::array
的内容做指针运算。例如,data2.data() + 1
是指向data2[1]
的指针。请注意,您必须调用.data()
来获取指向底层数组的指针。
无论如何,不要动态分配std::array
对象。尽可能避免动态分配,但如果需要,请使用std::vector
。
我们可以使用传统的指针算法与
std::array
?
是的,确定你可以 - 但不是数组本身,这是一个对象。相反,你使用数组中的数据地址,你可以使用std::array
的data()
method,如下所示:
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;
这个compiles and runs fine。但是你可能不需要使用指针算法,并且可以使用std::array
的更好的抽象来编写不同的代码。
PS - 避免使用new
和delete
进行显式内存分配(有关此问题,请参阅C++ Core Guidelines item)。在您的情况下,您根本不需要堆分配 - 就像您不需要使用常规数组一样。
您可以使用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;
}
一般化的含义:
type* name = new othertype[size];
结束是“我需要一个变量name
,这是一个指向type
的指针,并使用size
连续分配othertype
new[]
实例”。请注意,这涉及到投射,甚至可能不起作用,因为othertype
和type
可能不支持该操作。 std::array
的double
不等于指向double
的指针。这是一个指向std::array
,句号的指针,但是如果你想假装它是一个double
并且你不介意你的程序由于未定义的行为而崩溃,你可以继续。你的编译器应该在这里警告你,如果没有,你的警告不够严格。
标准库容器都是关于迭代器而不是指针,尤其不是指针算术。迭代器比指针更灵活,更强大,它们可以处理异常数据结构,如链表,树和更多,而不会给调用者带来大量开销。
像std::vector
和std::array
这样的容器支持“随机访问迭代器”,这是一种直接指针式访问其内容的形式:a[1]
等。请仔细阅读任何给定容器的文档,因为有些人允许这样做,许多人不这样做。
请记住,“变量”和“在堆栈上分配”不一定是一回事。优化编译器可以并且将把指针放在任何它想要的地方,包括寄存器而不是内存,或者如果它认为它可以在不破坏代码的表达行为的情况下逃脱它,则根本没有。
如果你想要std::array
,你真正做的标准库容器几乎总是比C风格的数组更好:
std::array<double, 2> data2;
如果你需要分享这个结构,你需要考虑使用std::unique_ptr
的费用是否值得。这个东西的内存占用空间很小,复制它将是微不足道的,因此使用相对昂贵的内存管理功能毫无意义。
如果您在更大的结构周围传递,请考虑使用引用,并在最中心的数据结构中找到结构,这样就不需要按设计进行复制。
当然,这些都是合法的:
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互换。
您必须自己跟踪数组大小。
我不建议这样做。
std::array
毕竟是STL容器!
auto storage = std::array<double, 1 << 20>{};
auto data = storage.begin();
std::cout << *data << " " << *(data + 1) << std::endl;