Это c ++ !Все возможно!Но это c ++ , поэтому требуется некоторый уровень понимания.
Для этого давайте начнем с простого примера 2 одномерных массивов: char firstName[4] = { 'J', 'o', 'n', '\0' }
и char lastName[4] = { 'M', 'e', 'e', '\0' }
Давайте посмотримпри возможном расположении памяти здесь:
+------------+-------+
| Address | Value |
+------------+-------+
| 0x76543210 | 0x4A | <- firstName[0] - 'J'
| 0x76543211 | 0x6F | <- firstName[1] - 'o'
| 0x76543212 | 0x6E | <- firstName[2] - 'n'
| 0x76543213 | 0x00 | <- firstName[3] - '\0'
+------------+-------+
| 0x76543214 | 0x4D | <- lastName[0] - 'M'
| 0x76543215 | 0x65 | <- lastName[1] - 'e'
| 0x76543216 | 0x65 | <- lastName[2] - 'e'
| 0x76543217 | 0x00 | <- lastName[3] - '\0'
+------------+-------+
Учитывая это расположение памяти, если бы вы сделали cout << firstName << ' ' << lastName
, вы получите:
0x76543210 0x76543214
Эти массивы действительно просто указатели на их первый элемент!Это иллюстрирует Array to Pointer Decay, о котором вы можете прочитать подробнее здесь: http://en.cppreference.com/w/cpp/language/array#Array-to-pointer_decay
Прежде чем мы продолжим, отметим кое-что важное, char
s занимают ровно 1 байт, поэтому адрес каждогопоследующие char
в массиве будут просто следующими адресами.Это используется оператором Subscript следующим образом: firstName[1]
эквивалентно *(firstName + 1)
.Это верно для char
с, но также верно для любого другого типа, который занимает более 1 байта.Давайте возьмем для примера: short siArray = { 1, 2, 3, 4 }
, возможная структура памяти siArray
будет выглядеть следующим образом:
+------------+--------+
| Address | Value |
+------------+--------+
| 0x76543218 | 0x0001 | <- siArray[0] - 1
| 0x7654321A | 0x0002 | <- siArray[1] - 2
| 0x7654321C | 0x0003 | <- siArray[2] - 3
| 0x7654321E | 0x0004 | <- siArray[3] - 4
+------------+--------+
Даже если cout << siArray << ' ' << &(siArray[1])
выведет:
0x76543218 0x7654321A
*(siArray + 1)
по-прежнему будет индексировать тот же элемент siArray
, что и siArray[1]
.Это связано с тем, что при арифметике указателей c ++ учитывает тип обрабатываемого адреса, поэтому увеличение short*
фактически увеличит адрес на sizeof(short)
.Вы можете прочитать больше об арифметике указателей здесь: http://en.cppreference.com/w/cpp/language/operator_arithmetic
Наконец, давайте посмотрим, как c ++ хранит двумерные массивы.Учитывая: char name[2][4] = { { 'J', 'o', 'n', '\0' }, { 'M', 'e', 'e', '\0' } }
возможный макет памяти будет:
+------------+-------+
| Address | Value |
+------------+-------+
| 0x76543220 | 0x4A | <- name[0][0] - 'J'
| 0x76543221 | 0x6F | <- name[0][1] - 'o'
| 0x76543222 | 0x6E | <- name[0][2] - 'n'
| 0x76543223 | 0x00 | <- name[0][3] - '\0'
| 0x76543224 | 0x4D | <- name[1][0] - 'M'
| 0x76543225 | 0x65 | <- name[1][1] - 'e'
| 0x76543226 | 0x65 | <- name[1][2] - 'e'
| 0x76543227 | 0x00 | <- name[1][3] - '\0'
+------------+-------+
Поскольку мы знаем, что одномерное значение массива действительно является просто указателем, мы можем видеть из этого макета памяти, что name[0]
это не указатель, это просто первый символ первого массива.Таким образом, name
не содержит 2 указателя одномерного массива, но содержит содержимое 2 массивов.(Между прочим, на 32-разрядной машине, где не хранятся указатели, сохраняется 8 байтов памяти, что довольно существенно для 8-байтового 2-мерного массива.) Таким образом, попытка трактовать name
как char**
будет пытаться использоватьсимволы как указатель.
Поняв это, нам действительно нужно просто избегать использования арифметики указателей c ++ , чтобы найти разыменование значения.Для этого нам нужно работать с char*
, так что добавление 1 - это просто добавление 1. Так, например:
const short si2DArray[2][3] = { { 11, 12, 13 }, { 21, 22, 23 } };
const auto psi2DPointer = reinterpret_cast<const char*>(si2DArray);
for(auto i = 0U; i < size(si2DArray); ++i) {
for(auto j = 0U; j < size(*si2DArray); ++j) {
cout << *reinterpret_cast<const short*>(psi2DPointer + i * sizeof(*si2DArray) + j * sizeof(**si2DArray)) << '\t';
}
cout << endl;
}
Live Example
Обратите внимание, что в этом примере, хотя я и ссылаюсь на si2DArray
думал psi2DPointer
, я все еще использую информацию из si2DArray
для индексации, а именно:
- Какмногие массивы находятся в главном измерении:
size(si2DArray)
- Сколько элементов в младшем измерении:
size(*si2DArray)
- Какой размер в памяти младшего измерения:
sizeof(*si2DArray)
- Каков тип элемента массива:
sizeof(**si2DArray)
Таким образом, вы можете видеть, что потеря информации при преобразовании из массива в указатель является существенной.Вы можете испытать желание сохранить тип элемента, тем самым упрощая арифметику указателя.Стоит отметить, что только преобразование в char*
считается определенным поведением reinterpret_cast
: http://en.cppreference.com/w/cpp/language/reinterpret_cast#Type_aliasing