Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Информатика 1.docx
Скачиваний:
11
Добавлен:
26.09.2019
Размер:
364.88 Кб
Скачать

Массивы и константные указатели.

Массив (без квадратных скобок) указывает на начало области памяти, где хранятся данные. Этот адрес не может быть изменён, поэтому в большинстве случаев можно считать, что массив является константным указателем на область хранения его данных, и оперировать с ним как с обычным константным указателем. Тем не менее, между ними существует небольшая разница, на практике проявляющаяся в очень редких случаях.

Основных различий два:

  • результат применения оператора sizeof.

  • результат применения опреации взятия адреса &.

Оператор sizeof, применённый к массиву возвращает его размер в байтах. Результат же применения оператора sizeof к любому константному (и не константному) указателю всегда равен одной и той же константе, зависящей только от целевой платформы. Для win32 и большинства других платформ она равна 4.

Таким образом, следующая программа:

main()

{

char arr[10];

char* p;

p=arr;

printf("sizeof(arr)=%d, sizeof(p)=%d",sizeof(arr),sizeof(p));

}

в результате выполнения распечатает строку "sizeof(arr)=10, sizeof(p)=4", хотя и массив, и указатель p указывают на одну и ту же область памяти. Более того, как мы увидим позднее, в случае одномерного вектора и к arr, и к p можно полностью идентично применять операцию индексации [].

Второе различие заключается в применении оператора взятия адреса &. Поскольку указатель, и константный в том числе, является переменной и хранится в определённой области памяти, то операция &, применённая к нему, вернёт адрес этой области памяти, где хранится указатель. Если же применить эту операцию к массиву, то мы получим в результате адрес области памяти, в которой хранится значение массива, т.е. тот же саамы адрес, на который указывает сам массив. Т.е., в отличие от указателей, для массива arr значения arr и &arr будут одинаковыми.

Также существует ряд тонкостей в использовании операции индексации для массивов и указателей. Для одномерных массивов не существует никакой разницы в применении этой операции к массиву или к указателю. Например:

main()

{

char arr[10];

char* p;

p=arr;

printf("arr[5]=%d, p[5]=%d", arr[5],p[5]);

}

В результате выполнения этой программы 2 раза будут напечатаны значения 5-го элемента массива. В первом случае значение элемента получено с помощью операции, применённой к массиву, во втором – с помощью указателя, указывающего на ту же область памяти, что и массив. При этом операция индексации p[5] точно так же, как и в случае массива обозначает "сдвинуть указатель на пять блоков и взять значение по полученному адресу".

Различия в применении операции [] начинаются в случае применения многомерных массивов.

Пусть у нас есть двумерный массив:

char arr[2][4];

Если вместо двойного применения операции индексации к массиву arr применить её только один раз, то результатом такой операции будет являться адрес начала соответствующей строки в памяти (это действительно и для массивов больших размерностей), например arr[1] вернёт адрес начала 1й строки.

arr[0][0]

arr[0][1]

arr[0][2]

arr[0][3]

arr[1][0]

arr[1][1]

arr[1][2]

arr[1][3]

↑ ↑

arr arr[1]

p

|­­–– p[1] –––| |­­ (arr[1])[2] |

XX

XX

XX

XX

p[1] (указатель)

|­­– (p[1])[2] –|

Подходя формально, операцию получения элемента [1][2] массива arr можно записать как двойное последовательное применение операции индексации [], т.е. выражение

e=arr[1][2]; можно переписать в виде e=(arr[1])[2];

Тогда первая операция индексации вернёт адрес начала первой строки, а вторая операция – сместит этот указатель на 2 позиции (т.е. на начало второго элемента этой строки) и считает из памяти значение ячейки. Таким образом, мы получим значение элемента arr[1][2], что и требовалось.

Теперь рассмотрим применение точно таких же операций к указателю

char** p=arr;

указывающему на начало массива. Две звёздочки здесь означают, что p является указателем на указатель на char. На самом деле, можно было написать и только одну звёздочку, смысл от этого особенно не изменится, но тогда при двойном применении операции [] пришлось бы использовать явное приведение типов к типу указатель.

Полностью аналогично, выражение e=p[1][2]; можно переписать в виде e=(p[1])[2]; (именно так компилятор расставит порядок приоритетов).

Но, первое применение операции индексации к указателю p вернёт значение, лежащее в ячейке p[1] (т.е. значение ячейки arr[0][1]). Т.е., в отличие от массива, p[1] указывает не на начало первой строки, а на совершенно постороннюю область памяти, адресуемую значением, хранящимся в arr[0][1].

При втором применении операции индексации уже к указателю p[1], она вернёт значение второй ячейки из этой посторонней области памяти, лежащей в области памяти, не имеющей никакого отношения к массиву. Поэтому это значение будет абсолютно бессмысленным. Также программа может выдать сообщение об ошибочной попытке доступа к запрещённой области памяти (т.е. о попытке доступа к памяти, которая не была выделена менеджером памяти).

Поэтому, если вам необходимо организовать хранение данных на основе указателей с многомерной индексацией (а это необходимо при динамическом распределении памяти, описанном ниже), то это делается с помощью выделения нескольких областей памяти. В первой, адресуемой непосредственно указателем p хранятся указатели на N других областей, в которых хранятся данные, или ссылки на другие области данных более низкого уровня и т.д. Например, массив char arr[2][4]; можно сохранить вручную в памяти с помощью указателей следующим образом (где char** p; – указатель для доступа к данным):

Addr1

Addr2

↑p

p[0][0]

p[0][1]

p[0][2]

p[0][3]

↑p[0]=Addr1

p[1][0]

p[1][1]

p[1][2]

p[1][3]

↑p[1] =Addr2

В этом случае операция p[i][j] будет корректно адресовать нужную ячейку памяти.

Такой способ хранения данных очень трудоёмок. Поэтому в случае, когда требуется хранение данных на основе указателей (динамическое) с многомерной индексацией, гораздо проще хранить их в виде одной непрерывной области памяти, пересчитывая многомерные индексы в одномерные по приведённой в предыдущем параграфе формуле.

В любом случае, везде, где возможно применение массивов вместо указателей, лучше применять массивы.

  1. Динамическое выделение памяти. Работа с выделенным блоком памяти (пример). Отличия использования динамически выделенной памяти от обычных массивов. Плюсы и минусы динамического выделения памяти.