Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Язык Си - Уэйт, Прата, Мартин

.pdf
Скачиваний:
583
Добавлен:
01.06.2015
Размер:
4.92 Mб
Скачать

int mean(pa, n) int oра, n;

{

int index;

long sum; /*Если целых слишком много,

их можно суммировать в формате long int */

if(n > 0)

 

{

 

for(index=0, sum=0; index < n; index++)

 

sum + = *(pa + index);

 

return((int)(sum/n)); /* Возвращает

целое */ }

else {

 

printf("Нет массива.\n");

 

return(0); }

 

}

 

Это оказалось несложным, но возникает вопрос: должны ли мы изменить при этом вызов функции, в частности numbs, который был именем массива в операторе mean(numbs, size)? Ничего не нужно менять, поскольку имя массива является указателем. Как мы уже говорили в предыдущем разделе, операторы описания:

int ра[ ];

и

int *ра;

идентичны по действию: оба объявляют ра указателем. В программе можно применять любой из них, хотя до сих пор мы использовали второй в виде *(ра + index).

Понятно ли вам, как работать с указателями? Указатель устанавливается на первый элемент массива, и значение, находящееся там, добавляется в sum. Затем указатель передвигается на следующий элемент (к указателю прибавляется единица), и значение, находящееся в нем, также прибавляется к sum и т. д. Это похоже на механизм работы с массивом, где индекс действует как стрелка часов, показывающая по очереди на каждый элемент массива.

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

ОПЕРАЦИИ С УКАЗАТЕЛЯМИ

Далее Содержание

Что же мы теперь умеем делать с указателями? Язык Си предлагает пять основных операций, которые можно применять к указателям, а нижеследующая программа демонстрирует эти возможности. Чтобы показать результаты каждой операции, мы будем печатать значение указателя (являющегося адресом, на который ссылается указатель), значение, находящееся по этому адресу, и адрес самого указателя.

/* операции с указателями */ #define PR(X)

printf("X = %u,*X = %d, &X = %u\n",X, *X,&X); /* печатает значение указателя (адрес),

значение, находящееся по */ /* этому адресу, и адрес самого указателя */

241

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 = %u\n", 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,

242

являющееся адресом начала массива 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. РАЗНОСТЬ. Можно находить разность двух указателей. Обычно это делается для указателей, ссылающихся на элементы одного и того же массива, чтобы определить, на каком расстоянии друг от друга находятся элементы. Помните, что результат имеет тот же тип, что и переменная, содержащая размер массива.

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

243

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

МНОГОМЕРНЫЕ МАССИВЫ

Далее Содержание

Темпест Клауд, метеоролог, занимающаяся явлением перисто-сти облаков, хочет проанализировать данные о ежемесячном количестве осадков на протяжении пяти лет. В самом начале она должна решить, как представлять данные. Можно использовать 60 переменных, по одной на каждый месяц. (Мы уже упоминали о таком подходе ранее, но в данном случае он также неудачен.) Лучше было бы взять массив, состоящий из 60 элементов, но это устроило бы нас только до тех пор, пока можно хранить раздельно данные за каждый год. Мы могли бы также использовать 5 массивов по 12 элементов каждый, но это очень примитивно и может создать действительно большие неудобства, если Темнеет решит изучать данные о количестве осадков за 50 лет вместо пяти. Нужно придумать что-нибудь получше.

Хорошо было бы использовать массив массивов. Основной массив состоял бы тогда из 5 элементов, каждый из которых в свою очередь был бы массивом из 12 элементов. Вот как это записывается:

static float rain[5][12];

Можно также представить массив rain в виде двумерного массива, состоящего из 5 строк и 12 столбцов.

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

РИС. 12.3. Двумерный массив.

Используем этот двумерный массив в метеорологической программе. Цель нашей программы -

244

найти общее количество осадков для каждого года, среднегодовое количество осадков и среднее количество осадков за каждый месяц. Для получения общего количества осадков за год следует сложить все данные, находящиеся в нужной строке. Чтобы найти среднее количество осадков за данный месяц, мы сначала складываем все данные в указанном столбце. Двумерный массив позволяет легко представить и выполнить эти действия. Рис. 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("ГОД КОЛИЧЕСТВО ОСАДКОВ (дюймы)\n\n"); for(year = 0, total = 0; year < YRS; year++)

