Файл: Практикум по курсу информатика.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 10.01.2024

Просмотров: 74

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

31
6. ИНДЕКСИРОВАННЫЕ ПЕРЕМЕННЫЕ
Набор переменных одного типа, которые представляют собой упорядо- ченный список, удобно представлять переменной с индексом. Индексирован- ные переменные также часто называют массивами.
Массив – это пронумерованный набор переменных (элементов), фактиче- ски хранящийся в одной переменной. Доступ к отдельному элементу массива выполняется по его порядковому номеру, называемому индексом. А общее число элементов массива называется его размером [2].
Массивы удобны для хранения большого количества значений некото- рого параметра. Например, температура воздуха в нашем городе по часам мо- жет храниться в массиве температур.
6.1. Классификация массивов
Основным параметром массива является его размер.
int A[32]; //объявлен массив из 32 целых чисел, выделена память 128 байт.
В зависимости от того, может ли размер массива изменяться во время ис- полнения программы или он задается на момент компиляции программы, мас- сивы разделяют на динамические и статические. У статического массива раз- мер это всегда число, оно не может быть переменной на момент компиляции.
В текстах встречаются конструкции, где размер массива указывается че- рез подстановку макроопределением:
#define ARR_SIZE 32
int A[
ARR_SIZE
]; // здесь тоже 32 элемента и это статический массив.
В предыдущей строке компилятору дается инструкция «во всем тексте ниже сделать контекстную замену: вместо символов ARR_SIZE подставить число 10» и только после этого компилировать полученный текст. Использо- вание макроопределений удобно, если в нескольких местах программы мы ра- ботаем с массивами одного размера. Допустим, было 32 символа в двоичном коде. Когда мы изменим программу и будем работать с 64 символами, то из- меним только строчку макроса
#define ARR_SIZE 64, а все остальное в текстах изменит компилятор при первом проходе, когда просто выполнит контекст- ную замену
ARR_SIZE на число 64.
У динамических массивов размер неизвестен на момент компиляции про- граммы, а создавать простую индексированную переменную, у которой размер тоже переменная, в Си нельзя.
Подробнее с динамическими массивами вы познакомитесь в разделе 6.5.
По размерности массивы делят на одномерные и многомерные.

32
Примером одномерного массива может служить строка (одномерный
массив не обязательно строка, но строка – всегда одномерный массив), а примером многомерного (в данном случае двумерного) массива – таблица тек- ста или матрица чисел.
В языке С строка представляет собой массив печатных символов, кото- рый может включать в себя заглавные и прописные буквы, цифры, знаки пре- пинания и разделители, а также служебные символы. Обязательной частью
Си-строки является символ окончания строки (нуль-символ, «\0», символ все биты которого равны 0 – 0х00, код символа есть нуль). Не путайте этот символ с печатным нулем, код цифры 0 равен 48 (в десятичном представлении) или
30 (в шестнадцатеричном представлении). Нуль-символ является управляю- щим, и как все управляющие символы не виден пользователю, однако, про-
граммист должен всегда помнить о его существовании.
Важно! В С/С++ индексация массивов начинается с нуля. Пример мас- сива из 9 элементов представлен в Таблица 6.1.
Таблица 6.1
Номер элемента массива
1 2
3 4
5 6
7 8
9
Индекс элемента в С
0 1
2 3
4 5
6 7
8
Говоря о двумерном массиве, можно также представлять его как одномер- ный массив, где в качестве каждого элемента выступает другой одномерный массив.
Например, у нас может быть набор (массив) элементов, каждым из кото- рых является символьная строка. Тогда при работе с отдельными элементами каждой строки весь массив строк будет считаться двумерным массивом.
Так же, как и обычные переменные массивы могут хранить данные типов
int, float, double, char.
В зависимости от этого различные массивы будут объявляться следую- щим образом:
int intArray[5], double doubleArray[136]; //одномерные массивы, которые
хранят целочисленные элементы (int) и числа с плавающей точкой большого
размера (double)
float floatArray[5][4], char charArray[11]; //двумерные массивы, хранящие
числа с плавающей точкой (float) и строка из 10 символов (char).


