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

Лекция 6: массивы и строки, библиотечные функции ввода-вывода.

Лекция 1: введение в программирование. | Лекция 2: представление данных в компьютере. | Часть 1: хранение данных в компьютере. | Часть 2: типы данных языка Си. | Часть 1: основные операторы и их приоритеты. | Часть 2: функции. | Лекция 8: структуры и объединения. | Лекция 9: связные списки. | Часть 2: бинарные деревья. | Часть 3: динамическое программирование. |


Читайте также:
  1. III. B. Функции слова ONE
  2. Other Functions of Money. Другие функции денег
  3. Professional SPA Collection (Профессиональная СПА Коллекция).
  4. V) Массивы и функции
  5. Абстрактные базовые классы и чисто виртуальные функции
  6. Абстрактные базовые классы и чисто виртуальные функции.
  7. Автоматизация поиска информации. Категория «Ссылки и массивы».

Часто приходится иметь дело с большими наборами однотипных данных, к которым было бы удобно обращаться с помощью индексов (например, список дневных температур за месяц). Для работы с такими данными существуют массивы. Массив – это набор последовательно расположенных в памяти переменных одного типа, объединённых общим именем. Объявление массива выглядит следующим образом:

<тип> <имя массива> <[количество элементов]> [={<инициализация>}];

Например:

int a[10];

char b[5]={'H', 'e', 'l', 'l', 'o'};

Если производится инициализация, то размер массива становится очевиден, а потому указывать размер в явной форме не обязательно. Так, будет верна следующая конструкция:

char c[]={'H ', 'e', 'l', 'l', 'o'};

Имя массива имеет тип <тип массива>* и представляет собой указатель на первый элемент массива. Остальные элементы массива собственных имён не имеют. Вспомним, что элементы массива расположены в памяти последовательно. Значит, если b указывает на первый элемент, то b+1 – на второй, b+2 – на третий и так далее. Таким образом, чтобы получить значение первого элемента массива, нужно написать *b, второго – *(b+1), третьего – *(b+2). Кажется немного нелогичным, что обращение к первому элементу выглядит не так, как к остальным, но следует помнить, что *b и *(b+0) – одно и то же. В общем случае обращение к i-тому элементу массива a выглядит как *(a+(i-1)).

Минимальным адресуемым элементом памяти является байт, а потому для случая однобайтовых переменных верность такого способа обращения к элементам массива является очевидной. Но что если размер элемента массива больше одного байта? Казалось бы, тогда b+1 укажет не на второй элемент массива, а на второй байт первого элемента. Однако в языке Си действует правило: если к указателю прибавляется какое-то число i, то на самом деле к нему прибавляется размер переменной, на которую ссылается указатель, умноженный на i. Такое правило называется адресной арифметикой.

Конструкция *(a+i) отражает суть обращения к элементу массива, но является неудобной. Поэтому существует другая форма записи этого выражения: a[i]. Выражения *(a+i) и a[i] абсолютно эквивалентны. Этот факт наглядно демонстрируется следующим примером:

int main()

{

char c[]={'H', 'e', 'l', 'l', 'o'};

int i=3;

putchar(i[c]);

putchar('\n');

return 0;

}

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

Видно, что запись a[i] уже очень напоминает принятую в математике запись индекса: ai. Было бы неудобно каждый раз писать a[i], подразумевая «(i+1)ый элемент массива». Поэтому существует соглашение, которое обязательно нужно запомнить: нумерация элементов массива начинается с нуля. Когда шла речь о переводе чисел из двоичной системы в десятичную, мы уже сталкивались с тем, что нумерация начинается с нуля: было удобно нумеровать биты, начиная нулём. И это не единственные случаи. Так что это соглашение является очень важным, часто встречается и сильно упрощает написание программ.

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

char c [5, 10];

В таком случае c будет иметь тип char**, c[3] – char*, и только c[3, 7] – char. Аналогично определяются и массивы большей размерности.