{/* для каждого года, суммируем количество осадков для каждого месяца */ for(month = 0, subtot = 0; month < TWLV; month++)

subtot + = rain [year][month]; printf("%5d %15.lf\n", 1970 + year, subtot); total + = subtot; /* общее для всех лет */

}

printf(" \n среднегодовое количество осадков составляет %.lf дюймов. \n \n ", total/YRS );

printf(" Янв. Фев. Map. Апр.Май Июн.Июл. Авг.Сент."); printf(" Окт. Нояб. Дек.\n" );

for(month = 0; month < TWLV; month++)

{/* для каждого месяца, суммируем количество осадков за все годы */

for(year = 0, subtot = 0; year < YRS; year++) subtot += rain[year][month];

printf(" %4.lf ", subtot/YRS); } printf(" \n");

}

РИС. 12.4. Метеорологическая программа.

ГОД

 

КОЛИЧЕСТВО ОСАДКОВ (дюймы)

 

 

 

 

 

1970

 

50.6

 

 

 

 

 

 

 

 

 

1971

 

41.9

 

 

 

 

 

 

 

 

 

1972

 

28.6

 

 

 

 

 

 

 

 

 

1973

 

32.3

 

 

 

 

 

 

 

 

 

1974

 

37.8

 

 

 

 

 

 

 

 

 

Среднегодовое количество осадков составляет 38.2 дюйма.

 

 

ЕЖЕМЕСЯЧНОЕ КОЛИЧЕСТВО:

 

 

 

 

 

 

 

Янв. Фев. Mар. Апр. Mай. Июн. Июл. Авг. Ceнт. OКТ. Нояб. Дек.

 

7.8

7.2

4.1

3.0

2.1

0.8

1.2

0.3

0.5

1.7

3.6

6.l

В этой программе следует отметить два основных момента: инициализацию и вычисления. Инициализация сложнее, поэтому мы сначала рассмотрим вычисления.

Чтобы найти общее количество осадков за год, мы не изменяем year, а заставляем переменную month пройти все свои значения. Так выполняется внутренний цикл for, находящийся в первой части программы. Затем мы повторяем процесс для следующего значения year. Это внешний цикл

245

первой части программы. Структура вложенного цикла, подобная описанной, подходит для работы с двумерным массивом. Один цикл управляет одним индексом, а второй цикл - другим.

Вторая часть программы имеет такую же структуру, но теперь мы изменяем year во внутреннем

цикле, a month во внешнем. Помните, что при однократном прохождении внешнего цикла внутренний цикл выполняется полностью. Таким образом, программа проходит в цикле через все годы, прежде чем изменится месяц, и дает нам общее количество осадков за пять лет для первого месяца, затем общее количество за пять лет для второго месяца и т. д.

Инициализация двумерного массива

Далее Содержание

Для инициализации массива мы взяли пять заключенных в скобки последовательностей чисел, а все эти данные еще раз заключили в скобки. Данные, находящиеся в первых внутренних скобках, присваиваются первой строке массива, данные во второй внутренней последовательности - второй строке и т. д. Правила, которые мы обсуждали раньше, о несоответствии между размером массива и данных применяются здесь для каждой строки. Если первая последовательность в скобках включает десять чисел, то только первым десяти элементам первой строки будут присвоены значения. Последние два элемента в этой строке будут, как обычно, инициализированы нулем по умолчанию. Если чисел больше, чем нужно, то это считается ошибкой; перехода к следующей строке не произойдет.

Мы могли бы опустить все внутренние скобки и оставить только две самые внешние. До тех пор пока мы будем давать правильное количество входных данных, результат будет тем же самым. Однако, если данных меньше, чем нужно, массив заполняется последовательно (не обращается внимание на разделение по строкам), пока не кончатся все данные. Затем оставшимся элементам будут присвоены нулевые значения. См. рис. 12.5.

РИС. 12.5. Два метода инициализации массива.

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

int solido[10][20][30];

Вы можете представить его в виде десяти двумерных массивов (каждый 20х30), поставленных

246