33
6.2. Кодирование символов
Как вы знаете, компьютер не «понимает» символы как таковые и может оперировать только наборами единиц и нулей. Следовательно, каждому чело- веко-читабельному символу должна соответствовать некая двоичная комбина- ция. Для этого и была разработана кодировка ASCII. В этой кодировке каждый символ кодируется восемью битами, в связи с чем ее также называют одно- байтной кодировкой. Следовательно, максимальное количество символов, ко- торые можно закодировать, составляет 256 (2 8
=256).
Ниже представлена таблица ASCII, определяющая 128 символов (Рис.
15):
Рис. 15. Базовая таблица ASCII-символов
Исходя из представленной таблицы, видно, что в данную кодировку во- шли управляющие символы, буквы латинского алфавита, знаки препинания и др. специальные символы. Всего под эти основные символы было выделено 7 бит (128 символов). Остальными битами можно было кодировать символы, например, национальных языков. Однако такого количества доступных для кодирования символов, как правило, недостаточно, в связи с чем позднее по- явились расширенные кодировки.
Unicode же представляет собой таблицу символов, в которую занесены знаки практически всех письменных языков в мире. Всего Юникод включает в себя
1 114 112 позиций, большая часть из которых до сих пор не заполнена

34
(таким образом, диапазон значений, которые могут принимать символы Юни- кода, простирается от 0 до 0x10FFFF).
Существуют несколько форм представления Юникода:
1. ВUTF-8 для кодирования символа используется от 1 до 4 байт. По- скольку для кодировки наиболее часто используемых символов достаточно 1 байт (в частности, для вышеописанных ASCII-символов), то UTF-8 удобно ис- пользовать для английского текста, но не для азиатского;
2. UTF-16 использует кодирование 2 или 4 байтами. Эту кодировку счи- тают удобной для кодирования символов азиатских языков с иероглифиче- ским письмом;
3. ВUTF-32 все символы представляются 4 байтами. Данная кодировка, потребляет много памяти и, следовательно, используется не очень часто. Од- нако, она вполне успешно работает при наличии достаточной вычислительной мощности.
Заметим, что кодировки UTF вполне совместимы с кодировкой ASCII, по- скольку общие символы и в тех, и в других кодируются одним байтом. Таким образом, все латинские и управляющие символы находятся в UTF на тех же местах, что и в ASCII. Следовательно, представление всех символов из диапа- зона 0–127 будет единым для всех кодировок.
В настройках проекта нужно указывать тип кодировки, multi-byte
или Unicode. В рамках данного курса мы будем использовать multi-byte.
6.3. Инициализация и представление массивов в коде
Как и для всех переменных при создании массива требуется его объявле- ние. И так же, как в случае с обычными переменными, объявление массива без его инициализации будет означать, что в выделенных под массив ячейках па- мяти может храниться всякий «мусор».
Можно инициализировать сразу все элементы массива при объявлении:
int intArray[5] = { 3, 9, 6, 4, 10 };
По Таблица 6.2 можно увидеть, как отличается восприятие массива сим- волов человеком и процессором.
Таблица 6.2
Как пользователь видит номер элемента
1 2
3 4
5
Как видит массив пользователь
3 9
6 4
10
Как «видит» номер элемента
0 1
2 3
4


35 процессор
Как «видит» массив процессор 0x0…03 0x0…09 0x0…06 0x0…04 0x0…05
Пример инициализации двумерного массива приведен ниже:
int intMatrix[2][3]={{1, 2, 3} , {4, 5, 6}};
Для предотвращения возможных ошибок рекомендуется или инициали- зировать элементы массива в явном виде, или предварительно инициализиро- вать каждый элемент как цифру 0:
int intArray1[5] = { 0, 0, 0, 0, 0 };
int intArray2[100] = {0};
Необходимо различать случаи, когда массив типа char представляет со- бой массив целых чисел размером в 1 байт, а когда символьную строку.
Если массив является символьной строкой, при инициализации его лучше указывать как пустую строку, см. Таблица 6.3:
char charArray[5]="";
Таблица 6.3
Как пользователь видит номер элемента
1 2
3 4
5
Как «видит» номер эле- мента процессор
0 1
2 3
4
Как «видит» массив процессор (hex, ASCII)
0
(«\0»)
Символ не определен
Символ не определен
Символ не определен
Символ не определен
Если же нужно задать символы по отдельности, обязательно указать в конце массива нуль-символ, см. Таблица 6.4:
char charArray1[5] = {‘a’, ‘B’, ‘3’, ‘&’, 0};
char charArray2[5]= “aB3&”;
Таблица 6.4
Как пользователь представ- ляет номер элемента
1 2
3 4
5
Как видит массив пользователь
a
B
3
&
0
Как «видит» номер эле- мента процессор
0 1
2 3
4
Как «видит» массив процес- сор (hex, ASCII)
0x61
0x42
0x33
0x26
0x0
Как «видит» массив процес- сор (hex, UTF8)
0x0061
0x0042
0x0033
0x0026
0x0000
1   2   3   4   5   6

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

