Учебная работа. Реферат: Массивы и указатели

1 Звезда2 Звезды3 Звезды4 Звезды5 Звезд (5 оценок, среднее: 4,80 из 5)
Загрузка...
Контрольные рефераты

Учебная работа. Реферат: Массивы и указатели

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

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

Вы уже понимаете, что массив представляет собой группу частей 1-го типа. Когда нам требуется для работы массив, мы сообщаем о этом компилятору с помощью операторов описания. Для сотворения массива компилятору нужно знать тип данных и требуемый класс памяти, т. е. то же самое, что и для обычный переменной (именуемой «скалярной»). Не считая того, обязано быть понятно, сколько частей имеет массив. Массивы могут иметь те же типы данных и классы памяти, что и обыкновенные переменные, и к ним применим этот же принцип умолчания. Разглядим примеры, разных описаний массивов:

/* несколько описаний массивов */

int temp [365]; /* наружный массив из 365 целых чисел */

main ()

{

float rain [365]; /* автоматический массив из 365 чисел типа

float */

static char code [12]; /* статический массив из 12 знаков */

extern temp[]; /* наружный массив; размер указан выше */

}

Как уже упоминалось, квадратные скобки ([]) молвят о том, что temp и все другие идентификаторы являются именами массивов, а число, заключенное в скобки, показывает количество частей массива. Отдельный элемент массива определяется с помощью его номера, именуемого также индексом. Нумерация частей начинается с нуля, потому temp[0] является первым, а temp[364] крайним 365-элементом массива temp.

Но все это для вас уже обязано быть понятно, потому изучим чего-нибудть новое.

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

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

int fix = 1;

float flax = PI*2;

при всем этом предполагается, что PI — ранее введенное макроопределение. Можем ли мы созодать что-либо схожее с массивом? Ответ не однозначен: и да, и нет.

Наружные и статические массивы можно инициализировать. Автоматические и регистровые массивы инициализировать недозволено.

До этого чем попробовать инициализировать массив, давайте поглядим, что там находится, если мы в него ничего не записали.

/* проверка содержимого массива */

main ()

{

int fuzzy[2]; /* автоматический массив */

static int wuzzy[2]; /* статический массив */

printf(» %d %dn» , fuzzy[1], wuzzy[1];

}

программка напечатает

525 0

Приобретенный итог иллюстрирует последующее правило:

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

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

/* деньки месяца */

int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

main ()

{

int index;

extern int days[]; /* необязательное описание */

for (index = 0; index < 12; index++)

printf(» Месяц %d имеет %d дней.n», index + 1, days [index]);

}

Итог:

Месяц 1 имеет 31 дней

Месяц 2 имеет 28 дней

Месяц 3 имеет 31 дней

Месяц 4 имеет 30 дней

Месяц 5 имеет 31 дней

Месяц 6 имеет 30 дней

Месяц 7 имеет 31 дней

Месяц 8 имеет 31 дней

Месяц 9 имеет 30 дней

Месяц 10 имеет 31 дней

Месяц 11 имеет 30 дней

Месяц 12 имеет 31 дней

программка не совершенно корректна, так как она выдает неверный итог для второго месяца всякого 4-ого года.

Определив массив days[] вне тела функции, мы тем сделали его наружным. Мы инициировали его перечнем, заключенным в скобки, используя при всем этом запятые для разделения частей перечня.

количество частей в перечне обязано соответствовать размеру массива. А что будет, если мы ошиблись в подсчете? Попытайтесь переписать крайний пример, используя перечень, который короче, чем необходимо (на два элемента):

/* деньки месяца */

int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31};

main ()

{

int index;

extern int days[]; /* необязательное описание */

for (index = 0; index < 12; index++ )

printf(» Месяц %d имеет %d днейn», index + 1, days [index]);

}

В этом случае итог оказывается другим:

Месяц 1 имеет 31 дней

Месяц 2 имеет 28 дней

Месяц 3 имеет 31 дней

Месяц 4 имеет 30 дней

Месяц 5 имеет 31 дней

Месяц 6 имеет 30 дней

