Вы можете мучить себя без всякой причины или бить себя головой о кирпичную стену (не волнуйтесь - мы все были там ... и у вас есть синяки, чтобы доказать это).
Сначала давайте начнем с любого выделенного блока памяти, скажем:
int *a = new int[NELEM], ...
Что такое a
? (указатель - да, но на что?) Это указатель на начальный адрес в блоке памяти размером NELEM * sizeof *a
байт. Какой тип указателя это? (int
). Сколько байт на int? (обычно 4
).
Так почему указатель имеет тип int
? (ну, он устанавливает размер шрифта, который управляет тем, как работает арифметика указателей при обращении к блоку памяти через этот указатель) То есть, поскольку тип указателя int
, компилятор знает, что a + 1
равен a + 4-bytes
, что позволяет вам для ссылки на следующее значение в вашем блоке памяти.
Хорошо, но я выделил память для a
, каковы мои обязанности в отношении a
? В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняйте указатель на начальный адрес для блока памяти, так , (2) он может быть освобожден , когда он больше не нужен.
Что это значит для меня? Это означает, что если вы не можете просто увеличить a
(например, a++
) в области, где было объявлено a
. Если вы это сделаете, вы потеряли свою ссылку на начальный адрес блока, и этот блок больше не может быть освобожден (это утечка памяти ).
Так что, если я не могу использовать какое-либо индексирование (например, a[i]
или *(a + i)
) и не могу увеличить свой указатель a
- тогда каковы мои варианты? Использовать другой указатель ... , например,
int *a = new int[NELEM],
*p = a;
...
std::cout << "array : ";
for (int i = 0; i < NELEM; i++, p++) {
*p = rand() % 100 + 1;
std::cout << std::setw(5) << *p;
}
std::cout << '\n';
Удовлетворены ли вы своими обязанностями в отношении блока памяти, выделенного для a
? Конечно, a
все еще указывает на начальный адрес блока, поэтому его можно освободить. Все, что вы сделали, это использовали второй указатель p
и итерацию, используя p
, оставив a
без изменений.
Хмм .. Используя второй указатель .. Интересно, смогу ли я перевернуть массив, используя ту же схему Ага. В простейшей форме вы можете сделать что-то вроде:
void rev (int *a, size_t size)
{
int *e = a + size - 1; /* end pointer */
for (; e > a; a++, e--) { /* while end > start, swap start, end */
int tmp = *a;
*a = *e;
*e = tmp;
}
}
Но подождите! Вы сказали, что не можете увеличить a
без потери начального адреса для моего выделенного блока - как я могу free
сделать это сейчас? (a
в main()
никогда не меняется, функция rev
получает копию a
и в пределах rev
вы можете увеличивать / уменьшать или делать все, что захотите, до a
в пределах границ блок памяти, потому что a
в rev
имеет свой собственный (и очень отличный) адрес от исходного указателя в main()
.
(в сторону ...) Вы могли бы объявить третий указатель в пределах rev
, например,
int *s = a, /* start pointer */
*e = a + size - 1; /* end pointer */
и затем использовал s
вместо a
в вашей итерации и подкачке, но в этом нет необходимости. Вы можете делать это таким образом, если вам более ясно, с каким указателем вы работаете. Это просто еще 8 байтов (или 4 на x86), поэтому дополнительное хранилище не проблема.
В целом, в коротком примере, вы можете сделать что-то похожее на следующее:
#include <iostream>
#include <iomanip>
#include <cstdlib>
#define NELEM 10
void rev (int *a, size_t size)
{
int *e = a + size - 1; /* end pointer */
for (; e > a; a++, e--) { /* while end > start, swap start, end */
int tmp = *a;
*a = *e;
*e = tmp;
}
}
int main (void) {
int *a = new int[NELEM],
*p = a;
srand (20180502);
std::cout << "array : ";
for (int i = 0; i < NELEM; i++, p++) {
*p = rand() % 100 + 1;
std::cout << std::setw(5) << *p;
}
std::cout << '\n';
rev (a, NELEM);
p = a;
std::cout << "reverse: ";
for (int i = 0; i < NELEM; i++, p++)
std::cout << std::setw(5) << *p;
std::cout << '\n';
delete[] a;
}
Пример использования / Вывод
$ ./bin/array_reverse
array : 11 6 78 93 25 71 82 58 97 68
reverse: 68 97 58 82 71 25 93 78 6 11
На все это уходит немного времени. У всех нас синяки на лбу от одной стены. Просто примиритесь с тем фактом, что указатель - это просто переменная, которая содержит адрес другого значения в качестве значения (например, оно указывает на то, где хранится что-то еще).
Понять, как type
указателя влияет на арифметику указателя (и индексацию), например, сколько байтов добавляется с помощью p++
или for (i = 0; i < size; i++) p[i]
, и убедитесь, что вы точно знаете, куда указывает ваш указатель, и все должно начать становиться на свои места.
Если у вас возникнут проблемы с выяснением того, что происходит с указателем, вытащите лист бумаги размером 8,5 x 11 и карандаш № 2 и просто вытяните его - на каждой итерации заполняйте блок, где находится ваш указатель указывает и т.д .. - это действительно помогает. После того, как вы нарисовали достаточно диаграмм, сделали достаточно связанных списков, стеков и т. Д., Вам больше не понадобится бумага, как сейчас (она все равно понадобится - так что держите ее под рукой)
Реверс в main()
с функцией
В ответ на ваш комментарий, когда вы смотрите на main()
, у вас уже есть дополнительный указатель p
, объявленный. Таким образом, вы можете просто использовать его в качестве указателя начала и добавить e
из функции rev()
в качестве указателя конца. Простая реализация будет:
int main (void) {
int *a = new int[NELEM],
*p = a,
*e = a + NELEM - 1;;
srand (20180502);
std::cout << "array : ";
for (int i = 0; i < NELEM; i++, p++) {
*p = rand() % 100 + 1;
std::cout << std::setw(5) << *p;
}
std::cout << '\n';
p = a; /* reset pointer */
for (; e > p; p++, e--) { /* reverse array */
int tmp = *p;
*p = *e;
*e = tmp;
}
p = a; /* reset pointer -- again */
std::cout << "reverse: ";
for (int i = 0; i < NELEM; i++, p++)
std::cout << std::setw(5) << *p;
std::cout << '\n';
delete[] a;
}
(тот же вывод)
Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.