C++高维数组

 1、什么是高维数组

        所谓的高位数组就是,一个数组存储的其他数组的地址。下面以二维的数组为例,请看下面的代码示例

int main() {
	//二级指针指向一个整型指针数组
	int** array2d = new int* [5];
	for (int i = 0; i < 5; i++)
		array2d[i] = new int[5] {0,0,0,0,0}; //每个指针指向一个大小为5的数组

	array2d[0][4] = 3; array2d[1][3] = 5;

	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 5; j++)
			std::cout << array2d[i][j] << " ";

		std::cout << "\n";
	}
		

	std::cin.get();
}

从上面对高维数组的遍历当中可以看到,这样就可以像使用矩阵那样去使用这个高维数组。

2、为什么要使用高维数组

        从上面的代码不难看出,需要在程序当中使用一些矩阵运算的时候,使用高维数组是更加符合人们的数学直觉的,就比如上面的例子,需要修改第二行,第四列的数值时 array2d[1][3] = 5;还有其他涉及到需要进行矩阵运算的操作,高维数组都有发挥的地方。

3、高维数组存在的一些问题

        高维的数组虽然可以像矩阵那样去使用,但是高维数组的管理相当的麻烦,这可以说是普通数组的通病在它身上都有。先来看一下复制和释放的问题。

1、高维数组的复制

        这里先准备一个Vertex类型,定义如下

#ifndef VERTEX
#define VERTEX
struct Vertex{
	float x, y, z;
	Vertex() :x(0.0f), y(0.0f), z(0.0f) {}
	Vertex(float iX, float iY, float iZ) :x(iX), y(iY), z(iZ) {}
	~Vertex() { std::cout << "destroy function be called" << std::endl; }

	friend std::ostream& operator<<(std::ostream& out, const Vertex& iVertex);
};

std::ostream& operator<<(std::ostream& out, const Vertex& iVertex) {
	out << "(" << iVertex.x << "," << iVertex.y << "," << iVertex.z << ")";
	return out;
}
#endif // !VERTEX

   高维数组复制代码如下

Vertex** Copy2dArray(Vertex** pArray,int row,int col);

int main() {
	
	Vertex** vertex2d1 = new Vertex * [5];
	for (int i = 0; i < 5; i++)
		vertex2d1[i] = new Vertex[5];
		
	vertex2d1[1][2] = Vertex(15.0f, 30.0f, 40.0f);

	Vertex** vertex2d2 = Copy2dArray(vertex2d1, 5, 5);
	vertex2d2[1][1] = Vertex(27.0f, 11.0f, 30.0f);

	std::cout << "vertex2d1: " << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << "[";
		for (int j = 0; j < 5; j++)
			std::cout << vertex2d1[i][j] << " ";
		std::cout << "]" << "\n";
	}

	std::cout << "vertex2d2: " << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << "[";
		for (int j = 0; j < 5; j++)
			std::cout << vertex2d2[i][j] << " ";
		std::cout << "]" << "\n";
	}

	std::cin.get();
}

Vertex** Copy2dArray(Vertex** pArray, int row, int col) {
	Vertex** CopyArray = new Vertex*[row];
	for (int i = 0; i < row; i++)
		CopyArray[i] = new Vertex[col];

	for (int i = 0; i < row; i++)
		memcpy(CopyArray[i], pArray[i], col * sizeof(Vertex));

	return CopyArray;
}

运行的结果如下

这里可以看出一件事,在复制高维数组的时候我们需要手动的输入这个高维数组的行数和列数。可能有部分的读者提出使用sizeof()来确定数组的大小难道不行?如果看过笔者的《C++指针到底是什么》这篇文章的话,应该就能明白,这样是行不通的,所以高维数组的行数和列数只能手动的进行输入。

2、高维数组的释放

        像这种放在堆内存当中的数据需要手动的进行释放,不然就会造成内存溢出,高维数组数据释放代码如下

void Reales2dArray(Vertex** pArray, int row);

int main() {

	Vertex** vertex2d1 = new Vertex * [5];
	for (int i = 0; i < 5; i++)
		vertex2d1[i] = new Vertex[5];

	vertex2d1[1][2] = Vertex(15.0f, 30.0f, 40.0f);

	Vertex** vertex2d2 = Copy2dArray(vertex2d1, 5, 5);
	vertex2d2[1][1] = Vertex(27.0f, 11.0f, 30.0f);

	std::cout << "vertex2d1: " << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << "[";
		for (int j = 0; j < 5; j++)
			std::cout << vertex2d1[i][j] << " ";
		std::cout << "]" << "\n";
	}

	std::cout << "vertex2d2: " << std::endl;
	for (int i = 0; i < 5; i++) {
		std::cout << "[";
		for (int j = 0; j < 5; j++)
			std::cout << vertex2d2[i][j] << " ";
		std::cout << "]" << "\n";
	}

	Reales2dArray(vertex2d1, 5);
	Reales2dArray(vertex2d2, 5);

	std::cin.get();
}

void Reales2dArray(Vertex** pArray, int row) {
	for (int i = 0; i < row; i++)
		delete[] pArray[i];
}