Месяц 7 имеет 31 дней

Месяц 8 имеет 31 дней

Месяц 9 имеет 30 дней

Месяц 10 имеет 31 дней

Месяц 11 имеет 0 дней

Месяц 12 имеет 0 дней

Можно созидать, что у компилятора не появилось никаких заморочек: просто, когда он исчерпал весь перечень с начальными данными, то стал присваивать всем остальным элементам массива нулевые значения.

Но в случае лишне огромного перечня компилятор будет также не настолько «благороден» к для вас, так как посчитает выявленную избыточность ошибкой. Потому нет никакой необходимости заблаговременно подвергать себя «издевкам» компилятора. нужно просто выделить массив, размер которого будет достаточен для размещения перечня:

/* деньки месяца */

int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31};

main ()

{

int index;

extern int days[]; /* необязательное описание */

for (index = 0; index < sizeof days/(sizeof (int)); index++ )

printf(» Месяц %d имеет %d днейn» , index + 1, days [index]);

}

К данной нам программке следует создать два существенных замечания.

1-ое: если вы используете пустые скобки для инициализации массива, то компилятор сам обусловит количество частей в перечне и выделит для него массив подходящего размера.

2-ое: оно касается прибавления, изготовленного в управляющем операторе for. He полагаясь (полностью обоснованно) на свои вычислительные возможности, мы возложили задачку подсчета размера массива на компилятор. Оператор sizeof описывает размер в б объекта либо типа, последующего за ним. В нашей вычислительной системе размер всякого элемента типа int равен двум б, потому для получения количества частей массива мы делим общее число байтов, занимаемое массивом, на 2. Но в остальных системах элемент типа int может иметь другой размер. Потому в общем случае производится деление на

Ниже приведены результаты работы нашей программки:

Месяц 1 имеет 31 дней

Месяц 2 имеет 28 дней

Месяц 3 имеет 31 дней

Месяц 4 имеет 30 дней

Месяц 5 имеет 31 дней

Месяц 6 имеет 30 дней

Месяц 7 имеет 31 дней

Месяц 8 имеет 31 дней

Месяц 9 имеет 30 дней

Месяц 10 имеет 31 дней

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

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

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

/* присваивание значений массиву */

main ()

{

int counter, evens [50];

for (counter = 0; counter < 50; counter++)

evens [counter] = 2 * counter;

}

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

к примеру, имя массива описывает также его 1-ый элемент,

т.е. если flizny[] — массив, то

flizny == &flizny[0]

и обe части равенства определяют адресок первого элемента массива. (Вспомним, что операция & выдает адресок.) Оба обозначения являются константами типа указатель, так как они не меняются в протяжении всей программки. Но их можно присваивать (как значения) переменной типа указатель и изменять значение переменной, как показано в нижеследующем примере. Поглядите, что происходит со значением указателя, если к нему прибавить число.

/* прибавление к указателю */

main ()

{

int dates[4], *pti, index;

float bills [4], *ptf;

pti = dates; /* присваивает адресок указателю массива */

ptf = bills;

for (index = 0; index < 4; index++ )

printf(“ указатели + %d: %10 u %10un», index, pti + index, ptf + index);

}

Вот итог

указатели + 0: 56014 56026

указатели + 1: 56016 56030

указатели + 2: 56018 56034

указатели + 3: 56020 56038

1-ая написанная строчка содержит исходные адреса 2-ух массивов, а последующая строчка — итог добавления единицы к адресу и т. д. Почему так выходит?

56014 + 1 = 56016?

56026 + 1 = 56030?

