Лекции по C++
Лекции по C++
Астраханский государственный технический университет
Кафедра «Информационных
технологий и коммуникаций»
Конспект лекций по дисциплине
«Основы алгоритмического языка С++»
для специальности 220200.
Астрахань 2000 г.
1. Переменные и операции языка С++ 4
ИЗУЧАЕМЫЕ ПОНЯТИЯ 5
Буквы и цифры 6
Пробельные символы 6
Знаки пунктуации и специальные символы 6
ESC- последовательности 8
Операции 9
Константы 11
Целые константы 11
Константы с плавающей точкой 13
Константа-символ 14
Строковые литералы 14
Идентификаторы 15
Ключевые слова 15
Комментарии 16
Лексемы 17
ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ 17
2. Конструкции принятия решений и циклы 23
ВОПРОСЫ И ОТВЕТЫ 23
СТРУКТУРА ПРОГРАММЫ 26
Исходная программа 26
ОБЪЯВЛЕНИЯ 27
Спецификаторы типов 28
Деклараторы 30
Деклараторы массивов, функций и указателей 30
Составные деклараторы 31
Об"явления переменной 33
Объявление простой переменной 34
Объявление перечисления 34
Объявления структур 36
Битовые поля 37
Об"явление совмещений 38
Об"явление массива 39
Об"явление функций 42
Классы памяти 45
Об"явления переменной на внешнем уровне 45
Об"явление переменной на внутреннем уровне 48
Об"явление функции на внешнем и внутреннем уровнях 49
Инициализация 50
Базовые типы и типы указателей 50
Составные типы 51
Строковые инициализаторы 53
Об"явления типов 54
Типы структур, совмещений и перечислений 54
Об"явления typedef 55
Имена типов 56
КОНТРОЛЬНЫЕ ВОПРОСЫ: 57
Функции 57
Объявление и определение функций 58
ТИПОВЫЕ ВОПРОСЫ С ОТВЕТАМИ 62
ПРАКТИКУМ 62
Контрольные вопросы 62
Массивы 65
СОРТИРОВКА массива - ПРИМЕР в файле list6_4cpp. 67
- ПОИСК в массиве 67
БИБЛИОТЕЧНЫЕ ФУНКЦИИ ПОИСКА и СОРТИРОВКИ в непрерывных массивах: 68
Строки и управление вводом/выводом 69
Форматированный потоковый вывод 70
Листинг 1. Исходный текст программы OUT1.CPP 70
Функция printf 71
Функция printf 72
Таблица 1. Еsс - последовательности 72
Таблица 7.2. Значения флагов строки формата функции printf 72
Таблица 3. Символы типов данных строки формата функции printf 73
Листинг 3. Исходный текст программы OUT2.CPP в файле List7-3.CPP 74
Таблица 4. Результат действия спецификаций форматирования в функции
printf из строки 13 75
Ввод строк 76
Функция getline 76
Присвоение значений строкам 77
Инициализация строки 77
Функция strcpy 77
Функция strdup 77
Функция strncpy 78
Определение длины строки 78
Функция strlen 78
Функция strcat 78
Функция strncat 79
Сравнение строк 79
Функция strcmp 79
Пример 80
Функция stricmp 80
Пример 80
Функция strncmp 80
Пример 80
Пример 81
(см. List7_5.cpp - Исходный текст программы STRING2.CPP) 81
Преобразование строк 81
Функция strlwr 81
Пример 81
Функция strupr 81
Пример 81
Обращение строк 82
Функция strrev 82
Поиск символов 82
Функция strchr 82
Функция strrchr 82
Пример 83
Функция Strspn 83
Пример 83
Функция strcspn 83
Пример 83
Функция strpbrk 83
Пример 84
Поиск строк 84
Функция strstr 84
Пример 84
Функция strtok 84
Пример 84
Основы объектно-ориентированного программирования СИНТАКСИС ОСНОВНЫХ
КОНСТРУКЦИЙ 85
Объявление базовых классов 85
Конструкторы 88
Деструкторы 90
Объявление иерархии классов 91
Виртуальные функции 92
Дружественные функции 95
Операции и дружественные операции 96
Виртуальные функции 97
Правило виртуальной функции 99
Операции и дружественные операции 101
ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ 103
ВОПРОСЫ И ОТВЕТЫ 103
Контрольные вопросы 104
ФАЙЛОВЫЕ ОПЕРАЦИИ ВВОДА/ВЫВОДА 105
Stream-библиотека C++ 105
ОБЩИЕ ФУНКЦИИ ПОТОКОВОГО ВВОДА/ВЫВОДА 106
Функция-компонент open 106
Функция-компонент close 107
ПОСЛЕДОВАТЕЛЬНЫЙ ТЕКСТОВЫЙ ПОТОК ВВОДА/ВЫВОДА 107
Функция-элемент getline 108
ПОСЛЕДОВАТЕЛЬНЫЙ ДВОИЧНЫЙ ФАЙЛОВЫЙ ВВОД/ВЫВОД 109
Функция-элемент write 110
Функция-элемент read 110
Файловый ввод/вывод с прямым доступом 113
Функция-элемент seekg 113
Заключение 115
Вопросы и ответы 115
Практикум 116
Контрольные вопросы 116
Упражнение 116
Переменные и операции языка С++
Здесь представлены базовые компоненты программ на С++. В их число
входят типы данных, переменные, константы и выражения.
ИЗУЧАЕМЫЕ ПОНЯТИЯ
- Предопределенные типы данных в С++ включают в себя типы int, char,
float, double и void. В языке С++ гибкость типов данных увеличивается
благодаря применению модификаторов типов. Эти модификаторы изменяют
точность представления и диапазон значений переменных. Модификаторами
типа являются signed, unsigned, short и long.
- Идентификаторы в С++ могут иметь длину до 32 символов и
должны начинаться с буквы или подчеркивания. Последующие символы
идентификатора могут быть буквой, цифрой или подчеркиванием. Иден-
тификаторы С++ чувствительны к регистру. Ограничение на 32 символа
может быть, однако, изменено путем установки опций компилятора.
- Директива #include является специальной командой компилятора. Она
предписывает компилятору включить в программу содержимое опреде-
ленного файла, как если бы вы сами ввели его в текущий исходный
файл.
- Объявление констант предусматривает использование директивы #define
для объявления констант, определенных при помощи макросов, или ис-
пользование ключевого слова const для объявления формальных кон-
стант. Формальные константы требуют от вас определения их типа
(значением по умолчанию является int), имени и ассоциированного с
ними значения.
- Объявление переменной требует, чтобы вы задали ее тип и имя, С++
дает вам возможность инициализировать переменную при ее объявлении.
Вы можете объявить несколько переменных в одном операторе объявле-
ния.
- Арифметическими операциями являются +, -, *, / и % (деление по
модулю).
- Арифметические выражения различаются по сложности. Самое простое
выражение содержит единственный элемент данных (литерал, константу
или переменную). Сложные выражения включают набор операций, функ-
ции, литералы, константы и переменные.
- Операции инкремента и декремента используются в префиксной и пост-
фиксной формах. Язык С++ дает вам возможность применять эти опе-
рации к переменным, в которых хранятся символы, целые числа и даже
числа с плавающей точкой.
- Арифметические операции присваивания дают вам возможность записы-
вать более короткие арифметические выражения, в которых первый опе-
ранд является также переменной, принимающей результат вычислений.
- Оператор sizeof возвращает как для типов данных, так и для переменных
их размер в байтах.
- Механизм приведения типа дает вам возможность форсировать преобра-
зование типа выражения.
- Операции отношений и логические операции дают вам возможность стро-
ить логические выражения.
- Булевы выражения объединяют операции отношений и логические опе-
рации для формулирования нетривиальных условий. Эти выражения позволяют
программе принимать сложные решения.
- Условное выражение предлагает вам короткую форму для простого опе-
ратора if-else с двумя альтернативами.
- Операции манипулирования битами выполняют поразрядные операции
AND, OR, XOR и NOT. Кроме того, в С++ поддерживаются поразрядные
операции сдвига >.
- Операции манипулирования битами с присваиванием предлагают корот-
кие формы для простых операций манипулирования битами.
Буквы и цифры
Множество символов Си включает большие и малые буквы из английского
алфавита и 10 десятичных арабских цифр:
-большие английские буквы:
A B C D E F G H I J K L M N O P Q R T U V W X Y Z
-малые английские буквы:
a b c d e f g h i j k l m n o p q r t u v w x y z
-десятичные цифры:
0 1 2 3 4 5 6 7 8 9
Буквы и цифры используются при формировании констант, иден-
тификаторов и ключевых слов. Все эти конструкции описаны ниже. Компилятор
Си рассматривает одну и ту же малую и большую
буквы как отличные символы. Если в данной записи использованы малые буквы,
то замена малой буквы "a" на большую букву "A" сделает отличной данную
запись от предшествующей.
Пробельные символы
Пробел, табуляция, перевод строки, возврат каретки, новая
страница, вертикальная табуляция и новая строка- это сиволы, называемые
пробельными, поскольку они имеют то же самое назначение, как и пробелы
между словами и строками на печатной странице. Эти символы разделяют
об"екты, определенные пользователем, такие, как константы и идентификаторы,
от других об"ектов программы.
Символ CONTROL-Z рассматривается как индикатор конца файла.
Компилятор игнорирует любой текст, следующий за символом
CONTROL-Z.
Компилятор Си игнорирует пробельные символы, если они не
используются как разделители или как компоненты константы-символа или
строковых литералов. Это нужно иметь в виду, чтобы дополнительно
использовать пробельные символы для повышения наглядности программы
(например,для просмотра редактором текстов).
Знаки пунктуации и специальные символы
Знаки пунктуации и специальные символы из множества символов Си
используются для различных целей, от организации текста программы до
определения заданий, которые будут выполнены компилятором или
откомпилированной программой. В таблице 2.1 перечислены эти символы.
-----------------------------------------------------------
Символ Наименование Символ Наименование
-----------------------------------------------------------
, Запятая ! Восклицатель-
ный знак
. Точка | Вертикальная
черта
; Точка с за- / Наклонная чер-
пятой та вправо
: Двоеточие \ Наклонная чер-
та влево
? Знак вопроса ~ Тильда
' Одиночная ка _ Подчеркивание
вычка
( Левая круглая # Знак номера
скобка
) Правая круглая % Знак процента
скобка
{ Левая фигурная & Амперсанд
скобка
} Правая фигурная ^ Caret
скобка
< Левая угловая - Знак минус
скобка
> Правая угловая = Знак равно
скобка
[ Левая квадратная + Знак плюс
скобка
] Правая квадратная
скобка ---------------------------------------------------------
--
Табл. 2.1. Знаки пунктуации и специальные символы
Эти символы имеют специальный смысл для компилятора Си. Их
использование в языке Си описывается в дальнейшем содержании руководства.
Знаки пунктуации из множества представимых символов, которые не
представлены в данном списке, могут быть использованы только в строковых
литералах, константах-символах и комментариях.
ESC- последовательности
ESC- последовательности- это специальные символьные комбинации,
которые представляют пробельные символы и неграфические символы в
строках и символьных константах.
Их типичное использование связано со спецификацией таких
действий, как возврат каретки и табуляция , а также для задания
литеральных представлений символов, таких как символ двойная кавычка.
ESC-последовательность состоит из наклонной черты влево, за которой
следует буква, знаки пунктуации ' " \ или комбинация цифр. В таблице 2.2.
приведен список ESC- последовательностей языка Си.
-------------------------------------------------
ESC- последовательность Наименование
-------------------------------------------------
\n Новая строка
\t Горизонтальная табу-
ляция
\v Вертикальная табуля-
ция
\b Пробел
\r Возврат каретки
\f Новая страница
\a Звонок(сигнал)
\' Одиночная кавычка
\" Двойная кавычка
\\ Наклонная черта влево
\ddd ASCII символ в восьми-
ричном представлении
\xdd ASCII символ в шестнад-
цатиричном представлении
Табл. 2.2. ESC- последовательности
Если наклонная черта влево предшествует символу, не включенному в
этот список, то наклонная черта влево игнорируется, а символ
представляется как литеральный. Например, изображение \c
представляет символ "c" в литеральной строке или константе-символе.
Последовательности \ddd и \xdd позволяют задать любой символ в ASCII
(Американский стандартный код информационного интерфейса) как
последовательность трех восьмеричных цифр или двух шестнадцатеричных цифр.
Например, символ пробела может быть задан как \010 или \x08. Код ASCII
"нуль" может быть задан как \0 или \x0 . В восьмеричной ESC-
последовательности могут быть использованы от одной до трех восьмеричных
цифр.
Например, символ пробела может быть задан как \10 . Точно так же в
шестнадцатеричной ESC- последовательности могут быть использованы от одной
до двух шестнадцатеричных цифр. Так, шестнадцатеричная последовательность
для символа пробела может быть задана как \x08 или \x8 .
Замечание:
Когда используется восьмеричная или шестнадцатеричная
ESCпоследовательность в строках, то нужно полностью задавать все цифры
ESC- последовательности (три цифры для восьмеричной и две цифры для
шестнадцатеричной ESC- последовательностей). Иначе, если символ
непосредственно следующий за ESC- последовательностью, случайно окажется
восьмеричной или шестнадцатеричной цифрой, то он проинтерпретируется
как часть последовательности. Например, строка \x7Bell при выводе на
печать будет выглядеть как {ell , поскольку \x7B проинтерпретируется как
символ левой фигурной скобки({) . Строка \x07Bell будет правильным
представлением сим-
вола "звонок" с последующим словом Bell.
ESC- последовательности позволяют посылать неграфические управляющие
символы к внешним устройствам. Например, ESC- последовательность\033 часто
используется как первый символ команд управления терминалом и принтером.
Неграфические символы всегда должны представляться ESC-
последовательностями, поскольку, непосредственное использование в
программах на Си неграфических символов будет иметь непредсказуемый
результат.
Наклонная черта влево (\) помимо определения ESC-последовательностей
используется также, как символ продолжения строки в препроцессорных
определениях.
Если символ "новая строка" следует за наклонной чертой влево, то новая
строка игнорируется и следующая строка рассматривается, как часть
предыдущей строки.
Операции
Операции- это специальные комбинации символов, специфицирующие действия
по пробразованию различных величин. Компилятор интерпретирует каждую из
этих комбинаций как самостоятельную единицу, называемую лексемой (token).
В Табл. 2.3 представлен список операций. Операции должны
использоваться точно так, как они представлены в таблице: без пробельных
символов между символами в тех операциях, которые представлены несколькими
символами.
Операция sizeof не включена в эту таблицу. Она скорее представляет собой
ключевое слово, чем символ.
-------------------------------------------------
Операция Наименование ----------------------------------
---------------
! Логическое НЕ
~ Побитовое дополнение
+ Сложение
- Вычитание, арифмети-
ческое отрицание
* Умножение
/ Деление
% Остаток
> Сдвиг вправо
< Меньше
Больше
>= Больше или равно
== Равно
!= Не равно
& Побитовое И, адрес от
| Побитовое включающее ИЛИ
^ Побитовое исключающее ИЛИ
&& Логическое И
|| Логическое ИЛИ
' Последовательное выполне-
ние (запятая)
?: Операция условного вы-
ражения
++ Инкремент
-- Декремент
= Простое присваивание
+= Сложение с присваиванием
-= Вычитание с присваиванием
*= Умножение с присваиванием
/= Деление с присваиванием
%= Остаток с присваиванием
>>= Сдвиг вправо с присваива-
иванием
?:
Константы
Константа- это число, символ или строка символов. Константы
используются в программе как неизменяемые величины. В языке Си различают
четыре типа констант: целые константы, константы с плавающей точкой,
константы-символы и строчные литералы.
Целые константы
Целая константа- это десятичное, восьмеричное или шестнадцатеричное
число, которое представляет целую величину. Десятичная константа имеет
следующий формат представления:
,
где - это одна или более десятичных цифр от 0 до 9.
Восьмеричная константа имеет следующий формат представления:
0,
где - это одна или более восьмеричных цифр от 0 до 7.
Запись ведущего нуля необходима.
Шестнадцатеричная константа имеет один из следующих форматов
представления:
0x
0X,
где одна или более шестнадцатеричных цифр. Шестнадцатеричная
цифра может быть цифрой от 0 до 9 или
буквой (большой или малой) от A до F. В представлении константы
допускается "смесь" больших и малых букв. Запись ведущего нуля и
следующего за ним символа x или X необходима.
Пробельные символы не допускаются между цифрами целой константы. В
Табл. 2.4 иллюстрируются примеры целых констант.
-----------------------------------------------------------
Десятичные Восьмеричные Шестнадцатеричные
константы константы константы
-----------------------------------------------------------
10 012 0xa или 0xA
132 0204 0x84
32179 076663 0x7dB3 или 0x7DB3
-----------------------------------------------------------
Табл. 2.4 Примеры констант
Целые константы всегда специфицируют положительные величины. Если
требуется отрицательные величины, то необходимо сформировать константное
выражение из знака минус и следующей за ним
константы. Знак минус рассматривается как арифметическая операция.
Каждая целая константа специфицируется типом, определяющим ее
представление в памяти и область значений. Десятичные константы могут быть
типа int или long.
Восьмеричные и шестнадцатеричные константы в зависимости от размера
могут быть типа int, unsigned int, long или unsigned long. Если
константа может быть представлена как int, она специфицируется типом
int. Если ее величина больше, чем максимальная положительная величина,
которая может быть представлена типом int, но меньше величины,
которая представляется в том же самом числе бит как и int, она задается
типом unsigned int. Наконец, константа, величина которой больше чем
максимальная величина, представляемая типом unsigned int, задется
типом long или unsigned long, если это необходимо. В Табл. 2.5 показаны
диапазо-
ны величин восьмеричных и шестнадцатеричных констант, представимых
соответствующими типами на машине, где тип int имеет длину 16 бит.
-----------------------------------------------------------
Шестнадцатеричные Восьмеричные Тип
диапазоны диапазоны
-----------------------------------------------------------
0x0-0x7FFF 0-077777 int
0x8000-0xFFFF 0100000-0177777 unsigned int
0x10000-0x7FFFFFFF 0200000-017777777777 long
0x80000000-0xFFFFFFFF 020000000000-030000000000 unsigned long
-----------------------------------------------------------
Табл. 2.5 Диапазоны величин восьмеричных и
шестнадцатеричных констант
Важность рассмотренных выше правил состоит в том, что восьмеричные и
шестнадцатеричные константы не содержат "знаковых" расширений, когда
они преобразуются к более длинным типам (преобразование типов смотри в
разделе 5 "Выражения и присваивания").
Программист может определить для любой целой константы тип
long, приписав букву "l" или "L" в конец константы. В Табл. 2.6 показаны
примеры целых констант.
------------------------------------------------------------
Десятичные Восьмеричные Шестнадцатеричные
константы константы константы
------------------------------------------------------------
10L 012L 0xaL или 0xAL
79l 0115l 0x4fl или 0x4Fl
------------------------------------------------------------
Табл. 2.6 Примеры целых констант типа long
Константы с плавающей точкой
Константа с плавающей точкой- это действительное десятичное
положительное число. Величина действительного числа включает целую,
дробную части и зкспоненту. Константы с плавающей точкой имеют
следующий формат представления:
[][.][E[-]],
где - одна или более десятичных цифр (от 0 до 9),
а E или e -символ экспоненты. Целая или дробная части константы могут
быть опушены, но не обе сразу. Десятичная точка может быть опущена только
тогда, когда задана экспонента.
Экспонента состоит из символа экспоненты, за которым следует
целочисленная величина экспоненты, возможно отрицательная.
Пробельные символы не могут разделять цифры или символы
константы.
Константы с плавающей точкой всегда специфицируют положительные
величины. Если требуются отрицательные величины, то необходимо сформировать
константное выражение из знака минус и следующей за ним константы. Знак
минус рассматривается как арифметическая операция.
Примеры констант с плавающей точкой и константных выраже-
ний:
15.75
1.575E1
1575e-2
-0.0025
-2.5e-3
25e-4
Целая часть константы с плавающей точкой может быть опущена, например:
.75
.0075e2
-.125
-.175E-2
Все константы с плавающей точкой имеют тип double.
Константа-символ
Константа-символ- это буква, цифра, знак пунктуации или ESC- символ,
заключенные в одиночные кавычки. Величина константы-символа равна
значению представляющего ее кода символа.
Константа-символ имеет следующую форму представления:
'',
где может быть любым символом иэ множества представимых
символов, включая любой ESC- символ, исключая одиночную кавычку ('),
наклонную черту влево (\) и символ новой строки.
Чтобы использовать одиночную кавычку или наклонную черту влево в
качестве константы-символа, необходимо вставить перед этими знаками
наклонную черту влево. Чтобы представить символ новой строки, необходимо
использовать запись '\n'.
----------------------------------------------
Константа Название величины
----------------------------------------------
'a' Малая буква а
'?' Знак вопроса
'\b' Знак пробела
'0x1B' ASCII ESC- символ
'\'' Одиночная кавычка
'\\' Наклонная черта влево
-------------------------------------------------
Табл. 2.7 Примеры констант-символов.
Константы-символы имеют тип int.
Строковые литералы
Строковый литерал- это последовательность букв, цифр и символов,
заключенная в двойные кавычки. Строковый литерал рассматривается как
массив символов, каждый элемент которого представляет отдельный символ.
Строковый литерал имеет следующую форму представления:
"" ,
где - это нуль или более символов из множества представимых
символов, исключая двойную кавычку ("), наклонную черту влево (\) и
символ новой строки. Чтобы использовать символ новой строки в строковом
литерале, необходимо напечатать наклонную черту влево, а затем символ
новой строки.
Наклонная черта влево вместе с символом новой строки будут
проигнорированы компилятором , что позволяет формировать строковые
литералы, располагаемые более чем в одной строке. Например, строковый
литерал:
"Long strings can be bro\
cken into two pieces."
идентичен строке:
"Long strings can be brocken into two pieces."
Чтобы использовать двойные кавычки или наклонную черту влево внутри
строкового литерала, нужно представить их с предшествующей наклонной чертой
влево, как показано в следующем примере:
"This is a string literal"
"First \\ Second"
"\"Yes, I do,\" she said."
"The following line shows a null string:"
""
Заметим, что ESC- символы (такие как \\ и \") могут появляться в
строковых литералах. Каждый ESC- символ считается одним отдельным символом.
Символы строки запоминаются в отдельных байтах памяти. Символ null (\0)
является отметкой конца строки. Каждая строка в программе
рассматривается как отдельный об"ект. Если в программе содержатся две
идентичные строки, то каждая из них будет храниться в отдельном месте
памяти.
Строчные литералы имеют тип char[]. Под этим подразумевается, что
строка- это массив, элементы которого имеют тип char. Число элементов в
массиве равно числу символов в строчном литерале плюс один, поскольку
символ null (отметка конца строки) тоже считается элементом массива.
Идентификаторы
Идентификаторы- это имена переменных, функций и меток, используемых в
программе. Идентификатор создается об"явлением соот-
ветствующей ему переменной или функции.После этого его можно использовать
в последующих операторах программы. Идентификатор- это последовательность
из одной или более букв, цифр или подчерков(_), которая начинается с
буквы или подчерка. Допускается любое число символов в идентификаторе,
однако только первые 31 символ распознаются компилятором. (Программы,
использующие результат
работы компилятора, такие как, линкер, могут распознавать меньшее число
символов).
При использовании подчерков в идентификаторе нужно быть осторожным,
поскольку идентификаторы, начинающиеся с подчерка могут совпадать (войти в
конфликт) с именами "скрытых" системных программ.
Примеры идентификаторов:
temp1
toofpage
skip12
Компилятор Си рассматривает буквы верхнего и нижнего регистров как
различные символы. Поэтому можно создать отдельные независимые
идентификаторы, которые совпадают орфографически, но различаются большими и
малыми буквами. Например, каждый из следующих идентификаторов является
уникальным:
add
ADD
Add
aDD
Компилятор Си не допускает идентификаторов, которые имеют ту же самую
орфографию, что и ключевые слова. Ключевые слова описаны в следующем
раздела
Замечание:
По сравнению с компилятором, сборщик может в большей степени
ограничивать количество и тип символов для глобальных идентификаторов, и в
отличие от компилятора не делать различия между большими и малыми
буквами. (Подробнее смотри руководство по пакету MSC).
Ключевые слова
Ключевые слова- это предопределенные идентификаторы, которые имеют
специальное значение для компилятора Си. Их можно использовать только так
как они определены. Имена об"ектов программы не могут совпадать с
названиями ключевых слов.
Список ключевых слов:
auto double int struct
break else long switch
case enum register typedef
char extern return union
const float short unsigned
continue for signed void
default goto sizeof while
do if static volatile
Ключевые слова не могут быть переопределены. Тем не менее, они могут
быть названы другим текстом, но тогда перед компиляцией они должны быть
заменены посредством препроцессора на соответствующие ключевые слова.
Ключевые слова const и volatile зарезервированы для будущего
использования.
Следующие идентификаторы могут быть ключевыми словами для некоторых
приложений:
cdecl
far
fortran
huge
near
pascal
Комментарии
Комментарий- это последовательность символов, которая воспринимается
компилятором как отдельный пробельный символ или, другими словами,
игнорируется.
Комментарий имеет следующую форму представления:
/**/,
где может быть любой комбинацией символов из множества
представимых символов, включая символы новой строки, но исключая комбинацию
*/. Это означает, что комментарии могут занимать более одной строки, но не
могут быть вложенными.
Комментарии допускаются везде, где разрешены пробельные символы.
Компилятор игнорирует символы комментария, в частности, в комментариях
допускается запись ключевых слов и зто не приведет к ошибке. Так как
компилятор рассматривает комментарий как символ пробела, то комментарии не
могут появляться внутри лексем.
Следующие примеры иллюстрируют некоторые комментарии:
/* Comments can separate and document
lines of a program. */
/* Comments can contain keywords such as for
and while */
/*******************************************
Comments can occupy several lines.
*******************************************/
Так как комментарии не могут содержать вложенных комментариев, то
следующий пример будет ошибочным:
/* You cannot/* nest */ comments */
Компилятор распознает первую комбинацию */ после слова nest как конец
комментария. Затем, компилятор попытается обрабатывать оставшийся текст и
выработает сообщение об ошибке. Чтобы обойти
компиляцию комментариев больших размеров, нужно использовать директиву #if
препроцессора.
Лексемы
Когда компилятор обрабатывает программу, он разбивает программу на
группы символов, называемых лексемами. Лексема- это единица текста
программы, которая имеет определенный смысл для компилятора и которая не
может быть разбита в дальнейшем. Операции, константы, идентификаторы и
ключевые слова, описанные в этом разделе,являются примерами лексем. Знаки
пунктуации, такие как квадратные скобки ([]), фигурные скобки ({}),
угловые скобки (<>), круглые скобки и запятые, также являются лексемами.
Границы лексем определяются пробельными символами и другими лексемами,
такими как операции и знаки пунктуации. Чтобы предупредить неправильную
работу компилятора, запрещаются пробельные символы между символами
идентификаторов, операциями, состоящими из нескольких символов и символами
ключевых слов.
Когда компилятор выделяет отдельную лексему, он последовательно
об"единяет столько символов, сколько возможно, прежде чем перейти к
обработке следующей лексемы. Поэтому лексемы, не разделенные пробельными
символами, могут быть проинтерпретированы неверно.
Например, рассмотрим следующее выражение:
i+++j
В этом примере компилятор вначале создает из трех знаков плюс самую
длинную из возможных операций (++), а затем обработает оставшийся знак +,
как операцию сложения (+). Выражение проинтерпретируется как (i++)+(j), а
не как (i)+(++j). В таких случаях необходимо использовать пробельные
символы или круглые скобки, чтобы однозначно определить ситуацию.
ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ
// Программа VAR.CPP, иллюстрирующая простые переменные
#include
int main()
{
int i, j = 2;
double x, y = 355.0 / 113;
i = 3 * j;
cout
#define SEC_IN_MIN 60
#define MIN_IN_HOUR 60
int main()
{
long hours, minutes, seconds;
long totalSec;
cout > hours;
cout > minutes;
cout > seconds;
totalSec = ((hours * MIN_IN_HOUR + minutes) *
SEC_IN_MIN) + seconds;
cout
const int SEC_IN_MIN = 60; // глобальная константа
int main()
{
const int MIN_IN_HOUR = 60; // локальная константа
long hours, minutes, seconds;
long totalSec;
cout > hours;
cout > minutes;
cout > seconds;
totalSec = ((hours * MIN_IN_HOUR + minutes) *
SEC_IN_MIN) + seconds;
cout
int main()
{
int int1, int2;
long long1, long2, long3, long4, long5;
float x, y, real1, real2, real3, real4;
cout > int1;
cout > int2;
cout > x;
cout > y;
cout
int main()
{
short int aShort;
int anInt;
long aLong;
char aChar;
float aReal;
cout
int main()
{
short shortInt1, shortInt2;
unsigned short aByte;
int anInt;
long aLong;
char aChar;
float aReal;
// присваиваются значения
shortInt1 = 10;
shortInt2 = 6;
// действия выполняются без приведения типа
aByte = shortInt1 + shortInt2;
anInt = shortInt1 - shortInt2;
aLong = shortInt1 * shortInt2;
aChar = aLong + 5; // автоматическое преобразование
// в символьный тип
aReal = shortInt1 * shortInt2 + 0.5;
cout = lowVal && i 0 )
j = i * 1;
else
j = 10 - i;
Сравните этот листинг и его вариант с отступами
if ( i > 0 )
j = i * i;
else
j = 10 - i;
Последний вариант читается много легче; легко указать, где операторы if
и else. Более того, если вы будете работать с вложенными циклами, отступы
еще более значимы в отношении удобочитаемости кода.
Каковы правила написания условий в операторе if-else?
Здесь существуют два подхода. Первый рекомендует писать условия так,
что true будет чаще, чем false. Второй подход рекомендует избегать
отрицательных выражений (тех, которые используют операции сравнения != и
булевы операции !).
Программисты из последнего лагеря преобразуют такой оператор if:
if ( i != 0 )
j = 100/i;
else
j = 1;
в следующую эквивалентную форму:
if ( i == 0 )
j = 1;
else
j = 100/i;
хотя вероятность равенства нулю переменной i достаточно низка.
Как обработать условие, подобное нижеследующему, где имеется деление на
переменную, которая может оказаться равной нулю?
if ( i != 0 && 1/i > 1 )
j = i * i;
С++ не всегда оценивает проверяемые условия полностью. Эта частичная
оценка происходит, когда член булева выражения превращает все выражение в
false или true, независимо от значения других членов. В этом случае, если
переменная i равна 0, исполняющая система не будет оценивать 1/i > 1,
потому что член i != 0 есть false и обращает в false все выражение,
независимо от значения второго члена. Это называется укороченной оценкой
булевых выражений.
Действительно ли необходимо включать предложения else или default в
многоальтернативные операторы if-else и switch?
Программисты настоятельно рекомендуют включение этих всеохватывающих
предложений для гарантии того, что многоальтернативные операторы будут
обрабатывать все возможные условия. Однако технически для компиляции
программы это не является необходимым.
Как смоделировать цикл while циклом for?
Рассмотрим простой пример.
int i; int i = 1;
for (i=1; i 10) break;
cout 10) break;
i += 2; i += 2;
} while (i ][][=]
[,[=...],
где:
- спецификатор класса памяти; - имя
определяемого типа;
- идентификатор, который может быть модифицирован
при об"явлении указателя, массива или функции;
- задает значение или последовательность значений,
присваиваемых переменной при об"явлении.
Все переменные Си должны быть явно об"явлены перед их
использованием. Функции Си могут быть об"явлены явно или неявно в случае
их вызова перед определением.
Язык Си определяет стандартное множество типов данных. К этому
множеству можно добавлять новые типы данных посредством их
об"явлений на типах данных уже определенных.
Об"явление Си требует одного или более деклараторов. Декларатор-
это идентификатор, который может быть определен с квадратными скобками
([]), эвездочкой (*) или круглыми скобками () для об"явления массива,
указателя или функции. Когда об'является простая переменная (такая как
символ, целое или плавающее), структура или совмещение простых
переменных, то декларатор- это идентификатор.
В Си определено четыре спецификатора класса памяти, а именно:
auto, extern, register и static.
Спецификатор класса памяти определяет, каким образом об"являемый
об"ект запоминается и инициализируется и из каких частей программы можно
ссылаться на него. Расположение об"явления внутри программы, а также
наличие или отсутствие других об"явлений- также важные факторы при
определении видимости переменных.
Об"явления функций описаны в разделе 4.4.
Спецификаторы типов
Язык Си поддерживает определения для множества базовых типов
данных, называемых "основными" типами. Названия этих типов перечислены в
Табл. 4.1.
------------------------------------------------------------
Типы целых Типы плавающих Другие типы
------------------------------------------------------------
signed char float void
signed int double
signed short intsigned long int
unsigned char
unsigned int
unsignet short int unsigned long int
-----------------------------------------------------------
Табл. 4.1. Основные типы.
Перечислимые типы также рассматриваются как основные типы.
Спецификаторы перечислимых типов рассмотрены в разделе 4.7.1. Типы signed
char, signed int, signed short int и signed long int
вместе с соответствующими двойниками unsigned называются типами целых.
Спецификаторы типов float и double относятся к типу "плавающих". В
об"явлениях переменых и функций можно использовать любые спецификаторы
"целый" и "плавающий".
Тип void может быть использован только для об"явления функций,
которые не возвращают значения. Типы функций рассмотрены в разделе 4.4.
Можно задать дополнительные спецификаторы типа путем об"явления
typedef, описанного в разделе 4.7.2.
При записи спецификаторов типов допустимы сокращения как показано
в табл. 4.2. В целых типах ключевое слово signed может быть опущено. Так,
если ключевое слово unsigned опускается в записи спецификатора типа, то
тип целого будет знаковым, даже если опущено ключевое слово signed.
В некоторых реализациях могут быть использованы опции компилятора,
позволяющие изменить умолчание для типа char со знакового на беззнаковый.
Когда задана такая опция, сокращение char имеет то же самое значение,
что и unsigned char, и следовательно ключевое слово sidned должно быть
записано при об"явлении символьной величины со знаком.
-----------------------------------------------------------
Спецификатор типа Сокращение
-----------------------------------------------------------
signed char char
signed int signed, int
signed short int short, signed short
signed long int long, signed long
unsigned char -
unsigned int unsigned
unsigned short int unsignet short
unsignet long int unsignet long
float -
long float double
------------------------------------------------------------
Табл. 4.2. Спецификаторы и сокращения
Замечание: в этом руководстве в основном используются сокращенные
формы, перечисленные в Табл. 4.2, при этом предполагается, что char по
умолчанию знаковый.
В табл. 4.3 для каждого типа приведены: размер распределяемой памяти
и области значений переменных для данного типа. Поскольку тип void не
представляет переменных, он не включен в эту таблицу.
-----------------------------------------------------------
Тип Представление Область значений
в памяти величины
-----------------------------------------------------------
char 1 байт -128 до 127
int зависит от
реализации
short 2 байта -32768 до 32767
long 4 байта -2.147.483.648 до 2.147.483.647
unsigned char 1 байт 0 до 255
unsigned зависит от
реализации
unsigned short 2 байта 0 до 65535
unsigned long 4 байта 0 до 4.294.967.295
float 4 байта IEEE стандартное
соглашение
double 8 байт IEEE стандартное
соглашение ------------------------------------------------------
------
Табл 4.3 Размер памяти и область значений типов
Тип char используется для запоминания буквы, цифры или символа из
множества представимых символов. Значением об"екта типа char является
ASCII код, соответствующий данному символу. Так как тип char
интерпретируется как однобайтовая целая величина с областью значений от
-128 до 127, то только величины от 0 до 127
имеют символьные эквиваленты. Аналогично, тип unsigned char может
запоминать величины с областью значений от 0 до 255.
Заметим, что представление в памяти и область значений для типов
int и unsigned int не определены в языке Си. По умолчанию размер int (со
знаком и без знака) соответствует реальному размеру целого на данной
машине. Например, на 16-ти разрядной машине тип int всегда 16 разрядов или
2 байта. На 32-ух разрядной машине тип int всегда 32 разряда или 4 байта.
Таким образом, тип int эквивалентен типам short int или long int в
зависимости от реализации.
Аналогично, тип unsigned int эквивалентен типам unsigned short
или unsigned long. Спецификаторы типов int и unsigned int широко
используются в программах на Си, поскольку они позволяют наиболее
эффективно манипулировать целыми величинами на данной машине.
Однако, размер типов int и unsigned int переменный, поэтому
программы, зависящие от специфики размера int и unsigned int могут быть
непереносимы. Переносимость кода можно улучшить путем включения выражений
с sizeof операцией.
Деклараторы
Синтаксис:
[]
[constant-expression>]
*
()
()
()
Си позволяет об"являть: массивы величин, указатели на величины, величины
возвратов функций. Чтобы об"явить эти об"екты, нужно использовать
декларатор, возможно модифицированный квадратными скобками ([]),
круглыми скобками () и звездочкой (*), что соответствует типам массива,
функции или указателя. Деклараторы появляются в об"явлениях указателей,
массивов и функций.
Деклараторы массивов, функций и указателей
Когда декларатор состоит из немодифицируемого идентификатора, то об'ект,
который об"является, имеет немодифицированный тип. Звездочка, которая может
появиться слева от идентификатора, модифицирует его в тип указателя.
Если за идентификатором следуют квадратные скобки ([]), то тип
модифицируется на тип массива. Если за идентификатором следуют круглые
скобки, то тип модифицируется на тип функции. Сам по себе декларатор не
образует полного об"явления. Для этого в об"явление должен быть включен
спецификатор типа. Спецификатор типа задает тип элементов массива или тип
адресуемых об"ектов и возвратов функции.
Следующие примеры иллюстрируют простейшие формы деклараторов:
1. int list[20]
2. char *cp
3. double func(void),
где:
1. Массив list целых величин
2. Указатель cp на величину типа char
3. Функция func без аргументов, возвращающая величину double
Составные деклараторы
Любой декларатор может быть заключен в круглые скобки. Обычно,
круглые скобки используются для спецификации особенностей интерпретации
составного декларатора. Составной декларатор- это идентификатор,
определяемый более чем одним модификатором массива, указателя или функции.
С отдельным идентификатором могут появиться различные комбинации
модификаторов массива, указателя или функции. Некоторые комбинации
недопустимы. Например, массив не может быть композицией функций, а функция
не может возвратить массив или функцию. При интерпретации составных
деклараторов квадратные и круглые скобки (справа от идентификатора) имеют
приоритет перед звездочкой (слева от идентификатора). Квадратные или
круглые скобки имеют один и тот же приоритет и рассматриваются слева
направо. Спецификатор типа рассматривается на последнем шаге, когда
декларатор уже полностью проинтерпретирован. Можно использовать круглые
скобки, чтобы изменить порядок интерпретации на необходимый в данном
случае.
При интерпретации составных деклараторов может быть предложено простое
правило, которое читается следующим образом: "изнутри- наружу". Нужно
начать с идентификатора и посмотреть вправо, есть ли квадратные или круглые
скобки. Если они есть, то проинтерпретировать эту часть декларатора,
затем посмотреть налево, если ли звездочка. Если на любой стадии справа
встретится закрывающая круглая скобка, то вначале необходимо применить
все эти правила внутри круглых скобок, а затем продолжить интерпретацию.
на последнем шаге интерпретируется спецификатор типа. В следующем примере
проиллюстрированы эти правила. Последовательность шагов при интерпретации
перенумерована.
char *(*(*var) ()) [10];
^ ^ ^ ^ ^ ^ ^
7 6 4 2 1 3 5
1. Идентификатор var об'явлен как
2. Указатель на
3. Функцию, возвращающую
4. Указатель на
5. Массив из 10 элементов, который состоит 6. Из указателей на
7. Величины типа char.
В следующих примерах показывается каким образом круглые скобки могут
поменять смысл об"явлений.
1. int *var[5]; - массив указателей на величины типа int.
2. int (*var)[5]; - указатель на массив величин типа int.
3. long *var(long,long); - функция, возвращающая указатель на величину
типа long.
4. long (*var) (long,long); - указатель на функцию, возвращающую
величину типа long.
5. struct both {
int a;
char b;
} ( *var[5] ) ( struct both, struct both); массив указателей на
функции, возвращающих структуры.
6. double ( *var( double (*) [3] ) ) [3];
функция, возвращающая указатель на массив из трех величин типа double.
7. union sign {
int x;
unsigned y;
} **var[5] [5];
массив массивов указателей на указатели совмещений.
8. union sign *(*var[5]) [5];
массив указателей на массив указателей на совмещения.
Описание примеров:
В первом примере, модификатор массива имеет высший приоритет, чем
модификатор указателя, так что var об"является массивом. Модификатор
указателя определяет тип элементов массива; элементами являются указатели
на величины типа int.
Во втором примере скобки меняют значение об"явления первого примера.
Теперь модификатор указателя имеет более высокий приоритет, чем модификатор
массива, и переменная var об"является как указатель на массив из пяти
величин типа int.
В третьем примере модификатор функции имеет более высокий приоритет,
чем модификатор указателя, так что переменная var об"является
функцией, возвращающей указатель на величину типа long. Функция
об"явлена с двумя аргументами типа long.
Четвертый пример похож на второй. Скобки задают более высокий приоритет
модификатору указателя, и поэтому переменная var об"является как
указатель на функцию, возвращающую величину типа long. По прежнему функция
об"явлена с двумя аргументами типа long.
Элементы массива не могут быть функциями. Взамен этому в пятом
примере показано, как об"явить массив указателей на функции. В этом
примере переменная var об"явлена как массив из пяти указателей на функции,
возвращающие структуры с двумя элементами. Оба аргумента функции об"явлены
как структуры типа both. Заметим, что круглые скобки, в которые заключено
выражение *var[5], обязательны. Без них об"явление будет неверным,
поскольку будет об"явлен массив функций:
/* ILLEGAL */
struct both *var[5] ( struct both, struct both );
В шестом примере показано, как об"являть функцию, возвращающую указатель
на массив. Здесь var об"явлена функцией, возвращающей указатель на массив
из трех величин типа double. Тип аргумента функции задан составным
абстрактным декларатором. Круглые скобки, заключающие звездочку, требуются,
так как в противном случае типом аргумента был бы массив из трех
указателей на величины типа double.
В седьмом примере показано, что указатель может указывать на другой
указатель и массив может состоять из массивов. Здесь var- это массив из
пяти элементов. Каждый элемент, в свою очередь, так же массив из пяти
элементов, каждый из которых является указателем на указатель совмещения,
состоящего из двух элементов.
В восьмом примере показано, как круглые скобки изменили
смысл об"явления. В этом примере var- это массив из пяти указателей на
массив из пяти указателей на совмещения.
Об"явления переменной
В этом разделе дано описание синтаксиса и семантики об"явлений
переменной. В частности, здесь об"ясняется каким образом об"явить
следующие переменные:
Тип переменной Описание
Простая переменная Переменная целого или плаваю-
щего типа.
Переменная перечис- Простая переменная целого типа
ления. которая принимает значения из
предопределенного набора зна-
чений поименованных констант. Структура Переменная, которой
соответс-
твует композиция отдельных переменных, типы
которых могут отличаться.
Совмещение Переменная, которой соответс-
твует композиция отдельных переменных,
занимающих одно и то же пространство
памяти. Типы переменных композиции могут
отличаться.
Массив Переменная, представляющая на-
бор элементов одного типа.
Указатель Переменная, которая указывает
на другую переменную (содержит
местоположение другой переменной в форме
адреса).
Общий синтаксис об"явлений переменных следующий:
[] [,...],
где - задает тип данных, представляемых переменной,
а - это имя переменной, возможно модифицированное для
об"явления массива или указателя. В об"явлении мо жет быть задана более чем
одна переменная путем задания множественного об"явления, в котором
деклараторы разделены запятыми. задает класс памяти
переменной. В некоторых случаях переменные могут быть инициализированы
при их определении. Классы памяти и инициализация описаны в разделах 4.6 и
4.7 соответственно.
Объявление простой переменной
Синтаксис:
[,...];
Об"явление простой переменной определяет имя переменной и ее тип;
оно может также определять класс памяти переменной, как это описано в
разделе 4.6. Имя переменной- это идентификатор, заданный в об"явлении.
Спецификатор типа задает имя определяемого типа данных.
Можно определить имена различных переменных в том же самом
об"явлении, задавая список идентификаторов, разделенных запятой. Каждый
идентификатор списка именует переменную. Все переменные, заданные в
об"явлении, имеют один и тот же тип.
Примеры
int x; /* Example 1 */
unsigned long reply, flag /* Example 2 */ double order;
/* Example 3 */
В первом примере об"является простая переменная x. Эта пе-
ременная может принимать любое значение из множества значений,
определяемых для типа int.
Во втором примере об"явлены две переменные: reply и flag. Обе
переменные имеют тип unsigned long.
В третьем примере об"явлена переменная order, которая имеет тип
double. Этой переменной могут быть присвоены величины с плавающей запятой.
Объявление перечисления
Синтаксис: enum[]{}[,...];
enum[,...];
Об"явление перечисления задает имя переменной перечисления и
определяет список именованных констант, называемый списком перечисления.
Значением каждого имени списка является целое число. Переменная
перечисления принимает значение одной из именованных констант списка.
Именованные константы списка имеют тип int. Та-
ким образом, память соответствующая переменной перечисления- это память,
необходимая для размещения отдельной целой величины.
Объявление перечисления начинается с ключевого слова enum и
имеет две формы представления. В первой форме представления имена
перечисления задаются в списке перечисления .
Опция - это идентификатор, который именует тип перечисления,
определенного в .
Переменную перечисления именует . В об"явлении может быть
описана более чем одна переменная перечисления.
Во второй форме используется тег перечисления, который ссы-
лается на тип перечисления. В этой форме об"явления список перечисления не
представлен, поскольку тип перечисления определен в другом месте. Если
задаваемый тег не ссылается на уже определенный тип перечисления, или если
именуемый тегом тип находится вне текущей видимости, то выдается ошибка.
имеет следующий синтаксис:
[=][,
[= переопределяет последовательность
значений, заданных по умолчанию. Идентификатор, следующий перед записью
= принимает значение, задаваемое этим константным
выражением. Константное выражение имеет тип int и может быть отрицательным.
Следующий идентификатор в списке ассоциируется с величиной, равной
+1, если он явно не задается другой величиной.
Перечисление может содержать повторяющиеся значения идентификаторов, но
каждый идентификатор должен быть уникальным. Кроме того, он должен быть
отличным от всех других идентификаторов перечислений с той же видимостью.
Например, двум различным идентификаторам null и zero может быть задано
значение 0 в одном и том же перечислении. Идентификаторы должны быть
отличны от других идентификаторов с той же самой видимостью, включая
имена обычных переменных и идентификаторы других перечислений. Теги
перечислений должны быть отличны от тегов перечислений, тегов структур и
совмещений с той же самой видимостью.
Примеры:
/**************** Example 1 ***************/
enum day {
saturday,
sunday = 0,
monday,
tuesday,
wednesday,
thursday,
friday
} workday;
/***************** Example 2 ***************/
enum day today = wednesday;
В первом примере определяется тип перечисления, поименованный day и
об"является переменная workday этого типа перечисления. С saturday по
умолчанию ассоциируется значение 0. Идентификатор sunday явно
устанавливается в 0. Оставшиеся идентификаторы по умолчанию принимают
значение от 1 до 5.
Во втором примере переменной today типа enum day присваивается значение
из перечисления. Заметим, что для присваивания используется имя константы
из перечисления. Так как тип перечисления day был предварительно
об"явлен, то достаточно сослаться только на тег перечисления.
Объявления структур
Синтаксис: struct[]{}[,...];
struct[,...];
Об"явление структуры задает имя типа структуры и специфицирует
последовательность переменных величин, называемых элементами структуры,
которые могут иметь различные типы.
Об"явление структуры начинается с ключевого слова struct и имеет
две формы представления, как показано выше. В первой форме
представления типы и имена элементов структуры специфицируются в
списке об"явлений элементов . - это
идентификатор, который именует тип структуры, определенный в
списке об"явлений элементов.
Каждый задает имя переменной типа структуры. Тип
переменной в деклараторе может быть модифицирован на указатель к
структуре, на массив структур или на функцию, возвращающую структуру.
Вторая синтаксическая форма использует тег- структуры для
ссылки на тип структуры. В этой форме об"явления отсутствует список
об"явлений элементов, поскольку тип структуры определен в другом
месте. Определение типа структуры должно быть видимым для тега, который
используется в об"явлении и определение должно предшествовать
об"явлению через тег, если тег не используется для об"явления
указателя или структурного типа typedef. В последних случаях
об"явления могут использовать тег структуры без предварительного
определения типа структуры, но все же определение должно находиться в
пределах видимости об"явления.
Список об"явлений элементов - это одно
или более об"явлений переменных или битовых полей. Каждая
переменная, об"явленная в этом списке, называется элементом
структурного типа. Об"явления переменных списка имеют тот же самый
синтаксис, что и об"явления переменных обсуждаемых в этой главе,
за исключением того, что об"явления не могут содержать
спецификаторов класса памяти или инициализаторов. Элементы структуры
могут быть любого типа: основного, массивом, указателем,
совмещением или структурой.
Элемент не может иметь тип структуры, в которой он появляется.
Однако, элемент может быть об"явлен, как указатель на тип структуры,
в которую он входит, позволяя создавать списочные структуры.
Битовые поля
Об"явления битовых полей имеют следующий синтаксис:
[]:; Битовое
поле состоит из некоторого числа бит, специфициро-
ванных константным выражением- . Для бито-
вого поля спецификатор типа должен специфициро-
вать беззнаковый целый тип, а константное выражение должно быть
неотрицательной целой величиной. Массивы битовых полей, указатели на
битовые поля и функции, возвращающие битовые поля не допускаются.
Идентификатор- именует битовое поле. Неименованное битовое
поле, чей размер специфицируется как нулевой, имеет специальное
назначение: оно гарантирует, что память для следующей переменной
об"явления будет начинаться на границе int.
Идентификаторы элементов внутри об"являемой структуры должны быть
уникальными. Идентификаторы элементов внутри разных структур могут
совпадать. В пределах той же самой видимости теги структур должны
отличаться от других тегов (тегов других структур, совмещений и
перечислений).
Переменные (элементы) структуры запоминаются последовательно в том же
самом порядке, в котором они об"являются: первой переменной соответствует
самый младший адрес памяти, а последнейсамый старший. Память каждой
переменной начинается на границе свойственной ее типу. Поэтому могут
появляться неименованные участки между соседними элементами.
Битовые поля не располагаются на пересечении границ, обявленных для них
типов. Например, битовое поле, об"явленое с типом unsigned int,
упаковывается или в пространстве, оставшимся от предидущего unsigned int
или начиная с нового unsigned int.
Примеры
/**************** Example 1 ****************/
struct {
float x,y;
} complex;
/**************** Example 2 *****************/
struct employee {
char name[20];
int id;
long>
} temp;
/**************** Example 3 ******************/
struct employee student, faculty, staff;
/**************** Example 4 ******************/ struct sample {
char c;
float *pf;
struct sample *next;
} x;
/***************** Example 5 ******************/
struct {
unsigned icon : 8;
unsigned color : 4; unsigned underline : 1; unsigned
blink : 1;
} screen[25][80];
В первом примере об"является переменная с именем complex типа
структура. Эта структура состоит из двух элементов x и y типа float. Тип
структуры не поименован.
Во втором примере об"является переменная с именем temp типа структура.
Структура состоит из трех элементов с именами name, id и >
с именем name- это массив иэ 20- ти элементов типа char. элементы с именами
id и>
Идентификатор employee является тегом структуры.
В третьем примере об"явлены три переменных типа структура с именами:
student, faculty и staff. Каждая из структур состоит из трех элементов
одной и той же конструкции. Элементы определены при об"явлении типа
структуры с тегом employee в предыдущем примере.
В четвертом примере об"является переменная с именем x типа
структура. Первые два элемента структуры представлены переменной c
типа char и указателем pf на величину типа float. Третий элемент с
именем next об"являются как указатель на описываемую структуру
sample.
В пятом примере об"является двумерный массив поименованный screen,
элементы которого имеют структурный тип. Массив состоит из 2000
элементов и каждый элементэто отдельная структура, состоящая из
четырех элементов типа bit-fild с именами icon, color, underline и
blink.
Об"явление совмещений
Синтаксис: union[]{}[,...];
union[,...];
Об"явление совмещения определяет имя переменной совмещения и
специфицирует множество переменных, называемых элементами совмещения,
которые могут быть различных типов. Переменная с типом совмещения
запоминает любую отдельную величину, определяемую набором элементов
совмещения.
Об"явление совмещения имеет тот же самый синтаксис, как и
об"явление структуры, за исключением того, что она начинается с
ключевого слова union вместо ключевого слова struct. Для об"явления
совмещения и структуры действуют одни и те же правила, за исключением
того, что в совмещении не допускаются элементы типа битовых полей.
Память, которая соответствует переменной типа совмещение,
определяется величиной для размещения любого отдельного элемента
совмещения.
Когда используется наименьший элемент совмещения, то переменная
типа совмещения может содержать неиспользованное пространство. Все
элементы совмещения запоминаются в одном и том же пространстве памяти
переменной, начиная с одного и того же адреса. Запомненные значения
затираются каждый раз, когда присваивается значение очередного
элемента совмещения.
Примеры:
/************** Example 1 ********************/
union sign {
int svar;
unsigned uvar;
} number;
/************** Example 2 ********************/
union {
char *a, b;
float f[20];
} jack;
/*************** Example 2 *******************/
union {
struct {
char icon;
unsigned color : 4;
} window1, window2, window3, window4;
} screen[25][80];
В первом примере об"является переменная типа совмещения,
поименованная number. Список элементов совмещения состоит из двух
об"явлений переменных: svar типа int и uvar типа unsigned. Это
об"явление позволяет запоминать текущее значение number в знаковом или
беззнаковом виде. Тип совмещения поименован идентификатором sign.
Во втором примере об"является переменная типа совмещения с именем jack.
Список элементов об"явления состоит из трех об"явлений: указателя a на
величину типа char, переменной b типа char и массива f из 20 элементов типа
float. Тип совмещения не поименован.
Память, распределенная для переменной jack, равна памяти,
распределенной под массив f, поскольку f самый большой элемент
совмещения.
В третьем примере об"является двумерный массив совмещений с именем
screen. Массив состоит из 2000 об"ектов. Каждый об"ектэто отдельное
совмещение из четырех элементов: window1, window2, window3, window4, где
каждый элемент- это структура. В любое заданное время каждый об"ект
совмещения поддерживается одним из четырех возможных элементов типа
структура. Таким образом, переменная screen- это композиция четырех
возможных "windows".
Об"явление массива
Синтаксис: []; [];
Здесь квадратные скобки- это терминальные символы. Об"явление массива
определяет тип массива и тип каждого элемента. Оно может определять
также число элементов в массиве. Переменная типа массив рассматривается
как указатель на элементы массива. Об"явление массива может представляться
в двух синтаксических формах, указанных выше. Декларатор
задает имя переменной. Квадратные скобки, следующие за декларатором,
модифицируют декларатор на тип массива. Константное
выражение
, заключенное в квадратные скобки, определяет число
элементов в массиве. Каждый элемент имеет тип, задаваемый спецификатором
типа , который может специфицировать любой тип, исключая
void и тип функции.
Во второй синтаксической форме опущено константное выражение в
квадратных скобках. Эта форма может быть использована только тогда, когда
массив инициализируется или об"явлен как формальный параметр или об"явлен
как ссылка на массив, явно определенный где-то в программе.
Массив массивов или многомерный массив определяется путем задания
списка константных выражений в квадратных скобках, следущего за
декларатором:
[]
[]...
Каждое константное выражение- в квадратных скобках
определяет число элементов в даннном иэмерении
массива, так что об"явление двумерного массива содержит два константных
выражения, трехмерного- три и т.д. Если многомерный массив об"является
внутри функции или если он инициализируется либо об"является как
формальный параметр или об"является как ссылка на
массив, явно определенный где- то в программе, то первое константное
выражение может быть опущено.
Массив указателей на величины,заданного типа, может быть определен
посредством составного декларатора, как было описано в разделе 4.3.2.
Типу массив соответствует память, которая требуется для размещения
всех его элементов. Элементы массива с первого до последнего запоминаются в
последовательных возрастающих адресах памяти. Между элементами массива в
памяти разрывы отсутствуют. Элементы массива запоминаются друг за другом
построчно. Например, массив, содержащий две строки с тремя столбцами
каждая,
char A[2][3]
будет запомнен следующим образом. Сначала запоминаются три столбца
первой строки, затем элементы трех столбцов второй строки. Смысл этого в
том, чтобы последний индекс был более быстрым. Чтобы сослаться на
отдельный элемент массива, нужно использовать индексное выражение, которое
описано в разделе 5.2.5.
Примеры:
/*************** Example 1 ******************/
int scores[10], game;
/*************** Example 2 ******************/
float matrix[10][15];
/*************** Example 3 ******************/
struct {
float x,y;
} complex[100];
/*************** Example 4 *******************/
char *name[20];
В первом примере об"является переменная типа массив с именем scores
из 10 элементов типа int. Переменная с именем game об"явлена как простая
переменная целого типа.
Во втором примере об"является двумерный массив с именем matrix.
Массив состоит из 150-ти элементов типа float.
В третьем примере об"является массив структур. Массив состоит из 100
об"ектов. Каждый об"ект массива представляет собой структуру, состоящую
из двух элементов.
В четвертом примере об"явлен массив указателей. Массив состоит из 20-ти
элементов, каждый из которых является указателем на величину типа char.
4.4.6. Об"явление указателей
Синтаксис:
*;
Об"явление указателя определяет имя переменной типа указатель и тип
об"екта, на который указывает эта переменная. Декларатор-
определяет имя переменной с возможной модификацией ее типа. Спецификатор
типа- задает тип об"екта, который может быть
базового типа, типа структуры или совмещения.
Переменная типа указатель может указывать также на функции, массивы и
другие указатели. Более полная информация о типах указателей дана в
разделе 4.3.2. "Составные деклараторы".
Если указатель не используется до определения типа структуры или
совмещения, то он может быть об"явлен ранее этого определения. Такие
об"явления допускаются, поскольку компилятору не требуется знать
размера структуры или совмещения, чтобы распределить память под
переменную типа указатель. Указатель может быть об"явлен посредством
использования тега структуры или совмещения (смотри ниже пример 4).
Переменная, об"явленная как указатель, хранит адрес памяти. Размер
памяти, требуемый для адреса, и смысл адреса зависит от данной
конфигурации машины. Указатели на различные типы не обязательно имеют одну
и ту же длину.
Для некоторых реализаций используются специальные ключевые слова near,
far и huge, чтобы модифицировать размер указателя. Об"явления,
использующие специальные ключевые слова, были описаны в разделе 4.3.3.
Информация о смысле ключевых слов дана в системной документации.
Примеры:
char *message; /* Example 1 */
int *pointers[10]; /* Example 2 */ int
(*pointer)[10]; /* Example 3 */ struct list *next,
*previous; /* Example 4 */
struct list { /* Example 5 */
char *token;
int count;
struct list *next;
} line;
struct id { /* Example 6 */ unsigned int
id_no;
struct name *pname;
} record;
В первом примере об"является переменная- указатель поименованная
message. Она указывает на величину типа char.
Во втором примере об"явлен массив указателей, поименованный pointers.
Массив состоит из 10 элементов. Каждый элемент- это указатель на
переменную типа int.
В третьем примере об"явлена переменная- указатель, поименованная
pointer. Она указывает на массив из 10 элементов. Каждый элемент в этом
массиве имеет тип int.
В четвертом примере об"явлены две переменныхуказателя, которые
ссылаются на величины структурного типа list (смотри следующий пример).
Определение типа с именем list должно находиться в пределах видимости
об"явления.
В пятом примере об"является переменная с именем line, структурного
типа, поименованного list. Тип структуры с именем list определяется
тремя элементами. Первый элементэто указатель на величину типа char,
второй- на величину типа int, а третийэто указатель на следующую структуру
типа list.
В шестом примере об"является переменная с именем record, имеющая тип
структуры с именем id. Заметим, что третий элемент с именем pname об"явлен
как указатель на другой тип структуры с именем name. Это об"явление
может появиться перед об"явление структуры с именем name.
Об"явление функций
Синтаксис:
[]([])[,...];
Об"явление функции определяет имя, тип возврата функции и, возможно,
типы и число ее аргументов. Об"явление функции также называется
forward- об"явлением. Декларатор функции об"являет имя функции, а
спецификатор типа задает тип возврата. Если спецификатор типа опущен
в об"явлении функции, то предполагается, что функция возвращает
величину типа int.
Об"явление функции может включать спецификаторы класса памяти extern
или static.
Список типов аргументов.
Список типов аргументов- определяет число и типы
аргументов функции. Синтаксис списка аргументов следующий:
[,...]
Список имен типов- это список из одного или более имен типов. Каждое
имя типа отделяется от другого запятой. Первое имя типа задает тип
первого аргумента, второе имя типа задает тип второго аргумента и т. д.
Если список имен типов заканчивается запятой с многоточием (,...), то
это означает, что число аргументов функции переменно. Однако,
предполагается, что функция будет иметь не меньше аргументов, чем имен
типов, предшествующих многоточию.
Если список типов аргументов- содержит
только многоточие (...), то число аргументов функции является пе-
ременным или равно нулю.
Замечание:
Чтобы поддержать совместимость с программами предидущих версий,
компилятор допускает символ запятой без многоточия в конце списка типов
аргументов для обозначения их переменного числа. Запятая может быть
использована и вместо многоточия для об"явления нуля или более
аргументов функции. Использование запятой поддерживается только для
совместимости. Использование многоточия рекомендуется для нового
представления.
Имя типа- для типов структуры, совмещения или базового
типа состоит из спецификатора этого типа (такого как int ). Имена типов
для указателей, массивов и функций формируются путем комбинации
спецификатора типа с "абстрактным декларатором". Абстрактный
декларатор- это декларатор без идентификатора. В разделе 4.9 "Имена
типов" об"ясняется, каким об"разом формировать и интерпретировать
абстрактные деклараторы.
Для того чтобы об"явить функцию, не имеющую аргументов, может быть
использовано специальное ключевое слово void на месте списка типов
аргументов. Компилятор вырабатывает предупреждающее сообщение, если в
вызове такой функции будут специфицированы аргументы.
Еще одна специальная конструкция допускается в списке типов
аргументов. Это фраза void *, которая специфицирует аргумент типа
указатель. Эта фраза может быть использована в списке типов аргументов
вместо имени типа.
Список типов аргументов может быть опущен. В зтом случае скобки
после идентификатора функции все же требуются, хотя они и пусты. В
этом случае в об"явлении функции не определяются ни типы, ни число
аргументов в функции. Когда эта информация опускает-
ся, то компилятор не проверяет соответствия между формальными и
фактическими параметрами при вызове функции. Более подробная информация
дана в разделе 7.4 "Вызовы функций".
Тип возврата
Функции могут возвращать величины любого типа за исключением массивов и
функций. Для этого посредством спецификатора типа"type-specifier" в
об"явлении функции можно специфицировать любой тип: основной, структуру
или совмещение. Идентификатор функции может быть модифицирован одной или
несколькими звездочками (*), чтобы об"явить возвращаемую величину типа
указателя.
Хотя функции и не допускают возвратов массивов или функций, но они
могут возвращать указатели на массивы или функции. Функции, которые
возвращают указатели на величины типа массив или функция, об"являются
посредством модификации идентификатора функции квадратными скобками,
звездочкой и круглыми скобками, чтобы сформировать составной декларатор.
Формирование и интерпретация составных деклараторов рассматривались в
разделе 4.3.2.
Примеры:
int add(int, int); /* Example 1 */
double calc(); /* Example 2 */
char *strfind(char *,...); /* Example 3 */
void draf(void); /* Example 4 */
double (*sum(double, double)) [3]; /* Example 5 */ int
(*select(void)) (int) ; /* Example 6 */
char *p; /* Example 7 */
short *q;
int prt(void *);
В первом примере об"является функция, поименованная add, которая
требует два аргумента типа int и возвращает величину типа int.
Во втором примере об"является функция, поименованная calc, которая
возвращает величину типа double. Список типов аргументов не задан. В
третьем примере об"является функция, поименованная strfind, которая
возвращает указатель на величину типа char. Функция требует, по крайней
мере один аргументуказатель на величину типа char. Список типов аргументов
заканчивается запятой с многоточием, обозначающим, что функция может
потребовать большее число аргументов.
В четвертом примере об"является функция с типом возврата void (нет
возвращаемой величины). Список типов аргументов также void, означающий
отсутствие аргументов для этой функции.
В пятом примере sum об"является как функция, возвращающая указатель на
массив из трех величин типа double. Функция sum требует два аргумента,
каждый из которых является величиной типа double.
В шестом примере функция, поименованная select, об"явлена без
аргументов и возвращает указатель на функцию. Указатель возврата ссылается
на функцию, требующую один аргумент типа int и возвращающую величину
типа int.
В седьмом примере об"явлена функция prt, которая требует аргумент-
указатель любого типа, и которая возвращает величину типа int. Любой
указатель p или q могли бы быть использованы как аргументы функции без
выдачи при этом предупреждающего сообщения.
Классы памяти
Класс памяти переменной, которая определяет какой либо об"ект,
имеет глобальное или локальное время жизни. Об"ект с глобальным временем
жизни существует и имеет значение на протяжении всей программы. Все функции
имеют глобальное время жизни.
Переменные с локальным временем жизни захватывают новую па-
мять при каждом выполнении блока, в котором они определены. Когда
управление на выполнение передается из блока, то переменная теря-
ет свое значение.
Хотя Си определяет два типа классов памяти, но, тем не менее, имеется
следующих четыре спецификатора классов памяти:
auto
register
static
extern
Об"екты классов auto и register имеют локальное время жизни.
Спецификаторы static и extern определяют об"екты с глобальным временем
жизни. Каждый из спецификаторов класса памяти имеет определенный смысл,
который влияет на видимость функций и переменных в той же мере, как и
сами классы памяти. Термин "видимость" относится к той части программы, в
которой могут ссылаться друг на друга функции и переменные. Об"екты с
глобальным временем жизни существуют на протяжении выполнения исходной
программы, но они могут быть видимы не во всех частях программы.
Видимость и связанная с ней концепция времени жизни рассмотрена в разделе
3.5.
Месторасположение об"явления переменной или функции внутри
исходных файлов также влияют на класс памяти и видимость. Говорят, что
об"явления вне определения всех функций и переменных относятся к внешнему
уровню, а об"явления внутри определений функций относятся к внутреннему
уровню.
Точный смысл каждого спецификатора класса памяти зависит от того,
находится ли об"явление на внешнем или внутреннем уровне и от того,
об"явлен ли об"ект функцией или переменной. В следующем разделе
описывается смысл спецификаторов класса памяти в каждом случае об"явления,
а также об"ясняется режим умолчания, когда спецификатор класса памяти
опущен при об"явлении переменной или функции.
Об"явления переменной на внешнем уровне
Об"явления переменной на внешнем уровне используют спецификаторы класса
памяти static и extern или совсем опускают их. Спецификаторы класса памяти
auto и register не допускаются на внешнем уровне.
Об"явления переменных на внешнем уровне- это определения переменных
или ссылки на определения, сделанные в другом месте.
Об"явление внешней переменной, которое инициализирует эту
переменную (явно или неявно), называется определением этой переменной.
Определение на внешнем уровне может задаваться в следующих различных
формах:
-переменная на внешнем уровне может быть определена путем ее
об"явления со спецификатором класса памяти static. Такая переменная может
быть явно инициализирована константным выражением. Если инициализатор
отсутствует, то переменная автоматически инициализируется нулем во время
компиляции. Таким образом, об"явления static int k = 16; и static int
k; оба рассматриваются как определения;
-переменная определяется, когда она явно инициализируется
на внешнем уровне. Например, int j = 3; это определение переменной.
Так как переменная определяется на внешнем уровне, то она видима в
пределах остатка исходного файла, от места, где она определена. Переменная
не видима выше своего определения в том же самом исходном файле ни в
других исходных файлах программы, если не об"явлена ссылка, которая делает
ее видимой.
Переменная может быть определена на внешнем уровне внутри исходного
файла только один раз. Если задается спецификатор класса памяти static, то
в других исходных файлах могут быть определены переменные с тем же
именем. Так как каждое определение static видимо только в пределах
своего собственного исходного файла, то конфликта не возникнет.
Спецификатор класса памяти extern используется для об"явления ссылки на
переменную, определенную где-то в другом месте. Такие об"явления
используются в случае, когда нужно сделать видимым определение переменной в
других исходных файлах или выше места, где она определена в том же самом
исходном файле. Так как ссылка на переменную об"явлена на внешнем уровне,
то переменная видима в пределах остатка исходного файла от места об"явления
ссылки.
В об"явлениях, которые используют спецификатор класса памяти extern,
инициализация не допускается, так как они ссылаются на переменные, чьи
величины уже определены.
Переменная, на которую делается ссылка extern, должна быть определена
на внешнем уровне только один раз. Определение может быть сделано в любом
из исходных файлов, составляющих программу.
Есть одно исключение из правил, описанных выше. Можно опус-
тить из об"явления переменной на внешнем уровне спецификатор класса
памяти и инициализатор. Например, об"явление int n; будет правильным
внешним об"явлением. Это об"явление имеет два различных смысла в
зависимости от контекста.
1. Если где-нибудь в программе будет определена на внешнем уровне
переменная с тем же именем, то об"явление является ссылкой на эту
переменную, как если бы был использован спецификатор класса памяти extern в
об"явлении.
2. Если нет такого определения, то об"явленной переменной
распределяется память во время линкования и переменная инициализируется
нулем. Если в программе появится более чем одно такое об"явление, то
память распределится для наибольшего размера из об"явленных переменных.
Например, если программа содержит два неинициализированных об"явления
переменной i на внешнем уровне int i; и char i; то память во время
линкования распределится под переменную i типа int.
Неинициализированные об"явления переменной на внешнем уровне не
рекомендуются для файлов, которые могут быть размещены в библиотеку.
Пример:
/*****************************************************
SOURCE FILE ONE
*****************************************************/
extern int i; /* reference to i
defined below */
main()
{
i++;
printf("%d\n", i); /* i equals 4 */
next();
}
int i = 3; /* definition of i */
next()
{
i++;
printf("%d\n", i); /* i equals 5 */
other();
}
/***************************************************** SOURCE FILE
TWO
*****************************************************/
extern int i; /* reference to i in
first source file */
other()
{
i++;
printf("%d\n", i); /* i equals 6 */
}
Два исходных файла в совокупности содержат три внешних об"явления
i. Одно об"явление содержит инициализацию- int i = 3; , где глобальная
переменная i определена с начальным значением равным 3.
Самое первое об"явление extern в первом файле делает глобальную
переменную видимой выше ее определения в файле.
Без об"явления extern функция main не смогла бы сослаться на
глобальную переменную i. Об"явление extern переменной i во втором
исходном файле делает глобальную переменную видимой в этом исходном файле.
Все три функции выполняют одну и ту же задачу: они увеличивают i на 1 и
печатают получившееся значение. (Предполагается, что функция printf
определена где-то еще в программе.). Печатаются величины равные 4, 5 и 6.
Если бы переменная i не была бы инициализирована,она бы была
автоматически установлена в 0 при линковании. В этом случае напечатанные
значения были бы равны 1, 2 и 3.
Об"явление переменной на внутреннем уровне
Любой из четырех спецификаторов класса памяти может быть использован
для об"явления переменной на внутреннем уровне. Если спецификатор класса
памяти опускается в об"явлении переменной на внутреннем уровне, то
подразумевается класс памяти auto.
Спецификатор класса памяти auto об"являет переменную с ло-
кальным временем жизни. Переменная видима только в том блоке, где она
об"явлена. Об"явления переменных auto могут включать инициализаторы.
Переменные класса памяти auto автоматически не инициализируются, а
инициализируются явно при об"явлении или присваивании начальных значений,
посредством операторов внутри блока. Если нет инициализации, то величина
переменной auto считается неопределенной.
Спецификатор класса памяти register сообщает компилятору о том, чтобы
он распределил память под переменную в регистре, если это возможно.
Использование регистровой памяти обычно приводит к более быстрому времени
доступа и к меньшему размеру результирующего кода. Переменные, об"явленные
с классом памяти register име-
ют ту же самую видимость, что и переменные auto.
Число регистров, которое может быть использовано под память переменных,
зависит от машины. Когда компилятор встречает спецификатор класса памяти
register в об"явлении, а свободного регистра не имеется, то для переменной
распределяется память класса auto. Компилятор назначает переменным
регистровую память в том порядке, в котором появляются об"явления в
исходном файле. Регистровая память, если она имеется, гарантирована только
для целого и адресного типов.
Переменная, об"явленная на внутреннем уровне со спецификатором класса
памяти static,имеет глобальное время жизни и имеет видимость только
внутри блока, в котором она об"явлена. В отличие от переменных auto,
переменные, об"явленные как static, сохраняют свое значение при завершении
блока.
Переменные класса памяти static могут быть инициализированы константным
выражением. Если явной инициализации нет, то переменная класса памяти
static автоматически устанавливается в 0. Инициализация выполняется один
раз во время компиляции. Инициализация переменной класса памяти static не
повторяется при новом входе в блок.
Переменная, об"явленная со спецификатором класса памяти extern,
является ссылкой на переменную с тем же самым именем, определенную на
внешнем уровне в любом исходном файле программы.
Цель внутреннего об"явления extern состоит в том, чтобы
сделать определение переменной внешнего уровня видимой внутри блока.
Внутреннее об'явление extern не изменяет видимость глобальной переменной
в любой другой части программы.
Пример:
int i = 1;
main()
{ /* reference to i, defined above */
extern int i;
/* initial value is zero; a is
visible only within main */
static int a;
/* b is stored in a register, if possible */ register int b
= 0;
/* default storage>
int c = 0;
/* values printed are 1, 0, 0, 0 */
printf("%d\n%d\n%d\n%d\n", i, a, b, c);
other();
}
other()
{
/* i is redefined */
int i = 16;
/* this a is visible only within other */
static int a = 2;
a += 2;
/* values printed are 16, 4 */
printf("%d\n%d\n", i, a);
}
Переменная i определяется на внешнем уровне с инициализацией 1. В
функции main об"явлена ссылка extern на переменную i внешнего уровня.
Переменная класса памяти static автоматически
устанавливается в 0, так как инициализатор опущен. Вызов функции print
(предполагается, что функция print определена в каком-то месте исходной
программы.) печатает величины 1, 0, 0, 0.
В функции other, переменная i переопределяется как локальная переменная
с начальным значением 16. Это не влияет на значение внешней переменной i.
Переменная a об"является как переменная класса памяти static с начальным
значением 2. Она не противоречит переменной a, об"явленной в функции main,
так как видимость переменных класса памяти static на внутреннем уровне
ограничена блоком, в котором она об"явлена.
Значение переменной увеличивается на 2 и становится равным 4. Если бы
функция other была вызвана снова в той же самой программе, то начальное
значение a стало бы равным 4. Внутренние переменные класса памяти static
сохраняют свои значения, когда заканчивается выполнение блока, в котором
они об"явлены.
Об"явление функции на внешнем и внутреннем уровнях
Функции могут быть об"явлены со спецификаторами класса памяти static
или extern. Функции всегда имеют глобальное время жизни.
Правила видимости для функций отличаются от правил видимости для
переменных. Об"явления функций на внутреннем уровне имеют тот же самый
смысл, что и об"явления на внешнем уровне. Это значит, что функции не могут
иметь блочной видимости и видимость функций не может быть вложенной.
Функция об"явленная как static,
видима только в пределах исходного файла, в котором она определяется. Любая
функция в том же самом исходном файле может вызвать функцию static, но
функции static из других файлов нет. Функция static с тем же самым именем
может быть об"явлена в другом исходном файле.
Функции, об"явленные как extern видимы в пределах всех исходных
файлов, которые составляют программу. Любая функция может вызвать функцию
extern.
Об"явления функций, в которых опущен спецификатор класса памяти,
считаются по умолчанию extern.
Инициализация
В об"явлении переменной может быть присвоено начальное значение
посредством инициализатора. Величина или величины инициализатора
присваиваются переменной.
Синтаксически, записи инициализатора предшествует знак равно (=)
=
Могут быть инициализированы переменные любого типа. Функции не
инициализируются. Об"явления, которые используют спецификатор класса памяти
extern не могут содержать инициализатора.
Переменные, об"явленные на внешнем уровне, могут быть ини-
циализированы. Если они явно не инициализированы, то они устанавливаются в
нуль во время компиляции или линкования. Любая переменная, об"явленная со
спецификатором класса памяти static, может быть инициализирована
константным выражением. Инициализация переменных класса static выполняется
один раз во время компиляции. Если отсутствует явная инициализация, то
переменные класса памяти static автоматически устанавливаются в нуль.
Инициализация переменных auto и register выполняется каждый раз при
входе в блок, в котором они об"явлены. Если инициализатор опущен в
об"явлении переменной класса памяти auto или register, то начальное
значение переменной не определено. Инициализация
составных типов auto (массив, структура, совмещение) запрещена. Любое
составное об"явление класса памяти static может быть инициализировано на
внешнем уровне.
Начальными значениями для внешних об"явлений переменной и для всех
переменных static как внешних так и внутренних должно быть константное
выражение. Автоматические и регистровые переменные могут быть
инициализированы константными или переменными величинами.
Базовые типы и типы указателей
Синтаксис:
=
Величина выражения присваивается переменной. Для выражения допустимы
правила преобразования.
Примеры:
int x = 10; /* Example 1 */
register int *px = 0; /* Example 2 */ int c = (3 * 1024);
/* Example 3 */ int *b = &x; /* Example 4
*/
В первом примере x инициализируется константным выражением 10. Во
втором примере, указатель px инициализирован нулем, в результате чего
получился "null" указатель. В третьем примере используется константное
выражение для инициализации c. В четвертом примере инициализируется
указатель b адресом другой переменной x.
Составные типы
Синтаксис:
={}
Список инициализаторов - это последовательность
инициализаторов, разделенных запятыми. Каждый инициализатор в
последовательности- это либо константное выражение, либо список
инициализаторов. Поэтому, заключенный в фигурные скобки список, может
появиться внутри другого списка инициализации. Эта конструкция
используется для инициализации элементов составных конструкций.
Для каждого списка инициализации значения константных выражений
присваиваются в порядке следования элементов составной переменной. Когда
инициализируется совмещение, то список инициализаторов представляет собой
единственное константное выражение. Величина константного выражения
присваивается первому элементу совмещения.
Если в списке инициализации меньше величин, чем их имеется в составном
типе, то оставшиеся памяти инициализируются нулем. Если число
инициализирующих величин больше чем требуется, то выдается ошибка.
Эти правила применяются к каждому вложенному списку инициализаторов,
точно так же как и ко всей конструкции в целом.
Пример:
int p[4] [3] = {
{ 1, 1, 1 },
{ 2, 2, 2 }, { 3, 3, 3,}, { 4, 4, 4,},
};
В примере об"является массив p размерности 4 строки на 3 столбца.
Элементы первой строки инициализируются 1, второй строки
2 и т. д. Заметим, что списки инициализаторов третьей и четвертой строк
заканчиваются запятой. Последний список инициализаторов { 4, 4, 4,} также
заканчивается запятой.
Эти дополнительные запятые допускаются, но не требуются. Требуются
только те запятые, которые разделяют константные выражения и списки
инициализации. Если список инициализаторов не структурирован под
составной об"ект, то его величины присваиваются в том порядке, в котором
подстыкованы элементы об"екта. Поэтому вышеприведенная инициализация
эквивалентна следующей:
int p[4] [3] = {
1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4
};
Фигурные скобки могут также появляться вокруг индивидуальных
инициализаторов в списке.
Когда инициализируются составные переменные, то нужно позаботиться о
том, чтобы правильно использовать фигурные скобки и списки
инициализаторов. В следующем примере иллюстрируется более детально
интерпретация компилятором фигурных скобок.
typedef struct {
int n1, n2, n3;
} triplet;
triplet nlist[2] [3] = {
{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }, /* Line 1 */ { {
10,11,12}, { 13,14,15}, { 15,16,17} } /* Line 2 */
};
В примере nlist об"является как массив структур, состоящий из двух
строк и трех столбцов. Каждая структура состоит из трех элементов.
Первая строка инициализации назначает величины первой строке массива nlist
следующим образом:
1. Первая левая фигурная скобка Line 1 информирует компилятор о том, что
это начало инициализации первой строки массива nlist(nlist[0]).
2. Вторая левая фигурная скобка означает то, что начинается
инициализация первого элемента первой строки массива ( nlist[0] [0] ).
3. Первая правая фигурная скобка сообщает об окончании инициализации
первого элемента- структуры nlist[0] [0]. Следующая левая фигурная
скобка сообщает о начале инициализации второго элемента первой строки
nlist[0] [1].
4. Процесс продолжается до конца Line 1 и заканчивается по последней
правой фигурной скобке.
Аналогично, Line 2 назначает величины второй строке массива nlist.
Заметим, что внешние фигурные скобки инициализаторов Line 1 и Line 2
требуются. Следующая конструкция, в которой внешние фигурные скобки опущены
будет неверной.
/* THIS CAUSES AN ERROR */
triplet nlist[2] [3] = {
{ 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, /* Line 1 */
{ 10,11,12}, { 13,14,15}, {16,17,18 } /* Line 2 */
};
В этом примере первая левая фигурная скобка в Line 1 стартует
инициализацию nlist[0], которая является массивом из трех структур.
Величины 1, 2, 3 назначаются трем элементам первой структуры. Когда
встретится правая фигурная скобка (после величины 3), инициализация
nlist[0] закончится и две оставшиеся структуры автоматически
инициализируются нулем. Аналогично, { 4, 5, 6 } инициализирует первую
структуру во второй строке nlist, а оставшиеся две структуры nlist[1]
установятся в нуль. Когда компилятор встретит следующий список
инициализации { 7, 8, 9 }, то это приведет к попытке инициализировать
nlist[2]. Так как nlist содержит только две строки, то будет выдано
сообщение об ошибке.
Примеры:
/******************* Example 1 *********************/
struct list {
int i, j, k;
float n[2] [3];
} x = {
1,
2,
3,
{4.0, 4.0, 4.0}
};
/******************* Example 2 *********************/
union {
char x[2] [3];
int i, j, k;
} y = {
{'1'},
{'4'}
};
В первом примере три элемента int структурной переменной x
инициализированы 1, 2, и 3 соответственно. Три элемента первой строки
массива m инициализированы как 4.0. Элементы второй строки инициализированы
нулем по умолчанию.
Во втором примере инициализируется переменная y типа совмещения. Первым
элементом совмещения является массив, для которого требуется составной
инициализатор. Список инициализации {'1'} задает величины для первой строки
массива. Поскольку в списке всего одна величина, то только первый элемент
строки массива инициализируется символом 1 , а оставшиеся два элемента в
строке инициализируются нулем (символом \0) по умолчанию. Аналогично,
первый элемент второй строки массива x инициализируется символом 4, а
оставшиеся два элемента в строке инициализируются нулем.
Строковые инициализаторы
Массив может быть инициализирован строчным литералом.
Например,
char code[ ] = "abc";
инициализирует code как массив символов из четырех элементов. Четвертым
элементом является символ \0, который завершает все строковые литералы.
Если специфицируется размер массива, а строка больше чем
специфицированный размер, то лишние символы отбрасываются. Следующее
об"явление инициализирует переменную code, как трехэлементный массив
символов:
char code[3] = "abcd"
В примере только три первые символа инициализатора назначаются для
массива code. Символ d и сивол нуль отбрасываются.
Если строка короче, чем специфицированный размер массива, то
оставшиеся элементы массива инициализируются нулем (символом \0).
Об"явления типов
Об"явление типа определяет имя и элементы структурного или совмещающего
типов или имя и перечислимое множество перечислимого типа.
Имя типа может быть использовано в об"явлениях переменных и функций в
качестве ссылки на этот тип. Это полезно, когда многие переменные или
функции имеют один и тот же тип.
Об"явление typedef определяет спецификатор типа для типа. Это
об"явление используется для того, чтобы создавать более короткие или
более осмысленные имена типов уже определенных в Си или об"явленных
пользователем.
Типы структур, совмещений и перечислений
Об"явления типов структур, совмещений и перечислений имеют ту же самую
общую синтаксическую форму, как и об"явления переменных этих типов. В
об"явлении типа идентификатор переменной опущен, так как нет переменной
которая об"является. Именем структуры, совмещения или перечисления
является тег.
В об"явлении типа может появиться список об"явлений элементов-
или список перечисления-
, определяющие тип.
Сокращенная форма об"явления переменной, в котором tag ссылается на тип,
определенный где-то еще, при об"явлении типа не используется.
Примеры:
/******************** Example 1 ********************/
enum status {
loss = -1,
bye,
tie = 0,
win,
};
/********************* Example 2 *******************/
struct student {
char name[20];
int id, claas;
};
В первом примере об"является тип перечисления, поименованный status.
Имя типа может быть использовано в об'явлениях перменных типа
перечисления. Идентификатор loss явно устанавливается в -1.
Идентификаторы bye и tie ассоциируются со значением 0, а win принимает
значение 1. Во втором примере об"является структурный тип, поименованный
student. Теперь можно использовать такое об"явление, как struct student
employee, чтобы об"явить структур-
ную переменную employee типа student.
Об"явления typedef
Синтаксис:
typedef [,...]; Об"явления
typedef являются аналогом об"явления переменной,
за исключением того, что ключевое слово typedef заменяет спецификатор
класса памяти.
Об"явление интерпретируется тем же самым путем, как об"явления
переменной или функции, но вместо того, чтобы стать
переменной типа, специфицированного об"явлением, становится синонимом
имени типа. Об"явление typedef не создает типов. Оно создает синонимы для
существующих имен типов, которые были специфицированы другим способом.
Любой тип может быть об"явлен с typedef, включая типы указателя,
функции и массива. Имя с ключевым словом typedef для типов указателя,
структуры или совмещения может быть об"явлено прежде чем эти типы будут
определены, но в пределах видимости об"явления.
Примеры:
/******************** Example 1 ********************/
typedef int WHOLE;
/******************** Example 2 ********************/
typedef struct club {
char name[30];
int sise, year;
} GROUP;
/******************** Example 3 ********************/
typedef GROUP *PG;
/******************** Example 4 ********************/
typedef void DRAWE(int, int);
В первом примере об"является WHOLE как синоним для int .
Во втором примере об" является GROUP как структурный тип с тремя
элементами. Так как специфицирован также тег clab, то имя GROUP и тег club
могу быть использованы в об"явлениях.
В третьем примере используется предидущее имя typedef для об"явления
адресного типа. Тип PG об"является как указатель на тип GROUP, который в
свою очередь определен как структурный тип.
В последнем примере представлен тип DRAWE для функции не
возвращающей значения и требующей два аргумента типа int. Это означает,
например, что об"явление DRAWE box; эквивалентно об"явлению void box(int,
int);
Имена типов
Имя типа специфицирует особенности типа данных. Имена типов
используются в трех контекстах: в списках типов аргументов, при
об"явлении функций, в вычислениях cast (преобразованиях типов), и в sizeof
операциях. Списки типов аргументов рассматривались в
разделе 4.5. "Об"явления функций". Преобразования cast и операция sizeof
обсуждаются в разделах 5.7.2. и 5.3.4. соответственно.
Именами для основных, перечисляющих, структурных и совмещающих типов
являются спецификаторы типа для каждого из них. Имена для типов
указателя, массива и функции задаются следующей синтаксической формой:
Абстрактный декларатор - это декларатор без
идентификатора, состоящий из одного или более модификаторов указателей,
массивов и функций. Модификатор указателя (*) всегда появляется перед
идентификатором в деклараторе, в то время как модификатор массива ([]) или
функции ( () ) появляются после идентификатора. Таким образом, чтобы
правильно интерпретировать абстрактный декларатор, нужно начинать
интерпретацию с подразумеваемого идентификатора.
Абстрактные деклираторы могут быть составными. Скобки в составном
абстрактном деклараторе специфицируют порядок интерпретации, подобно тому
как это делается при интерпретации составных деклараторов об"явлений.
Абстрактный декларатор, состоящий из пустых круглых скобок () не
допускается, поскольку это двусмысленно. В этом случае невозможно
определить находится ли подразу-
меваемый идентификатор внутри скобок, и в таком случае- это
немодифицированный тип, или перед скобками, тогда- это тип функции.
Спецификаторы типа, установленные посредством об"явлений typedef, также
рассматриваются как имена типов.
Примеры:
long * /* Example 1 */
int (*) [5] /* Example 2 */
int (*) (void) /* Example 3 */
В первом примере задано имя типа как указатель на тип long. Во втором
и третьем примерах показано каким образом скобки
модифицируют составные абстрактные деклараторы. В примере 2 задано имя
типа для указателя на массив иэ пяти злементов. В третьем примере именуется
указатель на функцию, не требующую аргументов и возвращающую значение типа
int.
КОНТРОЛЬНЫЕ ВОПРОСЫ:
1. Какие ошибки содержат следующие операторы?
enum State { on, off };
enum YesNo { yes, no};
enum DiskDriveStatus { on, off };
2. Верно или нет, что объявление следующего перечислимого типа неправильно?
enum YesNo { no = 0, No = 0, yes = 1, Yes = 1 };
3. Что не так в следующей программе?
#include
int main()
{
int *p = new int;
cout > *p;
cout )
// обязателен тип возвращаемого значения
{
< объявление данных >
< тело функции>
return возвращаемоеЗначение; // - если возвращаемыйТип не void
}
- Выход из функции осуществляется по оператору return. Void-функции
могут не возвращать значения.
Список параметров:
[const] тип1 параметр1, [const] тип2 параметр2, ...
- Ключевое слово const предохраняет передаваемые по ссылке аргументы от
случайного изменения.
Программа USERINFO.CPP иллюстрирует использование модификатора
// const в списке параметров
*/
struct userInfo
{
int age;
char name[150];
};
void processUserInfo(/*const*/ userInfo &ui)
// при снятии комментария будет сообщение об ошибке,
// поскольку модификатор const запрещает изменение параметра
{
if ( ui.age < 18 ) {
cout );
При объявлении функции имена параметров могут быть опущены.
- Передача аргумента по ссылке позволяет функции изменять значение
переданного аргумента и экономит память, так как при этом не создается
локальная копия аргумента:
[const] тип1& параметр1, [const] тип2& параметр2, ...
void foo(int &); // - объявление функции - это ее прототип
int main()
{
int value = 5;
foo(value);
cout (n2)) ? (n1) : (n2))
double num1 = 50, num2 = 5, rslt;
rslt = min(num1 / 2, num2 * 2);
- При объявлении функции с модификатором inline компилятор заменяет вызов
функции ее телом. В этом смысле эти функции похожи на макросы.
Отличие состоит в том, что встроенные функции выполняют проверку типов
данных.
Программа INLINE.CPP, иллюстрирующая применение встроенной функции
- Используя аргументы по умолчанию для некоторых параметров, при вызове
функции вы можете не задавать аргументы для этих параметров; тогда им
автоматически будут присваиваться значения по умолчанию.
Программа DEFARGS.CPP, иллюстрирующая применение аргументов по умолчанию
- Рекурсивными называются функции, которые вызывают сами себя. Количество
рекурсивных вызовов должно быть ограничено, чтобы не столкнуться с
проблемой нехватки памяти. По этой причине каждая рекурсивная функция
должна выполнять проверку условия на окончание рекурсии.
Пример программы FACTOR.CPP, использующей рекурсивную функцию
- Перегрузка функций позволяет вам иметь несколько функций с одним именем,
но с разными списками аргументов (список аргументов еще называется
сигнатурой функции). Тип возвращаемого функцией значения не является частью
сигнатуры.
Программа OVERLOAD.CPP, иллюстрирующая перегрузку функции
ТИПОВЫЕ ВОПРОСЫ С ОТВЕТАМИ
Можно ли в С++ объявлять вложенные функции?
Нет, так как это приводит к большим накладным расходам во время
выполнения программы.
В каких случаях нужно использовать статические глобальные переменные?
Можете использовать их, где хотите. Когда вы объявляете статической
глобальную переменную (которые я вам не советую использовать вообще), вы
даете указание компилятору сделать ее невидимой для функций из других
файлов. Такая переменная недоступна из других файлов вашего проекта.
Как расходуется память при обслуживании вызовов рекурсивной функции?
Исполняющая система использует стек для хранения временных данных, в том
числе необходимых для генерирования вызова рекурсивной функции. Как и
другие ресурсы, стек ограничен в своем размере. В результате при длинной
цепочке вызовов рекурсивной функции стек может переполниться, что приведет
к остановке программы из-за ошибок выполнения или переполнения стека.
ПРАКТИКУМ
Контрольные вопросы
1. Каков будет результат работы следующей программы? Что вы можете сказать
по поводу функции swap?
*/
# include
void swap(int i, int j)
{
int temp = i;
i = j;
j = temp;
}
int main()
{
int a = 10, b = 3;
swap (a, b);
cout
void swap(int &i, int &j)
{
int temp = i;
i = j;
j = temp;
}
int main()
{
int a = 10, b = 3;
swap (a, b);
cout
int main()
{
double x = 5.2;
cout
const int MAX = 0x1FFF; //64K/8 - максимальный размер массива типа double
***
int main()
{
double array[MAX]; // объявление одномерного массива
***
int num_elem;
// Ввод количества обрабатываемых данных
do
{
cout > num_elem;
cout MAX);
// Ввод данных
for (int ix = 0; ix < num_elem; ix++)
{
cout > array[ix];
}
// Расчет среднего значения
double sum = 0;
for (ix = 0; ix < num_elem; ++ix)
sum += array[ix];
cout
const int MAX = 10; //50
int main()
{
double array[MAX] = { 12.2, 45.4, 67.2, 12.2, 34.6, 87.4,
83.6, 12.3, 14.8/*, 55.5*/ };
int num_elem = MAX;
//double array[] = { 12.2, 45.4, 67.2, 12.2, 34.6, 87.4,
// 83.6, 12.3, 14.8, 55.5 };
//int num_elem = sizeof(array) / sizeof(array[0]);
double sum = 0;
for (int ix = 0; ix < num_elem; ++ix)
{
sum += array[ix];
cout |
|04 | |
|05 |int main() |
|06 |14 |
Пример программной сессии:
Введите три числа через пробел: 123
Сумма чисел = 6
Среднее этих чисел = 2
Введите три символа: ABC
Вы ввели символы 'A', 'B', 'C'
Введите число, символ, и число: 12A34.4
Вы ввели 12 A 34.4
Введите символ, число и символ: A3.14Z
Вы ввели A 3.14 Z
В программе из листинга 2 объявляется четыре переменных типа double и три
переменных типа char. Оператор вывода в строке 10 предлагает вам ввести три
числа. Оператор ввода в строке 11 помещает введенные вами числа в
переменные х, у и z. He забывайте, что при вводе чисел их нужно разделять
пробелами. Либо вводите каждое число с новой строки. Первое введенное вами
число будет помещено в переменную х, второе — в у, а третье окажется в
переменной z. Данные в переменные заносятся в том порядке, в котором
переменные перечислены в операторе ввода в строке 11. Оператор в строке 12
вычисляет сумму значений переменных х, у и z. Оператор вывода в строках 13
и 14 выводит сумму и среднее значение введенных вами величин.
Оператор вывода в строке 15 предлагает вам ввести три символа. Оператор
(ввода в строке 16 последовательно размещает введенные символы в переменных
с1, с2, с3. Использовать пробел для разделения вводимых символов не
обязательно. Например, вы можете ввести данные и таким образом: 1А2, Bob и
1 D d. Оператор вывода в строках 17—19 выводит введенные вами символы,
разделенные пробелами и заключенные в одинарные кавычки.
Оператор вывода в строке 20 предлагает вам ввести число, символ и число.
Оператор ввода в строке 21 помещает ваши данные в переменные х, с1 и у.
Пробел-разделитель здесь нужен только в том случае, если символ может быть
интерпретирован как часть числа. Например, если вам нужно ввести число 12,
символ «точка» и число 55, вам нужно набрать на клавиатуре 12 . 55.
Вводимый символ «точка» лучше «заключить» в пробелы, чтобы быть уверенным,
что поток вода не воспримет эту точку как точку, разделяющую в вещественном
числе целую и дробную части. Оператор вывода в строке 22 выводит введенные
вами данные разделенные пробелами.
Оператор вывода в строке 23 предлагает вам ввести символ, число и символ.
Оператор ввода в строке 24 последовательно размещает введенные значения в
переменных с1, х, с2. Пробел-разделитель здесь нужно использовать только в
том случае, если символ может быть интерпретирован как часть числа.
Например, если вам нужно ввести символ «-», число 12 и цифру 0, вам нужно
набрать на клавиатуре 12 0. Оператор вывода в строке 25 выводит введенные
вами данные, разделяя их пробелами.
Функция printf
Просматривая программы, написанные разными людьми, вы часто можете
встретить функцию printf. Этот стандартный оператор вывода пришел из языка
С. Так как C++ является расширением С, эта функция поддерживается и в этом
языке. Многие программисты до сих пор предпочитают использовать старую
функцию printf, а не потоки ввода/вывода C++. Вот почему вам эта функция
наверняка уже знакома. Но, помимо этого, эта функция имеет несколько очень
мощных возможностей, и в ряде случаев она оказывается удобнее функций
потоков. Прототип функций можно найти в заголовочном файле STDIO.H.
Функция printf
Общая форма объявления функции printf:
int printf(const char *format[, argument,... ]);
Параметр format является символьным массивом, содержащим выводимый текст.
Кроме этого обязательного параметра, могут быть необязательные аргументы.
Массив format может содержать специальные форматирующие символы, которые
выполняют преобразование необязательных аргументов при выводе.
Функция printf является очень мощной функцией с богатыми возможностями
форматирования вывода. В качестве первого шага в освоении ее возможностей
рассмотрим Esc-последовательности, позволяющие представлять специальные
символы. Esc-последовательность начинается с символа «\» — «обратная косая
черта». Esc-коды представлены в таблице 1.
Таблица 1. Еsс - последовательности
|Последовательн|Десятичное |Шестнадцатеричное |Название |
|ость |значение |значение | |
|\а |7 |0х07 |Звонок |
|\b |8 |0х08 |Возврат назад |
|\f |12 |0х0С |Перевод страницы |
|\n |10 |0х0А |Новая строка |
|\г |13 |0x0D |Возврат каретки |
|\t |9 |0х09 |Табуляция |
|\v |11 |0х0В |Вертикальная табуляция|
|\\ |92 |0х5С |Обратная черта |
|\' |44 |0х2С |Апостроф |
|\" |34 |0х22 |Кавычка |
|\? |63 |0х3 F |Знак вопроса |
|\0 | | |Восьмеричное число, от|
| | | |1 до 3 цифр |
|\XHHH и \xhhh | |0xhhh |Шестнадцатеричное |
| | | |число |
Функция printf имеет специальные форматирующие спецификации (символы) для
вывода переменных. Общий вид этих спецификаций таков:
% [flags] [width] [.precision] [F | N | h | l | L ]
Опции flags могут определять выравнивание, отображение знака числа при
выводе, вывод десятичной точки и символов заполнения. Кроме того, эти флаги
определяют префиксы для восьмеричных и шестнадцатеричных чисел. Возможные
значения флагов приведены в таблице 2.
Таблица 7.2. Значения флагов строки формата функции printf
|Символ |Назначение |
|- |Выравнивать вывод по левому краю поля |
|+ |Всегда выводить знак числа |
|Пробел |Выводить пробел перед положительным числом и знак |
| |минус — перед отрицательным |
|# |Не влияет на вывод десятичных целых, для |
| |шестнадцатеричных чисел выводит префикс 0х или 0Х, |
| |перед восьмеричными целыми выводит ноль, десятичную |
| |точку для вещественных чисел. |
Спецификация width определяет минимальное количество выводимых символов.
Если необходимо, используются заполнители — пробелы или нули. Когда
значение для width начинается с нуля, printf использует в качестве
заполнителей нули, а не пробелы. Если в качестве значения для width
используется универсальный символ *, а не число, то printf подставляет на
место этого символа значение, которое должно содержаться в списке
аргументов. Это значение ширины поля должно предшествовать выводимому
значению. Ниже приведен пример вывода числа 2, занимающего три позиции,
согласно значению второго аргумента printf:
printf("%*d", 3, 2);
Спецификатор precision определяет максимальное количество выводимых цифр. В
случае целого числа он определяет минимальное количество выводимых
символов. Для precision также можно применить символ *, вместо которого
будет подставлено значение из списка аргументов. Это значение точности
представления должно предшествовать выводимому значению. Ниже приведен
пример вывода числа с плавающей точкой 3.3244 с использованием десяти
символов, как это задано вторым аргументом printf:
printf("%7.*f", 10, 3.3244);
Символы F, N, h, l и L являются символами размера, переопределяющими размер
по умолчанию. Символы F и N применяются с указателями, far и near
соответственно. Символы h, l, и L используются для указания соответственно
типов short int, long или long double.
Символам типа данных должен предшествовать форматирующий символ %. В
таблице 7.2 мы показали возможные значения флагов форматирующей строки
printf. Символы типов данных перечислены в таблице 7.3.
Таблица 3. Символы типов данных строки формата функции printf
|Тип данных|символ|результат |
| |типа | |
|Символ |c |Один символ |
| |d |Десятичное целое со знаком |
| |i |Десятичное целое со знаком |
| |O |Восьмеричное целое без знака |
| |N |Десятичное целое без знака |
| |X |Шестнадцатеричное целое без знака; набор цифр - |
| | |0123456789abcdef |
| |X |Шестнадцатеричное целое без знака; набор цифр - |
| | |0123456789ABCDEF |
|Указатель |P |Для указателей near выводит только смещение в виде: |
| | |0000. Указатели far отображаются в виде: SSSS:0000 |
|Указатель |N | |
|на целое | | |
|Вещественн|F |Выводит величину со знаком в формате [-]dddd.dddd |
|ое | | |
| |E |Выводит вещественную величину со знаком в |
| | |экспоненциальном формате [-]d.dddde[+|-]ddd |
| |Е |Выводит вещественную величину со знаком в |
| | |экспоненциальном формате [-]d.ddddE[+|-]ddd |
| |G |Выводит вещественную величину со знаком в формате f или |
| | |е в зависимости от ее значения и заданной точности |
| |G |Выводит вещественную величину со знаком в формате F или |
| | |Е в зависимости от ее значения и заданной точности |
|Указатель |S |Выводит строку символов, пока не встретит |
| | |нуль-терминатор строки |
Разберем небольшой пример. Программа OUT2.CPP, исходный код которой
приведен в листинге 3, создана на основе программы OUT1.CPP. В этой
программе используется форматированный вывод с использованием функции
printf. Программа выводит те же числа, что и OUT1.CPP, используя три
различных набора спецификаций преобразования.
Листинг 3. Исходный текст программы OUT2.CPP в файле List7-3.CPP
|01 |// Программа, использующая printf для форматирования вывода |
|02 | |
|03 |#include |
|04 | |
|05 |int main() |
|06 | Printf(" %6.4f / %10.4lf = %7.5lf\n", |
Пример вывода программы из листинга 3:
128 + 67 = 195
Вывод использует спецификации преобразования %lf :
355.0000 / 113.0000 = 3.14159
Вывод использует спецификации преобразования %le :
3.5500e+02 / 1.1300e+02 = 3.14159e+00
Вывод использует спецификации преобразования %lg :
355 / 113 = 3.1416
Символьная переменная aChar: @
ASCII-код @: 64
В программе из листинга 3 объявляется целый набор переменных различных
типов. Оператор вывода в строках 13 и 14 выводит целые, используя
спецификацию формата %d. В таблице 4 приведены результаты действия
спецификаций преобразования из строки 13. Обратите внимание на то, что
первая переменная была преобразована из типа unsigned char в тип integer.
Таблица 4. Результат действия спецификаций форматирования в функции printf
из строки 13
|Спецификация формата|Переменная |Тип данных |Тип после |
| | | |преобразования |
|%3d |aByte |unsigned |Int |
| | |char | |
|%2d |anInt |int |Int |
|%3d |aByte + |int |Int |
| |anInt | | |
Оператор вывода в строке 17 выводит переменные aSingle, aDouble и выражение
aSingle / aDouble, используя спецификации преобразования %6.4f, %6.41f и %
7.51f. Точность представления задается ими равной 4, 4 и 5 цифрам, а
минимальная ширина поля 6, 6 и 7 цифрам соответственно. Две последних
спецификации осуществляют преобразование величин двойной точности.
Оператор вывода в строке 21 подобен оператору из строки 17. Отличие состоит
в том, что используется е-формат вместо f-формата. Соответственно три
значения выводятся в экспоненциальном формате.
Оператор из строки 25 также похож на оператор из строки 17. Основное
отличие состоит в том, что вместо f-формата используется g-формат. В
результате первые два числа выводятся без дробной части, поскольку они
являются целыми величинами.
Оператор вывода в строке 28 выводит содержимое переменной aChar по формату
%с. Оператор вывода в строке 29 выводит ту же переменную aChar дважды,
первый раз как символ, а второй раз как целое (или, если быть точным,
выводится ASCII-код символа). Для этого используются спецификации
преобразования %с и %d соответственно.
Массивы символов в C++
В C++ имеется специальный класс для работы со строками, которого, конечно,
не было в языке С. В С строки вводились как массивы символов, ограниченные
нуль-символом (ASCII-код которого равен нулю), поэтому большое количество
программ, написанных на С, используют символьные массивы. Более того, и в
C++, несмотря на то, что он имеет класс для работы со строками, находится
применение массивам символов. Поэтому термин «строка» имеет два значения:
строка в смысле C++ и строка как массив символов. Весь этот раздел будет
посвящен тому, как нужно и не нужно использовать символьные массивы.
Символ '\0' также называют нуль-терминатором. Строки, оканчивающиеся нуль-
терминатором, называют еще ASCIIZ-строками, где символ Z обозначает ноль —
ASCII-код нуль-терминатора. Еще этот символ называют NUL-символом,
поскольку этот термин является его именем в ASCII.
Все строки обязательно должны оканчиваться нуль-терминатором, и при
объявлении размера массива необходимо это учитывать. Когда вы объявляете
строковую переменную как массив символов, увеличьте размер массива на один
символ для нуль-терминатора. Использование строк с конечным нулем также
имеет то преимущество, что здесь отсутствуют ограничения, накладываемые
реализацией C++. Кроме того, структура ASCIIZ-строк очень проста.
Ввод строк
В программах, которые мы рассматривали, операторы потокового вывода
выводили строковые константы; C++ поддерживает потоковый вывод для строк
как специального не-предопределенного типа данных. (Можно сказать, что это
было сделано по требованию масс.) Операции и синтаксис для вывода строковых
переменных остаются прежними. При вводе строк операция извлечения из потока
» не всегда будет работать так, как вы ожидаете, поскольку строки часто
содержат пробелы, которые игнорируются оператором ввода; поэтому вместо
оператора ввода вам нужно использовать функцию getline. Эта функция вводит
заданное количество символов.
Функция getline
Перегруженная функция getline объявляется следующим образом:
istreams getline( signed char *buffer,
int size,
char delimiter = '\n') ;
istreams getline( unsigned char *buffer,
int size,
char delimiter = '\n') ;
istream& getline( char *buffer,
int size,
char delimiter = '\n') ;
Параметр buffer является указателем на строку, в которую помещаются
вводимые символы. Параметр size задает максимальное количество вводимых
символов. Параметр delimeter определяет символ-ограничитель, при появлении
которого ввод символов прекращается прежде, чем будут введены все size
символов. Параметр delimeter имеет аргумент по умолчанию, равный '\n'. В
случае ввода символов с клавиатуры этот символ появляется в потоке ввода
при нажатии клавиши
Пример
#include //см. файл Ex01.cpp
int main()
{
char name[80] ;
cout « "Enter your name: ";
cin.getline(name, sizeof(name) - 1);
cout « "Hello " « name « ", how are you?";
return 0;
}
Функции, объявленные в STRING. H
Стандартная библиотека для работы со строками содержит много полезных
функций (объявляемых в STRING.H), разработанных коллективными усилиями
многих программистов на С. В файлах заголовка STDIO.H и IOS-TREAM.H также
имеются прототипы строковых функций. Комитетом ANSI/ISO C++ предложен класс
для работы со строками. Строки этого класса больше похожи на строки в
языках Pascal и BASIC. (Мы познакомимся с классами в День 8, а со строковым
классом в День 11.) Этот раздел будет посвящен рассмотрению некоторых (ни в
коей мере не всех) функций, объявленных в STRING.H.
Некоторые функции из STRING.H имеют несколько версий. Дополнительные версии
этих функций, имеющих в имени префиксы _f, f или _ работают с указателями
типа far. Этих версий вы не встретите в плоской, 32-битной модели памяти
компилятора Borland.
Присвоение значений строкам
C++ поддерживает два способа присвоения значений строкам. Вы можете
присвоить строковой переменной строковую константу, произведя инициализацию
при объявлении строки. Этот метод прост: требуется операция присваивания и
строковая константа.
Инициализация строки
Общий метод инициализации строки:
char stringVar[stringSize] = stringLiteral;
Пример
char a3tring[81] = "Borland C++ 5 in 21 days";
char Named = "Rene Kinner";
Второй способ присвоить значение строке — это вызвать функцию, которая
копирует содержимое одной строки в другую, — не забывая при этом и нуль-
символ. Эта функция называется strcpy. Она предполагает, что копируемая
строка оканчивается символом NUL и прекращает копирование, как только
встретит этот символ.
Функция strcpy
Прототип функции strcpy таков:
char* strcpy(char *target, const char *source);
Функция копирует строку source в строку target. Функция предполагает, что
целевая строка имеет размер, достаточный для того, чтобы вместить
содержимое строки-источника.
Пример
char name[41] ;
strcpy(name, "Borland C++ 5");
Переменная name содержит строку "Borland C++ 5".
Функция strdup
Функция strdup копирует одну строку в другую, при этом отводит необходимое
количество памяти для целевой строки.
Прототип функции strdup таков:
char* strdup(const char *source);
Функция копирует строку source и возвращает указатель на строку-копию.
Пример
char *string1 = "Монархия в Испании";
char *string2;
string2 = strdup(string1);
После того, как будет отведено необходимое количество памяти для строки
string2, строка string1будет скопирована в строку string2.
Функция strncpy
Библиотека строковых функций предлагает также функцию strncpy, копирующую
заданное количество символов из одной строки в другую.
Прототип функции strncpy таков:
char * strncpy(char *target, const char *source, size_t num);
Функция копирует num символов из строки source в строку target. Функция не
выполняет ни усечение, ни заполнение строки.
Пример
char str1[] = "Pascal";
char str2[] = "Hello there";
strcnpy(strl, str2, 5);
Переменная strl содержит строку "Hellol". Заметьте, что символ ‘l’ строки-
приемника, следующий за скопированной частью строки, сохранился.
Определение длины строки
При работе со строками часто бывает нужно знать длину строки.
Функция strlen
Функция strlen возвращает количество символов в строке, в которое не
включается нуль-терминатор.
Прототип функции strncpy таков:
size_t strlen (const char *string) ,
Функция strlen возвращает длину строки string. size_t — это имя,
приписанное типу unsigned int оператором typedef.
Пример
char str[] = "1234567890";
size_t i;
i = strlen(str),
Переменной i будет присвоено значение 10.
Конкатенация строк
Операция конкатенации используется достаточно часто, когда новая строка
получается объединением двух или более строк.
Присоединить одну строку к другой можно функцией strcat.
Функция strcat
Конкатенация строк означает их последовательное присоединение друг к другу.
Прототип функции strcat таков:
char *strcat(char *target, const char *source) ;
Функция добавляет к содержимому целевой строки содержимое строки-источника
и возвращает указатель на целевую строку. Функция предполагает, что целевая
строка может вместить содержимое объединенной строки.
Пример
char string[81] ;
strcpy(string, "Turbo");
strcat (string, " C++");
Переменная string содержит строку "Turbo C++".
Функция strncat
Функция strncat добавляет к содержимому целевой строки указанное количество
символов из строки-источника.
Прототип функции strcat :
char *strncat(char *target, const char *source, size_t num);
Функция добавляет к содержимому целевой строки num символов из строки-
источника и возвращает указатель на целевую строку.
char strl[81] = "Hello I am ";
char str2[41] = "Keith Thompson";
strncat(strl, str2, 5);
Переменная strl теперь содержит строку "Hello I am Keith".
Пример использования функций getline, strlen и strcat в файле List7_4.cpp
(исходный код программы STRING.CPP). Программа выполняет следующие задачи:
. Предлагает вам ввести строку; ввод не должен превышать 40 символов
. Предлагает вам ввести вторую строку; ввод не должен превышать 40 символов
. Выводит число символов, содержащихся в каждой строке
. Присоединяет вторую строку к первой
. Выводит результат конкатенации
. Выводит длину объединенной строки
. Предлагает вам ввести символ для поиска
. Предлагает вам ввести символ для замены
. Выводит содержимое объединенной строки после замены символа
Сравнение строк
Поскольку строки являются массивами символов, вы не можете применить
операцию сравнения для проверки равенства двух строк. Библиотека функций
STRING.H предлагает набор функций для сравнения строк. Эти функции
сравнивают символы двух строк, используя для этого ASCII-коды символов. Это
функции strcmp, stricmp, strncmp и strnicmp.
Вообще говоря, все функции сравнения работают одинаково: возвращают 0, если
две строки совпали, отрицательную величину, если вторая строка больше по
величине, и положительное значение, если большей оказалась первая строка.
Функция strcmp
Функция strcmp выполняет сравнение двух строк с учетом регистра символов.
Прототип функции strcmp:
int strcmp(const char *strl, const char *str2);
Функция сравнивает строки strl и str2. Возвращает в качестве результата
сравнения целую величину:
< 0 когда strl меньше, чем str2;
= 0 когда strl равна str2;
> 0 когда strl больше, чем str2.
Пример
char stringl[] = "Borland C++";
char string2[] = "BORLAND C++";
i = strcmp(string1, string2);
В последнем операторе переменной i присваивается положительное значение,
так как string1 больше string2 (ASCII-коды символов в нижнем регистре
больше ASCII-кодов символов в верхнем.)
Функция stricmp
Функция stricmp выполняет сравнение двух строк, не учитывая регистра
символов.
Прототип функции stricmp:
int stricmp(const char *strl, const char *str2);
Функция сравнивает строки strl и str2, не делая различия между символами в
нижнем и верхнем регистре. Возвращает в качестве результата сравнения целую
величину:
< 0 когда strl меньше, чем str24
= 0 когда strl равна str24
> 0 когда strl больше, чем str2.
Пример
char string1[] = "Borland C++";
char string2[] = "BORLAND C++";
int i = strcmp(string1, string2);
В последнем операторе переменной i присваивается значение 0, так как
string1 и string2 отличаются друг от друга только регистром символов.
Функция strncmp выполняет сравнение заданного количества символов двух
строк с учетом регистра символов.
Функция strncmp
Прототип функции strncmp:
int strncmp(const char *strl, const char *str2, size_t num);
Функция сравнивает первые num символов строк strl и str2. Возвращает в
качестве результата сравнения целую величину:
< 0 когда strl меньше, чем str2;
= 0 когда strl равна str2;
> 0 когда strl больше, чем str2.
Пример
char string1[] = "Borland C++";
char string2[] = "Borland Pascal";
i = stricmp(string1, string2, 9);
В последнем операторе переменной i присваивается отрицательное значение,
так как значение "Borland С" меньше, чем "Borland Р".
Функция strnicmp
Функция strnicmp выполняет сравнение заданного количества символов двух
строк без учета регистра символов.
Прототип функции strnicmp :
int strnicmp(const char *strl, const char *str2, size_t num);
Функция сравнивает первые num символов строк strl и str2, не делая
различия в регистре символов. Возвращает в качестве результата сравнения
целую величину:
< 0 когда strl меньше, чем str2;
= 0 когда strl равна str2;
> 0 когда strl больше, чем str2.
Пример
char string1[] = "Borland C++";
char string2[] = "BORLAND Pascal";
i = strnicmp(string1, string2, 7);
В последнем операторе переменной i присваивается значение 0, так как
подстрока "Borland" отличается в этих строках только регистром.
Рассмотрим пример программы, в которой применяются функции сравнения строк.
Программа из листинга 5 объявляет массив строк и присваивает им значения.
Затем программа выводит исходный массив, сортирует его и выводит значения
строк отсортированного массива. |
(см. List7_5.cpp - Исходный текст программы STRING2.CPP)
Преобразование строк
Функция strlwr
Прототип функции strlwr:
char* strlwr (char *source)
Функция преобразует символы верхнего регистра в символы нижнего регистра в
строке source. Другие символы не затрагиваются. Функция возвращает
указатель на строку source.
Пример
char str[] = "HELLO THERE";
strlwr(str);
Переменная str теперь содержит строку "hello there".
Функция strupr
Прототип функции strupr:
char* strupr(char *source)
Функция преобразует символы нижнего регистра в символы верхнего регистра в
строке source. Другие символы не затрагиваются. Функция возвращает
указатель на строку source.
Пример
char str[] = "Borland C++";
strupr(str);
Переменная str теперь содержит строку "BORLAND С ++".
Обращение строк
Библиотека STRING.H предлагает функцию strrev для записи символов в строке
в обратном порядке.
Функция strrev
Прототип функции strrev:
char* strrev(char *str)
Функция обращает порядок символов в строке str и возвращает указатель на
строку str. char str[] = "Hello";
strrev(str) ;
cout « str;
Будет выведено "olleH".
Рассмотрим программу, которая манипулирует символами в строке.
List7_6.cpp показывает исходный текст программы STRING3.CPP. Программа
выполняет следующие задачи:
. Запрашивает у вас ввод строки
. Отображает ваш ввод
. Выводит вашу строку в нижнем регистре
. Выводит вашу строку в верхнем регистре
. Отображает символы, которые вы ввели, в обратном порядке
. Выводит сообщение, что ваш ввод не содержит символов верхнего регистра,
если это так
. Выводит сообщение, что ваш ввод не содержит символов в нижнем регистре,
если это так
. Выводит сообщение, что ваша строка симметрична, если это так
Поиск символов
Библиотека STRING.H предлагает ряд функций для поиска символов в строках.
Это функции strchr, strrchr, strspn, strcspn и strpbrk. Они осуществляют
поиск в строках символов и простых символьных шаблонов.
Функция strchr
Функция strchr определяет первое вхождение символа в строку.
Прототип функции strchr:
char* strchr(const char *target, int c)
Функция находит первое вхождение символа с в строку target. Функция
возвращает указатель на символ в строке target, который соответствует
заданному образцу с. Если символ с в строке не обнаруживается, функция
возвращает 0.
Пример
char str[81] = "Borland C++";
char *strPtr;
strPtr = strchr(str, '+');
Указатель strPtr теперь содержит адрес подстроки "++" в строке str.
Функция strrchr
Функция strrchr определяет последнее вхождение символа в строке.
Прототип функции strrchr:
char* strrchr(const char *target, int c)
Функция находит последнее вхождение символа с в строку target. Функция
возвращает указатель на символ в строке target, который соответствует
заданному образцу с. Если символ с в строке не обнаруживается, функция
возвращает 0.
Пример
char str[81] = "Borland C++ is here";
char* strPtr;
strPtr = strrchr(str, '+');
Указатель strPtr теперь указывает на подстроку "+ is here " в строке str.
Функция Strspn
Функция strspn возвращает число символов с начала строки, совпадающих с
любым символом из шаблона.
Прототип для функции strspn:
size_t strspn(const char *target, const char *pattern)
Функция strspn возвращает число символов от начала строки target,
совпадающих с любым символом из шаблона pattern.
Пример
char str[] = "Borland C++ 5";
char substr[] = "narlBod";
int index;
index = strspn(str, substr);
Этот оператор присваивает 8 переменной index, потому что первые восемь
символов из str содержатся в подстроке substr.
Функция strcspn
Функция strcspn просматривает строку и выдает число первых символов в
строке, которые не содержатся в шаблоне.
Прототип функции strcspn:
size_t strcspn(const char* str1, const char* str2)
Функция strcspn просматривает строку str1 и выдает длину подстроки,
отсчитываемой с начала строки, символы которой полностью отсутствуют в
строке str2.
Пример
char strng[] = "The rain in Spain";
int i = strcspn(strng, " in");
Этот пример возвращает 3 (расположение первого пробела в строке strng)
переменной i.
Функция strpbrk
Функция strpbrk просматривает строку и определяет первое вхождение любого
символа из образца.
Прототип функции strpbrk:
char* strpbrk(const char* target, const char* pattern)
Функция strpbrk ищет в строке target первое вхождение любого символа из
образца pattern. Если символы из образца не содержатся в строке, функция
возвращает 0.
Пример
char *str = "Hello there how are you";
char *substr = "hr";
char *ptr;
ptr = strpbrk(str, substr);
cout « ptr « endl;
Вы увидите на экране строку "here how are you", потому что 'h' встречается
в строке str раньше, чем 'r'.
Поиск строк
Библиотека функций STRING.H предлагает для поиска подстроки в строке
функцию strstr.
Функция strstr
Прототип функции strstr:
char* strstr(const char *str, const char *substr);
Функция ищет в строке str первое вхождение подстроки substr. Функция
возвращает указатель на первый символ найденной в строке str подстроки
substr. Если строка substr не обнаружена в строке str, функция возвращает
0.
Пример
char str[] = "Hello there! how are you";
char substr[] = "how";
char *ptr;
ptr = strstr (str, substr);
cout « ptr « endl ;
Это приведет к выводу строки "how are you", поскольку в строке str ,
была обнаружена подстрока "how". Указатель ptr содержит адрес остатка
первоначальной строки, начинающегося с подстроки "how".
Функция strtok
Библиотека функций для работы со строками имеет функцию strtok, которая
дает вам возможность разбить строку на подстроки на основании заданного
набора символов-ограничителей.
Подстроки иногда называются лексемами.
Прототип функции strtok:
char* strtok(char *target, const char * delimiters);
Функция разбивает строку на лексемы, согласно символам-ограничителям,
заданным в параметре delimeters. В следующем примере показано, как работать
с этой функцией и как получать лексемы, на которые была разбита строка.
Функция strtok вводит символ '\0' после каждой лексемы. (Опять же не
забудьте сохранить копию вашей строки в другой строковой переменной.)
Пример
#include // см. файл Ex02.cpp
#include
int main()
{
char *str = "(Base_Cost + Profit) * Margin";
char *tkn = "+*()";
char *ptr = str;
printf("%s\n", str);
// Первый вызов функции
ptr = strtok(str, tkn);
printf("Лексемы этой строки: %s", ptr);
while (ptr)
{
// Первый аргумент должен быть равен нулю
if ((ptr = strtok(0, tkn)) != 0)
printf (",%s", ptr);
}
printf("\n");
return 0;
}
В результате выполнения этой программы на экран выводятся следующие строки:
(Base_Cost + Profit) * Margin
Лексемы этой строки: Base_Cost, Profit, Margin
Рассмотрим пример программы поиска символов и строк. Листинг 7
(List7_7.cpp) содержит исходный текст программы STRING4.CPP. Программа
выполняет следующие задачи:
. Запрашивает у вас ввод основной строки
. Запрашивает строку поиска
. Предлагает вам ввести символ поиска
. Выводит линейку цифр и основную строку
. Выводит номер символа в основной строке, с которого начинается строка
поиска *
. Выводит номер символа в основной строке, совпавшего с символом поиска.
Основы объектно-ориентированного программирования СИНТАКСИС ОСНОВНЫХ
КОНСТРУКЦИЙ
Объявление базовых классов
В С++ мы имеем возможность объявлять классы, которые инкапсулируют
элементы-данные и функции-элементы. Эти функции изменяют и позволяют
обращаться к значениям данных-элементов и выполняют другие задачи.
Базовый класс
Базовый класс определяется следующим образом (синтаксис):
class>
{
private:
protected:
public:
};
Пример 1:
class point
{
protected:
double х;
double у;
public:
point(double xVal, double yVal);
double getX();
double getY();
void assign(double xVal, double yVal);
point& assign(point &pt);
};
Разделы класса
Классы С++ имеют три различных уровня доступа к своим элементам - как к
данным, так и к функциям:
- Закрытые (частные) элементы
- Защищенные элементы
- Открытые элементы
К данным в закрытом разделе имеют доступ только функции-элементы
класса.
Классам-потомкам запрещен доступ к закрытым данным своих 6азовых
классов.
К данным в защищенной секции имеют доступ функции-элементы класса и
классов-потомков. Данные из открытой секции находятся в области видимости
функций-элементов класса, функций-элементов классов-потомков, и вообще
доступны кому угодно.
Существуют следующие правила для разделов класса:
1. Разделы могут появляться в любом порядке.
2. Один и тот же раздел можно определять несколько раз.
3. Если не определен ни один раздел, компилятор (по умолчанию) объявляет
все элементы закрытыми.
4. Помещать данные-элементы в открытый раздел следует только в том случае,
если в этом есть необходимость, например, если это упрощает вашу задачу.
Обычно элементы-данные помещаются в защищенный раздел, чтобы к ним
имели доступ функции-элементы классов-потомков.
5. Используйте для изменения значений данных и доступа к ним функции-
элементы. При использовании функции вы можете осуществлять проверку
данных и, если нужно, изменять другие данные.
6. Класс может иметь несколько конструкторов.
7. Класс может иметь только один деструктор, который должен объявляться в
открытом разделе класса.
8. Функции-элементы (в том числе конструкторы и деструкторы), состоящие из
нескольких операторов, должны определяться вне объявления класса.
Определение функции может содержаться в том же файле, в котором
определяется класс. Это напоминает порядок работы с обычными функциями:
задание прототипа и определение функции.
Конструкторы являются специфическим типом функций-элементов, тип
возвращаемого значения для которых не указывается, а имя должно совпадать с
именем класса-хозяина. Вызываются они при создании нового представителя
класса. Деструктор вызывается для разрушения представителя класса.
При определении функции-элемента вы должны указать ее имя и имя ее
класса. Сначала вы должны Сначала необходимо указать имя класса (т.н.
квалификатор), а затем, через два двоеточия (::), имя функции. В качестве
примера рассмотрим такой класс:
class point
{
protected:
double x;
double y;
public:
point(double xVal, double yVal);
double getX();
// другие функции-элементы
};
Определения конструктора и функций-элементов должны выглядеть так
point::point (double xVal, double yVal)
{
// операторы
}
double point::getX()
{
// операторы
}
После того, как вы объявили класс, вы можете использовать имя класса в
качестве спецификатора типа данных при объявлении представителей класса.
Синтаксис объявления тот же, что и при объявлении переменной.
В листинге 8.1 приведен исходный текст программы RECT.CPP. Программа
предлагает вам ввести длину и ширину прямоугольника (в данном примере
прямоугольник является объектом). Затем программа выводит значения длины,
ширины и площади определенного вами прямоугольника.
Конструкторы
Конструкторы и деструкторы в С++ вызываются автоматически, что
гарантирует правильное создание и разрушение объектов класса.
Общий вид (синтаксис) объявления конструктора:
class>
{
public:
>
>
>
};
Пример 2:
// Замечание: Здесь только объявление класса без описания объявленных
// функций-параметров
class point
{
protected:
double x;
double y;
public:
point();
point(double xVal, double yVal);
point(const point &pt);
double getX();
double getY();
void assign(double xVal, double yVal);
point& assign(point &pt);
};
int main()
{
point p1;
point p2(10, 20);
point p3(p2);
p1.assign(p2);
cout ] parentClass
{
private:
protected:
public:
};
Пример 4 объявления класса Rectangle и класса-потомка Box:
class Rectangle
{
protected:
double length;
double width;
public:
Rectangle(double len, double wide);
double getLength() const;
double getWidth() const;
double assign(double len, double wide);
double calcArea();
};
class Вох : public Rectangle
{
protected:
double height;
public:
Box(double len, double wide, double height);
double getHeight () const;
assign(double len, double wide, double height);
double calcVolume();
};
(см. LIST8-3.CPP)
Виртуальные функции
Мы уже упоминали о полиморфизме - важной особенности объектно-
ориентированного программирования. Рассмотрим следующий пример (6):
#include
class X
{
public:
double A(double x) { return x * x; }
double B(double x) { return A(x) / 2; }
};
class Y : public X
{
public:
double A(double x) { return x * x * x; }
};
int main ()
{
Y y;
cout );
};
class>
{
// функции-элементы
virtual returnType functionName();
};
Пример 7, показывающий, как при помощи виртуальных функций можно
реализовать полиморфное поведение классов X и Y:
#include
class X
{
public:
virtual double A(double x) { return x * x; }
double B (double x) { return A(x) / 2; }
};
class Y : public X
{
public:
virtual double A(double x) { return x * x * x; }
};
main()
{
Y y;
cout
class A
{
public:
A() {}
virtual void foo(char c)
{ cout );
};
Пример 9:
class String
{
protected:
char *str;
int len;
public:
String();
~String();
// другие функции-элементы
friend String& append(String &str1, String &str2);
friend String& append(const char* str1, String &str2);
friend String& append(String &str1, const char* str2);
};
Дружественные функции могут решать задачи, которые при помощи
функций-элементов решаются с трудом, неуклюже или не могут быть решены
вообще.
Рассмотрим простой пример использования дружественных функций. Текст
программы FRIEND.CPP представлен в листинге 8.5. Программа следит за
памятью, отведенной для хранения массива символов. Эта программа - первый
шаг к созданию класса string.
Операции и дружественные операции
Последняя программа использовала функции-элементы и дружественную
функцию, которые реализовали действия, выполняемые в стандартных типах с
помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому
что эти языки не поддерживают определяемые пользователем операции. В
отличии от них C++ позволяет вам объявлять операции и дружественные
операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, =,
>, +=, -=, *=, /=, %=, [],
(), >. Обратитесь к описанию языка C++, где обсуждаются детали
определения этих операций. С++ трактует операции и дружественные операции
как специальный тип функций-элементов и дружественных функций.
Общий синтаксис для объявления операций и дружественных операций:
class>
{
public:
// конструкторы и деструктор
// функции-элементы
// унарная операция
returnType operator operatorSymbol();
// бинарная операция
returnType operator operatorSymbol(operand);
// унарная дружественная операция
friend returnType operator operatorSymbol(operand);
// бинарная дружественная операция
friend returnType operator operatorSymbol(firstOperand, secondOperand);
};
Пример 10:
class String
{
protected:
char *str;
int num;
public:
String();
~String();
// другие функции-элементы
// операция присваивания
String& operator =(String& s);
String& operator +=(String& s);
// операции конкатенации
friend String& operator +(String& s1, String& s2);
friend String& operator +(const char* s1, String& s2);
friend String& operator +(String& s1, const char* s2);
// операции отношения
friend int operator >(String& s1, String& s2);
friend int operator =>(String& s1, String& s2);
friend int operator
class X
{
public:
double A(double x) { return x * x; }
double B(double x) { return A(x) / 2; }
};
class Y : public X
{
public:
double A(double x) { return x * x * x; }
};
int main ()
{
Y y;
cout );
};
class>
{
// функции-элементы
virtual returnType functionName();
};
Пример 7, показывающий, как при помощи виртуальных функций можно
реализовать полиморфное поведение классов X и Y:
#include
class X
{
public:
virtual double A(double x) { return x * x; }
double B (double x) { return A(x) / 2; }
};
class Y : public X
{
public:
virtual double A(double x) { return x * x * x; }
};
main()
{
Y y;
cout
class A
{
public:
A() {}
virtual void foo(char c)
{ cout );
};
Пример 9:
class String
{
protected:
char *str;
int len;
public:
String();
~String();
// другие функции-элементы
friend String& append(String &str1, String &str2);
friend String& append(const char* str1, String &str2);
friend String& append(String &str1, const char* str2);
};
Дружественные функции могут решать задачи, которые при помощи
функций-элементов решаются с трудом, неуклюже или не могут быть решены
вообще.
Рассмотрим простой пример использования дружественных функций.
Текст программы FRIEND.CPP представлен в листинге 8.5. Программа следит за
памятью, отведенной для хранения массива символов. Эта программа - первый
шаг к созданию класса string.
Операции и дружественные операции
Последняя программа использовала функции-элементы и дружественную
функцию, которые реализовали действия, выполняемые в стандартных типах с
помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому
что эти языки не поддерживают определяемые пользователем операции. В
отличии от них C++ позволяет вам объявлять операции и дружественные
операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, =,
>, +=, -=, *=, /=, %=, [], (), >. Обратитесь к описанию языка C++,
где обсуждаются детали определения этих операций. С++ трактует операции и
дружественные операции как специальный тип функций-элементов и
дружественных функций.
Общий синтаксис для объявления операций и дружественных операций:
class>
{
public:
// конструкторы и деструктор
// функции-элементы
// унарная операция
returnType operator operatorSymbol();
// бинарная операция
returnType operator operatorSymbol(operand);
// унарная дружественная операция
friend returnType operator operatorSymbol(operand);
// бинарная дружественная операция
friend returnType operator operatorSymbol(firstOperand, secondOperand);
};
Пример 10:
class String
{
protected:
char *str;
int num;
public:
String();
~String();
// другие функции-элементы
// операция присваивания
String& operator =(String& s);
String& operator +=(String& s);
// операции конкатенации
friend String& operator +(String& s1, String& s2);
friend String& operator +(const char* s1, String& s2);
friend String& operator +(String& s1, const char* s2);
// операции отношения
friend int operator >(String& s1, String& s2);
friend int operator =>(String& s1, String& s2);
friend int operator , если вы ссылаетесь
на них при помощи указателей.
Complex *pC;
pC = new Complex;
// операции с объектом, к которому обращаются по указателю pC
delete pC;
или
Complex *pC = new Complex;
// операции с объектом, к которому обращаются по указателю pC
delete pC;
Контрольные вопросы
1. Найдите ошибку в следующем объявлении класса:
>
char *str;
unsigned len;
String ();
String(const String& s);
String(unsigned size, char = ' ');
String(unsigned size);
String& assign(String& s);
~String();
unsigned getLen() const;
char* getString();
// другие функции-элементы
};
2. Найдите ошибку в следующем объявлении класса:
>
protected:
char *str;
unsigned len;
public:
String();
String(const char* s);
String(const String& s);
String(unsigned size, char = ' ');
String(unsigned size);
~String();
// другие функции-элементы
3. Верно или нет? Следующий оператор, который создает объект s класса
String, объявленного ранее, является правильным:
s = String("Hello Borland C++");
4. Если в программе OPERATOR.CPP вы следующим образом измените объявления
объектов, будет ли программа компилироваться без ошибок?
String s1 = String("Kevin");
String s2 = String(" Нау");
String s3 = s1;
ФАЙЛОВЫЕ ОПЕРАЦИИ ВВОДА/ВЫВОДА
Сегодняшний урок посвящен файловым операциям ввода/вывода с
использованием библиотеки управления потоками C++. У вас есть две
возможности: либо использовать функции файлового ввода/вывода, описанные в
заголовочном файле STDIO.H, либо функции stream-библиотеки C++. Каждая из
этих библиотек имеет множество мощных и удобных функций. Сегодня будут
представлены основные операторы, которые позволят вам читать и записывать
данные в файл. Вы изучите следующие темы:
Стандартные функции потоков ввода/вывода
- Последовательный ввод/вывод потока с текстовой информацией
- Последовательный ввод/вывод двоичных данных
- Прямой доступ к потоку двоичных данных
Stream-библиотека C++
Stream-библиотека (известная также как библиотека iostream) выполнена
в виде иерархии классов, которые описаны в нескольких заголовочных файлах.
Файл IOSTREAM.H, используемый до сих пор, - это только один из них. Другой,
который будет интересен в этой главе, - FSTREAM.H. Файл IOSTREAM.H
поддерживает основные классы для ввода/вывода потока. Файл FSTREAM.H
содержит определения для основных классов файлового ввода/вывода.
Существуют дополнительные файлы библиотеки ввода/вывода, в которых
имеются более специализированные функции ввода/вывода.
ОБЩИЕ ФУНКЦИИ ПОТОКОВОГО ВВОДА/ВЫВОДА
В этом разделе представлены функции-элементы ввода/вывода, являющиеся
общими как для последовательного, так и для прямого доступа. Эти функции
включают open, close, good и fail в дополнение к операции !. Функция open
открывает файловый поток для ввода, вывода, добавления, а также для ввода и
вывода. Эта функция позволяет указывать тип данных, с которыми вы
собираетесь работать: двоичные или текстовые.
При работе с файловым вводом/выводом очень важно знать различие между
текстовым и двоичным режимами. Текстовый режим предназначен для текстовых
файлов, в которых имеются строки обычного текста. Двоичный режим
используется для любых других и особенно для файлов, которые сохраняются в
форматах, неудобных для чтения человеком.
Существуют некоторые особые тонкости, связанные с файлами текстового
режима, на которые следует обратить особое внимание и запомнить. Первая из
них - символ EOF (26 в коде ASCII или Ctrl+Z) - представляет собой метку
(символ) конца файла. В текстовом режиме, где встречается символ EOF,
система C++ низкого уровня автоматически продвигается к концу файла; вы
ничего не можете прочитать после специального символа. Это может вызвать
проблемы, если такой специальный символ окажется в середине файла.
Другая особенность текстового режима заключается в том, как
интерпретируются строки текстового файла. Каждая строка заканчивается
последовательностью конца строки (EOL). На компьютерах PC и совместимых с
ними EOL-последовательность представлена двумя символами кода ASCII: CR (13
в коде ASCII или Ctrl+M) и LF (10 в коде ASCII или Ctrl+J). Эта CRLF-
последовательность используется функциями чтения и записи текстовой строки,
которые автоматически, вставляют ее в файл или удаляют из него. Заметьте,
что на большинстве других, систем (UNIX и Macintosh) EOF просто является
символом LF.
Функция-компонент open
Прототип функции open
void open (const char* filename, int mode, int m = filebuf::openprot);
Параметр filename задает имя открываемого файла. Параметр mode
указывает режим ввода/вывода. Далее следует список аргументов для mode,
описанных в заголовочном файле FSTREAM.H:
- in открыть поток для ввода,
- out открыть поток для вывода,
- ate установить указатель потока на конец файла,
- app открыть поток для добавления,
- trunk удалить содержимое файла, если он уже существует (bc++5),
- nocreate инициировать ошибку, если уже не существует,
- noreplace инициировать ошибку, если файл уже существует,
- binary открыть в двоичном режиме.
Пример 1.
// открыть поток для ввода
fstream f;
f.open("simple.txt", ios::in);
// открыть поток для вывода fstream f;
fstream f;
f.open ("simple.txt", ios::out);
// открыть поток ввода/вывода для двоичных данных fstream f;
fstream f;
f.open("simple.txt", ios::in | ios::out | ios::binary);
Внимание: Классы файловых потоков предусматривают конструкторы,
которые выполняют действия (и имеют такие же параметры) функции-компонента
open.
Функция close закрывает поток и освобождает использовавшиеся ресурсы.
Эти ресурсы включают буфер памяти для операции потокового ввода/вывода.
Функция-компонент close
Прототип для функции close:
void close();
Пример 2.
fstream f;
// открыть поток
f.open ( "simple.txt", ios:: in);
// работа с файлом
// закрыть поток
f.close ();
Stream-библиотека C++ включает в себя набор основных функций, которые
контролируют состояние ошибки потоковой операции. Эти функции включают
следующие:
1. Функция good() возвращает ненулевое значение, если при выполнении
потоковой операции не возникает ошибки. Объявление функции good: int
good();
2. Функция fail() возвращает ненулевое значение, если при выполнении
потоковой операции возникает ошибка. Объявление функции fail: int fail();
3. Перегруженная операция ! применяется к экземпляру потока для определения
состояния ошибки.
Stream-библиотека C++ предоставляет дополнительные функции для
установки и опроса других аспектов и типов ошибок потока.
ПОСЛЕДОВАТЕЛЬНЫЙ ТЕКСТОВЫЙ ПОТОК ВВОДА/ВЫВОДА
Функции и операции последовательного текстового ввода/вывода являются
довольно простыми. Вы уже имели дело со многими из них в предыдущих уроках.
Эти функции и операции включают:
- Операция извлечения из потока > читает символы потока.
- Функция getline читает строку из потока.
Функция-элемент getline
Прототипы функции-элемента getline:
istream& getline (char* buffer, int size, char delimiter = '\n');
istream& getline (signed char* buffer, int size, char delimiter = '\n');
istream& getline (unsigned char* buffer, int size, char delimiter = '\n');
Параметр buffer - это указатель на строку, принимающую символы из
потока. Параметр size задает максимальное число символов для чтения.
Параметр delimiter указывает разделяющий символ, который вызывает
прекращение ввода строки до того, как будет введено количество символов,
указанное в параметре size. По умолчанию параметру delimiter присваивается
значение '\n'.
Пример 3.
fstream f;
char textLine[MAX];
f.open("sample.txt", ios::in);
while (!f.eof()) {
f.getline(textLine, MAX);
cout пробелы.
- Записывает эти строки в выходной файл и также в стандартное окно
вывода.
Листинг 10.1. Исходный код программы TRIM.CPP
// C++ программа демонстрации последовательного файлового
// ввода/вывода
Программа в листинге 10.1 не объявляет никаких классов, вместо этого
она фокусируется на использовании файловых потоков для ввода и вывода
текста. Эта программа описывает функции trimStr, getInputFilename,
getOutputFilename, processLines и обязательную функцию main.
Функция trimStr вычищает пробелы в строках, передаваемых
через параметр s. Эта функция объявляет переменную i и присваивает ей
индекс символа, находящегося сразу за завершающим нулем. Функция использует
цикл while, начинающийся в строке 14, чтобы выполнить обратное сканирование
символов в строке s до первого символа, не являющегося пробелом. Оператор в
строке 16 присваивает завершающий нуль символу, стоящему справа от
последнего символа, не являющегося пробелом, в строке s.
Функция getInputFilename получает имя входного файла и открывает
соответствующий файловый поток. Параметр inFile передает это имя вызывающей
функции. Ссылочный параметр f передает открытый входной поток вызывающей
функции. Функция getInputFilename объявляет локальный флажок ok и
использует цикл do-while (строки с 23 по 34), чтобы открыть входной файл.
Строка 25 содержит первый оператор тела цикла, в котором флажок ok
инициализируется значением true. Оператор вывода в строке 26 запрашивает
ввод имени входного файла; в строке 27 с помощью вызова функции getline это
имя принимается и сохраняется в переменной inFile. Оператор в строке 28
пытается открыть входной файл, используя параметр потока f. Оператор open
использует значение ios::in для указания на то, что входной текстовый файл
был открыт. Если вызов возвращает ошибку, оператор if (строка 29) определит
это, сообщит об ошибке открытия файла пользователю и присвоит переменной ok
значение false. При значении ok, равном true, цикл do-while будет
выполняться и сохранять ответы пользователя до тех, пока не произойдет
успешное открытие файла.
Функция getOutputFilename подобна функции getInputFilename в попытках
получить имя файла от пользователя и открыть файл. Однако в этом случае
файл открывается на запись и его имя сравнивается с именем входного файла.
В строке 47 проверяется совпадение имен входного и выходного файлов, и,
если пользователь ввел одно и то же имя, ему предлагается снова ввести имя
выходного файла.
Функция processLines читает строки их входного файлового потока,
приводя их в порядок и записывая в выходной файловый поток. Параметры fin и
font передают файловые указатели входного и выходного потоков,
соответственно. Эта функция объявляет локальную строковую переменную line и
использует (строки с 69 по 74) цикл while для обработки текстовых строк.
Предложение while содержит вызов функции getline, которая читает следующую
строку входного потока fin и присваивает переменной line содержимое этой
строки. В теле этого цикла просто вызывается функция trimStr, а затем line
передается в потоки fout и cout. Заметьте, что команды для получения строки
текста из файла и передачи ее в файл в точности совпадают с теми, которые
могли бы использоваться для получения текста с экрана и передачи его на
экран. Это происходит потому, что используемые вами cout и cin - на самом
деле только потоки, которые открываются автоматически и работают
непосредственно с
экраном.
Функция main, как обычно со всеми уже описанными функциями, довольно
простая. Она только объявляет переменные файловых потоков fin, fout и
inFile, outFile для сохранения имен этих потоков. Далее входной и выходной
файлы открываются в функциях getInputFilename и getOutputFilename. Наконец,
функция processLine приводит в порядок и копирует содержимое файла, а
функция-компонент close вызывается для каждого из потоков.
ПОСЛЕДОВАТЕЛЬНЫЙ ДВОИЧНЫЙ ФАЙЛОВЫЙ ВВОД/ВЫВОД
Stream-библиотека C++ имеет перегруженные потоковые функции-элементы
write и read для последовательного двоичного файлового ввода/вывода.
Функция write посылает ряд байт в выходной поток. Эта функция может
записывать любую переменную или экземпляр в поток.
Функция-элемент write
Прототип перегруженной функции-элемента:
ostream& write(const char* buff, int num);
ostream& write(const signed char* buff, int num);
ostream& write(const unsigned char* buff, int num);
Параметр buff - это указатель на буфер, содержащий данные, которые
будут посылаться в выходной поток. Параметр num указывает число байт в
буфере, которые передаются в этот поток.
Пример 4.
const MAX = 80;
char buff[MAX+1] = "Hello World!";
int len = strlen (buff) + 1;
fstream f;
f.open("CALC.DAT", ios::out | ios::binary);
f.write((const unsigned char*) &len, sizeof(len));
f.write((const unsigned char*) buff, len);
f.close();
В этом примере открывается файл CALC.DAT, записывается целое,
содержащее число байт в строке и записывается сама строка перед тем, как
файл закрывается.
Функция read считывает некоторое количество байт из входного потока.
Эта функция может считывать любую переменную или экземпляр из потока.
Функция-элемент read
Прототип перегруженной функции-элемента read:
ostream& read(char* buff, int num);
ostream& read(signed char* buff, int num);
ostream& read(unsigned char* buff, int num);
Параметр buff - это указатель на буфер, который принимает данные из
входного потока. Параметр num указывает число считываемых из потока байт.
Пример 5.
const MAX = 80;
char buff [MAX+1];
int len;
fstream f;
f.open("CALC.DAT", ios::in | ios::binary);
f.read((unsigned char*) &len, sizeof(len));
f.read((unsigned char*) buff, len);
f.close();
В этом примере считывается информация, записанная в предыдущем
примере.
Рассмотрим пример, выполняющий последовательный двоичный потоковый
ввод/вывод. В листинге 10.2 представлен исходный код программы ARRAY.CPP.
Эта программа объявляет класс, который моделирует численный динамический
массив. Операции ввода/вывода позволяют программе читать и писать как
отдельные элементы массива, так и целый массив в двоичный файл. Эта
программа создает массивы arr1, arr2 и аrrЗ, а затем выполняет следующие
задачи:
- Присваивает значения элементам массива arr1. (Этот массив имеет 10
элементов).
- Присваивает значения элементам массива аrrЗ. (Этот массив имеет 20
элементов).
- Отображает значения массива arr1.
- Записывает элементы массива arr1 в файл ARRAY1.DAT (по одному за
операцию).
- Читает элементы массива arr1 из этого файла в массив arr2 (по
одному за операцию). (Массив arr2 имеет 10 элементов, то есть он
одного размера с массивом arr1).
- Отображает элементы массива arr2.
- Отображает элементы массива аrrЗ.
- Записывает элементы массива аrrЗ в файл ARRAY3.DAT, все сразу.
- Читает (все сразу) данные из файла ARRAY3.DAT и сохраняет их в
массиве arr1.
- Отображает значения массива arr1. (Выход показывает, что массив
arr1 имеет тот же размер, что и массив arr3).
Листинг 10.2. Исходный код программы ARRAY.CPP
// C++ демонстрация последовательного двоичного
// ввода/вывода
Программа листинга 10.2 объявляет версию класса Array, который похож
на приводимый в главе 8 в листинге 8.2. Основное отличие в том, что здесь
мы использовали operator[ ] для замены и функции store, и recall. Эта
операция проверяет правильность указания индекса и возвращает значение в
badIndex, если аргумент выходит за диапазон массива. В дополнение к
operator[ ] мы добавили функции-элементы writeElem, readElem, writeArray и
readArray для выполнения последовательного двоичного файлового
ввода/вывода. Мы также добавили функции resize и getPrt как вспомогательные
функции соответственно для изменения размера массива и для возвращения
указателя на соответствующий элемент массива.
Обратите внимание также на копирующий конструктор в строке 29, и
особенно на то, что он действительно ничего не определяет. Это - прием,
который может быть использован для защиты других функций или классов от их
автоматического копирования, когда вы знаете, что это плохо (в этом случае
потому, что используются динамические данные, содержимое которых необходимо
копировать).
Функция writeElem, определенная в строках с 43 по 49, записывает
одиночные элементы массива в выходной поток. Параметр os представляет
выходной поток. Параметр index определяет элемент массива для записи.
Функция writeElem возвращает true, если индекс правильный и если операция
по выводу осуществляется без ошибок. После того, как writeElem записывает
элемент массива, внутренний указатель потока продвигается в следующее
положение.
Функция readElem, определяемая в строках с 51 по 57, считывает
одиночный элемент массива из входного потока. Параметр Is представляет
входной поток. Параметр index определяет индекс элемента массива для
чтения. Эта функция возвращает true, если индекс массива правильный и если
операция по вводу осуществляется без ошибок. После того, как readElem
считывает элемент массива, внутренний указатель потока продвигается в
следующее положение.
Функции writeElem и readElem позволяют экземпляру класса
соответственно писать и читать элементы данных из различных потоков.
Функция writeArray, определенная в строках с 59 по 69, записывает все
элементы массива в двоичный файл. Параметр filename определяет имя
выходного файла. Функция открывает выходной поток и записывает значение
элемента класса size, а затем и элементы динамического массива. Функция
writeArray возвращает true, если массив в поток записан успешно. Иначе она
возвращает false. Эта функция открывает локальный выходной поток, используя
потоковую функцию open и передавая ей имя файла и режим ввода/вывода. Режим
ввода/вывода представляет собой выражение ios::out|ios::binary, которое
определяет, что поток открывается только для вывода двоичных записей. Эта
функция дважды вызывает потоковую функцию write - первый раз для записи
компонента класса size, второй - для записи элементов динамического
массива.
Функция readArray, определенная в строках с 71 по 83, читает все
элементы массива из двоичного файла. Параметр filename определяет имя
входного файла. Функция открывает входной поток и считывает значение
компонента класса size, а затем считывает элементы динамического массива.
Функция readArray возвращает true, если она успешно считывает массив из
потока. В противном случае, возвращается false. Функция открывает локальный
входной поток, используя потоковую функцию open и передавая ей имя файла и
аргументы режима ввода/вывода. Аргумент режима ввода/вывода - это выражение
ios::in | ios::binary, которое определяет, что поток открыт только для
двоичного ввода. Функция делает два вызова потоковой функции read, первый -
для чтения элемента класса size, и второй - для чтения элементов
динамического массива. Другим свойством функции readArray является то, что
она изменяет размер экземпляра класса Array для настройки его в
соответствии с данными двоичного файла, вызывая функцию-элемент resize. Это
означает, что динамический массив, который доступен посредством экземпляра
класса, может либо уменьшаться, либо расширяться в зависимости от размера
массива, сохраняемого в файле.
Функция-элемент resize, которая начинается в строке 65, на самом деле
очень простая. Она проверяет, является ли требуемый размер тем же, что и
установленный ранее. Если нет, то память, зарезервированная функцией
dataPtr, освобождается, а затем создается новая область памяти,
соответствующая новому размеру. Этот новый размер присваивается компоненту
класса size.
Функция dispArray чаще всего является функцией-элементом, но я решил
сделать ее здесь обычной функцией, чтобы лучше показать, как использование
функции operator[ ] позволяет тем, кто работает с классом Array, обращаться
к нему таким же способом, какой они применяют к элементам стандартного
массива. В этом случае есть простой цикл for, который выполняется для
каждого элемента arr и отображает его содержимое.
Наконец, мы подходим к функции main (строка 104). Обычно она в
основном
только запускает функции, которые уже были созданы, для выполнения
следующих задач:
- Объявляет (строка 108) три экземпляра класса Array с именами arr1,
arr2 и аrr3. (Первые два экземпляра имеют тот же самый размер
динамического массива, заданный константой SIZE1, в то время как
аrr3 имеет больший размер, определенный константой SIZE2).
- Объявляет (строка 111) файловый поток f и открывает его (используя
конструктор потока) для доступа к файлу ARRAY1.DAT в двоичном
режиме.
- Использует циклы for (строки с 114 по 116), чтобы произвольно
присвоить значения экземплярам arr1 и аrr3.
- Отображает элементы экземпляра arr1 (строка 119).
- Записывает элементы массива arr1 в выходной файловый поток f,
используя цикл for (строка 122) для вызова функции-компонента
writeElem с выходным файловым потоком f и переменной цикла i.
- Закрывает файловый поток f, вызывая функцию-элемент close этого
потока.
- Открывает (строка 127) файловый поток f для доступа к файлу
ARRAY1.DAT. (На это раз сообщение open определяет режим двоичного
ввода)
- Считывает элементы в arr2 (которому до сих пор не присваивались
никакие значения) из входного файлового потока f, используя цикл
for (строка 128).
- Закрывает входной поток (строка 130). D Отображает элементы
экземпляров arr2 и аrr3 (строки 132 и 133).
- Записывает все содержимое аrr3, вызывая функцию-компонент
writeArray. (Функция writeArray имеет аргумент имени файла
ARRAY3.DAT).
- Считывает массив файла ARRAY3.DAT в экземпляр arr1, вызывая функцию-
компонент readArray и передавая ей в качестве аргумента имени файла
ARRAY3.DAT.
- Отображает новые элементы экземпляра arr1.
Файловый ввод/вывод с прямым доступом
Файловые операции ввода/вывода прямого доступа также используют
потоковые функции-элементы read и write, представленные в предыдущем
разделе. Stream-библиотека имеет ряд функций, позволяющих вам передвигать
указатель потока в любое необходимое положение. Функция-элемент seekg -
одна из таких функций.
Функция-элемент seekg
Прототип для перегруженной функции-компонента seekg:
istream& seekg(long pos);
istream& seekg(long offset, seek_dir dir);
Параметр pos в первой версии определяет абсолютное положение байта в
потоке. Во второй версии параметр offset определяет относительное смещение,
в зависимости от аргумента dir. Аргументы для последнего параметра:
ios::beg С начала файла
ios::cur С текущей позиции файла
ios::end С конца файла
Пример
const BLOCK SIZE = 80
char buff[BLOCK_SIZE] = "Hello World!";
f.open("CALC.DAT", ios::in | ios::out | ios::binary);
f.seekg(3 * BLOCK_SIZE); // продвинутся к блоку 4
f.read((const unsigned char*)buff, BLOCK_SIZE);
cout < buff < endl;
fclose ();
Виртуальный массив - это базирующийся на диске массив, который
сохраняет строки фиксированного размера на диске. Вы можете рассматривать
его как обычный массив, который вместо сохранения своих элементов в памяти
записывает их в дисковый файл.
Рассмотрим пример файлового ввода/вывода прямого доступа. В листинге
10.3 приведен исходный код программы VIRTUAL.CPP и реализует виртуальный
массив. Программа выполняет следующие задачи:
- Использует внутренний список имен для создания объекта виртуального
массива.
- Отображает элементы неупорядоченного объекта виртуального массива.
- Сортирует элементы объекта виртуального массива.
- Отображает элементы сортированного объекта виртуального массива.
-
Листинг 10.3. Исходный код прогшраммы VIRTUAL.CPP
// C++ демонстрация файлового ввода/вывода прямого доступа
Программа листинга 10.3 объявляет класс VmArray. Этот класс моделирует
динамический базирующийся на диске массив, который сохраняет все его
элементы в двоичном файле прямого доступа. Заметьте, что в этом классе
объявлен экземпляр класса fstream и что не существует указателя на
динамический массив. Класс объявляет конструктор, деструктор и ряд функций-
компонентов.
Конструктор класса имеет два параметра: Size и filename. Параметр Size
задает размер виртуального массива. Параметр filename именует двоичный
файл, который сохраняет элементы экземпляров класса. Конструктор открывает
поток f, используя потоковую функцию open и передавая ей в качестве
аргументов filename и выражение для режима работы с файлом ios::in |
ios::out | ios::binary.
Это выражение указывает, что поток открывается для двоичного
ввода/вывода (то есть режима прямого доступа). Если конструктор успешно
открывает файловый поток, он заполняет файл пустыми строками. Деструктор
класса выполняет простую задачу закрытия файлового потока f.
Функции setElem и getElem поддерживают прямой доступ к элементам
массива. Эти функции используют потоковую функцию seekg, чтобы
устанавливать указатель потока на соответствующий элемент массива. Затем
функция setElem вызывает потоковую функцию write для сохранения элемента
массива (передаваемый параметром str). Напротив, функция getElem называет
потоковую функцию read, чтобы получить элемент массива (возвращаемый через
аргумент str). Обе функции возвращают результат типа bad, который указывает
на успешность операции ввода/вывода.
Класс VmArray также объявляет функцию BubbleSort для сортировки
элементов виртуального массива. Эта функция использует функции-элементы
getElem и setElem для доступа и свопинга элементов массива. Затем, наконец,
запускается последняя функция-элемент display для элементов виртуального
массива, которая посылает их на экран. Функция main выполняет следующие
задачи:
- Объявляет экземпляр arr класса VmArray. (Этот экземпляр сохраняет
10 строк в двоичном файле ARR.DAT)
- Присваивает случайное значение элементам экземпляра аот, используя
цикл for (строки 97 и 98).
- Отображает несортированные элементы экземпляра arr, вызывая функцию-
элемент display.
- Сортирует массив, вызывая функцию BubbleSort.
- Отображает сортированные элементы экземпляра arr.
Заключение
Сегодняшний урок представил краткое введение в библиотеку ввода/вывода
C++ и вынес на обсуждение следующие вопросы:
- Общие функции ввода/вывода, включая open, close, good, fail и
оператор !.
- Функция open открывает файловый поток ввода/вывода и поддерживает
попеременный и множественный режимы ввода/вывода. Функция close
закрывает файловый поток. Функции good и fail индицируют успешную
или ошибочную, соответственно, потоковую операцию ввода/вывода.
- C++ позволяет выполнять последовательный потоковый ввод/вывод для
текста с использованием операций < и >, так же как и при помощи
потоковой функции getline. Операция < позволяет записать символы и
строки (а также и другие предопределенные типы данных). Операция >
применяется для
- получения символов. Функция getline позволяет вашему приложению
считывать строки с клавиатуры или из текстового файла.
- Последовательный потоковый ввод/вывод двоичных данных использует
потоковые функции write или read для записи или считывания данных
из переменных любого типа.
- Потоковый ввод/вывод прямого доступа для двоичных данных использует
функцию seekg в объединении с функциями read и write. Функция seekg
позволяет вам передвигать потоковый указатель либо в абсолютное,
либо в относительное положение в потоке.
Вопросы и ответы
Как можно эмулировать прямой доступ к строкам в текстовом файле?
Сначала считывайте строки из файла как текст, получайте длину строк
(плюс два символа для конца каждой строки) и сохраняйте накапливаемую длину
в специальном массиве. (Назовите его, например, lineIndex) Этот массив
сохраняет позицию байта, где начинается каждая строка. Последний элемент
массива будет содержать размер файла. Для доступа к строке номер i,
используйте функцию seek или seekg, чтобы найти смещение для lineIndex[i].
Размер строки номер i равен lineIndex[i+1] - lineIndex[i+1].
Как написать процедуру общего назначения для копирования между входным
ивыходным файловым потоком?
Вам необходимо использовать потоковую функцию gcount() для получения
ряда байт фактически читаемых в последнем неформатированном потоковом
вводе. Вот функция copyStream:
void copyStream(fstreamit fin, fstreamil fout,
unsigned char* buffer, int buffSize)
{
int n;
while (fin. read (buffer, buffaize))
{
n = fin.gcount();
fout.write (buffer, n);
}
}
Практикум
Контрольные вопросы
1. Верно или нет? Потоковые функции ввода/вывода read и write способны
правильно считывать и записывать данные любого типа.
2. Верно или нет? Потоковые функции ввода/вывода read и write способны
правильно считывать и записывать данные любого типа, не имеющих указателей.
3. Верно или нет? Функции seek и seekg расширяют файл, когда вы передаете
индекс, который на один или более байт превышает текущий конец файла.
4. Верно или нет? Аргументы функций seek и seekg не требуют проверки
диапазона.
Упражнение
Создайте программу VSEARCH.CPP, модифицируя программу VIRTUAL.CPP.
Класс VmArray в VSEARCH.CPP должен иметь функцию binSearch, которая
проводит двоичный поиск в элементах сортированного массива. Добавьте цикл в
конец функции main для поиска в массиве arr, используя неупорядоченные
данные инициализирующего списка. (Элементы этого списка доступны при
использовании данных-указателей.)