друг на друга, или в виде массива из массивов. То есть это массив из 10 элементов, и каждый его элемент также является массивом. Все эти массивы в свою очередь имеют по 20 элементов, каждый из которых состоит из 30 элементов. Преимущество этого второго подхода состоит в том, что можно довольно просто перейти к массивам большей размерности, если окажется, что вы не можете представить наглядно четырехмерный объект! Мы же останемся верны двум измерениям.

УКАЗАТЕЛИ И МНОГОМЕРНЫЕ МАССИВЫ

Далее Содержание

Как создать указатели для многомерных массивов? Чтобы найти ответ на этот вопрос, рассмотрим несколько примеров.

Предположим, что у нас есть описания

int zippo[4][2]; /*

массив типа int

 

из 4

строк и

2 столбцов */

int *pri;

/* указатель на

целый тип */

Тогда на что pri = zippo; указывает? На первый столбец первой строки:

zippo == &zippo[0][0]

А на что указывает pri + 1? На zippo[0][l], т.е. на 1-ю строку 2-го столбца? Или на zippo[l][0], элемент, находящийся во второй строке первого столбца? Чтобы ответить на поставленный вопрос, нужно знать, как располагается в памяти двумерный массив. Он размещается, подобно одномерным массивам, занимая последовательные ячейки памяти. Порядок элементов определяется тем, что самый правый индекс массива изменяется первым, т. е. элементы массива располагаются следующим образом:

zippo[0][0] zippo[0][1] zippo[1][0] zippo[1][1] zippo[2][0]

...

Сначала запоминается первая строка, за ней вторая, затем третья и т. д. Таким образом в нашем примере:

pri == &zippo[0][0] /* 1-я строка, 1 столбец */ pri + 1 == &zippo[0][1] /* 1-я строка, 2 столбец */ pri + 2 == &zippo[1][0] /* 2-я строка, 1 столбец */ pri + 3 == &zippo[1][1] /* 2-я строка, 2 столбец */

Получилось? Хорошо, а на что указывает pri + 5? Правильно, на zippo[2][l].

Мы описали двумерный массив как массив массивов. Если zippo является именем нашего двумерного массива, то каковы имена четырех строк, каждая из которых является массивом из двух

элементов? Имя первой строки zippo[0], имя четвертой строки zippo[3]; вы можете заполнить пропущенные имена. Однако имя массива является также указателем на этот массив в том смысле, что оно ссылается на первый его элемент. Значит,

zippo[0] == &zippo[0][0] zippo[1] == &zjppo[1][0] zippo[2] == &zippo[2][0] zippo[3] == &zippo[3][0]

Это свойство является более, чем новшеством. Оно позволяет использовать функцию, предназначенную для одномерного массива, для работы с двумерным массивом! Вот доказательство (хотя мы надеемся, что теперь вы бы поверили нам и так) использования двумерного массива в нашей программе нахождения среднего значения:

/* одномерная функция, двумерный массив */ main( )

247

{

static int junk[3][4] = { {2, 4, 6, 8}, {100, 200, 300, 400}, {10, 40, 60, 90} };

int row;

for(row = 0; row < 3; row ++)

printf(" Среднее строки %d равно %d.\n", row, mean(junk[row],4)); /* junk [row] - одномерный массив ИЗ четырех элементов */

}

/* находит среднее в одномерном массиве */ int mean(array,n)

int array[ ], n;

{

int index; long sum; if(n > 0) {

for(index = 0, sum = 0; index < n; index++) sum += (long)array[index];

return((int)(sum/n)); } else {

printf(" Нет массива. \n"); return(0); }

}

Результат работы программы:

Cреднее строки 0 равно 5.

Cреднее строки 1 равно 250.

Cреднее строки 2 равно 50.

Функции и многомерные массивы

Далее Содержание

Предположим, что вы хотите иметь функцию, работающую с двумерным массивом, причем со всем целиком, а не с частями. Как вы запишите определения функции и ее описания? Подойдем к

этому более конкретно и скажем, что нам нужна функция, управляющая массивом junk[ ][ ] в нашем последнем примере. Пусть функция main( ) выглядит так:

/* junk в main */ main( )

{

static int junk[3][4] = { {2, 4, 5, 8}, {100, 200, 300, 400} {10, 40, 60, 90} }; stuff(junk);

}