Не понимаете, что сказать? В нашей системе единицей адресации является б, но тип int употребляет два б, а тип float — четыре. Что произойдет, если вы скажете: «прибавить единицу к указателю?» Компилятор языка Си добавит единицу памяти. Для массивов это значит, что мы перейдем к адресу последующего элемента, а не последующего б. Вот почему мы должны специально клеветать тип объекта, на который ссылается указатель; 1-го адреса тут недостаточно, потому что машинка обязана знать, сколько байтов будет нужно для запоминания объекта. (Это справедливо также для указателей на скалярные переменные; другими словами, с помощью операции *pt недозволено получить

Благодаря тому, что компилятор языка Си умеет это созодать, мы имеем последующие равенства:

dates + 2 == &dates[2] /* один и этот же адресок */

*(dates + 2) == dates[2] /* одно и то же

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

Меж иным, постарайтесь различать выражения * (dates + 2), и *dates + 2. Операция

имеет наиболее высочайший Ценность, чем +, потому крайнее выражение значит

(* dates) + 2:

*(dates + 2) /*

*dates + 2 /* 2 добавляется к значению 1-го элемента массива */

Связь меж массивами и указателями нередко дозволяет нам использовать оба подхода при разработке программ. Одним из примеров этого является функция с массивом в качестве аргумента.

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

О этом уже говорилось в гл. 10. на данный момент, когда мы познакомились с указателями, можно заняться наиболее глубочайшим исследованием массивов-аргументов. Давайте проанализируем скелет программки, обращая внимание на описания:

/* массив-аргумент */

main ()

{

int ages [50]; /* массив из 50 частей */

convert(ages);

}

convert (years)

int years []; /* каковой размер массива? */

{

}

Разумеется, что массив ages состоит из 50 частей. А что можно сказать о массиве years? Оказывается, в программке нет такового массива. Описатель

int years[];

делает не массив, а указатель на него. Поглядим, почему это так.

Вот вызов нашей функции:

convert(ages);

ages — аргумент функции convert. Вы помните, что имя ages является указателем на 1-ый элемент массива, состоящего из 50 частей. Таковым образом, оператор вызова функции передает ей указатель, т. е. адресок функции convert (). Это означает, что аргумент функции является указателем, и мы можем написать функцию convert () последующим образом:

convert (уears)

int *years;

{

}

Вправду, операторы

int years [];

int *years;

синонимы. Оба они объявляют переменную years указателем массива целых чисел. Но основное их отличие заключается в том, что 1-ый из их припоминает нам, что указатель years ссылается на массив.

Как сейчас связать его с массивом ages? Вспомним, что при использовании указателя в качестве аргумента, функция ведет взаимодействие с соответственной переменной в вызывающей программке, т.е. операторы, использующие указатель years в функции convert (), практически работают с массивом ages, находящимся в теле функции main ().

Поглядим, как работает этот механизм. Во-1-х, вызов функции инициализирует указатель years, ссылаясь на ages[0]. сейчас представим, что кое-где снутри функции convert () есть выражение years [3]. Как вы лицезрели в прошлом разделе, оно аналогично «(years + 3). Но если years показывает на ages[0], то years + 3 ссылается на ages[3]. Это приводит к тому, что *(years + 3) значит ages[3]. Если пристально проследить данную цепочку, то мы увидим, что years [3] аналогично «(years + 3), которое в свою очередь совпадает с ages[3]. Что и требовалось обосновать, т. е. операции над указателем years приводят к этим же результатам, что и операции над массивом ages. Короче говоря, когда имя массива применяется в качестве аргумента, функции передается указатель. Потом функция употребляет этот указатель для выполнения конфигураций в начальном массиве, принадлежащем программке, вызвавшей функцию. Разглядим пример.

Попробуем написать функцию, использующую массивы, а потом перепишем ее, применяя указатели.

Разглядим ординарную функцию, которая находит (либо пробует отыскать) среднее имя массива и количество частей. На выходе получаем среднее

/* X54.C */

#include <stdio.h>

void main(void)

{

int n = 9,i;

int array[] = {1,3,5,9,0,-9,-5,-3,-1};

int mean(int array[],int n);

i = mean(array,n);

printf(«Среднее из данных значений %dn»,i);

}

int mean(int array[],int n)

{

int index;

long sum;

if(n > 0)

{

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

sum += array[index];

return((int) (sum/n));

}

else

{

printf(«Нет массиваn»);

return(0);

}

}

Эту программку просто переработать, применяя указатели. Объявим ра указателем на тип int. Потом заменим элемент массива array[index] на соответственное

/* Внедрение указателей для нахождения среднего значения

массива n целых чисел */

int mean(pa, n)

int *pa, 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 pa[];

и

int *pa;

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

Понятно ли для вас, как работать с указателями? Указатель устанавливается на 1-ый элемент массива, и работы с массивом, где индекс действует как стрелка часов, показывающая по очереди на любой элемент массива.

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

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

/* операции с указателями */

#define PR(X) printf(» X = %u, *X = %d, &X = %un», X, *X, &X)

/* печатает

/* этому адресу, и адресок самого указателя */

main()

static int urn[] = {100, 200, 300};

int *ptr1, *ptr2;

{

ptr1 = urn; /* присваивает адресок указателю */

ptr2 = &urn[2]; /* то же самое */

PR(ptr1); /* см. макроопределение, обозначенное выше */

ptr1++; /* повышение указателя */

PR(ptr1);

PR(ptr2);

++ptr2; /* выходит за конец массива */

PR(ptr2);

printf(“ptr2 – ptr1 = %un», ptr2 – ptr1);

}

В итоге работы программки получены последующие результаты:

ptr1 = 18, *ptr1 = 100, &ptr1 = 55990

ptr1 = 20, *ptr1 = 200, &ptr1 = 55990

ptr2 = 22, *ptr2 = 300, &ptr2 = 55992

ptr2 = 24, *ptr2 = 29808, &ptr2 -= 55992

ptr2 – ptr1 = 2

программка показывает 5 главных операций, которые можно делать над переменными типа указатель.

1. ПРИСВАИВАНИЕ. Указателю можно присвоить адресок. Обычно мы исполняем это действие, используя имя массива либо операцию получения адреса (&). программка присваивает переменной ptr1 адресок начала массива urn; этот адресок принадлежит ячейке памяти с номером 18. (В нашей системе статические переменные запоминаются в ячейках оперативки.) Переменная ptr2 получает адресок третьего и крайнего элемента массива, т. е. urn [2].

2. ОПРЕДЕЛЕНИЕ значения. Операция * выдает значение, хранящееся в обозначенной ячейке. Потому результатом операции *ptr1 в самом начале работы программки является число 100, находящееся в ячейке с номером 18.

3. ПОЛУЧЕНИЕ АДРЕСА УКАЗАТЕЛЯ. Подобно хоть каким переменным переменная типа указатель имеет адресок и

4. УВЕЛИЧЕНИЕ УКАЗАТЕЛЯ. Мы можем делать это действие при помощи обыкновенной операции сложения или при помощи операции роста. Увеличивая указатель, мы перемещаем его на последующий элемент массива. Потому операция ptr1++ наращивает числовое адресок самой ячейки ptr1 остается постоянным, т. е. 55990. Опосля выполнения операции сама переменная не переместилась, поэтому что она лишь изменила

Аналогичным образом можно и уменьшить указатель.

Но при всем этом следует соблюдать осторожность. машинка не смотрит, ссылается ли еще указатель на массив либо уже нет. Операция ++ptr2 перемещает указатель ptr2 на последующие два б, и сейчас он ссылается на некую информацию, расположенную в памяти за массивом.

Не считая того, оператор роста можно применять для переменной типа указатель, но не для констант этого типа подобно тому, как вы не сможете использовать оператор роста для обыденных констант. Для переменных и констант типа указатель можно применять обычное сложение:

Верно Некорректно

ptr1 ++ ; urn++ ;

х++ ; 3++;

ptr2 = ptr1 + 2; ptr2 = urn++;

ptr2 = urn + 1; x = у + 3++;

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

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

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

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

static float rain[5][12];

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

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

Используем этот двумерный массив в метеорологической программке. Цель нашей программки — отыскать общее количество осадков для всякого года, среднегодовое количество осадков и среднее количество осадков за любой месяц. Для получения полного количества осадков за год следует сложить все данные, находящиеся в подходящей строке. Чтоб отыскать среднее количество осадков за данный месяц, мы поначалу складываем все данные в обозначенном столбце. Двумерный массив дозволяет просто представить и выполнить эти деяния.

/* отыскать общее количество осадков для всякого года, средне*/

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

#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.1fn», 1970 + year, subtot);

total + = subtot; /* общее для всех лет */

}

