Язык Си - руководство для начинающих - M. УЭИТ
Шрифт:
Интервал:
Закладка:
Что же мы теперь умеем делать с указателями? Язык Си предлагает пять основных операций, которые можно применять к указателям, а нижеследующая программа демонстрирует эти возможности. Чтобы показать результаты каждой операции, мы будем печатать значение указателя (являющегося адресом, на который ссылается указатель), значение, находящееся по этому адресу, и адрес самого указателя.
/* операции с указателями */
#define PR(X)
printf("X = %u,*X = %d, &X = %un",X, *X,&X);
/* печатает значение указателя (адрес),
значение, находящееся по */
/* этому адресу, и адрес самого указателя */
main( )
static int urn[ ] = [100, 200, 300];
int *ptrl, *ptr2;
{
ptrl = urn; /* присваивает адрес указателю */
ptr2 = &urn [2]; /* то же самое */
PR(ptrl); /* см. макроопределение, указанное выше */
ptrl++; /* увеличение указателя */
PR(ptrl);
PR(ptr2);
++рtr2; /* выходит за конец массива */
PR(ptr2);
printf("ptr2 - ptrl = %un", ptr2 - ptrl);
}
В результате работы программы получены следующие результаты:
ptrl = 18, *ptrl = 100, &ptrl = 55990
ptrl = 20, *ptrl = 200, &ptrl = 55990
ptr2 =22, *ptr2 = 300, &ptr2 = 55992
ptr2 =24, *ptr2 = 29808, &ptr2 = 55992
ptr2 - ptrl = 2
Программа демонстрирует пять основных операций, которые можно выполнять над переменными типа указатель.
1. ПРИСВАИВАНИЕ. Указателю можно присвоить адрес. Обычно мы выполняем это действие, используя имя массива или операцию получения адреса (&). Программа присваивает переменной ptrl адрес начала массива urn; этот адрес принадлежит ячейке памяти с номером 18. (В нашей системе статические переменные запоминаются в ячейках оперативной памяти.) Переменная ptr2 получает адрес третьего и последнего элемента массива, т. е. urn[2].
2. ОПРЕДЕЛЕНИЕ ЗНАЧЕНИЯ. Операция выдает значение, хранящееся в указанной ячейке. Поэтому результатом операции *ptrl в самом начале работы программы является число 100, находящееся в ячейке с номером 18.
3. ПОЛУЧЕНИЕ АДРЕСА УКАЗАТЕЛЯ. Подобно любым переменным переменная типа указатель имеет адрес и значение. Операция & сообщает нам, где находится сам указатель. В нашем примере указатель ptrl находится в ячейке с номером 55990. Эта ячейка содержит число 18, являющееся адресом начала массива urn.
4. УВЕЛИЧЕНИЕ УКАЗАТЕЛЯ. Мы можем выполнять это действие с помощью обычной операции сложения либо с помощью операции увеличения. Увеличивая указатель, мы перемещаем его на следующий элемент массива. Поэтому операция ptr1++ увеличивает числовое значение переменной ptrl на 2 (два байта на каждый элемент массива целых чисел), после чего указатель ptrl ссылается уже на urn[l] (рис. 12.2). Теперь ptrl имеет значение 20 (адрес следующего элемента массива), а операция *ptrl выдает число 200, являющееся значением элемента urn[1]. Заметим, что адрес самой ячейки ptrl остается неизменным, т.е. 55990. После выполнения операции сама переменная не переместилась, потому что она только изменила значение!
РИС. 12.2. Увеличение указателя типа int.
Аналогичным образом можно и уменьшить указатель. Однако при этом следует соблюдать осторожность. Машина не следит, ссылается ли еще указатель на массив или уже нет. Операция ++ptr2 перемещает указатель ptr2 на следующие два байта, и теперь он ссылается на некоторую информацию, расположенную в памяти за массивом.
Кроме того, оператор увеличения можно использовать для переменной типа указатель, но не для констант этого типа подобно тому, как вы не можете применять оператор увеличения для обычных констант. Для переменных и констант типа указатель можно использовать простое сложение:
Правильно Неправильно ptr1++; urn ++; х ++; 3++; ptr2 = ptr1 + 2; ptr2 = urn++; ptr2 = urn + 1; х = у + 3++;5. РАЗНОСТЬ. Можно находить разность двух указателей. Обычно это делается для указателей, ссылающихся на элементы одного и того же массива, чтобы определить, на каком расстоянии друг от друга находятся элементы. Помните, что результат имеет тот же тип, что и переменная, содержащая размер массива.
Перечисленные выше операции открывают большие возможности. Программисты на языке Си создают массивы указателей, указатели на функции, массивы указателей на указатели, массивы указателей на функции и т. д. Мы будем придерживаться основных применений, которые уже упоминались. Первое из них - передача информации в функцию и из нее. Мы использовали указатели, когда хотели, чтобы функция изменила переменные, находящиеся в вызывающей программе. Второе - использование указателей в функциях, работающих с многомерными массивами.
МНОГОМЕРНЫЕ МАССИВЫ
Темпест Клауд, метеоролог, занимающаяся явлением перисто-сти облаков, хочет проанализировать данные о ежемесячном количестве осадков на протяжении пяти лет. В самом начале она должна решить, как представлять данные. Можно использовать 60 переменных, по одной на каждый месяц. (Мы уже упоминали о таком подходе ранее, но в данном случае он также неудачен.) Лучше было бы взять массив, состоящий из 60 элементов, но это устроило бы нас только до тех пор, пока можно хранить раздельно данные за каждый год. Мы могли бы также использовать 5 массивов по 12 элементов каждый, но это очень примитивно и может создать действительно большие неудобства, если Темнеет решит изучать данные о количестве осадков за 50 лет вместо пяти. Нужно придумать что-нибудь получше.
Хорошо было бы использовать массив массивов. Основной массив состоял бы тогда из 5 элементов, каждый из которых в свою очередь был бы массивом из 12 элементов. Вот как это записывается:
static float rain[5][12];
Можно также представить массив rain в виде двумерного массива, состоящего из 5 строк и 12 столбцов.
При изменении второго индекса на единицу мы передвигаемся вдоль строки, а при изменении первого индекса на единицу, передвигаемся вертикально вдоль столбца. В нашем примере второй индекс дает нам месяцы, а первый - годы.
РИС. 12.3. Двумерный массив.
Используем этот двумерный массив в метеорологической программе. Цель нашей программы - найти общее количество осадков для каждого года, среднегодовое количество осадков и среднее количество осадков за каждый месяц. Для получения общего количества осадков за год следует сложить все данные, находящиеся в нужной строке. Чтобы найти среднее количество осадков за данный месяц, мы сначала складываем все данные в указанном столбце. Двумерный массив позволяет легко представить и выполнить эти действия. Рис. 12.4 содержит программу.
/* найти общее количество осадков для каждого года, среднего */
/* довое, среднемесячное количество осадков, за несколько лет */
#define TWLV 12 /* число месяцев в году */
#define YRS 5 /* число лет */
main( )
{
static float rain [YRS][TWLV] = {
{10.2, 8.1, 6.8, 4.2, 2.1, 1.8, 0.2, 0.3, 1.1, 2.3, 6.1, 7.4},
{9.2, 9.8, 4.4, 3.3, 2.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 5.2},
{6.6, 5.5, 3.8, 2.8, 1.6, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 4.2},
{4.3, 4.3, 4.3, 3.0, 2.0, 1.0, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6},
{8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.2}
};
/* инициализация данных по количеству осадков за 1970-1974 */
int year, month;
float subtot, total;
printf("ГОД КОЛИЧЕСТВО ОСАДКОВ (дюймы)nn");
for(year = 0, total = 0; year < YRS; year++)
{ /* для каждого года, суммируем количество осадков для каждого месяца */
for(month = 0, subtot = 0; month < TWLV; month++)
subtot + = rain [year][month];
printf("%5d %15.lfn", 1970 + year, subtot);
total + = subtot; /* общее для всех лет */
}
printf(" n среднегодовое количество осадков
составляет %.lf дюймов. n n ", total/YRS );
printf(" Янв. Фев. Map. Апр.Май Июн.Июл. Авг.Сент.");
printf(" Окт. Нояб. Дек.n" );
for(month = 0; month < TWLV; month++)
{ /* для каждого месяца, суммируем