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

C_Kurs_Lekt / C_I_семестр / 06_Массивы

.pdf
Скачиваний:
11
Добавлен:
13.02.2016
Размер:
170.24 Кб
Скачать

МАССИВЫ

1

МАССИВЫ

Массив - это один из структурированных типов языка. Массивом называется конечная именованная последовательность однотипных величин. При описании массива, в квадратных скобках задается количество элементов массива (размерность): Определение одномерного массива типа type выглядит следующим образом:

type имя_массива[êонстантêое_выражение];

float а [10]; // описание массива из 10 вещественных чисел

При описание внешнего массива, который определен в другой части программы, где ему выделена память и (возможно) присвоены начальные значения его элементам. допустимо описание массива без указания количества его элементов: extern double rtt[];

Элементы массива нумеруются с нуля. При описании массива используются те же модификаторы (класс памяти, const и инициализатор), что и для простых переменных.

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

Явная инициализация элементов массива разрешена только при его определении и возможна двумя способами: либо с указанием размера массива в квадратных скобках, либо без явного указания (без конкретного выражения) в квадратных скобках. Количество элементов массива в этом случае компилятор определяет по числу начальных зна- чений в списке инициализации, помещенном в фигурных скобках при определении массива.

char СН[] = {'А', 'В', 'С'}; int IN[6] = { 10, 20, 30, 40};

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

В тех случаях, когда массив не определяется, а описывается, список начальных значений задавать нельзя Имя массива является указателем-константой, значением которой служит адрес первого элемента массива (с

индексом 0). Таким образом, для любого массива соблюдается равенство:

имя_массива = &имя_массмва = имя_массива[0]

К имени массива (указателю) применимы все правила адресной арифметики. Более того запись имя_массива[индеêс] является выражением с двумя операндами. Первый из них, т.е. имя_массива, - это константный указатель - адрес начала массива в основной памяти. Индекс - это выражение целого типа, определяющее смещение от начала массива. Используя операцию обращения по адресу * (раскрытие ссылки, разыменование), действие бинарной операции [ ] можно объяснить так: * (имя_массива + индеêс).

Пример массив int z[3] из трех элементов включает индексированные элементы z [0], z [1], z [2]. Это соглашение языка становится очевидным, если учесть, что индекс определяет не номер элемента, а его смещение относительно начала массива. Таким образом, *z - обращение к первому элементу z[0], * (z + 1) - обращение ко второму элементу z[l] и т.д.

Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. Обращение к элементу массива в языке Си относят к постфиксному выражению вида ÐÅ [IE]. Постфиксное выражение ÐÅ должно быть указателем на нужный тип, выражение IE в квадратных скобках должно быть целочисленного типа. Таким образом, если ÐÅ - указатель на массив, то ÐÅ [IE] - индексированный элемент этого массива. * (ÐÅ + IE) - другой путь доступа к тому же элементу массива. Поскольку сложение коммутативно, то возможна такая эквивалентная запись* (IE + ÐÅ) и, следовательно, IE [ÐÅ] именует тот же элемент массива, что и РЕ [IE]

Размерность массивов предпочтительнее задавать с помощью именованных констант.

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

Многомерные массивы

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

type имя_массива[К1][К2]...[KN];

Здесь type - допустимый тип (основной или производный), имя_массива - идентификатор, N - размерность массива, К1 - количество в массиве элементов размерности N-1 каждый и т.д. Например:

int ARRAY[4][3][6];

Трехмерный массив ARRAY состоит из четырех элементов, каждый из которых - двухмерный массив с размерами 3 на 6. В памяти массив ARRAY, как и все многомерные массивы, размещается в порядке возрастания самого правого(последнего) индекса , т.е. самый младший адрес имеет элемент ARRAY [0] [0] [0], затем идет ARRAY [0] [0] [1] è ò.ä.

I Схема размещения в памяти трехмерного массива

Ïðè инициализации многомерного массива он представляется либо как массив из массивов, при этом каждый массив заключается в свои фигурные скобки, либо задается общий список элементов в том порядке, в котором элементы располагаются в памяти:

МАССИВЫ

2

int ARRAY[4][3][6] = {0, 1, 2, 3, 4, 5, 6, 7}; -Â данном определении начальные значения получи-