Чаще всего нельзя заранее узнать, сколько элементов потребуется в массиве. Первый пусть решения этой проблемы: выделить заведомо больше, чем нужно. Если это пара сотен элементов типа int, то такое вполне допустимо. Однако если речь идёт о тысячах элементов, то программа, требующая столько памяти «на всякий случай» эффективной считаться не будет. Поэтому используется работа с динамической памятью. Для работы с динамической памятью в библиотеке stdlib есть функции malloc, realloc и free.

Функция malloc ( от англ. m emory alloc ation) запрашивает у операционной системы место в динамической памяти. Сколько байт будет запрошено, определяется её единственным аргументом типа int. Она имеет тип void* – безтиповый указатель. Этот указатель представляет собой только адрес в памяти, без указания типа данных, которые там находятся. Чтобы что-то сделать с этим указателем, его надо преобразовать в указатель на конкретный тип. Например, мы ходим создать динамический массив c длиной в 500 элементов. Это делает следующий код:

char* c;

c=(char*)malloc(500);

В первой строке объявляется указатель на переменную типа char, который пока никуда не указывает. Во второй строке запрашивается 500 байт динамической памяти. Безтиповый указатель на первый байт этой последовательности преобразовывается в указатель типа char* и записывается в переменную c. Теперь с c можно работать как с обычным массивом. В случае, если выделить память не удалось (сбой, нехватка памяти…), указатель имеет значение 0[21]. Как было сказано в лекции 3, ячейки памяти с таким номером нет, а потому такой указатель считается «указывающим никуда». В реальных программах следует писать код, обрабатывающий ситуацию получения нулевого указателя. Но что если мы хотим выделить место под переменные размером в несколько байт? Для этого есть оператор sizeof(<тип>). Он возвращает значение длины переменной данного типа в байтах. Например, sizeof(int)==4 в большинстве архитектур. Тогда, чтобы выделить место под массив из элементов типа int, следует написать

int* a;

a=(int*)malloc(500*sizeof(int));

Функция realloc (от англ. realloc ation) изменяет размер участка выделенной по данному адресу памяти. Первым аргументом ей передается указатель на участок, который требуется изменить, а вторым – требуемый размер в байтах. Например,