36 конце появились непонятные «кракозябры» или если программа почему-то за- висла, проверьте, не забыли ли вы добавить в строку этот волшебный нулевой символ [3].
Если вы не хотите добавлять к строке нуль-символ, при работе вам нужно будет каждый раз передавать размер массива.
Для задания массива символов можно использовать указатель на сим- вольный тип.
Итак, мы подошли к еще одному новому типу – указателю.
Указатель — это переменная, которая содержит адрес другой перемен- ной (то есть ее расположение в памяти). Адреса в памяти традиционно запи- сываются в шестнадцатеричном коде.
Вернемся к примеру. Предположим, имеется массив вида
char charArray[5] = {‘a’, ‘B’, ‘3’, ‘&’, 0};
Указатели всегда имеют размер, соответствующий разрядности системы.
Для 32 разрядной системы размер адреса 32 бита, или 4 байта. Указатель дол- жен определить какого размера данные будут извлечены системой по этому адресу. Для типа char – это 1 байт, для int – 4 байта. Значит указатель должен быть связан с определенным типом, так что он пишется как тип со звездочкой, например указатель на элемент char или на начало массива из элементов char
это char *:
char * pointer;
В этом случае объявленной переменной pointer может быть присвоен ад- рес массива:
pointer = charArray;
*pointer; //эквивалентно charArray [0]='a' – разыменование указателя, из-
влечение числа, находящегося по адресу, на который указывает указатель.
*( pointer +1); //эквивалентно charArray [1]='B' – значение второго эле-
мента массива.
Здесь charArray – константа-указатель, то есть неизменяемый указатель.
Нельзя изменить charArray, поскольку это будет означать изменение адреса массива в памяти. А pointer – переменная типа указатель на символ, может меняться. Так pointer++ будет указывать на следующий (второй) элемент мас- сива.
*(pointer++); //эквивалентно charArray [1]='B' – значение второго эле-
мента массива.
Получить указатель на переменную можно написав «&» перед именем пе- ременной, например:

37
pointer = &charArray[1]; // указатель на второй элемент массива, то
есть на символ 'B'.
Массивы символьных строк, по сути, являются аналогом двумерного массива.Например:
char *symStrArr [3] = {“Znakomstvo”, “s Visual”, “Studio”};
Здесь symStrArr – массив из трех указателей на символьные строки. Каж- дая строка символов представляет собой символьный массив, поэтому имеется три указателя на массивы. То есть:
*symStrArr[0]; //соответствует 'Z',
*symStrArr[l]; //соответствует 's'.
Инициализация выполняется по правилам, определенным для массивов.
Тексты в кавычках эквивалентны инициализации каждой строки в мас- сиве. Запятая разделяет соседние последовательности [4].
Существует также понятие свободного массива:
char *Array[4]; //определяет свободный массив, где длина каждой строки
определяется тем указателем, который эту строку инициализирует.
Свободный массив позволяет экономнее использовать память [4].
Обратиться к конкретному элементу массива при работе с символьной строкой можно следующим образом:
char Elem1 = charArray[0]; //обращение к первому элементу
char Elem2 = charArray[3]; //обращение к четвертому элементу.
6.4. Выход за границы массива
В памяти машины разделяют блок исполняемого кода и блок данных. За программой присматривает система безопасности процессора и операционной системы. Программа выполняется на уровне привилегий пользователя, и у нее нет прямого доступа к системным ресурсам.
В памяти данных расположение индексированных переменных всегда обеспечивается в непрерывном интервале адресов ячеек памяти. Например, с адреса 0x40110000 до 0x401010013 будут расположены 19 элементов массива char.
Программа не может обращаться по адресам 0–31 (в десятичном пред- ставлении) или 0-0х1F (в шестнадцатеричном представлении) – это коды слу- жебных символов – в этом случае вызывается ошибка доступа к памяти и оста- новка программы.
К сожалению, в C++ не проверяется выход индекса за пределы массива.
Этот язык будет рад предоставить вам доступ к элементу integerArray [200].


