Bootstrap

【C++学习笔记 13】C++中的数组

什么是数组

本质上就是以特定顺序排列的元素的集合,一般来说,数组中的类型是相同的。

举个例子

#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++ 教程

;