ли только "первые" 8 элементов трехмерного массива.т.е.: ARRAY[0][0][0]

=

0, АRRAY[0][0][1]

= 1,

ARRAY[0][0][2]

= 2, ARRAY[0][0][3]

= 3, ARRAY[0][0][4] =

4,

ARRAY[0][0][5] =

5,

ARRAY[0][1][0]

= 6, ARRAY[0][1][1] =

7

 

 

 

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

íèõ.)

 

 

Следующее определение с инициализацией трехмерного массива

int А[4][5][6] = { {

{0} } ,

{

(100),

{110, 111) },

{

{200},

{210}, {220, 221, 222} } ;

задает только некоторые значения

его элементов: А[0][0][0]=0, А[1][0][0]=100,

А[1][1][0]=110,

А[1][1][1]=111,

А[2][0][0]=200,

А[2][1][0]=210,

А[2][2][0]=220,

А[2][2][1]=221,

А[2][2][2]=222.

Остальные элементы массива явно не инициализируются.

 

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

float matrix [][5] ={ {1}, (2}, {3} };

формирует массив matrix с размерами 3 на 5, но не определяет явно начальных значений всех его элементов. Как и в случае одномерных массивов, доступ к элементам многомерных массивов возможен с помощью индек-

сированных переменных и с помощью указателей. Возможно объединение обоих способов в одном выражении. Чтобы не допускать ошибок при обращении к элементам многомерных массивов с помощью указателей, нужно помнить, что при добавлении целой величины к указателю его внутреннее значение изменяется на "длину" элемента соответствующего типа. Имя массива всегда константа-указатель. Для массива, определенного как type AR [N][M][L], AR - указатель, поставленный в соответствие элементам типа type [M][L] . Добавление 1 к указателю AR приводит к изменению значения адреса на величину sizeof(type) * М * L.

Именно поэтому выражение *(AR + 1) есть адрес элемента AR[1], т.е. указатель на массив меньшей размерности, отстоящий от начала массива, т.е. от &AR[0], на размер одного элемента type[M][L]. Пример: задан массив int b[3][2][4], адрес массива b[][][] = b, адрес массива b[0][][] = *b, адрес массива b[0][0][] = **b, элемент b[0][0][0] = ***b,

адрес массива b[1][][] = *(b+1), адрес массива b[2][][] = *(b+2), адрес массива b[0][1][] = *(*b+1)

В общем случае для трехмерного массива индексированный элемент b[i] [j] [k] соответствует выражению

*(*(*(b + i) + j) + k

Допустимо в одном выражении комбинировать обе формы доступа к элементам многомерного массива: Пример: доступ к элементу b[1][1][1] можно осуществить разыменованием *(*(*(b+1)+1)+1) или *(b[1][1]+1) или *(b[1]+1)[1]

Как бы ни был указан путь доступа к элементу многомерного массива, внутренняя адресная арифметика, используемая компилятором, всегда предусматривает действия с конкретными числовыми значениями адресов. Компилятор всегда реализует доступ к элементам массива с помощью указателей и операции разыменования. Если в программе использована, например, такая индексированная переменная: AR[i] [j] [k], принадлежащая массиву type AR[N] [M] [L], где N, M, L - целые положительные константы, то последовательность действий компилятора такова:

выбирается адрес начала массива, т.е. целочисленное значение указателя AR, равное (unsigned long) AR;

добавляется смещение i * (M * L) * sizeof(type) для вычисления начального адреса i-ro массива с размерами M на L, входящего в исходный трехмерный массив;

добавляется смещение для вычисления начального адреса j-й строки (одномерный массив), включающей L элементов. Теперь смещение равно (i * (M * L) + j * L) * sizeof (type);

добавляется смещение для получения адреса k-ro элемента в строке, т.е. получается адрес (unsigned long)(i*(M * L) + j * L + k) * sizeof(type);

применяется разыменование, т.е. обеспечивается доступ к содержимому элемента по его адресу: * ((unsigned long) (i * (M * L) + j * L + k)).

Строки

Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ — это символ с кодом, равным 0, что записывается в виде управляющей последовательности '\0'. По положению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литералом:

char str[10] = "Vasia";

// выделено 10 элементов с номерами от 0 до 9

// первые элементы- 'V'. 'a', 's', 'i', 'a', '\0'-

В этом примере под строку выделяется 10 байт, 5 из которых занято под символы строки, а шестой — под нуль-символ. Если строка при определении инициализируется, ее размерность можно опускать (компилятор сам выделит соответствующее количество байт):

char str[] - "Vasia": // выделено и заполнено 6 байт

Оператор

char *str = "Vasia"

создает не строковую переменную, а указатель на строковую константу, изменить которую невозможно (к примеру, оператор str[l]='o' не допускается). Знак равенства перед строковым литералом означает инициализацию, а не присваивание. Операция присваивания одной строки другой не определена (поскольку строка является массивом) и может выполняться с помощью цикла или функций стандартной библиотеки. Библиотека предоставляет возможности копирования, сравнения, объединения строк, поиска подстроки, определения длины строки и т. д.

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

Следующее определение - int *array[6]; - вводит массив указателей на объекты типа int. Имя массива array, он состоит из шести элементов, тип каждого int *.

МАССИВЫ

3

Определение int (*ptr)[6]; вводит указатель ptr на массив из шести элементов, каждый из которых имеет тип int. Таким образом, выражение (array + 1) соответствует перемещению в памяти на sizeof (int *) байтов от нача- ла массива (т.е. на длину указателя типа int *). Если прибавить 1 к ptr, то адрес изменится на величину sizeof (int [6]), т.е. на 12 байт при двухбайтовом представлении данных типа int.

По определению массива, его элементы должны быть однотипными и одного "размера". Предположим, что мы хотим определить массив для представления списка фамилий (учеников класса, сотрудников фирмы и т.п.). Если определять его как двухмерный массив элементов типа char, то в определении для элементов массива необходимо задать предельные размеры каждого из двух индексов.

Таким образом, "прямолинейное" определение массива для хранения списка фамилий может быть таким: char

spisok[25][20];

Для примера здесь предполагается, что количество фамилий в списке не более 25 и что длина каждой фамилии не превышает 19 символов (букв). После такого определения или с помощью инициализа ции в самом определении в элементы spisok[0], spisok[l], ... можно занести конкретные фамилии, представленные в виде строк. Размеры так определенного массива всегда фиксированы.

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

char spisok[][20] = { "Иванов",

"Петров", "Сидоров" };

Теперь в массиве spisok только 3 элемента, каждый из них длиной 20 элементов типа char .

Двухмерный массив char spisok[3] [20] и одномерный массив указателей char *pointer [ 3 ], инициализированные одинаковыми строками

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

В противоположность этому при определении и инициализации теми же символьными строками одномерного массива указателей типа char * память распределяется гораздо рациональнее:

char *pointer[] = { "Иванов",

"Петров", "Сидоров" };

Для указателей массива pointer, в котором при таком определении 3 элемента и каждый является указателемпеременной типа char *, выделяется всего 3*sizeof (char *) байтов. Кроме того, компилятор размещает в памяти три строковые константы "Иванов" (7 байт), "Петров" (7 байт), "Сидоров" (8 байт), а их адреса становятся значениями элементов pointer[0], pointer[l], pointer[2].

Динамические массивы создают с помощью функции malloc библиотеки С, например:

int n = 100;

float *p = (float *) malloc(n * sizeof(float));

В этой строке создается переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес ее начала записывается в указатель р. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.

Преимущество динамических массивов состоит в том, что размерность может быть переменной, то есть объем памяти, выделяемой под массив, определяется на этапе выполнения программы. Доступ к элементам динамического массива осуществляется точно так же, как к статическим, например, к 5-му элементу приведенного выше массива можно обратиться как р[5] или *(р+5).

Операция преобразования типа, записанная перед обращением к функции та malloc, требуется потому, что функция возвращает значение указателя типа void*, а инициализируется указатель на float.

Память, зарезервированная под динамический массив с помощью функции malloc, должна освобождаться функции free, например: free (p);

ПРИМЕЧАНИЕ: Для правильной интерпретации объявлений полезно запомнить мнемоническое правило: «суффикс прежде префикса». Если при описании переменной используются одновременно префикс * (указатель) и суффикс [] (массив), то переменная интерпретируется как массив указателей, а не указатель на массив: int *p[10]: — массив из 10 указателей на int.

Соседние файлы в папке C_I_семестр