运行的结果如下

从上面的代码当中不难看出,虽然输入数据的个数有所减少,但是依然需要手动的进行管理。综合来看高维数组的管理是一件非常困难,而且有很大风险的事情,程序员需要时刻牢记每个数组对应的规模是多少,否则很难可能出现数组越界,整个程序直接崩溃的情况。(普通动态数组都有上述问题)

4、更深层次的问题

        如果对程序的底层逻辑有所了解的朋友就会知道,前面所说的高维数组,相对于一维的数组,在程序效率上更低,有读者说是计算复杂程度的问题,毕竟高维数组的遍历是两个循环套在一起的。但是一维数组和二维数组如果遍历数据的量都一样的话,其实二者的性能是差不到哪里去的,真正导致二者性能差距的原因是数据的连续性

       程序在执行时会将需要使用的数据和它有关系的数据一并加载到缓存当中,这样程序就可以快速读取数据,处理器怎么判断数据之间是不是有关联了?放在一起的就会判断为是有关联的数据,相隔很远的就判断为没关联的数据。

        这个时候问题就来了,C++当中通过new操作符开辟出来的内存空间是系统随机分配到的,有可能两个数组之间隔得非常的远,如下图所示。

当程序去访问vertex2d1[3]当中的数据很可能会出现缓存非命中的情况,这是因为vertex2d1[3]数组当中的数据并没被加载到缓存当中,它和其他的数组相隔太远了。出现缓存非命中,那么程序就需要去其他的缓存甚至是主存当中找到对应的数据,这非常的耗费时间。

5、回避上述的问题

        上述当中,高维数组难以管理,因为数据的分散导致程序执行的效率也低。那么怎么解决这些问题了?答案也很简单,只需要让一维数组伪装成二维的数据就可以了

准备一个Array2D<class T>类的模板,Array2D.h头文件如下

#pragma once
#include<iostream>

template<typename T>
class Array2D {
	T* mArray;
	int mRow, mCol;
public:
	Array2D() :mArray(nullptr), mRow(0), mCol(0) {}
	Array2D(T* iArray, int iRow, int iCol);
	Array2D(const Array2D<T>& iArray2D);
	~Array2D() { delete[] mArray; }

	int GetRowNumber() const { return mRow; }
	int GetColNumber() const { return mCol; }

	T& operator()(int iRow, int iCol);
	const T& GetElement(int iRow, int iCol)const;

	friend std::ostream& operator<<(std::ostream& out, const Array2D<T>& iArray) {
		for (int i = 0; i < iArray.GetRowNumber(); i++) {
			std::cout << "[";
			for (int j = 0; j < iArray.GetColNumber(); j++)
				std::cout << iArray.GetElement(i, j) << " ";
			std::cout << "]" << "\n";
		}
		return out;
	}
};

template<typename T>
Array2D<T>::Array2D(T* iArray, int iRow, int iCol) {
	mRow = iRow; mCol = iCol;
	mArray = new T[iRow * iCol];
	memcpy(mArray, iArray, iRow * iCol * sizeof(T));
}

template<typename T>
Array2D<T>::Array2D(const Array2D<T>& iArray2D) {
	mRow = iArray2D.mRow; mCol = iArray2D.mCol;
	mArray = new T[mRow * mCol];
	memcpy(mArray, iArray2D.mArray, mRow * mCol * sizeof(T));
}

template<typename T>
T& Array2D<T>::operator()(int iRow, int iCol) {
	return mArray[iRow * mCol + iCol];
}

template<typename T>
const T& Array2D<T>::GetElement(int iRow, int iCol) const{
	return mArray[iRow * mCol + iCol];
}

测试代码如下

#include<iostream>
#include"Arrary2D.h"

#ifndef VERTEX
#define VERTEX
struct Vertex{
	float x, y, z;
	Vertex() :x(0.0f), y(0.0f), z(0.0f) {}
	Vertex(float iX, float iY, float iZ) :x(iX), y(iY), z(iZ) {}
	~Vertex() { std::cout << "destroy function be called" << std::endl; }

	friend std::ostream& operator<<(std::ostream& out, const Vertex& iVertex);
};

std::ostream& operator<<(std::ostream& out, const Vertex& iVertex) {
	out << "(" << iVertex.x << "," << iVertex.y << "," << iVertex.z << ")";
	return out;
}
#endif // !VERTEX

int main() {
	Vertex arra1[16] = {};

	Array2D<Vertex> vertex2d1(arra1, 4, 4);
	Array2D<Vertex> vertex2d2(arra1, 4, 4);

	vertex2d2(1, 2) = Vertex(32.0f, 44.0f, 88.0f);

	std::cout << "vertex2d1: " << std::endl;
	std::cout << vertex2d1;

	std::cout << "vertex2d2: " << std::endl;
	std::cout << vertex2d2;
	
}

运行结果

6、总结

        我们完全可以,对地址进行一些偏移计算,让一维数组伪装成高维的数组,这样可以提高程序的运行效率。通过一些封装,我们不再需要手动去管理数组的规模。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值