Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_СП_2004_1_00.doc
Скачиваний:
69
Добавлен:
04.11.2018
Размер:
882.69 Кб
Скачать

Лекция 4

Адреса и указатели. Операции получения адреса и косвенной адресации. Отождествление массивов и указателей. Адресная арифметика. Указатели на массивы. Массивы указателей и многомерные массивы. Динамическое выделение памяти под массивы. Инициализация указателей.

1. Адреса и указатели

Во время выполнения всякой программы, используемые ею данные размещаются в оперативной памяти ЭВМ, причем каждому элементу данных ставится в соответствие его индивидуальный адрес. При реализации многих алгоритмов и представлении сложных логических структур данных часто оказывается полезной возможность непосредственной работы с адресами памяти. Подобная ситуация возникает, например, при обработке массивов переменных. Действительно, поскольку соседние элементы массива располагаются в смежных ячейках памяти, то для перехода от одного его элемента к другому можно вместо изменения значения индексного выражения манипулировать адресами этих элементов. Предположим для определенности, что нулевой элемент целочисленного массива расположен в ячейке памяти с номером Ao. Тогда, зная, что длина элемента данных типа int составляет два байта, нетрудно вычислить номер ячейки, в которой будет находиться i-ый элемент этого массива:

Ai = Ao + 2*i

На первый взгляд работа с адресами может показаться утомительной и бесполезной. На самом же деле она является даже более естественной, нежели работа с индексами, поскольку в процессе компиляции программы всякое индексное выражение трансформируется в операции над адресами. Объекты языка Си, значениями которых являются адреса оперативной памяти, получили название указателей. В общем случае указатели являются переменными величинами и над ними можно выполнять определенный набор операций подобно тому, как мы оперировали обычными числовыми переменными. В языке Си всякий указатель имеет базовый тип, который совпадает с типом элемента данных, на который может ссылаться этот указатель. Такое соглашение, возможно несколько ограничительное, существенно упрощает и делает значительно более эффективной работу с указателями. Переменные-указатели, как и переменные любых других типов, перед их использованием в программе должны быть предварительно объявлены в одной из инструкций описания данных. В случае указателей на простые переменные это делается следующим образом:

<sc-specifier> type-specifier *identifier <, ... >;

Здесь type-specifier задает тип переменной, на которую ссылается указатель с именем identifier, а символ звездочка (*) определяет саму переменную как указатель. Описатель класса памяти sc-specifier будет подробно рассмотрен в Лекции 7. Приведем несколько примеров правильного описания указателей в программе:

int *ptr;

long *sum;

float *result, *value;

Каждая из этих инструкций говорит о том, что соответствующая переменная есть указатель на элемент данных определенного типа, а комбинация, например, вида *ptr представляет собой величину типа int. По существу это означает, что подобные комбинации могут использоваться как операнды произвольных выражений. В частности, сохраняя обозначения предыдущего примера, мы могли бы написать

*sum = 0;

for (*ptr = 1; *ptr <= 100; (*ptr)++)

*sum = *sum + (*ptr)*(*ptr);

что соответствует фрагменту программы вычисления суммы квадратов

первых ста натуральных чисел. Круглые скобки в корректирующем выражении оператора цикла являются существенными. Замечание. Несмотря на то, что всякий указатель в языке Си имеет привязку к конкретному типу данных, существует возможность в отдельных случаях создавать указатели неопределенного типа. Это делается при помощи ключевого слова void, используемого вместо имени типа в инструкции описания этого указателя. Так, например, инструкция

void *poiter;

определяет переменную poiter как указатель без конкретной ссылки на его базовый тип. Однако подобные описания не являются типичными и могут быть использованы лишь при объявлении формальных параметров функций и определении типа возвращаемого той или иной функцией значения (см. Лекцию 5, $ 1). Строго говоря, компилятор языка Си рассматривает комбинации вида

*identifier

в составе выражений как некоторую операцию над указателями. Эта операция, символом которой как раз и является звездочка перед именем указателя, носит название операции косвенной адресации и служит для доступа к значению, расположенному по заданному адресу. Существует и другая операция, в определенном смысле противоположная операции косвенной адресации и именуемая операцией получения адреса. Она обозначается символом амперсенда (&) перед именем простой переменной или элемента массива:

&identifier или &identifier[expression]

и сопоставляет своему аргументу адрес его размещения в памяти, т. е. указатель. Естественно, что этим аргументом может быть и указатель, поскольку указатели, как и другие переменные, хранятся в ячейках оперативной памяти. Всевозможные выражения, построенные с использованием указателей или операторов * и &, принято называть адресными выражениями, а сами арифметические операции над указателями - адресной арифметикой. Одноместные операции * и & имеют такой же высокий приоритет, как и другие унарные операции, и в составе выражений обрабатываются справа налево. Именно по этой причине мы обратили внимание на необходимость круглых скобок в выражении (*ptr)++ предыдущего примера, ибо без них оператор ++ относился бы к указателю ptr, а не к значению, на которое ссылается этот указатель. Замечание. Если, например, mas есть массив переменных, то выражениe &mas[0] равносильно простому употреблению имени массива без следующего за ним индексного выражения, поскольку последнее отождествляется с адресом размещения в памяти самого первого элемента этого массива. Вот несколько примеров использования указателей и адресных выражений.

1. Аргументами функции форматированного ввода scanf являются адреса переменных, которым должны быть присвоены прочитанные значения:

scanf("%d %d", &m, &n);

2. Следующая пара операторов

px = &x;

y = *px;

где переменная px об'явлена предварительно как указатель, равносильна непосредственному присваиванию

y = x;

3. При выполнении следующего фрагмента программы сравнение в операторе if всегда будет истинно, поскольку значение указателя numptr совпадает с адресом переменной number:

int number;

int *numptr = &number;

scanf("%d %d", &number, numptr);

if (number == *numptr)

printf("Сравнение истинно");

else

printf("Сравнение ложно");