38
Более того, C++ позволит вам обратиться даже к integerArray [-15]. Угадать, куда в этом случае попадешь, довольно трудно, но хранить там что-то еще рис- кованнее.
Пытаясь прочитать 20-й элемент (array[20]) 10-элементного массива, вы получите то или другое случайное число (а, возможно, программа просто ава- рийно завершит работу). Записывая в array[20] какое-то значение, вы полу- чите непредсказуемый результат. Скорее всего, ничего в него не запишется, но возможны и другие странные реакции вплоть до аварийного завершения программы.
Одной из самых распространенных ошибок является неправильное обра- щение к последнему элементу. Например, при использовании массива типа
char это будет обращение по адресу integerArray [128]. Хотя это всего лишь следующий за концом массива элемент, записывать или считывать его не ме- нее опасно, чем любой другой некорректной адрес [3].
Следите за тем, по каким адресам ячеек вы обращаетесь и куда запи-
сываете свои данные.
6.5. Динамические массивы
Очень часто мы не знаем заранее, сколько элементов будет содержаться в массиве. Например, когда пользователь вводит числа, или мы работаем с ме- няющимся набором объектов. В этом случае массивы можно ограничить до- статочно большим числом элементов, но при этом в системе будет резервиро- ваться лишняя неиспользуемая память, или наоборот может произойти пере- полнение массива, а, следовательно, и потери информации.
Динамический массив целых чисел размером a в языке Сможно создать следующим образом:
int *pN = (int *)malloc(a*sizeof(int));
Размер памяти будет представлен в байтах, индекс массива будет лежать в пределах (0, a). Память такого массива необходимо освобождать функцией
free(pN), где – pN указатель на память, выделенную функцией malloc.
Функция realloc() позволяет менять размер массива. Данные при этом бу- дут скопированы (если хватит памяти на новый блок).
В языке С++ динамический массив целых чисел размером a может быть создан с помощью оператора new:
int *pN = new int[a];
Размер памяти такого массива будет представлен в элементах, индекс массива также будет лежать в пределах (0, a). Память также требует

39 освобождения, что нужно сделать с помощью функции delete [] pN. Здесь квад- ратные скобки указывают на удаление всех элементов массива.
Выделенная в блоке память (malloc, calloc, new) не будет освобождаться
автоматически при выходе из блока. Если в процессе работы не вызван (free или delete) то возникают утечки памяти.
Для компилятора нет разницы между указателем и индексированной пе- ременной, в то время как для программиста важно корректно использовать ин- дексы и указатели в динамических массивах.
Помните – выход за границы всегда вызывает нарушение памяти и
сбой программы!
Всего у 32-битного приложения 1,5 Гб памяти данных.
6.6. Работа с массивами
Как и с любым другим объектом языка Си, работа с массивами подразу- мевает использование существующих библиотечных функций, а также созда- ние своих собственных функций, а, следовательно, передачу массивов в эти функции.
Часто для ввода строки используется функция scanf(). Однако, нужно помнить, что функция scanf() в большей степени предназначена для получения слова (то есть без использования пробелов, табуляции, перевода строки). Если применять формат «%s» для ввода, строка вводится до (но не включая) следу- ющего пустого символа [4]:
scanf("%9s", name);
char * gets(char *); //для ввода строки, включая пробелы,
или ее эквивалент
char * gets_s(char *);
Нужно отметить, что аргументом функции является указатель на строку, в которую осуществляется ввод. Пользователь вводит строку, которую функ- ция будет помещать в массив, пока пользователь не нажмет Enter.
Для вывода строк можно воспользоваться функцией printf() [4]:
printf("%s", str); //где str — указатель на строку
или в сокращенном формате
printf(str);
также можно использовать функцию puts():
int puts (char *s); //печатает строку s и переводит курсор на новую
строку (в отличие от printf()) – функция вывода строк