a=(int*)realloc(a, (600*sizeof(int));

c=(char*)realloc(c, (10*sizeof(char));

Функция realloc может как увеличивать, так и уменьшать объём выделенной памяти. При увеличении объёма существующие элементы массива не изменяются, при уменьшении удаляются только последние. Если передать функции realloc указатель NULL, то она будет работать как malloc.

Функция free нужна чтобы избавиться от данного участка динамической памяти, когда он станет не нужен. Её единственным аргументом является указатель, выделенная под который память будет освобождена. Адрес, записанный в указатель, после этого станет недоступен программе, а попытка обратиться к нему приведёт к аварийному завершению работы. Поэтому сразу после использования free рекомендуется присваивать указателю значение NULL. Например,

free(a);

a=NULL;

Ясно, что массивы являются самым естественным способом хранения текстовой информации. Однако, просто применение массивов является не очень удобным: ведь тогда придётся везде явно указывать, сколько символов массива считывать как строку[22]. Нужно как-то указать конец строки. Его принято обозначать нуль-символом. Такая форма хранения текста в языке Си и называется строкой [23]. Чаще всего приходится иметь дело с не очень длинными (до сотен символов) строками, а потому обычно для хранения строк используют статические массивы. Именем строки служит указатель на её нулевой элемент.

Строки в языке Си заключаются в двойные кавычки. Иными словами, конструкции "Hello" и {'H', 'e', 'l', 'l', 'o', 0} эквивалентны. Первая форма записи, конечно, удобнее. Частью строки может быть и символьная константа, например "Hello\n world". Для обработки символьных строк существует библиотека функций string. Перечислим наиболее употребляемые функции из этой библиотеки:

strlen(<строка>) – имеет тип int, определяет количество символов в строке (завершающий нуль-символ игнорируется).

strcpy(<приёмник>, <источник>) – копирует символы из источника в приёмник (и то, и другое – строки) начиная с нулевого и заканчивая завершающим нуль-символом. Если приёмник короче источника, возникает ошибка.

strcmp(<строка1>, <строка2>) – сравнение строк. Если строки равны (все символы одинаковые), возвращает 0. Возвращает 1, если строка1 больше строки2, и -1 если меньше. Отношения больше-меньше определяются на основе лексикографического порядка.

strcat(<приёмник>, <источник>) – добавление источника в конец приёмника. При переполнении буфера приёмника возникает ошибка.

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

Строки находят широкое применение в программировании. В частности, именно с помощью строк работают самые распространённые библиотечные функции ввода-вывода – printf и scanf – содержащиеся в библиотеке stdio.

Функция printf, осуществляющая форматный вывод на экран имеет всего один обязательный аргумент – строку, которая будет выведена на экран. Вот пример программы, осуществляющей вывод строки на экран:

#include <stdio.h>

int main()

{

printf("\tHello world!\n");

return 0;

}

Результатом работы этой программы будет строка "Hello world!", выведенная с отступом от края экрана, и перевод курсора на следующую строку.

Для того чтобы выводить на экран переменные, в строку вводятся управляющие последовательности. Управляющая последовательность – это набор символов в строке, передаваемой printf, начинающийся символом '%', который при выводе будет заменён значением, передаваемым printf после строки, способ отображения которого зависит от того, какой именно является управляющая последовательность. Поскольку символ % при одиночном появлении считается началом управляющей последовательности, для вывода этого знака на экран нужно написать его два раза: "%%".В общем виде управляющая последовательность выглядит так: "%[<флаги>][<ширина>][.<точность>][<размер>]<тип>". В необязательном поле «флаги» могу стоять следующие символы:

Символ Значение
'-' Выравнивание по левому краю в рамках указанной ширины (по умолчанию – по правому краю). Если ширина не указана - игнорируется
'+' Указывать знак числа, даже если это плюс.
'0' Заполнять нулями свободное пространство слева от выводимого значения, до ширины, указанной в поле «ширина». Если указана точность или не указана ширина, этот флаг игнорируется.

В необязательном поле «ширина» указывается минимальное количество символов, которые будут зарезервированы под вывод. Если требуется больше символов – выделяется больше, если меньше – оставшиеся по умолчанию заполняются пробелами. Если в поле «ширина» стоит знак '*', то в качестве значения ширины будет взят аргумент printf, стоящий перед выводимой величиной.

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

Поле «тип» является обязательным, в нём указывается способ интерпретации входных данных. Есть следующие типы:

Тип Значение
'd', 'i' Целое десятичное
'o' Восьмеричное беззнаковое
'x', 'X' Целое шестнадцатеричное, x выводит буквы в нижнем регистре, а X – в верхнем.
'f' С плавающей точкой
'e' Экспоненциальная форма записи числа с плавающей точкой
'c' Выводится символ, ASCII-код которого равен значению переменной
's' Строка
'p' Указатель

Порядок управляющих последовательностей и соответствующих им аргументов должен совпадать.

Например, результатом работы кода

printf("%3d%% done. Now scanning address %8p, its value is %.5f\r", percent, pointer, *pointer);

будет вывод такого вида:

30% done. Now scanning address 000AF49E, its value is 3.00649

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

Функция scanf позволяет считывать с клавиатуры данные в тех же форматах, что и printf выводить на экран. Чтобы считать сразу несколько переменных, управляющие последовательности следует разделять пробелами. Поскольку функция не может возвращать несколько значений, scanf в качестве аргументов передаются не сами переменные, а их адреса[24]. Например, следующий код считывает целое число и строку:

int a;

char c[256];

scanf("%d %s", &a, c);

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

После своего вызова scanf оставляет в буфере клавиатуры лишний символ перевода строки. Поэтому если после scanf вызвать getchar, то getchar не будет дожидаться ввода и заберёт из буфера этот символ. Если в программе планируется использовать как scanf, так и getchar, то во избежание ошибок лучше всего после каждого вызова scanf «вхолостую» вызывать gecthar:

scanf("%e", &number);

getchar();


Дата добавления: 2015-11-13; просмотров: 53 | Нарушение авторских прав


<== предыдущая страница | следующая страница ==>
Лекция 5: Функция main, функции ввода-вывода, препроцессор.| Лекция 7: операторы выбора, безусловный переход, циклы.

mybiblioteka.su - 2015-2024 год. (0.01 сек.)