printf(» n среднегодовое количество осадков составляет

%.1f дюймов.nn» , 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.1f», subtot/YRS);

}

printf(» n»);

}

ГОД количество ОСАДКОВ (дюймы)

1970 50.6

1971 41.9

1972 28.6

1973 32.3

1974 37.8

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

ЕЖЕМЕСЯЧНОЕ количество:

Янв. Фев. Мар. Апр. Май Июн. Июл. Авг. Сен. Окт. Нояб. Дек.

7.8 7.2 4.1 3.0 2.1 0.8 1.2 0.3 0.5 1.7 3.6 6.1

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

Чтоб отыскать общее количество осадков за год, мы не изменяем year, а заставляем переменную month пройти все свои значения. Так производится внутренний цикл for, находящийся в первой части программки. Потом мы повторяем процесс для последующего значения year. Это наружный цикл первой части программки. Структура вложенного цикла, схожая описанной, подступает для работы с двумерным массивом. Один цикл управляет одним индексом, а 2-ой цикл — иным.

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

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

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

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

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

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

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

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

int zippo[4][2]; /* массив типа int из 4 строк и 2 столбцов */

int *pri; /* указатель на целый тип */

Тогда на что

pri = zippo;

показывает? На 1-ый столбец первой строчки:

zippo == &zippo[0][0]

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

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

