To make things clear (may be):
An array is a collection of data of the same type, arranged sequentially in memory.
The data can be of any type, including the array type
So a 2D array is a 1D array whose data is 1D arrays
a 3D array is a 1D array whose data is 2D arrays
...
for example, this is a 4D array
byte myArray[10][2][2][3];
just don't try to imaging a table such as (X,Y), just use the notation the way it is and decide what each dimension will represent for you.
Sometimes it's useful to understand how the memory is laid out (because at the end of the day everything is sequential), data will be stored in increasing indexes starting from the right index.
myArray[0][0][0][0]
myArray[0][0][0][1]
myArray[0][0][0][2]
myArray[0][0][1][0]
myArray[0][0][1][1]
myArray[0][0][1][2]
myArray[0][1][0][0]
myArray[0][1][0][1]
myArray[0][1][0][2]
myArray[0][1][1][0]
myArray[0][1][1][1]
myArray[0][1][1][2]
...
here is a small example
const byte UIDSize = 4;
const byte ID[][UIDSize] = {
{0x01, 0x02, 0x03, 0x04},
{0x05, 0x06, 0x07, 0x08},
{0x09, 0x0A, 0x0B, 0x0C}
};
const byte IDCount = sizeof ID / sizeof ID[0];
void printHexByte(const byte b) {
Serial.print(b < 0x10 ? F(" 0") : F(" "));
Serial.print(b, HEX);
}
void printlnUID(const byte *uid, byte uidSize) {
for (byte i = 0; i < uidSize; i++) printHexByte(uid[i]);
Serial.println();
}
void setup() {
Serial.begin(115200); Serial.println();
Serial.println(F("As a 2D Array"));
for (byte index = 0; index < IDCount; index++) printlnUID(ID[index], UIDSize);
Serial.println(F("\nOr going through memory sequentially"));
const byte *ptr = &(ID[0][0]);
for (byte index = 0; index < IDCount*UIDSize; index++) printHexByte(ptr[index]);
}
void loop() {}
Serial Monitor (@ 115200 bauds) will show
As a 2D Array
01 02 03 04
05 06 07 08
09 0A 0B 0C
Or going through memory sequentially
01 02 03 04 05 06 07 08 09 0A 0B 0C