Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
Строки как одномерные массивы данных типа char
В языке Си отдельного типа данных «строка символов» нет. Работа со строками реализована путем использования одномерных массивов типа char, т.е. строка символов – это одномерный массив символов, заканчивающийся нулевым байтом. Нулевой байт – это байт, каждый бит которого равен нулю, при этом для нулевого байта определена символьная константа ´ \0´ (признак окончания строки, или «нуль-символ»). Поэтому если строка должна содержать k символов, то в описании массива размер должен быть k+1. По положению нуль-символа определяется фактическая длина строки. Например, char s[7]; – означает, что строка может содержать не более шести символов, а последний байт отводится под нуль-символ. Отсутствие нуль-символа и выход указателя при просмотре строки за ее пределы – распространенная ошибка при работе со строками. Строку можно инициализировать строковой константой (строковым литералом), которая представляет собой набор символов, заключенных в двойные кавычки. Например: сhar S[ ] = “Работа со строками”; для данной строки выделено и заполнено 19 байт – 18 на символы и 19-й на нуль-символ. В конце строковой константы явно указывать символ ´ \0´ не нужно. Компилятор добавит его автоматически. Символ ´ \0´ нужно использовать явно тогда, когда символьный массив при декларации инициализируется списком начальных значений, например, следующим образом: char str[10] ={‘V’, ‘a’, ‘s’, ‘j’, ‘а’, ‘\0’}; или когда строка формируется посимвольно в коде программы. Пример такого формирования приведен в конце этого раздела. При работе со строками можно пользоваться указателями, например: char *x; x = " БГУИР"; x = (i> 0)? " положительное" : (i< 0)? " отрицательное" : " нулевое"; Такая декларация строки – единственный случай, когда в коде программы можно использовать операцию присваивания явно. Операция char *str = " БГУИР" создает не строковую переменную, а указатель на строковую константу, изменить которую невозможно, причем это касается не только адреса ОП, но и его размера. Знак равенства перед строковым литералом означает инициализацию, а не присваивание. Операция присваивания одной строки другой в языке Си не определена (поскольку строка является массивом) и может обрабатываться при помощи оператора цикла (с использованием стандартной библиотечной функций). Процесс копирования строки s1 в строку s2 имеет вид char s1[25], s2[25]; for (int i = 0; i < = strlen(s1); i++) s2[i] = s1[i]; Длина строки определяется с помощью стандартной функции strlen, которая вычисляет длину, выполняя поиск нуль-символа (прототип функции приведен ниже). Таким образом, строка фактически просматривается дважды. А вот следующие действия будут ошибочными: сhar s1[51]; s1 = ”Minsk”; Это связано с тем, что s1 – константный указатель и не может использоваться в левой части операции присваивания. Большинство действий со строковыми объектами в Си выполняются при помощи стандартных библиотечных функций, так, для правильного выполнения операции присваивания в последнем примере необходимо использовать стандартную функцию strcpy(s1, ”Minsk”); Напомним, что для ввода строк, как и для других объектов программы, обычно используются две стандартные функции: Функция scanf вводит значения для строковых переменных при помощи формата (спецификатора ввода) %s до появления первого символа “пробел” (символ «& » перед ID строковых данных указывать не надо); Функция gets осуществляет ввод строки, которая может содержать пробелы. Завершается ввод нажатием клавиши Enter. Обе функции автоматически ставят в конец строки нулевой байт. Вывод строк производится функциями printf или puts до нулевого байта. Функция printf не переводит курсор после вывода на начало новой строки, а puts автоматически переводит курсор после вывода строковой информации в начало новой строки. Например: char Str[30]; printf(“ Введите строку без пробелов: \n”); scanf(“%s”, Str); или puts(“ Введите строку ”); gets(Str); Остальные операции над строками, как уже отмечалось ранее, выполняются с использованием стандартных библиотечных функций, декларация прототипов которых находятся в файле string.h. Приведем наиболее часто используемые стандартные строковые функции. Функция strlen (S) возвращает длину строки (количество символов в строке), при этом завершающий нулевой байт не учитывается, например: char *S1 = ”Минск! \0”, S2[] = ”БГУИР–Ура! ”; printf(“ %d, %d.”, strlen(S1), strlen(S2)); Результат выполнения данного участка программы: 6, 10. Функция strcpy (S1, S2) – копирует содержимое строки S2 в строку S1. Функция strcat (S1, S2) – присоединяет строку S2 к строке S1 и помещает ее в массив, где находилась строка S1, при этом строка S2 не изменяется. Нулевой байт, который завершал строку S1, заменяется первым символом строки S2. Функция int strcmp (S1, S2) сравнивает строки S1 и S2 и возвращает значение < 0, если S1< S2; > 0, если S1> S2; =0, если строки равны, т.е. содержат одно и то же число одинаковых символов. Функции преобразования строковых объектов в числовые описаны в библиотеке stdlib.h. Рассмотрим некоторые из них. Преобразование строки S в число: – целое: int atoi (S); – длинное целое: long atol (S); – действительное: double atof (S); при возникновении ошибки данные функции возвращают значение 0. Функции преобразования числа V в строку S: – целое: itoa (V, S, kod); – длинное целое: ltoa (V, S, kod); 2 £ kod £ 36, для десятичных чисел со знаком kod = 10. Пример участка кода программы, в котором из строки s удаляется символ, значение которого содержится в переменной с каждый раз, когда он встречается char s[81], c; ... for( i = j = 0; s[i]! = '\0'; i++) if( s[i]! = c) s[j++] = s[i]; s[j]='\0'; ... __________________________________________________________________ В режиме консольных приложений в среде Visual C++ 6.0 вывод символов русского языка сопряжен с определенными неудобствами. Разрешение данной проблемы рассматривается в разд. 16.3. __________________________________________________________________ Указатели на указатели Указатели, как и переменные любого другого типа, могут объединяться в массивы. Объявление массива указателей на целые числа имеет вид int *a[10], y; Теперь каждому из элементов массива указателей a можно присвоить адрес целочисленной переменной y, например: a[1]=& y; Чтобы теперь найти значение переменной y через данный элемент массива а, необходимо записать *a[1]. В языке Си можно описать переменную типа « указатель на указатель ». Это ячейка оперативной памяти (переменная), в которой будет храниться адрес указателя на некоторую переменную. Признак такого типа данных – повторение символа «*» перед идентификатором переменной. Количество символов «*» определяет уровень вложенности указателей друг в друга. При объявлении указателей на указатели возможна их одновременная инициализация. Например: int a=5; int *p1=& a; int **pp1=& p1; int ***ppp1=& pp1; Если присвоить переменной а новое значение, например 10, то одинаковые результаты будут получены в следующих операциях: a=10; *p1=10; **pp1=10; ***ppp1=10; Для доступа к области ОП, отведенной под переменную а, можно использовать и индексы. Эквивалентны следующие выражения: *p1 ~ p1[0]; **pp1 ~ pp1[0][0]; ***ppp1 ~ ppp1[0][0][0]. Фактически, используя указатели на указатели, мы имеем дело с многомерными массивами.
Многомерные массивы Декларация многомерного массива имеет следующий формат: тип ID[размер1][размер2]…[размерN] = { {список начальных значений}, {список начальных значений}, … }; Списки начальных значений – атрибут необязательный. Наиболее быстро изменяется последний индекс элементов массива, поскольку многомерные массивы в языке Си размещаются в памяти компьютера построчно друг за другом (см. следующую тему «Адресная функция»). Рассмотрим особенности работы с многомерными массивами на конкретном примере двухмерного массива. Например, пусть приведена следующая декларация двухмерного массива: int m[3][4]; Идентификатор двухмерного массива – это указатель на массив указателей (переменная типа указатель на указатель: int **m; ). Поэтому двухмерный массив m[3][4]; компилятор рассматривает как массив трех указателей, каждый из которых указывает на начало массива со значениями размером по четыре элемента каждый. В ОП данный массив будет расположен следующим образом:
(А) (В) Рис. 10.1. Схема размещения элементов массива m размером 3× 4
Причем в данном случае указатель m[1] будет иметь адрес m[0]+4*sizeof(int), т.е. каждый первый элемент следующей строки располагается за последним элементом предыдущей строки. Приведем пример программы конструирования массива массивов: #include < stdio.h> void main() { int x0[4] = { 1, 2, 3, 4}; // Декларация и инициализация int x1[4] = {11, 12, 13, 14}; // одномерных массивов int x2[4] = {21, 22, 23, 24}; int *m[3] = {x0, x1, x2, }; // Создание массива указателей int i, j; for (i=0; i< 3; i++) { printf(" \n Cтрока %d) ", i+1); for (j=0; j< 4; j++) printf(" %3d", m[ i ] [ j ]); } }
Результаты работы программы: Cтрока 1) 1 2 3 4 Cтрока 2) 11 12 13 14 Cтрока 3) 21 22 23 24
Такие же результаты будут получены и в следующей программе: #include < stdio.h> void main() { int i, j; int m[3][4] = { { 1, 2, 3, 4}, {11, 12, 13, 14}, {21, 22, 23, 24} }; for (i=0; i< 3; i++) { printf(" \n %2d)", i+1); for (j=0; j< 4; j++) printf(" %3d", m[ i ] [ j ]); } } В последней программе массив указателей на соответствующие массивы элементов создается компилятором автоматически, т.е. данные массива располагаются в памяти последовательно по строкам, что является основанием для декларации массива m в виде int m[3][4] = {1, 2, 3, 4, 11, 12, 13, 14, 21, 22, 23, 24}; Замена скобочного выражения m[3][4] на m[12] здесь не допускается, так как массив указателей не будет создан. Таким образом, использование многомерных массивов в языке Си связано с расходами памяти на создание массивов указателей. Очевидна и схема размещения такого массива в памяти – последовательное (друг за другом) размещение «строк» – одномерных массивов со значениями (векторная организация памяти). Обращению к элементам массива при помощи операции индексации m[i][j] соответствует эквивалентное выражение, использующее адресную арифметику – *(*(m+i)+j). Аналогичным образом можно установить соответствие между указателями и массивами с произвольным числом измерений.
Адресная функция Векторная память поддерживается почти всеми языками высокого уровня и предназначена для хранения массивов различной размерности и различных размеров. Каждому массиву выделяется непрерывный участок памяти указанного размера. При этом элементы, например, двухмерного массива X размерностью n1´ n2 размещаются в ОП в следующей последовательности: Х(0, 0), Х(0, 1), Х(0, 2),... Х(0, n2–1), ..., Х(1, 0), Х(1, 1), Х(1, 2),... Х(1, n2–1), ..., Х(n1–1, 0), Х(n1–1, 1), Х(n1–1, 2), ..., Х(n1–1, n2–1). Адресация элементов массива определяется некоторой адресной функцией, связывающей адрес и индексы элемента. Пример адресной функции для массива Х: K(i, j) = n2*i + j; где i = 0, 1, 2,..., (n1–1); j = 0, 1, 2,..., (n2–1); j – изменяется в первую очередь. Адресная функция двухмерного массива A(n, m) будет выглядеть так: N1 = K(i, j) = m*i + j, i=0, 1,..., n–1; j=0, 1,..., m–1. Тогда справедливо следующее: A(i, j) « B(K(i, j)) = B(N1), B – одномерный массив с размером N1 = n*m. Например, для двухмерного массива A(2, 3) имеем:
Проведем расчеты: i = 0, j = 0 N1 = 3*0+0 = 0 B(0) i = 0, j = 1 N1 = 3*0+1 = 1 B(1) i = 0, j = 2 N1 = 3*0+2 = 2 B(2) i = 1, j = 0 N1 = 3*1+0 = 3 B(3) i = 1, j = 1 N1 = 3*1+1 = 4 B(4) i = 1, j = 2 N1 = 3*1+2 = 5 B(5)
Аналогично получаем адресную функцию для трехмерного массива Х(n1, n2, n3): K(i, j, k) = n3*n2*i + n2*j + k, где i = 0, 1, 2,..., (n1–1); j = 0, 1, 2,..., (n2–1); ); k = 0, 1, 2,..., (n3–1); значение k – изменяется в первую очередь. Для размещения такого массива потребуется участок ОП размером (n1*n2*n3)*sizeof(type). Рассматривая такую область как одномерный массив Y(0, 1,..., n1*n2*n3), можно установить соответствие между элементом трехмерного массива X и элементом одномерного массива Y: X(i, j, k) « Y(K(i, j, k)). Необходимость введения адресных функций возникает лишь в случаях, когда требуется изменить способ отображения с учетом особенностей конкретной задачи. Популярное:
|
Последнее изменение этой страницы: 2016-03-16; Просмотров: 2770; Нарушение авторского права страницы