• 多维数组,其实就是由数组作为元素,构成的数组

二维数组与三维数组#

#include <iostream>

int main()
{
	//存储50个整数, 指针存储这50个整数的第一个数的地址
	//50*4=200字节
	int* array = new int[50];

	//A2D, 2D数组,50个[整数指针]
	//分配50个[整数指针]; 32位系统,一个指针4字节
	//50*4=200字节
	int** a2d = new int*[50];

	//都是分配了200个字节,4个字节一组,总共50组

	//类型,其实就是在设置处理这些属性的语法,比如 int* array,
	//int** a2d等等
	array[0] = 0;//处理的是整数
	a2d[0] = nullptr;//处理的是指针

	//有空间存储50个指针,这些指针共占200字节,之后我们可以遍历
	//它们,并将每个指针都设置为指向一个数组. 其实最终得到的就是50个数组
	//二维数组: 有一个数组,包含着(50个)[数组的内存地址] 
	for (int i = 0; i < 50; i++)
	//为50个数组元素(指针)处理操作
	{
		//让每一个数组元素(指针)的值,等于new(50个元素)出来的数组的首地址
		a2d[i] = new int[50];
	}


	//分配50个(二维数组)
	int*** a3d = new int**[50];
	//遍历每一个二维数组
	for (int i = 0; i < 50; i++)
	{
		//初始化二维数组
		a3d[i] = new int* [50];
		//遍历a3d[i]这个数组的50个元素
		for (int j = 0; j < 50; j++)
		{
			int** ptr = a3d[i];
			//让每个元素指向new出的50个元素的数组
			ptr[j] = new int[50];
		}
	}
	//a3d[0]:得到一个二级指针数组
	//a3d[0][0]:得到一个指针数组
	//a3d[0][0][0]:得到整数
	a3d[0][0][0] = 0;

	std::cin.get();
}

详解二维数组#

#ifdef LY_EP64
#include <iostream>

int main()
{
	//创建一个数组,每个数组元素是指针
	int** a2d = new int* [50];

	//对50个元素赋值
	for (int i = 0; i < 50; i++)
	{
		//对第i个指针元素赋值,每个指针元素
		//指向新创建的数组
		a2d[i] = new int[50];

	}
	//第1个数组的第一个元素赋值为0
	//左边的0是指针的索引
	//右边的0,1,2是整数的索引
	a2d[0][0] = 0;
	a2d[0][1] = 0;
	a2d[0][2] = 0;

	//没有这种语法,编译报错
	//delete[][] a2d;


	for (int i = 0; i < 50; i++)
	{
		//删除所有内部的数组
		delete[] a2d[i];
	}
	//这个删除的是,保存指针元素的那个数组(最外层)
	delete[] a2d;

	std::cin.get();
}
#endif

内存碎片化#

#ifdef LY_EP64
#include <iostream>

int main()
{ 
	//包含5个指针元素的数组
	int** a2d = new int* [5];
	 
	for (int i = 0; i < 5; i++)
	{ 
		//每个元素都包含五个数组
		a2d[i] = new int[5];
	} 

	//设置元素的值
	for (int y = 0; y < 5; y++)
	{
		for (int x = 0; x < 5; x++)
		{
			a2d[y][x] = 2;
		}
	}

	//这25个元素并不是一个连续的能
	//容纳25个整数的缓冲区,而是5个独立
	//的缓冲区,每个缓冲区能容纳5个整数
	//这5个缓冲区,被分配到内存中的随机位置

	for (int i = 0; i < 5; i++)
	{
		//删除所有内部的数组
		delete[] a2d[i];
	}

	//这个删除的是,保存指针元素的那个数组(最外层)
	delete[] a2d;

	std::cin.get();
}

#endif;
  • 如果我们必须遍历,每次遍历5个整数后(访问25个整数中的每一组),然后转到数组的下一行(下一个整数数组),到内存中的另一个位置读取/写入数据,这可能会导致缓存未命中,这意味着从内存中获取数据时会浪费时间(如果他们分配得很近可能不会缓存未命中)
    • 如上所示,这样遍历这25个整数,比在内存中连续分配一个25个整数的一维数组慢很多,因为那段内存是连续的
    • 当你编程优化时,当你处理内存时,优化时最重要的一件事就是优化内存访问
    • 如果能够在内存中紧凑存储要访问的内存,并且通过某种方式摆放,以获得更多的缓存命中,使程序运行更快
  • 缓存学习包括:CPU缓存如何工作

代替二维数组#

#ifdef LY_EP64
#include <iostream>

int main()
{
	//这是一个栈上的原生二维数组,且内存中连续(对比int** a2d = new int* [5];方式)
	//物理形态:[ 1, 2, 3, 4, 5, 6, 7, ... 25 ] 
	//特点:编译器通过公式 Address + (row x 5 + col) x 4 直接定位。没有额外的寻址操作。(字节定位)
	int a[5][5] = { 1,2,3,4,5,6,7,8,9,10, 11,12,13,14,15,16,17,18,19,20, 21, 22, 23, 24, 25 };
	for (int i = 0; i < 5; i++)
		for (int j = 0; j < 5; j++)
			std::cout << a[i][j] << std::endl;


	//使用一维数组(连续的一块内存)完成二维数组5 * 5 的数组功能
	int* array = new int[5 * 5];
	for (int y = 0; y < 5; y++)
	{
		for (int x = 0; x < 5; x++)
		{
			array[x + y * 5] = 2;
		}
	}

	std::cin.get();
}

#endif;

建议#

  • 避免使用二维数组,如果存储一个位图,有一幅图像的所有元素,会把图像(正方形或长方形)看成二维的,但不代表要把它存为二维数组,建议存为一维数组

为什么推荐“平铺的一维数组”而不是int a[5][5]#

  • 动态需求:实际开发中(如加载一张图片),你往往在运行时才知道宽度和高度。这时你无法使用 int a[W][H] (W和H如果是动态则编译失败),只能用 new
  • 统一性:如果你习惯了 array[y * width + x],那么无论数组是在栈上(int array[25];)还是堆上(int* array = new int[width * height];),你的逻辑 底层逻辑 都是统一的。
    • 对比:如果你用原生语法,栈上是 a[y][x],堆上如果你用 int** 也是 a[y][x] 如果用int* array = new int[width * height];就没办法用这种形式. 和前者甚至不🙆‍♀️ ,但它们的底层指令完全不同(一个计算偏移,一个跳转指针),且不能互相传递。
  • 传递方便:将 int a[5][5] 作为参数传递给函数很麻烦(必须指定列数,如 void func(int arr[][5])),而传递一个 int* 则非常简单通用
    • 编译器必须知道“每一行有多少个元素”,否则它无法计算出a[i][j]的内存地址。