Поначалу запоминается 1-ая строчка, за ней 2-ая, потом 3-я и т. д. Таковым образом, в нашем примере:

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][1].

Мы обрисовали двумерный массив как массив массивов. Если zippo является именованием нашего двумерного массива, то каковы имена 4 строк, любая из которых является массивом из 2-ух частей? имя первой строчки zippo [0], имя четвертой строчки zippo [3]; вы сможете заполнить пропущенные имена. Но имя массива является также указателем на этот массив в том смысле, что оно ссылается на 1-ый его элемент. означает,

zippo[0] == &zippo[0][0]

zippo[1] == &zippo[1][0]

zippo[2] == &zippo[2][0]

zippo[3] == &zippo[3][0]

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

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

main ()

{

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] — одномерный массив из 4 частей */

}

/* находит среднее в одномерном массиве */

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(«Heт массиваn»);

return(0);

}

}

Итог работы программки:

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

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

Среднее строчки 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[][];?

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

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

stuff(junk)

int junk[][4];

Они докладывают компилятору, что массив следует разбить на строчки по четыре столбца.

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

char *list[];

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

Что вы должны были выяснить

· Как объявить одномерный массив: 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];

· 5 операций, которые можно использовать для переменных типа указатель: см. текст.

· Способ указателей для функций, работающих с массивами.

Вопросцы и ответы

Вопросцы

1. Что напечатается в итоге работы данной нам программки?

#define PC(X, Y) printf(«%c %cn», X, Y)

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

main ()

{

char *ptr;

int index;

for (index = 0; ptr = ref; index < 4; index++, ptr++ )

PC(ref[index], *ptr);

}

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

3. Обусловьте

a.

int *ptr;

static int boop[4] = {12, 21, 121, 212};

ptr = boop;

b. float *ptr;

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

ptr = awk[0];

c. int *ptr;

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

ptr = jirb;

d. int = *ptr;

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

ptr = torf[0];

e. int *ptr;

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

ptr = fort[0];

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

a. Выразите адресок grid [22] [56] по другому.

b. Выразите адресок grid[22] [0] 2-мя методами.

c. Выразите адресок grid[0][0] 3-мя методами.

Ответы

1.

D D

О О

L L

Т Т

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

3.

a. 12 и 121

b. 1.0 и 3.0

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

d. 12 и 16

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

4.

a. &grid[22][56]

b. &grid[22][0] и grid[22]

c. &grid[][] и grld[0] и grid

Упражнение

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

]]>