Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


Строки как одномерные массивы данных типа 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 = "БГУИР" создает не строковую переменную, а указатель на строковую константу, изменить которую невозможно, причем это касается не только адреса ОП, но и его размера. Знак равенства перед строковым литералом означает инициализацию, а не присваивание.

Операция присваивания одной строки другой в языке Си не определена (поскольку строка является массивом) и может обрабатываться при помощи оператора цикла (с использованием стандартной библиотечной функций).

Процесс копирования строки s 1 в строку s 2 имеет вид

char s1[25], s2[25];

for (int i = 0; i <= strlen(s1); i++)

s2[i] = s1[i];

Длина строки определяется с помощью стандартной функции strlen, которая вычисляет длину, выполняя поиск нуль-символа (прототип функции приведен ниже). Таким образом, строка фактически просматривается дважды.

А вот следующие действия будут ошибочными:

сhar s1[51];

s1 = ”Minsk”;

Это связано с тем, что s 1 – константный указатель и не может использоваться в левой части операции присваивания.

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

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 (S 1, S 2) – копирует содержимое строки S 2 в строку S 1.

Функция strcat (S 1, S 2) – присоединяет строку S 2 к строке S 1 и помещает ее в массив, где находилась строка S 1, при этом строка S 2 не изменяется. Нулевой байт, который завершал строку S 1, заменяется первым символом строки S2.

Функция int strcmp (S 1, S 2) сравнивает строки S 1 и S 2 и возвращает значение <0, если S 1< S 2; >0, если S 1> S 2; =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]; компилятор рассматривает как массив трех указателей, каждый из которых указывает на начало массива со значениями размером по четыре элемента каждый. В ОП данный массив будет расположен следующим образом:

 

Указа-тели m [0] ® m [0][0] m [0][1] m [0][2] m [0][3]  
m [1] m [1][0] m [1][1] m [1][2] m [1][3]
m [2] m [2][0] m [2][1] m [2][2] m [2][3]

(А) (В)

Рис. 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 размерностью nn 2 размещаются в ОП в следующей последовательности:

Х (0,0), Х (0,1), Х (0,2),... Х (0, n 2–1),..., Х (1,0), Х (1,1), Х (1,2),... Х (1, n 2–1),..., Х (n 1–1,0), Х (n 1–1,1), Х (n 1–1,2),..., Х (n 1–1, n 2–1).

Адресация элементов массива определяется некоторой адресной функцией, связывающей адрес и индексы элемента.

Пример адресной функции для массива Х:

K (i, j) = n 2* i + j;

где i = 0,1,2,...,(n 1–1); j = 0,1,2,...,(n 2–1); j – изменяется в первую очередь.

Адресная функция двухмерного массива A (n, m) будет выглядеть так:

N 1 = K (i, j) = m * i + j,

i =0,1,..., n –1; j =0,1,..., m –1.

Тогда справедливо следующее:

A (i, j) «B (K (i, j)) = B (N 1),

B – одномерный массив с размером N 1 = n * m.

Например, для двухмерного массива A (2,3) имеем:

(0,0) (0,1) (0,2) (1,0) (1,1) (1,2) – индексы массива А;
            – индексы массива В.

Проведем расчеты:

i = 0, j = 0 N 1 = 3*0+0 = 0 B (0)

i = 0, j = 1 N 1 = 3*0+1 = 1 B (1)

i = 0, j = 2 N 1 = 3*0+2 = 2 B (2)

i = 1, j = 0 N 1 = 3*1+0 = 3 B (3)

i = 1, j = 1 N 1 = 3*1+1 = 4 B (4)

i = 1, j = 2 N 1 = 3*1+2 = 5 B (5)

 

Аналогично получаем адресную функцию для трехмерного массива Х (n 1, n 2, n 3):

K (i, j, k) = n 3* n 2* i + n 2* j + k,

где i = 0,1,2,...,(n 1–1); j = 0,1,2,...,(n 2–1);); k = 0,1,2,...,(n 3–1); значение k – изменяется в первую очередь.

Для размещения такого массива потребуется участок ОП размером (n 1* n 2* n 3)* sizeof (type). Рассматривая такую область как одномерный массив Y (0,1,..., n 1* n 2* n 3), можно установить соответствие между элементом трехмерного массива X и элементом одномерного массива Y:

X (i, j, k) «Y (K (i, j, k)).

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





Поделиться с друзьями:


Дата добавления: 2016-11-12; Мы поможем в написании ваших работ!; просмотров: 569 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Не будет большим злом, если студент впадет в заблуждение; если же ошибаются великие умы, мир дорого оплачивает их ошибки. © Никола Тесла
==> читать все изречения...

2541 - | 2236 -


© 2015-2024 lektsii.org - Контакты - Последнее добавление

Ген: 0.013 с.