什么是数组
本质上就是以特定顺序排列的元素的集合,一般来说,数组中的类型是相同的。
举个例子
#include <iostream>
int main() {
int example[5];
example[0] = 2;
example[4] = 4;
std::cin.get();
}
使用上述语句创建一个具有5个整形的数组,并给它的第一个和最后一个元素赋值。
当我使用std::cout << example[0] << std::endl;
语句时,可以打印出第一个元素,而当我使用std::cout << example << std::endl;
打印数组本身时,会获得一个内存地址,因为其本质上还是一个指针。因此,当使用int* ptr = example;
可以编译通过。
更进一步的,当我使用指针赋值*(ptr + 2) = 6;
相当于对数组赋值example[2] = 6
。
值得一提的是,上述用指针赋值的操作仅仅添加了2,而没有根据指针类型计算实际地址的偏移量,这是因为编译器替我们做了这件事,它会根据数据类型进行偏移操作。如果想用字节操作的话,那么可以这样写:*(int*)((char*)ptr + 8) = 6
,意思是将指针类型转换为字符,进行地址的操作,再将指针类型转换为整形。因为字符的类型是1个字节,而整形的是4个字节。
内存访问冲突
当我用语句给数组外的元素赋值,如example[-1] = 5;
或者example[5] = 2;
,就存在内存访问冲突问题(memory access violation),因为正在访问不属于它的内存。当处于debug模式时,编译器会报错提示,但处于release时,则不会存在报错,意味着这个数组已经访问了不改访问的内存。因此必须非常小心这个问题,确保是在数组边界内操作。
数组在内存中长什么样
当我采用如图代码给数组赋值,并查看其内存,会发现,得到了一排的2。这告诉我们,设定数组大小的操作,是在内存中取出连续的空间以供调用。
在堆上创建数组
就像用new
关键字来创建类的实例,我们也可以用new
来创建数组,如int* another = new int[5];
。上面的语句int example[5];
是将数组创建在栈中,它将在离开这个作用域时被销毁,而在堆中的数组则会一直存活到我们把它销毁delete[] another;
,或者是程序结束。
如果想要返回在函数中创建的数组,那就需要使用new
关键字。 但也需要考虑内存间接寻址(memory indirection)的问题:指针 --> 另一个指针 --> 数组数据,这可能导致一些内存碎片与缓存丢失,在内存间跳来跳去也会影响性能,所以尽量在栈上创建数组。
值得一提的是,C++中的原生数组没法直接调用类似example->size()
等方法得知数组的大小,因此总是需要自己维护数组大小。因此也可以使用std::array
#include <array>
int main(){
std::array<int, 5> another;
std::cout << another.size() << std::endl;
}
它同时也会做边界检查、维持整形等等操作,带来一定的性能花销,但通常来说都很值得。
教程来源:The Cherno C++ 教程