Функция stuff( ) использует в качестве аргумента junk, являющийся указателем на весь массив. Как написать заголовок функции, не зная, что делает stuff( )?

Попробуем написать:

stuff(junk) int junk[ ];

или

stuff(junk) int junk[ ][ ];

Нет и нет. Первые два оператора еще будут работать некоторым образом, но они рассматривают junk как одномерный массив, состоящий из 12 элементов. Информация о расчленении массива на

248

строки отсутствует.

Вторая попытка ошибочна, потому что хотя оператор и указывает что junk является двумерным массивом, но нигде не говорится, из чего он состоит. Из шести строк и двух столбцов? Из двух строк и шести столбцов? Или из чего-нибудь еще? Компилятору недостаточно этой информации. Ее дают следующие операторы:

stuff(junk) int junk[ ][4];

Они сообщают компилятору, что массив следует разбить на строки по четыре столбца. Массивы символьных строк являются особым случаем, так как у них нулевой символ в каждой строке сообщает компилятору о конце строки. Это разрешает описания, подобные следующему:

char *list[ ];

Символьные строки представляют одно из наиболее частых применений массивов и указателей; мы вернемся к этой теме в гл. 13.

ЧТО ВЫ ДОЛЖНЫ БЫЛИ УЗНАТЬ В ЭТОЙ ГЛАВЕ

Далее Содержание

Как объявить одномерный массив: long id_no[200]; Как объявить двумерный массив: short chess[8][8];

Какие массивы можно инициализировать: внешние и статические.

Как инициализировать массив: static int hats[3]=[10,20,15];

Другой способ инициализации: static int caps[ ]=[3,56,2]; Как получить адрес переменной: использовать операцию &.

Как получить значение, ссылаясь на указатель: использовать операцию *. Смысл имени массива: hats == &hats[0].

Соответствие массива и указателя: если ptr = hats; то ptr + 2 == &hat[2]; и *(ptr+2) == hat[2];

Пять операций, которые можно применять для переменных типа указатель: см. текст. Метод указателей для функций, работающих с массивами.

ВОПРОСЫ И ОТВЕТЫ

Далее Содержание

Вопросы

1. Что напечатается в результате работы этой программы?

#define PC(X, Y) printf(" %с %c \n", X, Y)

char ref[ ] = { D, О, L, Т}; main( )

{

char *ptr; int index;

for(index =0; ptr = ref; index < 4; index++, ptr++) PC(ref[indcx], *ptr);

}

2.Почему в вопросе 1 массив ref описан до оператора main( )?

3.Определите значение *ptr и *(ptr + 2) в каждом случае:

249

а. int *ptr;

static int boop[4] = {12, 21, 121, 212}; ptr = bоор;

б. float *ptr;

static float awk[2][2] = { {1.0, 2.0}, {3.0, 4.0}}; ptr = awk[0];

в. int *ptr;

static int jirb[4] = {10023, 7}; ptr = jirb;

г. int = *ptr;

static int torf[2][2] = {12, 14, 16}; ptr = torf[0];

д. int *ptr;

static int fort[2][2] = { { 12}, {14, 16} }; ptr = fort[0];

4. Предположим, у нас есть описание static int grid[30][100];

а. Выразите адрес grid [22][56] иначе.

б. Выразите адрес grid[22][0] двумя способами. в. Выразите адрес grid[0][0] тремя способами.

Ответы

1.

D D

O O

L L

ТТ

2.По умолчанию такое положение ref относит его к классу памяти типа extern, a массивы этого класса памяти можно инициализировать.

3.

а. 12 и 121 б. 1.0 и 3.0

в. 10023 и 0 (автоматическая инициализация нулем)

г. 12 и 16

д. 12 и 14 (именно 12 появляется в первой строке из-за скобок).

4.

a. &grid[22][56]

б. &grid[22][01 и grid[22] в. &grid[ ][ ] и grid[0] и grid

УПРАЖНЕНИЕ

1. Модифицируйте нашу метеорологическую программу таким образом, чтобы она выполняла вычисления, используя указатели вместо индексов. (Вы по-прежнему должны объявить и инициализировать массив.)

[Содержание] [Вверх]

Язык Си

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

250