Реферат: Лекции по 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

1.    Переменные иоперации языка С++

  Здесь представлены базовые компоненты программ на С++. В их число

входяттипы данных, переменные, константы и выражения.

ИЗУЧАЕМЫЕ ПОНЯТИЯ

  - Предопределенные типы данных в С++ включают в себя типы int, char,

float,double и void. В языке С++ гибкость типов данных увеличивается

благодаряприменению модификаторов типов. Эти модификаторы изменяют

точностьпредставления и диапазон значений переменных. Модификаторами

типаявляются signed, unsigned, short и long.

  - Идентификаторы в С++ могут иметь длину до 32 символов и

должныначинаться с буквы или подчеркивания. Последующие символы

идентификаторамогут быть буквой, цифрой или подчеркиванием. Иден-

тификаторыС++ чувствительны к регистру. Ограничение на 32 символа

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

  - Директива #include является специальной командой компилятора. Она

предписываеткомпилятору включить в программу содержимое опреде-

ленногофайла, как если бы вы сами ввели его в текущий исходный

файл.

  - Объявление констант предусматривает использование директивы #define

дляобъявления констант, определенных при помощи макросов, или ис-

пользованиеключевого слова const для объявления формальных кон-

стант.Формальные константы требуют от вас определения их типа

(значениемпо умолчанию является int), имени и ассоциированного с

нимизначения.

  - Объявление переменной требует, чтобы вы задали ее тип и имя, С++

даетвам возможность инициализировать переменную при ее объявлении.

Выможете объявить несколько переменных в одном операторе объявле-

ния.

  - Арифметическими операциями являются +, -, *, / и % (деление по

модулю).

  - Арифметические выражения различаются по сложности. Самое простое

выражениесодержит единственный элемент данных (литерал, константу

илипеременную). Сложные выражения включают набор операций, функ-

ции,литералы, константы и переменные.

  - Операции инкремента и декремента используются в префиксной и пост-

фикснойформах. Язык С++ дает вам возможность применять эти опе-

рациик переменным, в которых хранятся символы, целые числа и даже

числас плавающей точкой.

  - Арифметические операции присваивания дают вам возможность записы-

ватьболее короткие арифметические выражения, в которых первый опе-

рандявляется также переменной, принимающей результат вычислений.

  - Оператор sizeof возвращает как для типов данных, так и для переменных

ихразмер в байтах.

  - Механизм приведения типа дает вам возможность форсировать преобра-

зованиетипа выражения.

  - Операции отношений и логические операции дают вам возможность стро-

итьлогические выражения.

  - Булевы выражения объединяют операции отношений и логические опе-

рациидля формулирования нетривиальных условий. Эти выражения позволяют

программепринимать сложные решения.

  - Условное выражение предлагает вам короткую форму для простого опе-

ратораif-else с двумя альтернативами.

  - Операции манипулирования битами выполняют поразрядные операции

AND,OR, XOR и NOT. Кроме того, в С++ поддерживаются поразрядные

операциисдвига << и >>.

  - Операции манипулирования битами с присваиванием предлагают корот-

киеформы для простых операций манипулирования битами.

     Буквы и цифры

     Множествосимволов Си включает большие и малые буквы из ан­глийского алфавита и 10десятичных арабских цифр:

          -большиеанглийские буквы:

AB C D E F G H I J K L M N O P Q R T U V W X Y Z

          -малыеанглийские буквы:

ab c d e f g h i j k l m n o p q r t u v w x y z

          -десятичныецифры:

          01 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 не включена в эту таблицу. Она скорее пред­ставляет собой ключевоеслово, чем символ.

-------------------------------------------------

Операция                    Наименование -------------------------------------------------

              !                        Логическое НЕ

~                        Побитовое дополнение

              +                        Сложение

-    Вычитание,арифмети-

     ческоеотрицание

*   Умножение

/    Деление

%  Остаток

<<     Сдвигвлево

>>     Сдвигвправо

<   Меньше

<=     Меньшеили равно

>   Больше

>=     Большеили равно

==     Равно

!= Неравно

&  ПобитовоеИ, адрес от

|    Побитовое включающее ИЛИ

^   Побитовоеисключающее ИЛИ

&&    ЛогическоеИ

||   ЛогическоеИЛИ

'    Последовательноевыполне-

     ние(запятая)

?:  Операцияусловного вы-

     ражения

++     Инкремент

--  Декремент

=   Простоеприсваивание

+=     Сложениес присваиванием

-= Вычитаниес присваиванием

*= Умножениес присваиванием

/=  Делениес присваиванием

%=    Остатокс присваиванием

>>=   Сдвигвправо с присваива-

     иванием

<<=   Сдвигвлево с присваива-

     нием

&=    ПобитовоеИ с присваива-

     нием

|=  Побитовоевключающее ИЛИ

     сприсваиванием

^= Побитовоеисключающее ИЛИ

     сприсваиванием

              -------------------------------------------------------

                        Табл.2.3. Операции

          Замечание:

          Операция условного выражения ?: -это тернарная, а не двух­символьная  операция.  Формат условного   выражения   следующий:<expression>?<expression>:<expression>

          Константы

          Константа-это число, символ или строка символов. Константы используются  в  программе как неизменяемые величины. В языке Си различают четыре типа констант: целыеконстанты, константы с пла­вающей точкой, константы-символы и строчныелитералы.

          Целые константы

          Целаяконстанта- это десятичное, восьмеричное или  шестнад­цатеричное число, котороепредставляет целую величину. Десятичная константа имеет следующий формат представления:

          <digits>,

          где <digits>  — это одна или более десятичных цифр от 0 до 9.

          Восьмеричнаяконстанта имеет следующий  формат  представле­ния:

          0<odigits>,

          где <odigits>  — это одна или более восьмеричных цифр от 0 до 7. Записьведущего нуля необходима.

          Шестнадцатеричнаяконстанта имеет один из следующих  форма­тов представления:

          0x<hdigits>

          0X<hdigits>,

где<hdigits> одна или более шестнадцатеричных цифр. Шестнадцатеричная цифра  может  быть  цифрой от 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   или unsignedlong, если это необходимо. В Табл.   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

          Константы с плавающей точкой

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

          [<digits>][.<digits>][E[-]<digits>],

          где <digits> — одна или более десятичных цифр (от 0 до 9),

аE или e -символ экспоненты. Целая или дробная  части  константы могут  бытьопушены, но не обе сразу. Десятичная точка может быть опущена только тогда,когда задана экспонента.

     Экспонентасостоит из символа экспоненты, за которым следу­ет целочисленная величинаэкспоненты, возможно отрицательная.

Пробельныесимволы не могут  разделять  цифры  или  символы

константы.

     Константы с  плавающей точкой всегда специфицируют положи­тельные величины. Еслитребуются отрицательные величины, то необ­ходимо сформировать константноевыражение из знака минус и следу­ющей за ним константы. Знак минусрассматривается  как  арифмети­ческая операция.

     Примеры констант  с плавающей точкой и константных выраже-

ний:

     15.75

     1.575E1

     1575e-2

     -0.0025

     -2.5e-3

     25e-4

     Целаячасть константы с плавающей точкой может быть  опуще­на, например:

     .75

     .0075e2

     -.125

     -.175E-2

Всеконстанты с плавающей точкой имеют тип double.

     Константа-символ

     Константа-символ- это  буква,  цифра,  знак пунктуации или ESC- символ, заключенные в одиночныекавычки.  Величина  констан­ты-символа равна значению представляющего ее кодасимвола.

Константа-символимеет следующую форму представления:

     '<char>',

     где <char> может быть любым символом иэ множества предста­вимых символов,включая любой ESC- символ, исключая одиночную ка­вычку ('), наклонную чертувлево (\) и символ новой строки.

     Чтобыиспользовать одиночную кавычку  или  наклонную  черту влево  в  качестве константы-символа,  необходимо вставить перед этими знаками наклонную чертувлево. Чтобы представить символ но­вой строки, необходимо использовать запись'\n'.

----------------------------------------------

Константа                  Название величины

----------------------------------------------

'a'                Малаябуква а

'?'                Знаквопроса

'\b'                   Знакпробела

'0x1B'                  ASCIIESC- символ

'\''                Одиночнаякавычка

'\\'                Наклоннаячерта влево

-------------------------------------------------

          Табл.2.7 Примеры констант-символов.

     Константы-символыимеют тип int.

     Строковые литералы

     Строковыйлитерал- это последовательность букв, цифр и сим­волов, заключенная в двойныекавычки. Строковый литерал  рассмат­ривается как массив символов, каждыйэлемент которого представля­ет  отдельный  символ.  Строковый  литерал  имеетследующую форму представления:

     "<characters>",

     где<characters> — это нуль или более символов из множества представимыхсимволов, исключая двойную  кавычку  ("),  наклонную черту влево (\)  исимвол новой строки. Чтобы использовать символ новой строки в строковомлитерале, необходимо напечатать  наклон­ную черту влево, а затем символ новойстроки.

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

     «Longstrings can be bro\

     ckeninto two pieces.»

     идентиченстроке:

     «Longstrings can be brocken into two pieces.»

     Чтобыиспользовать двойные кавычки или наклонную черту вле­во внутри строковоголитерала, нужно представить их с предшеству­ющей наклонной чертой влево, какпоказано в следующем примере:

     «Thisis a string literal»

     «First\\ Second»

     "\«Yes,I do,\» she said."

     «Thefollowing 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

     Комментарии

     Комментарий-это последовательность символов, которая восп­ринимается компилятором какотдельный пробельный символ или, дру­гими словами, игнорируется.

Комментарийимеет следующую форму представления:

     /*<characters>*/,

     где <characters>  может быть любой комбинацией символов из множествапредставимых символов, включая символы новой строки, но исключая комбинацию */.Это означает, что комментарии могут зани­мать более одной строки, но не могутбыть вложенными.

     Комментариидопускаются  везде,  где  разрешены  пробельные символы.  Компилятор игнорируетсимволы комментария, в частности, в комментариях допускается запись ключевыхслов и зто не приведет к ошибке. Так как компилятор рассматривает комментарийкак символ пробела, то комментарии не могут появляться внутри лексем.

Следующиепримеры иллюстрируют некоторые комментарии:

     /*Comments can separate and document

     linesof a program. */

     /*Comments can contain keywords such as for

     andwhile */

/*******************************************

     Commentscan occupy several lines. *******************************************/

     Таккак комментарии не могут содержать вложенных  коммента­риев, то следующийпример будет ошибочным:

     /*You cannot/* nest */ comments */

     Компиляторраспознает первую комбинацию */ после слова nest как  конец комментария. Затем,компилятор попытается обрабатывать оставшийся текст и выработает сообщение обошибке.  Чтобы  обойти

компиляцию комментариев больших размеров, нужно использовать ди­рективу #if препроцессора.

     Лексемы

     Когдакомпилятор обрабатывает программу, он разбивает прог­рамму на группы символов,называемых лексемами. Лексема- это еди­ница текста программы, которая имеетопределенный смысл для  ком­пилятора  и которая не может быть разбита вдальнейшем. Операции, константы, идентификаторы и ключевые слова, описанные вэтом раз­деле, являются примерами лексем. Знаки пунктуации, такие как квад­ратныескобки ([]), фигурные скобки ({}),  угловые  скобки  (<>), круглые скобки и запятые, также являются лексемами. Границы лек­сем определяютсяпробельными символами и другими лексемами, таки­ми как операции и знакипунктуации. Чтобы предупредить неправиль­ную работу компилятора, запрещаютсяпробельные символы между сим­волами идентификаторов, операциями, состоящими изнескольких сим­волов и символами ключевых слов.

     Когда компилятор выделяет отдельную лексему, он последова­тельно об»единяетстолько символов, сколько возможно, прежде  чем перейти  к обработке следующейлексемы. Поэтому  лексемы, не раз­деленные пробельными символами,  могут  быть проинтерпретированы неверно.

     Например,рассмотрим следующее выражение:

          i+++j

     В этом  примере  компилятор вначале создает из трех знаков плюс самую длинную извозможных операций (++), а затем обработает оставшийся знак +, как операциюсложения (+). Выражение проинтер­претируется как (i++)+(j), а не как(i)+(++j).  В  таких  случаях необходимо  использовать  пробельные  символы иликруглые скобки, чтобы однозначно определить ситуацию.

ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ

//Программа VAR.CPP, иллюстрирующая простые переменные

#include<iostream.h>

intmain()

{

  int i, j = 2;

  double x, y = 355.0 / 113;

  i = 3 * j;

  cout << «i = » << i << endl

       << «j = » << j << endl;

  x = 2 * y;

  x = x * x;

  cout << «y = » << y << endl

       << «x = » << x << endl;

  return 0;

}

/*

   Результаты:

   i = 6

   j = 2

   y = 3.141593

   x = 39.4784

*/

//Программа CONST1.CPP, иллюстрирующая константы

#include<iostream.h>

#defineSEC_IN_MIN 60

#defineMIN_IN_HOUR 60

intmain()

{

  long hours, minutes, seconds;

  long totalSec;

  cout << «Введите часы: »;

  cin >> hours;

  cout << «Введите минуты: »;

  cin >> minutes;

  cout << «Введите секунды: »;

  cin >> seconds;

  totalSec = ((hours * MIN_IN_HOUR + minutes) *

               SEC_IN_MIN) + seconds;

  cout << endl << totalSec << " секунд прошло сполуночи" << endl;

  return 0;

}

/* Тест и результаты:

   Введите часы: 10

   Введите минуты: 0

   Введите секунды: 0

   36000 секунд прошло сполуночи

*/

//Программа CONST2.CPP, иллюстрирующая формальные константы

#include<iostream.h>

constint SEC_IN_MIN = 60; // глобальная константа

intmain()

{

  const int MIN_IN_HOUR = 60; // локальная константа

  long hours, minutes, seconds;

  long totalSec;

  cout << «Введите часы: »;

  cin >> hours;

  cout << «Введите минуты: »;

  cin >> minutes;

  cout << «Введите секунды: »;

  cin >> seconds;

  totalSec = ((hours * MIN_IN_HOUR + minutes) *

               SEC_IN_MIN) + seconds;

  cout << endl << endl << totalSec << " секундпрошло с полуночи" << endl;

  return 0;

}

/* Тест и результаты:

   Введите часы: 1

   Введите минуты: 10

   Введите секунды: 20

   4220 секунд прошло с полуночи

*/

//Программа OPER1.CPP, иллюстрирующая простые математические операции

#include<iostream.h>

intmain()

{

  int int1, int2;

  long long1, long2, long3, long4, long5;

  float x, y, real1, real2, real3, real4;

  cout << endl << «Введите первое целое число: »;

  cin >> int1;

  cout << «Введите второе целое число: »;

  cin >> int2;

  cout << endl;

  long1 = int1 + int2;

  long2 = int1 — int2;

  long3 = int1 * int2;

  long4 = int1 / int2;

  long5 = int1 % int2;

  cout << int1 << " + " << int2 << " =" << long1 << endl;

  cout << int1 << " — " << int2 << " =" << long2 << endl;

  cout << int1 << " * " << int2 << " =" << long3 << endl;

  cout << int1 << " / " << int2 << " =" << long4 << endl;

  cout << int1 << " % " << int2 << " =" << long5 << endl;

  cout << endl << endl;

  cout << «Веедите первое вещественное число: »;

  cin >> x;

  cout << «Введите второе вещественное число: »;

  cin >> y;

  cout << endl;

  real1 = x + y;

  real2 = x — y;

  real3 = x * y;

  real4 = x / y;

  cout << x << " + " << y << " = "<< real1 << endl;

  cout << x << " — " << y << " = "<< real2 << endl;

  cout << x << " * " << y << " = "<< real3 << endl;

  cout << x << " / " << y << " = "<< real4 << endl;

  cout << endl << endl;

  return 0;

}

/* Тест и результаты:

   Введите первое целое число: 10

   Введите второе целое число: 5

   10 + 5 = 15

   10 — 5 = 5

   10 * 5 = 50

   10 / 5 = 2

   10 % 5 = 0

   Введите первое вещественное число: 1.25

   Введите второе вещественное число: 2.58

   1.25 + 2.58 = 3.83

   1.25 — 2.58 = -1.33

   1.25 * 2.58 = 3.225

   1.25 / 2.58 = 0.484496

*/

//Демонстрацияопераций инкремента и декремента см. в программе OPER2.CPP

//Программа SIZEOF.CPP, которая возвращает размеры данных, используя

//для этого операцию sizeof() с переменными и типами данных.

#include<iostream.h>

intmain()

{

 short int aShort;

 int anInt;

 long aLong;

 char aChar;

 float aReal;

 cout << «Таблица 1. Размеры памяти для переменных» <<endl

      << endl;

 cout << "    Тип данных       Используемая " << endl;

 cout << "                    память (в байтах)"  << endl;

 cout << "------------------    -----------" << endl;

     cout<< "     short int            " << sizeof(aShort)<< endl;

 cout << "      integer             " << sizeof(anInt)<< endl;

  cout<< "   long integer           " << sizeof(aLong) <<endl;

 cout << "     character            " << sizeof(aChar)<< endl;

 cout << "      float               " << sizeof(aReal)<< endl;

 cout << endl << endl << endl;

 cout << «Таблица 2. Размеры памяти для типов данных»  <<endl

      << endl;

 cout << "    Тип данных       Используемая" << endl;

 cout << "                   память (в байтах)" << endl;

 cout << "------------------    -----------" << endl;

 cout << "     short int            " <<  sizeof(shortint) << endl;

 cout << "      integer             " <<  sizeof(int)<< endl;

 cout << "    long integer          " <<  sizeof(long)<< endl;

 cout << "     character            " <<  sizeof(char)<< endl;

 cout << "       float              " <<  sizeof(float)<< endl;

 cout << endl << endl << endl;

 return 0;

}

/* Результаты:

   Таблица 1. Размеры памяти для переменных"

   Тип данных       Используемая

                  память (в байтах)

------------------   -----------

   short int             2

   integer               2

   long integer          4

   character             1

   float                 4

   Таблица 2. Размеры памяти для типов данных

   Тип данных       Используемая

                  память (в байтах)

------------------   -----------

   short int             2

          integer              2

   long integer          4

   character             1

   float                 4

*/

//Простая программа TYPECAST.CPP, демонстрирующая приведение типа

#include<iostream.h>

intmain()

{

  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 << «shortInt1 = » << shortInt1 << endl

       << «shortInt2 = » << shortInt2 << endl

       << «aByte = » << aByte << endl

       << «anInt = » << anInt << endl

       << «aLong = » << aLong << endl

       << «aChar is » << aChar << endl

       << «aReal = » << aReal << endl << endl<< endl;

  // дейтсвия выполняются с приведением типа

  aByte = (unsigned short) (shortInt1 + shortInt2);

      anInt= (int) (shortInt1 — shortInt2);

  aLong = (long) (shortInt1 * shortInt2);

  aChar = (unsigned char) (aLong + 5);

  aReal = (float) (shortInt1 * shortInt2 + 0.5);

  cout << «shortInt1 = » << shortInt1 << endl

       << «shortInt2 = » << shortInt2 << endl

       << «aByte = » << aByte << endl

       << «anInt = » << anInt << endl

       << «aLong = » << aLong << endl

       << «aChar is » << aChar << endl

       << «aReal = » << aReal << endl << endl<< endl;

  return 0;

}

/*Результаты:

  shortInt1 = 10

  shortInt2 = 6

  aByte = 16

  anInt = 4

  aLong = 60

      aCharis A

      aReal= 60.5

      shortInt1= 10

      shortInt2= 6

      aByte= 16

      anInt= 4

      aLong= 60

      aCharis A

      aReal= 60.5

*/

/*                                        ***   ВОПРОСЫ И ОТВЕТЫ   ***

Существуютли особые соглашения о присвоении имен идентификаторам?

      Существуетнесколько стилей, которые стали популярными в последние

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

      имяпеременной с символа, набранного в нижнем регистре. Если идентифи-

      каторсостоит из нескольких слов, как, например, numberOfElements,

      набирайтепервый символ каждого последующего слова в верхнем реги-

      стре.

Какреагирует компилятор, если вы объявляете переменную, но никогда не

присваиваетеей значения?

      Компиляторвыдает предупреждение, что на переменную нет ссылок.

Каковобулево выражение для проверки того, что значение переменной i

находитсяв заданном диапазоне значений (например, определяемом пере-

меннымиlowVal и hiVal)?

      Выражением,которое определяет, находится ли значение переменной i

      внекотором диапазоне, является (i >= lowVal && i <= hiVal).

2.    Конструкциипринятия решений и циклы                          ВОПРОСЫ  И ОТВЕТЫ

Предъявляетли С++ какие-либо требования на отступ операторов в предложениях оператора?

  Нет. Отступ определяется только Вами. Типовые размеры отступа составляют дваили четыре пробела. Использование отступов делает ваш листинг намного болееудобочитаемым.

  Вот пример оператора if с записью предложений без отступа:

      if ( 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; i+=2) {           while ( i <= 10) {

        cout << i << endl;                 cout << i << endl;

      }                                    i += 2;

                                         }

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

Каксмоделировать цикл while циклом do-while?

  Рассмотрим простой пример.

      i = 1;                             i = 1;

      do {                               while (i <= 10) {

        cout << i << endl;                 cout << i << endl;

        i += 2;                            i += 2;

      } while (i <= 10);                 }

  Оба цикла имеют одинаковые условия в предложениях while.

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

Какоткрытый цикл for может эмулировать циклы while и do-while?

  Открытый цикл for эмулирует другие циклы С++ установкой оператора if выхода изцикла в начале или конце цикла. Рассмотрим пример эмуляции цикла while открытымциклом for:

      i = 1;                             i = 1;

      while (i <= 10) {                  for (;;) {

                                           if (i > 10) break;

        cout << i << endl;                 cout << i << endl;

        i += 2;                            i += 2;

      }                                  }

  Заметим, что открытый цикл for использует оператор if выхода из цикла  какпервый оператор внутри цикла. Условие, проверяемое оператором if, естьлогическое обращение условия цикла while.

  Рассмотрим простой пример, иллюстрирующий эмуляцию цикла do-while:

      i = 1;                             i = 1;

      do {                               for (;;) {

        cout << i << endl;                 cout << i << endl;

                                           if (i > 10) break;

        i += 2;                              i += 2;

      } while (i <= 10)                  }

  Открытый цикл for использует оператор if выхода из цикла перед концом  цикла.Оператор if проверяет обратное логическое условие, так же как в цикле do-while.Однако имейте, пожалуйста, в виду, что приведенные примеры довольно грубы инеэлегантны. Никто никогда не будет использовать открытый оператор for подобнымобразом. Конечно, можно было бы пропустить одно из трех предложений внутрискобок цикла for (например, предложение инициализации, если управляющаяпеременная уже  инициализирована). Открытые циклы for чаще всего используются вслучаях, когда выход из цикла бывает редким событием, например, если приобработке данных, вводимых пользователем с клавиатуры, нажатие клавиши Escдолжно приводить к выходу из программы.

Можноли во вложенном цикле for использовать переменную управления внешним циклом вкачестве границы диапазона значений для внутренних циклов?

  Да. С++ не только не запрещает такое использование, на самом деле

  оно в порядке вещей. Рассмотрим простой пример.

      for ( int i = 1; i <= 100; i += 5)

        for ( int j = i; i <= 100; j++)

          cout  <  i * j << endl;

Ограничиваетли С++ вложение циклов разных типов?

  Нет. В программе на С++ вы можете вкладывать любые комбинации

  циклов.

СТРУКТУРАПРОГРАММЫ

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

     Исходная программа

               Исходная программа- это  совокупность следующих  об«ектов: директив,  указаний компилятору, об»явлений иопределений. Дирек­тивы задают действия препроцессора по преобразованию текстапрог­раммы перед компиляцией. Указания компилятору- это  команды,  вы­полняемые компилятором  во время процесса компиляции. Об«явления задают имена иатрибуты переменных, функций и типов, используемых в программе. Определения-это об»явления, определяющие переменные и функции.

               Определение переменной в дополнении к ееимени и типу зада­ет начальное значение об«явленной переменной. Крометого, опреде­ление предполагает распределение памяти для переменной.

Определение функции специфицирует  ее  структуру,  которая

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

Исходная  программа  может содержать любое число директив,

указаний компилятору, об«явлений и определений.Любой из об»ектов программы имеет определенный синтаксис, описанный в этомруковод­стве, и каждая составляющая может появляться в любом порядке, хотявлияние порядка, в котором следуют  переменные  и  функции  может быть использовано в программе (см. раздел 3.5 «Время жизни и ви­димость»).

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

               В следующемпримере иллюстрируется простая  исходная  прог­рамма на языке Си.

int x = 1;/* Variable definitions */

int y = 2;

extern int printf(char *,...);/* Function declaration */

main ()   /* Function definition for main function */

{

int z;    /* Variable declarations */

int w;

z = y + x;  /* Executablestatements */

w = y — x;

printf(«z = %d \nw = %d \n», z, x);

}

               Эта  исходная программа определяет функцию с именем main и об«являет функцию printf.Переменные x и y задаются своими  опре­делениями. Переменные z и w толькооб»являются.

ОБЪЯВЛЕНИЯ

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

[<sc-specifier>][<type-specifier>]<declarator>[=<initializer>][,<declarator>[=<initializer>...],

                 где:

<sc-specifier>-спецификатор класса памяти; <type-specifier>- имя определяемого типа;

                 <declarator>- идентификатор,который может быть модифициро­ван при об«явлении указателя, массива илифункции;

               <initializer>-  задает значение илипоследовательность зна­чений, присваиваемых переменной при об»явлении.

               Все переменные Си должны быть явнооб«явлены перед  их  ис­пользованием.  Функции  Си могут бытьоб»явлены явно или неявно в случае их вызова перед определением.

               Язык Си определяет стандартное множество типов  данных.  К этому       множеству можно добавлять новые типы данныхпосредством их

об«явлений на типах данных уже определенных.

               Об»явление Си требует одного илиболее деклараторов. Декла­ратор- это идентификатор, который может бытьопределен с квадрат­ными скобками ([]), эвездочкой (*) или круглыми скобками ()  для об«явления  массива,  указателя  или  функции.  Когда об'являетсяпростая переменная  (такая  как  символ,  целое  или  плавающее), структура или  совмещение простых переменных, то декларатор- это идентификатор.

               В Си определено четыре спецификатора классапамяти, а имен­но: 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                                                     unsignetshort

                 unsignet long int                                                       unsignetlong

                 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

имеют символьные эквиваленты. Аналогично, тип unsignedchar может запоминать величины с областью значений от 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 операцией.

     Деклараторы

               Синтаксис:

               <identifier>

               <declarator>[]

               <declarator>[constant-expression>]

               *<declarator>

               <declarator>()

               <declarator>(<arg-type-list>)

               (<declarator>)

               Сипозволяет об«являть: массивы величин, указатели на вели­чины, величинывозвратов функций.  Чтобы  об»явить  эти  об«екты, нужно использоватьдекларатор, возможно модифицированный квадрат­ными  скобками  ([]),  круглымискобками () и звездочкой (*), что соответствует типам массива, функции или указателя.  Деклараторы появляются в об»явлениях указателей, массивов ифункций.

Деклараторы массивов, функцийи указателей

               Когда деклараторсостоит из немодифицируемого идентификато­ра, то об'ект, которыйоб«является, имеет немодифицированный тип. Звездочка, которая можетпоявиться слева от идентификатора, моди­фицирует  его  в  тип  указателя. Еслиза идентификатором следуют квадратные скобки ([]), то тип модифицируется на типмассива. Ес­ли за идентификатором следуют круглые скобки, то тип  модифициру­ется на  тип функции. Сам по себе декларатор не образует полного об»явления.Для этого в об«явление должен быть включен специфика­тор типа.Спецификатор типа задает тип элементов массива или  тип адресуемыхоб»ектов и возвратов функции.

               Следующие примеры иллюстрируют простейшие формы декларато­ров:

               1. int list[20]

               2. char *cp

               3. doublefunc(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] ( structboth, struct both );

               В шестом примере показано, какоб»являть функцию, возвраща­ющую указатель на массив. Здесь varоб«явлена функцией, возвраща­ющей указатель на массив из трех величин типаdouble.  Тип  аргу­мента  функции  задан составным абстрактным декларатором.Круглые скобки, заключающие звездочку, требуются,  так  как  в  противномслучае  типом аргумента был бы массив из трех указателей на вели­чины типаdouble.

               В седьмом примере показано, чтоуказатель  может  указывать на  другой  указатель  и массив может состоять измассивов. Здесь var- это массив из пяти элементов. Каждый элемент,  в  свою оче­редь, так же массив из пяти элементов, каждый из которых являетсяуказателем на указатель совмещения, состоящего из двух элементов.

В  восьмом  примере  показано, как круглые скобки изменили

смысл об»явления. В этом примере var- это массив изпяти указате­лей на массив из пяти указателей на совмещения.

     Об«явления переменной

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

               Тип переменной                           Описание

               Простая переменная                    Переменнаяцелого или плаваю-

                                                                      щеготипа.

               Переменная перечис-                  Простаяпеременная целого типа

               ления.                                            котораяпринимает значения из

                                                                      предопределенногонабора зна-

                                                       ченийпоименованных констант. Структура  Переменная, которой соответс-

твует композиция отдельных пе­ременных, типы которыхмогут отличаться.

               Совмещение                                 Переменная,которой соответс-

твует композиция отдельных пе­ременных,занимающих одно и то же пространство памяти. Типы переменных композиции могутотличаться.

                 Массив                                          Переменная,представляющая на-

                                                                        борэлементов одного типа.

                 Указатель                                      Переменная,которая указывает

на другую переменную (содержит местоположение другойперемен­ной в форме адреса).

Общий синтаксис об«явленийпеременных следующий:

[<sc-spesifier>]<type-spesifier> <declarator> [,<declarator>...],

     где  <type-  spesifier> — задает тип данных,представляемых переменной, а <declarator> — это имя переменной, возможномодифи­цированное для об»явления массива или указателя. В об«явлении может быть задана более чем одна переменная путем задания  множест­венного об»явления,  в  котором  деклараторы разделены запятыми. <sc-spesifier> задает класс памяти переменной. В некоторых  слу­чаях переменные  могут быть инициализированы при их определении. Классы памяти иинициализация описаны в разделах 4.6 и 4.7  соот­ветственно.

          Объявление простойпеременной

               Синтаксис:

               <type-specifier><identifier>[,<identifier>...];

               Об«явление простой  переменной определяет имя переменной и ее тип; оно может такжеопределять класс памяти  переменной,  как это описано в разделе 4.6. Имяпеременной- это идентификатор, за­данный  в  об»явлении.  Спецификатортипа <type-specifier> задает имя определяемого типа данных.

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

               Примеры

               intx;                            /* Example 1 */

unsigned longreply, flag         /* Example 2 */ double order;                     /*Example 3 */

В  первом примереоб«является простая переменная x. Эта пе-

ременная может принимать любоезначение  из  множества  значений, определяемых для типа int.

               Во  втором примере об»явлены две переменные: reply и flag. Обе переменные имеют типunsigned long.

               Втретьем примере об«явлена переменная order, которая имеет тип double. Этойпеременной могут быть присвоены величины с  пла­вающей запятой.

          Объявлениеперечисления

          Синтаксис:enum[<tag>]{<enum-list>}<identifier>[,<identifier>...];enum<tag><identifier>[,<identifier>...];

               Об»явление перечисления задает имя переменной перечисления и определяет список именованныхконстант, называемый списком  пе­речисления.  Значением каждого имени спискаявляется целое число. Переменная перечисления принимает значение одной  из именованных констант  списка. Именованные константы списка имеют тип int. Та-

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

Объявлениеперечисления начинается с ключевого слова enum и

имеет две формы представления. Впервой форме представления имена перечисления задаются в списке перечисления<enum-list>.

               Опция <tag>-  это идентификатор, который именует тип пере­числения,определенного в <enum-list>.

               Переменнуюперечисления именует <identifier>. В  об«явлении может быть описанаболее чем одна переменная перечисления.

               Во второй формеиспользуется тег перечисления, который ссы-

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

               <enum-list> имеет следующийсинтаксис:

<identifier>[=<constant-expression>][,<identifier>

                                                                        [=<constant-expression]]...

              .

              .

              .

               Каждый  идентификатор  именует  элементы перечисления.  По умолчанию первому идентификатору соответствует значение 0,следу­ющий идентификатор ассоциируется со значением 1 и т. д. Имя конс­тантыперечисления эквивалентно ее значению.

               Запись =<constant-expression>переопределяет последователь­ность значений, заданных по умолчанию. Идентификатор,  следующий перед записью =<constant-expression> принимаетзначение, задавае­мое  этим константным выражением. Константное выражение имееттип int и может быть отрицательным. Следующий идентификатор в  списке ассоциируется с  величиной, равной <constant-expression>+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[<tag>]{<member-declaration-list>}<declarator>[,<declarator>...];struct<tag><declarator>[,<declarator>...];

               Об«явлениеструктуры задает имя типа структуры и специфици­рует последовательностьпеременных величин, называемых элементами структуры, которые могут иметьразличные типы.

               Об»явление структуры начинается с ключевого слова struct и имеет две формы представления,как показано выше. В первой  форме представления  типы и имена элементовструктуры специфицируются в списке об«явлений элементов<member-declaration-list>. <tag>- это идентификатор, который именует  тип  структуры,  определенный  в списке об»явлений элементов.

               Каждый <declarator>  задает имя переменной типа структуры. Тип переменной в декларатореможет быть модифицирован  на  указа­тель к структуре, на массив структур или нафункцию, возвращающую структуру.

               Втораясинтаксическая форма использует тег- <tag> структуры для  ссылки на типструктуры. В этой форме об«явления отсутствует список об»явленийэлементов, поскольку тип структуры определен  в другом  месте. Определение типаструктуры должно быть видимым для тега, который используется  в об«явлении  и  определение  должно предшествовать об»явлению черезтег, если тег не используется для об«явления  указателя  или структурноготипа typedef. В последних случаях об»явления могут использовать тегструктуры без предвари­тельного определения типа структуры, но все жеопределение должно находиться в пределах видимости об«явления.

               Список об»явленийэлементов <member-declaration-list>-  это одно  или  более об«явлений переменных или битовых полей. Каждая

переменная, об»явленная  в  этом  списке,  называется  элементом структурного  типа.Об«явления переменных списка имеют тот же са­мый синтаксис, что иоб»явления  переменных  обсуждаемых  в  этой главе,  за  исключением того,  что об«явления не могут содержать спецификаторов класса памяти илиинициализаторов. Элементы струк­туры могут быть любого  типа:  основного, массивом,  указателем, совмещением или структурой.

               Элемент не может иметь тип структуры, в которой он появля­ется. Однако, элемент можетбыть об»явлен, как указатель  на  тип структуры,  в  которую  он  входит, позволяя создавать списочные структуры.

          Битовые поля

Об«явления битовых полейимеют следующий синтаксис:

   <type-specifier>[<identifier>]:<constant-expression>;Битовое  поле состоит из некоторого числа бит, специфициро-

ванных константным выражением-<constant- expression>. Для  бито-

вого поля спецификатор типа<type- specifier> должен специфициро-

вать  беззнаковый  целый тип, аконстантное выражение должно быть неотрицательной целой величиной. Массивыбитовых полей, указатели на битовые поля и функции, возвращающие битовые поляне  допуска­ются.  Идентификатор- <identifier> именует битовое поле.Неимено­ванное битовое поле, чей размер специфицируется как нулевой, име­етспециальное назначение: оно гарантирует, что память для следу­ющей переменнойоб»явления будет начинаться на границе int.

                        Идентификаторыэлементов внутри об«являемой структуры должны  быть  уникальными. Идентификаторы  элементов  внутри  разных структур  могут совпадать. В пределахтой же самой видимости теги структур должны отличаться от других тегов (тегов других  струк­тур, совмещений и перечислений).

               Переменные (элементы) структурызапоминаются последователь­но  в том же самом порядке, в котором ониоб»являются: первой пе­ременной соответствует самый младший адрес памяти, а  последней­самый  старший.  Память  каждой  переменной начинается на границесвойственной ее  типу.  Поэтому  могут  появляться  неименованные участки междусоседними элементами.

               Битовые  поля нерасполагаются на пересечении границ, обяв­ленных для них типов. Например,битовое поле, об«явленое с  типом unsigned  int,  упаковывается  или  впространстве, оставшимся от предидущего unsigned int или начиная с новогоunsigned int.

               Примеры

                 /**************** Example 1****************/

                 struct {

                                           float x,y;

                           }complex;

/**************** Example 2*****************/

                 struct employee {

                                              charname[20];

                                              int id;

                                              longclass;

                           }temp;

                 /****************Example 3 ******************/

struct employee student,faculty, staff;

/****************Example 4 ******************/ struct sample {

                                              charc;

                                              float*pf;

                                              structsample *next;

                           }x;

                 /*****************Example 5 ******************/

                 struct {

                                              unsignedicon: 8;

unsigned color:4; unsigned underline: 1; unsigned blink: 1;

                           }screen[25][80];

               В первом примереоб»является переменная  с  именем  complex типа структура. Эта структурасостоит из двух элементов x и y ти­па float. Тип структуры не поименован.

               Во втором примереоб«является переменная с именем temp типа структура. Структура состоит изтрех элементов с именами name, id и  class.  Элемент  с именем name- это массивиэ 20- ти элементов типа char. элементы с именами id и class- это простые переменные типа  int  и long соответственно. Идентификатор employee являетсятегом структуры.

               В третьем примереоб»явлены три переменных типа структура с именами: student, faculty иstaff. Каждая из структур состоит  из трех  элементов  одной  и той жеконструкции. Элементы определены при об«явлении типа структуры с тегомemployee в предыдущем  примере.

               В  четвертомпримере об»является переменная с именем x типа структура. Первые дваэлемента структуры представлены  переменной c  типа  char и указателем pf навеличину типа float. Третий эле­мент с именем  next  об«являются  как указатель  на  описываемую структуру sample.

               В  пятом примереоб»является двумерный массив поименованный screen, элементы которого имеютструктурный тип.  Массив  состоит из 2000 элементов и каждый элементэтоотдельная структура, состо­ящая  из  четырех  элементов типа bit-fild с именамиicon, color, underline и blink.

          Об«явлениесовмещений

                        Синтаксис:union[<tag>]{<member-declaration-list>}<declarator>[,<declarator>...];union<tag><declarator>[,<declarator>...];

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

               Об«явлениесовмещения имеет тот же самый синтаксис,  как  и об»явление  структуры, за исключением того, что она начинается с ключевого слова union вместоключевого слова struct. Для об«явле­ния совмещения и структуры действуютодни и те же правила, за ис­ключением того, что в совмещении не допускаютсяэлементы типа би­товых полей.

               Память,которая соответствует переменной  типа  совмещение, определяется  величиной дляразмещения любого отдельного элемента совмещения.

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

                        Примеры:

/**************Example 1 ********************/

                           union sign {

                                              int svar;

                                              unsigneduvar;

                                  }number;

/************** Example 2********************/

                           union {

                                              char *a,b;

                                              floatf[20];

                                    }jack;

                           /***************Example 2 *******************/

                           union {

                                              struct {

                                                                   charicon;

                                                                   unsignedcolor: 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“.

     Об»явление массива

Синтаксис:<type-specifier><declarator>[<constant-expression>];<type-specifier><declarator>[];

               Здесь квадратныескобки- это терминальные символы. Об«явле­ние массива определяет типмассива и тип  каждого  элемента.  Оно может определять также число элементов вмассиве. Переменная типа массив  рассматривается как указатель на элементымассива. Об»яв­ление массива может представляться в двух синтаксических формах, указанных  выше.  Декларатор<declarator>  задает  имя переменной.Квадратные скобки, следующие за декларатором, модифицируют декла­ратор               на  тип     массива.      Константное      выражение

<constant-expression>,заключенное в квадратные скобки, определя­ет  число элементов в массиве. Каждыйэлемент имеет тип, задавае­мый спецификатором типа <type-specifier>,который может  специфи­цировать любой тип, исключая void и тип функции.

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

               Массивмассивов или многомерный массив  определяется  путем задания списка константныхвыражений в квадратных скобках, следу­щего за декларатором:

<type-specifier><declarator>[<constant-expression>]

                                                                                  [<constant-expression>]...

               Каждое константное выражение-<constant-expression> в квад­ратных скобках определяет число  элементов в  даннном  иэмерении

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

массив, явно определенный где- то в программе, то первоеконстан­тное выражение может быть опущено.

               Массив  указателей  на величины, заданного типа, может быть определен посредством составногодекларатора, как было описано  в разделе 4.3.2.

               Типу  массив соответствует  память,  которая требуется для размещения всех его элементов.Элементы массива с первого до пос­леднего запоминаются в последовательныхвозрастающих адресах  па­мяти. Между элементами массива в памяти разрывыотсутствуют. Эле­менты  массива  запоминаются  друг за другом построчно.Например, массив, содержащий две строки с тремя столбцами каждая,

char A[2][3]

               будет запомненследующим образом. Сначала запоминаются  три столбца  первой строки, затемэлементы трех столбцов второй стро­ки. Смысл этого в том, чтобы последнийиндекс был более  быстрым. Чтобы  сослаться на отдельный элемент массива, нужноиспользовать индексное выражение, которое описано в разделе 5.2.5.

               Примеры:

/*************** Example 1******************/

                 int scores[10],game;

/*************** Example 2******************/

                 floatmatrix[10][15];

/*************** Example 3******************/

                 struct {

                                              floatx,y;

                                              }complex[100];

/*************** Example 4*******************/

                 char *name[20];

               В первом примереоб«является переменная типа массив с  име­нем  scores  из  10  элементовтипа int. Переменная с именем game об»явлена как простая переменная целоготипа.

               Во втором примереоб«является  двумерный  массив  с  именем matrix. Массив состоит из 150-тиэлементов типа float.

               В  третьемпримере об»является массив структур. Массив сос­тоит из 100 об«ектов.Каждый об»ект  массива  представляет  собой структуру, состоящую из двухэлементов.

               В четвертомпримере об«явлен массив указателей. Массив сос­тоит из 20-ти элементов,каждый из которых является указателем на величину типа char.

               4.4.6.Об»явление указателей

               Синтаксис:

                 <type-specifier>*<declarator>;

               Об«явление указателя определяет имя переменной типа указа­тель и тип об»екта, накоторый указывает эта переменная. Деклара­тор- <declarator> определяетимя переменной с возможной модифика­цией ее типа. Спецификатор типа- <type-  specifier>  задает  тип об«екта,  который  может  быть базового типа, типа структуры или совмещения.

               Переменная типауказатель может указывать также на функции, массивы и другие указатели. Болееполная информация о типах  ука­зателей дана в разделе 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.

          Об«явлениефункций

                      Синтаксис:

[<type-specifier>]<declarator>([<arg-type-list>])[,<declarator>...];

              Об»явлениефункции определяет имя, тип возврата функции  и, возможно,  типы  и  число ееаргументов. Об«явление функции также называется forward- об»явлением.Декларатор функции об«являет имя функции, а спецификатор типа задает типвозврата. Если специфика­тор  типа  опущен  в  об»явлении  функции, топредполагается, что функция возвращает величину типа int.

              Об«явление функции может включатьспецификаторы класса  па­мяти extern или static.

                      Списоктипов аргументов.

              Список типов аргументов-<arg-type-list> определяет число и типы аргументов функции. Синтаксиссписка аргументов следующий:

                      <type-name-list>[,...]

              Список  иментипов- это список из одного или более имен ти­пов. Каждое имя типа отделяетсяот другого  запятой.  Первое  имя типа  задает  тип  первого  аргумента, второеимя типа задает тип второго аргумента и т. д. Если список  имен  типов заканчивается запятой с многоточием (,...), то это означает, что число аргумен­тов функции переменно. Однако, предполагается, что функция будет иметь не меньшеаргументов, чем имен типов, предшествующих много­точию.

                      Если список  типов  аргументов-  <arg-type-list>  содержит

только многоточие (...), то число аргументов функцииявляется пе-

       ременным или равно нулю.

                      Замечание:

              Чтобы  поддержать  совместимость  с программами предидущих версий, компилятор допускает символ запятой безмноготочия в кон­це списка типов аргументов для обозначения их переменного числа. Запятая  может быть использована и вместо многоточия для об»явле­ниянуля или более аргументов функции. Использование запятой под­держивается толькодля  совместимости.  Использование  многоточия рекомендуется для новогопредставления.

              Имя  типа-<type- name> для типов структуры, совмещения или базового типа состоит изспецификатора этого типа (такого как 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;

               intprt(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.

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

               Пример:

                 /*****************************************************

                                                SOURCEFILE ONE *****************************************************/

                 extern int i;                                       /* reference to i

                                                                                                definedbelow */

                 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

                                                                                                firstsource 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 */

                                    externint i;

/* initial value is zero; a is

visible only within main */

                                    staticint a;

/* b is stored in a register, if possible */ registerint b = 0;

/* default storage class is auto */

                                    intc = 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 */

                                    inti = 16;

                                    /* this a is visibleonly 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.

     Инициализация

               В об»явлениипеременной может быть присвоено начальное зна­чение посредством инициализатора.Величина или величины инициали­затора присваиваются переменной.

               Синтаксически,записи инициализатора предшествует знак рав­но (=)

                 =<initializer>

               Могут быть инициализированы переменныелюбого типа. Функции не  инициализируются. Об«явления, которые используютспецификатор класса памяти extern не могут содержать инициализатора.

Переменные, об»явленные навнешнем уровне, могут быть  ини-

циализированы. Если они явно не инициализированы, то ониустанав­ливаются  в  нуль во время компиляции или линкования. Любая пере­менная,об«явленная со спецификатором класса памяти static, может бытьинициализирована константным выражением. Инициализация пере­менных классаstatic выполняется один раз  во  время  компиляции. Если отсутствует явнаяинициализация, то переменные класса памяти static автоматически устанавливаютсяв нуль.

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

составных  типов  auto (массив, структура, совмещение)запрещена. Любое составное об»явление класса памяти static может бытьиници­ализировано на внешнем уровне.

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

Базовые типы и типы указателей

               Синтаксис:

               =<expression>

               Величина  выраженияприсваивается переменной. Для выражения допустимы правила преобразования.

               Примеры:

                 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.

     Составные типы

               Синтаксис:

               ={<initializer-list>}

               Список инициализаторов <initializer-list> — это последова­тельностьинициализаторов, разделенных запятыми. Каждый инициали­затор впоследовательности- это либо константное выражение,  либо список инициализаторов.  Поэтому,  заключенный в фигурные скобки список, можетпоявиться внутри другого списка инициализации.  Эта конструкция  используется для  инициализации элементов составных конструкций.

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

               Если в спискеинициализации меньше величин, чем их  имеется в  составном  типе,  тооставшиеся памяти инициализируются нулем. Если число инициализирующих величинбольше чем требуется, то  вы­дается ошибка.

               Этиправила применяются к каждому вложенному списку инициа­лизаторов, точно так жекак и ко всей конструкции в целом.

               Пример:

               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  определяет спецификатор типа для типа. Это об»явлениеиспользуется для того, чтобы создавать  более  ко­роткие  или  более осмысленные имена типов уже определенных в Си или об«явленныхпользователем.

     Типы структур, совмещений иперечислений

               Об»явления типов структур,совмещений и перечислений  имеют ту же самую общую синтаксическую форму, как иоб«явления перемен­ных  этих  типов. В об»явлении типа идентификаторпеременной опу­щен, так как нет переменной которая об«является. Именем структу­ры, совмещения или перечисления является тег.

               В об»явлении типа может появитьсясписок об«явлений элемен­тов-       <member-declaration-list>   или   список   перечисления-

<enum-list>, определяющие тип.

               Сокращенная форма об»явленияпеременной, в котором 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 studentemployee, чтобы об«явить структур-

ную переменную employee типа student.

     Об»явления typedef

               Синтаксис:

typedef <type-spesifier><declarator>[,<declarator>...];Об«явления typedef являются аналогом об»явления переменной,

за исключением того, чтоключевое слово typedef заменяет специфи­катор класса памяти.

               Об«явление интерпретируется тем же самым путем, как об»яв­ления переменной илифункции, но <declarator> вместо того,  чтобы стать переменной типа, специфицированногооб«явлением, становится синонимом  имени  типа.  Об»явление 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 voidDRAWE(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.  соответственно.

Именами  для  основных, перечисляющих, структурных и совмещающих типов являются спецификаторы типа длякаждого из них.  Имена  для типов  указателя,  массива и функции задаютсяследующей синтакси­ческой формой:

                 <type-specifier><abstract-declarator>

               Абстрактный декларатор<abstract-declarator>- это  деклара­тор без идентификатора, состоящий изодного или более модификато­ров  указателей,  массивов  и  функций. Модификаторуказателя (*) всегда появляется перед идентификатором в деклараторе, в то времякак модификатор массива ([]) или функции ( () ) появляются  послеидентификатора.  Таким  образом, чтобы правильно интерпретировать абстрактныйдекларатор, нужно начинать интерпретацию с подразуме­ваемого идентификатора.

               Абстрактныедеклираторы могут  быть  составными.  Скобки  в составном абстрактномдеклараторе специфицируют порядок интерпре­тации,  подобно тому как этоделается при интерпретации составных деклараторов об«явлений. Абстрактный  декларатор,  состоящий  из пустых  круглых  скобок () недопускается, поскольку это двусмыс­ленно. В этом случае невозможно определитьнаходится ли  подразу-

меваемый идентификатор внутри скобок, и в таком случае-это немо­дифицированный  тип,  или перед скобками, тогда- это тип функции.Спецификаторы типа, установленные посредством об»явлений 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<iostream.h>

       int main()

       {

        int *p = new int;

        cout <<«Enter а number»;

        cin >> *p;

        cout << «Thesquare of » << *p << " = " << (*p * *p);

        return 0;

       }

ФункцииОбъявление и определениефункций

   — Общая форма определенияфункции такова:

   возвращаемыйТипимяФункции(<список параметров>)

// обязателен тип возвращаемогозначения

   {

    < объявление данных >

    < тело функции>

    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 << «Значениепараметра меньше 18» << endl;

    return;

  }

  if ( ui.age < 21 )

    ui.age = 21;

}

/*

   Если функция вызывается досвоего определения, обязательно должен быть задан прототип функции. Общая формаобъявления функции:

     возврТип имяФункции(<списокпараметров>);

   При объявлении функции именапараметров могут быть опущены.

   — Передача аргумента поссылке позволяет функции изменять значение переданного аргумента и экономитпамять, так как при этом не создается локальная копия аргумента:

   [const] тип1& параметр1,[const] тип2& параметр2, ...

void foo(int &);    // — объявление функции — это ее прототип

int main()

{

  int value = 5;

  foo(value);

  cout << value <<endl;

  return 0;

}

void foo(int &parm)  // — определение функции вызов параметра по ссылке

{

  ++parm;

}

/*  Результаты:

6

*/

void foo(int *); // пердачауказателя

int main()

{

  int value = 5;

  foo(&value); // передаетсяадрес

  cout << value <<endl;

  getch();

  foo(&value);

  cout << value <<endl;

  getch();

  return 0;

}

void foo(int* parm)

{

  ++*parm;  // параметр — указатель

}

/*  Результаты:

6

7

   — Локальные переменные иконстанты существуют и действуют только в теле данной функции, где ониобъявлены. Объявление локальных переменных подобно объявлению глобальных переменных.

Программа LOCAL.CPP знакомит спонятием локальной переменной

— Ключевое слово staticпозволяет объявить переменную как статическую.

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

Программа STATIC.CPP знакомит спонятием статической локальной переменной

— Макроопределения позволяют вамвводить компактные псевдо-функции, принимающие любые типы данных, посколькукомпилятор не выполняет в этом случае проверку типов:

#define min(n1, n2) (((n1) <(n2))? (n1): (n2))

#define max(n1, n2) (((n1) >(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 <iostream.h>

   void swap(int i, int j)

   {

    int temp = i;

    i = j;

    j = temp;

   }

   int main()

   {

    int a = 10, b = 3;

    swap (a, b);

    cout << «а =» << a << " and b = " << b;

    return 0;

   }

/*

2. Каков будет результат работыследующей программы? Что вы можете сказать по поводу еще одной функции swap?

*/

   #include <iostream.h>

   void swap(int &i, int&j)

   {

    int temp = i;

    i = j;

    j = temp;

   }

   int main()

   {

    int a = 10, b = 3;

    swap (a, b);

    cout << «а =» << a << " and b = " << b;

    return 0;

   }

/*

3. Что за проблема возникнет соследующими перегруженными функциями?

*/

   void inc(int &i)

   {

    i = i + 1;

   }

   void inc(int &i, int diff= 1)

   {

    i = + diff;

   }

/*

4. Найдите ошибку в функции.

/*

   double volume(double length,double width = 1, double height)

   {

    return length * width *height

   }

/*

5. Найдите ошибку в функции.

*/

   void inc (int &i, intdiff = 1)

   {

    i = I + diff;

   }

/*

6. В этой программе есть ошибка.Что это за ошибка и как ее исправить?

*/

   # include<iostream.h>

   int main()

   {

    double x = 5.2;

    cout  << x <<" ^ 2 = " << sqr(x);

    return 0;

   }

   double sqr( double х)

   { return x * x; }

/*

7. Попробуйте в функциивычисления факториала использовать операцию ?: .

Массивы

// Листинг 6.1. исходный текстпрограммы AVERAGE1.CPP

// Программа иллюстрируетиспользование одномерных массивов

// при расчете среднегозначения.

#include <iostream.h>

const int MAX = 0x1FFF; //64K/8- максимальный размер массива типа double ***

int main()

{

   double array[MAX]; //объявление одномерного массива                   ***

   int num_elem;

   // Ввод количестваобрабатываемых данных

   do

      {

      cout <<«Введите размер массива данных [2… „

           << MAX <<“]: »;

      cin >> num_elem;

      cout << endl;

      } while (num_elem < 2|| num_elem > MAX);

   // Ввод данных

   for (int ix = 0; ix <num_elem; ix++)

      {

      cout <<«массив[» << ix << "]: ";

      cin >> array[ix];

      }

   // Расчет среднего значения

   double sum = 0;

   for (ix = 0; ix <num_elem; ++ix)

      sum += array[ix];

   cout << endl <<«Среднее: » << sum / num_elem << endl;

   return 0;

/*

 - При объявлении одномерныхмассивов им можно присвоить начальные значения. Список ИНИЦИАЛИЗАЦИИ долженбыть заключен в фигурные скобки, а элементы в нем должны быть разделенызапятыми. Можно при инициализации задать данных МЕНЬШЕ, чем размер массива. Вэтом случае компилятор автоматически присвоит нулевые значения тем элементам,которые вы не инициализировали. И вдобавок, если вы не укажете размерностьинициализируемого массива, она будет определена по количеству элементов всписке инициализации.

*/

// Листинг 6.2. исходный текстпрограммы AVERAGE2.CPP

// Программа иллюстрируетиспользование одномерных массивов

// при расчете среднегозначения.

// Данные задаются приинициализации массива.

#include <iostream.h>

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 <<«массив[» << ix << "]: " << array[ix]<< endl;

      }

   cout << endl <<«Среднее: » << sum / num_elem << endl;

   return 0;

}

 - Объявление одномерныхмассивов в качестве параметров функции возможно в двух формах: массив-параметрфиксированной размерности и массив-параметр неопределенной длины (открытыймассив), При объявлении параметром массива фиксированной размерностиуказывается размер массива. В этом случае передаваемые функции аргументы должнысоответствовать параметру по типу и размеру. Массив- араметр неопределеннойдлины объявляется с пустыми скобками, означающими, что аргумент может бытьлюбого размера.

(Листинг 6.3а. исходный текстпрограммы MINMAX.CPP)

(Листинг 6.3. исходный текстпрограммы MINMAX.CPP)

СОРТИРОВКА массива — ПРИМЕР вфайле list6_4cpp.

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

-     ПОИСК в массиве

означает нахождение в массиве элемента, совпадающего сзаданным значением. Методы поиска делятся на две группы: для упорядоченных инеупорядоченных массивов. Метод линейного поиска применяется для неупорядоченныхмассивов, а метод двоичного поиска — для сортированных массивов. (Пример — list6_5.cpp)

 Рассмотрим понятияПАРАМЕТРОВ-ФУНКЦИЙ и УКАЗАТЕЛИ НА ФУНКЦИИ:

(Листинг 6.5. исходный текстпрограммы SEARCH.CPP)

БИБЛИОТЕЧНЫЕ ФУНКЦИИ ПОИСКА иСОРТИРОВКИ в непрерывных массивах:

*/

 void *bsearch(const void *key,const void *base, size_t nelem,

    size_t width, int(*fcmp)(const void*, const void*));

 // key — указатель на искомыйэлемент,

 //    возвращаемое значение — указатель на элемент (0 — не найден)

 // base — базовый адрес массива

 // num — число элементов вмассиве

 // width — размер элемента

 // fcmp — указатель на функциюсравнения элементов массива

 // Функция возвращает указательна элемент, а не значение индекса элемента

 //    Если элемент не обнаружен,возвращается 0.

 //     Для вычисления индексаможно использовать следующую формулу:

      index = (searchRslt — arrayBase) / sizeof(arrayBase[0]);

 void *lfind(const void *key,const void *base, size_t *num,

    size_t width, int(*fcmp)(const void *, const void*));

 void *lsearch(const void *key,void *base, size_t *num,

    size_t width, int(*fcmp)(const void *, const void *));

 // — если нет элемента, то онвставляется, поэтому возвращаемое значение

 //   всегда не ноль.

 void qsort(void *base, size_tnelem,

    size_t width, int(*fcmp)(const void *, const void *));

/*

 - При объявлении многомерныхмассивов вам нужно указать тип массива, его имя и размер (заключенный в своюпару скобок) по каждому измерению. Нижнее значение индекса для любого измеренияравно 0. Верхнее значение индекса по любому измерению равно количествуэлементов поэтому измерению минус единица.

 - Для того чтобы обратиться кмногомерному массиву, Вам нужно задать его имя и правильные значения индексов.Каждый индекс должен быть заключен в свою пару скобок.

   Пример работы с двумерныммассивом:

(Листинг 6.6. Исходный текстпрограммы MATRIX1.CPP)

— При объявлении многомерныхмассивов им можно присвоить начальные значения. Список ИНИЦИАЛИЗАЦИИ долженбыть заключен в фигурные скобки, а элементы в нем должны быть разделенызапятыми. Можно при инициализации задать данных меньше, чем размер массива, Вэтом случае компилятор автоматически присвоит нулевые значения тем элементам,для которых вы не указали начальные значения:

(Листинг 6.7. Исходный текстпрограммы MATRIX2.CPP.)

— Объявление многомерныхмассивов в качестве параметров функции воз- можно в двух формах:массив-параметр фиксированной размерности и массив-параметр неопределеннойдлины по первому измерению. При объявлении параметром массива фиксированнойразмерности указывается размер массива по каждому измерению. В этом случаепередаваемые функции аргументы должны соответствовать по типу и размерупараметру. Массив-параметр неопределенной длины объявляется с пустыми скобкамидля первого измерения, означающими, что передаваемый аргумент может быть любогоразмера по первому измерению. По другим измерениям размеры аргумента ипараметра должны совпадать:

(Листинг 6.8. Исходный текстпрограммы MATRIX3.CPP)

Строки и управление вводом/выводом

Здесьподробнее рассматриваются операции консольного ввода/вы­вода. C++, как и егопредок — язык С — не определяет операции ввода/вывода как часть языка, авыносит операции консольного ввода/вывода в библиотеки ввода/вывода. Такиебиблиотеки в основном предназначены для работы в MS-DOS.Рассмотрим небольшую выборку функций ввода/вывода, объявляемых в заголовочныхфайлах STDIO.H иIOSTREAM.H.

Сегоднямы рассмотрим следующие темы:

·    Форматированный потоковый вывод

·    Потоковый ввод

·    Функция printf

·    Строки в C++

·    Ввод строк

·    Использование стандартнойбиблиотеки функций для работы со строками

·    Присвоение значений строкам

·    Определение длины строки

·    Конкатенация строк

·    Сравнение строк

·    Преобразование строк

·    Перестановка символов в строке вобратном порядке

·    Поиск символа

·    Поиск подстроки

Форматированный потоковый вывод

C++имеет целое семейство гибких библиотек функций ввода/вывода. Разработчикамязыка было ясно, что функции ввода/вывода из STDIO.H, унаследованныеиз С, имеют ограничения при работе с классами (вы узнаете больше о классах вглаве 8)., В результате в C++ было введено понятие потоков. Вспомним, что потоки,которые уже существовали в С, означают последовательность данных, передаваемыхиз одной части компьютера в дру­гую. В программах, рассматриваемых ранее, вывидели операцию помещения в поток «, например — в стандартный поток вывода, cout.Встречалась вам и операция извлечения из потока », применяемая к стандартномупотоку ввода, cin. В этом разделе мы познакомимся с потоковыми функциямиwidth иprecision, используемыми приформатировании вывода. Библиотеки потоков C++ содержат большое количество такихфункций, позволяющих настроить ваш вывод.

Функция widthзадает ширину поля вывода. Общая форма использования функции widthс потоком cout:

cout.width (widthOf Output);

Функцияprecision определяет количество значащих цифр после точки для чисел с пла­вающейточкой. Общая форма использования функции precision с потоком cout:

cout.precision(numberOfDigits);

Обратимсяк примеру, программе OUT1.CPP, исходный текст которой при­веден в листинге 1.Программа, в которую ничего не вводится, просто выво­дит форматированные целыечисла, числа с плавающей точкой и символы с использованием функций width иprecision.

Листинг 1. Исходный текст программы OUT1.CPP01 // Программа иллюстрирует потоковый форматированный вывод в C++ 02 // с использованием функций width и precision 03 #include <iostream.h> 04 05 int main() 06 { 07 int      anInt      = 67; 08 unsigned char aByte = 128; 09 char     aChar      = '@'; 10 float    aSingle    = 355.1112; 11 double   aDouble    = 1.131112e+002; 12 13 // Вывод простых выражений 14 cout.width(3); cout << int(aByte) << " + "; 15 cout.width(2); cout << anInt << " = "; 16 cout.width(3); cout << (aByte + anInt) << endl; 17 18 cout.precision(3); cout << aSingle << " / "; 19 cout << aDouble << " ="; 20 cout.width(7); cout.precision(4); cout << (aSingle / aDouble) << endl; 21 22 cout << «Символьная переменная aChar: „ 23 << aChar << endl; 24 return 0; 25 }

Пример программной сессии:

Введитетри числа через пробел: 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:

intprintf(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 <stdio.h>

04

05

int main()

06

{

07

int      anInt      = 67;

08

Unsigned char aByte = 128;

09

char     aChar      = '@';

10

Float    aSingle    = 355.0;

11

Double   aDouble    = 1.130e+002;

12

13

Printf("%3d + %2d = %3d\n",

14

aByte, anInt, aByte + anInt );

15

16

Printf(«Вывод использует спецификации преобразования %%lf :\n»);

17

Printf("   %6.4f / %10.4lf = %7.5lf\n",

18

aSingle, aDouble, aSingle / aDouble );

19

20

Printf(«Вывод использует спецификации преобразования %%le :\n»);

21

printf("   %6.4e / %6.4le = %7.5le\n",

22

aSingle, aDouble, aSingle / aDouble );

23

24

printf(«Вывод использует спецификации преобразования %%lg :\n»);

25

printf("   %6.4g / %6.4lg = %7.5lg\n",

26

aSingle, aDouble, aSingle / aDouble );

27

28

printf(«Символьная переменная aChar: %c\n», aChar);

29

printf(«ASCII-код %c: %d\n», aChar, aChar);

30

return 0;

31

}

Пример вывода программы из листинга 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 char Int %2d anInt int Int %3d aByte + anInt int Int

Операторвывода в строке 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 объявляется следующим образом:

istreamsgetline(   signed char *buffer,

int size,

char delimiter = '\n') ;

istreamsgetline(   unsigned char *buffer,

int size,

char delimiter = '\n') ;

istream&getline( char *buffer,

int size,

char delimiter = '\n') ;

Параметрbuffer является указателем на строку, в которую поме­щаются вводимые символы.Параметр size задает максимальное коли­чество вводимых символов. Параметрdelimeter определяет символ-ог­раничитель, при появлении которого ввод символовпрекращается прежде, чем будут введены все size символов. Параметр delimeterимеет аргумент по умолчанию, равный '\n'. В случае ввода символов с клавиатурыэтот символ появляется в потоке ввода при нажатии клавиши

Пример

#include <iostream.h> //см. файл Ex01.cpp

int main()

{

char name[80] ;

cout « «Enter your name: »;

cin.getline(name, sizeof(name) — 1);

cout« «Hello » « name « ", how are you?";

return0;

}

Функции,объявленные в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++поддерживает два способа присвоения значений строкам. Вы можете присвоитьстроковой переменной строковую константу, произведя инициализацию приобъявлении строки. Этот метод прост: требуется операция при­сваивания истроковая константа.

Инициализация строки

Общий метод инициализации строки:

charstringVar[stringSize] = stringLiteral;

Пример

char a3tring[81]= «Borland C++ 5in 21 days»;

char Named = «Rene Kinner»;

Второйспособ присвоить значение строке — это вызвать функцию, ко­торая копируетсодержимое одной строки в другую, — не забывая при этом и нуль-символ. Этафункция называется strcpy. Она предполагает, что ко­пируемая строкаоканчивается символом NUL и прекращает копирование, как только встретит этотсимвол.

Функция strcpy

Прототипфункции strcpy таков:

char*strcpy(char *target, const char *source);

Функциякопирует строку source в строку target. Функция пред­полагает, что целеваястрока имеет размер, достаточный для того, чтобы вместить содержимоестроки-источника.                              

Пример

charname[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. Функция не выполняет ниусечение, ни заполнение строки.

Пример

charstr1[] = «Pascal»;

charstr2[] = «Hello there»;

strcnpy(strl,str2, 5);

Переменнаяstrl содержит строку «Hellol». Заметьте, что символ ‘l’ строки-приемника,следующий за скопированной частью строки, сохра­нился.

Определение длины строки

Приработе со строками часто бывает нужно знать длину строки.

Функция strlen

Функцияstrlen  возвращает количество символов в строке, в которое не включаетсянуль-терминатор.

Прототипфункции strncpy таков:

size_tstrlen (const char *string) ,

Функция strlenвозвращает длину строки string. size_t — это имя, приписанное типу unsigned intоператором typedef.

Пример

charstr[] = «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_tnum);

Функциядобавляет к содержимому целевой строки num символов из строки-источника ивозвращает указатель на целевую строку.

charstrl[81] = «Hello I am »;                         

charstr2[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_tnum);

Функциясравнивает первые 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.

Пример

charstr[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 <stdio.h> // см. файл Ex02.cpp

#include <string.h>

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 className

{

private:

  <закрытые элементы-данные>

  <закрытые конструкторы>

  <закрытые функции-элементы>

protected:

  <защищенные элементы-данные>

  <защищенные конструкторы>

  <защищенные функции-элементы>

public:

  <открытые элементы-данные>

  <открытые конструкторы>

  <открытый деструктор>

  <открытые функции-элементы>

};

   Пример 1:

class point

{

protected:

   double х;

   double у;

public:

   point(double xVal, double yVal);

   double getX();

   double getY();

   void assign(double xVal, doubleyVal);

   point& assign(point &pt);

};

Разделыкласса

   Классы С++ имеют три различных уровнядоступа к своим элементам — как к данным, так и к функциям:

-     Закрытые(частные) элементы

-     Защищенныеэлементы

-     Открытыеэлементы

К данным взакрытом разделе имеют доступ только функции-элементы класса.

Классам-потомкамзапрещен доступ к закрытым данным своих 6азовых классов.

К данным взащищенной секции имеют доступ функции-элементы класса и классов-потомков.Данные из открытой секции находятся в области видимости функций-элементовкласса, функций-элементов классов-потомков, и вообще доступны кому угодно.

Существуют следующие правила дляразделов класса:

1.    Разделы могутпоявляться в любом порядке.

2.    Один и тот жераздел можно определять несколько раз.

3.    Если неопределен ни один раздел, компилятор (по умолчанию) объявляет все элементызакрытыми.

4.    Помещатьданные-элементы в открытый раздел следует только в том случае, если в этом естьнеобходимость, например, если это упрощает вашу задачу.    Обычноэлементы-данные помещаются в защищенный раздел, чтобы к ним имели доступфункции-элементы классов-потомков.

5.    Используйте дляизменения значений данных и доступа к ним функции-элементы. При использованиифункции вы можете осуществлять проверку данных и, если нужно, изменять другиеданные.

6.    Класс можетиметь несколько конструкторов.

7.    Класс можетиметь только один деструктор, который должен объявляться в открытом разделекласса.

8.    Функции-элементы(в том числе конструкторы и деструкторы), состоящие  из нескольких операторов,должны определяться вне объявления класса. Определение функции можетсодержаться в том же файле, в котором определяется класс. Это напоминаетпорядок работы с обычными функциями: задание прототипа и определение функции.

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

   При определении функции-элемента выдолжны указать ее имя и имя ее класса. Сначала вы должны Сначала необходимоуказать имя класса (т.н. квалификатор), а затем, через два двоеточия (::),имя функции. В качестве примера рассмотрим такой класс:

class point

{

protected:

   double x;

   double y;

public:

   point(doublexVal, double yVal);

   doublegetX();

   // другие функции-элементы

};

Определения конструктора ифункций-элементов должны выглядеть так

point::point(double xVal, double yVal)

{

   // операторы

}

double point::getX()

{

  // операторы

}

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

   В листинге8.1 приведенисходный текст программы RECT.CPP. Программа предлагает вам ввести длинуи ширину прямоугольника (в данном примере прямоугольник является объектом).Затем программа выводит значения длины, ширины и площади определенного вамипрямоугольника.

Конструкторы

  Конструкторы и деструкторы в С++ вызываются автоматически, что гарантируетправильное создание и разрушение объектов класса.

   Общий вид (синтаксис) объявленияконструктора:

class className

{

public:

   className();                     //конструктор по умолчанию

   className(const className &c);  // конструктор копии

   className(<списокпараметров>);  // остальные конструкторы

};

   Пример 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, doubleyVal);

   point& assign(point &pt);

};

int main()

{

  point p1;

  point p2(10, 20);

  point p3(p2);

  p1.assign(p2);

  cout << p1.getX() <<" " << p1.getY() << endl;

  cout << p2.getX() <<" " << p2.getY() << endl;

  cout << p3.getX() <<" " << p3.getY() << endl;

  return 0;

}

  Конструктор копии создает объект класса, копируя при этом данные изсуществующего объекта класса.

   В С++ имеются следующие особенности иправила работы с конструкторами:

1.    Имя конструктора класса должносовпадать с именем класса.

2.    Нельзяопределять тип возвращаемого значения для конструктора, даже тип void.

3.    Класс можетиметь несколько конструкторов или не иметь их совсем.

4.   Конструктором по умолчаниюявляется конструктор, не имеющий параметров, или конструктор, у которого всепараметры имеют значения по умолчанию.

   Рассмотрим два примера с фрагментамиобъявления конструкторов.

// класс с конструктором без параметров

class point1

{

protected:

   double x;

   double y;

public:

   point1();

   // другие функции-элементы

};

// конструктор класса имеет параметры созначениями по умолчанию

class point2

{

protected:

   double x;

   double y;

public:

   point2(double xVal = 0, double yVal =0);

   // другие функции-элементы

};

5.    Конструкторкопии создает объект класса на основе существующего объекта.

 

Например:

class point

{

protected:

   double x;

   double y;

public:

   point();

   point(double xVal = 0, double yVal =0);

   point(const point &pt);

   // другие функции-элементы

};

6.   Объявление объекта класса, котороеможет содержать параметры и, в качестве параметра, имя уже существующегообъекта, влечет за собой вызов конструктора. Но какой из конструкторов будетиспользоваться в каждом конкретном случае? Ответ зависит от того, как многоконструкторов вы объявили и с какими аргументами вы объявляете объект класса.Например, рассмотрим следующие объявления объектов последней версии классаpoint:

point p1;                  //применяется конструктор по умолчанию

point p2(1.1, 1.3);   // используетсявторой по счету конструктор

point p3(p2);           // используется конструкторкопии

   Поскольку объект p1 объявляется безпараметров, компилятор использует

конструктор по умолчанию. Объект p2объявляется с двумя вещественными

аргументами, поэтому компилятор вызоветвторой конструктор. Объект p3

при объявлении имеет параметром объектp2, поэтому компилятор вызовет

конструктор копии, чтобы создатьновый объект из объекта p2.

   ВНИМАНИЕ:

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

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

данные-указатели.

Деструкторы

   Классы С++ могут содержатьдеструкторы, которые автоматически разрушают объекты класса.

   Общий синтаксис объявлениядеструктора:

class className

{

public:

   className();             //конструктор по умолчанию

   // другие конструкторы

   ~className();            //объявление деструктора

   // другие функции-элементы

};

   Пример 3 на синтаксис обявлениядеструктора:

class String

{

protected:

   char *str;

   int len;

public:

   String();

   String(const String& s);

   ~String();

   // другие функции-элементы

};

   Деструкторы в С++ имеют следующиеособенности и подчиняются следующим правилам:

1.    Имя деструкторадолжно начинаться со знака тильды (~), за которым должно следовать имя класса.

2.    Нельзяопределять тип возвращаемого значения, даже тип void.

3.    Класс можетиметь только один деструктор или ни одного. В последнем случае компиляторсоздаст деструктор по умолчанию.

4.    Деструктор недолжен иметь параметров.

5.    Исполняющаясистема автоматически вызывает деструктор класса, когда объект класса выходитза пределы области действия и может быть удален, или удаляется явным образом.

(см. LIST8-2.CPP)

Объявление иерархии классов

Производныйкласс

   Общая форма (синтаксис) объявленияпроизводного класса:

class classname: [<спецификатордоступа>] 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, doublewide);

   double calcArea();

};

class Вох: public Rectangle

{

protected:

   double height;

public:

   Box(double len, double wide, doubleheight);

   double getHeight () const;

   assign(double len, double wide,double height);

   double calcVolume();

};

(см. LIST8-3.CPP)

Виртуальные функции

   Мы уже упоминали о полиморфизме — важной особенности объектно-

ориентированного программирования.Рассмотрим следующий пример (6):

#include <iostream.h>

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 << y.B(3) << endl;

  return 0;

}

   В классе X объявляются функции A и B,причем функция B вызывает функцию А. Класс Y, потомок класса X, наследуетфункцию B, но переопределяет функцию A. Цель этого примера — демонстрацияполиморфного поведения класса Y. Мы должны получить следующий результат: вызовнаследуемой функции X::B должен привести к вызову функции Y::A. Что же выдастнам наша программа? Ответом будет 4.5, а не 13.5! В чем же дело? Почемукомпилятор разрешил выражение y.B(3) как вызов наследуемой функции X::B,которая, в свою очередь, вызывает X::A, а не функцию Y::A, что должно было быпроизойти в случае полиморфной реакции класса?

   Виртуальные функции объявляютсяследующим образом (синтаксис):

class className1

{

  // функции-элементы

  virtual returnTypefunctionName(<список параметров>);

};

class className2: public className1

{

  // функции-элементы

  virtual returnTypefunctionName(<список параметров>);

};

  Пример 7, показывающий, как при помощивиртуальных функций можно реализовать полиморфное поведение классов X и Y:

#include <iostream.h>

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 << y.B(3) << endl;

  return 0;

}

   Этот пример выведет вам правильноезначение 13.5, потому что в результате вызова наследуемой функции X::B,вызывающей функцию A, в качестве функции A во время выполнения программы будетиспользована замещающая функция Y::A.

   *** Правило виртуальной функции ***

   Правило виртуальной функции гласит:

           «Виртуальная однажды — виртуальна всегда».

   Это означает следующее. Если выобъявили функцию как виртуальную в некотором классе, то в классах-потомках,переопределяющих эту функцию, она также будет виртуальной, но только если онаимеет тот же список параметров. Если переопределенная функция в классе-потомкеимеет другой список параметров, то ее версия из базового класса будетнедоступна классу-потомку (и всем его потомкам). Это может показатьсянеудобным, но только на первый взгляд.

Правило это справедливо и для всехязыков объектно-ориентированного программирования, поддерживающих виртуальныефункции, но не допускающих перегрузку функций. В С++ положение несколько иное.Вы можете объявлять невиртуальные перегруженные функции, совпадающие по имени свиртуальными функциями, но имеющие другой список параметров. И, кроме того, выне можете наследовать невиртуальные функции, имя которых совпадает свиртуальными функциями. Рассмотрим пример 8, иллюстрирующий сказанное.

#include <iostream.h>

class A

{

public:

   A() {}

   virtual void foo(char c)

      { cout << «virtualA::foo() returns » << c << endl; }

};

class B: public A

{

public:

  B() {}

  void foo(const char* s)

     { cout << «B::foo()returns » << s << endl; }

  void foo(int i)

     { cout << «B::foo()retuzns » << i << endl; }

  virtual void foo(char c)

     { cout << «virtualB::foo() returns » << c << endl; }

};

class C: public B

{

public:

   C() {}

   void foo(const char* s)

      { cout << «C::foo()returns » << s << endl; }

   void foo(double x)

      { cout << «C::foo()returns » << x << endl; }

   virtual void foo(char c)

      { cout << «virtualC::foo() returns » << c << endl; }

};

int main()

{

  A Aobj;

  B Bobj;

  C Cobj;

  Aobj.foo('A');

  Bobj.foo('B');

  Bobj.foo(10);

  Bobj.foo(«Bobj»);

  Cobj.foo('C');

  Cobj.foo(144.123);

  Cobj.foo(«Cobj»);

  return 0;

}

   В этом примере вводятся три класса — A, B и C — образующих линейную иерархию наследования. В классе A объявляетсявиртуальная функция foo(char).

Класс B объявляет свою версиювиртуальной функции foo(char), но, кроме того, в классе B объявляютсяневиртуальные перегруженные функции foo(const char*) и foo(int). Класс Cобъявляет свою версию виртуальной функции foo(char) и невиртуальныеперегруженные функции foo(const char*) и foo(double). Обратите внимание на то,что в классе C приходится заново объявлять функцию foo(const char*), посколькув данном случае функция-элемент B::foo(const char*) не наследуется. Такимобразом, в С++ схема наследования отличается от обычной для случая виртуальнойи перегруженных функций с одинаковым именем. В функции main объявляются объектыдля всех трех классов и вызываются различные версии функции-элемента foo.

Дружественные функции

   В С++ функции-элементы имеют доступко всем данным-элементам своего класса. Кроме этого, С++ предусматривает такуювозможность еще и для дружественных функций. Объявление дружественной функциипроизводится в объявлении класса и начинается с ключевого слова friend. Кроменаличия спецификатора friend, объявление дружественной функции совпадает собъявлением функции-элемента, однако прямого доступа к классу дружественная функцияне имеет, поскольку для этого необходим скрытый указатель this, который ейнедоступен. Но если вы передаете такой функции указатель на объектдружественного класса, функция будет иметь доступ к его элементам. Когда выопределяете дружественную функцию вне объявления дружественного ей класса, вамне нужно в определении указывать имя класса. Дружественной называется обычнаяфункция, которой открыт доступ ко всем элементам-данным одного или несколькихклассов.

   Общий вид (синтаксис) объявлениядружественной функции следующий:

class className

{

public:

   className();

   // другие конструкторы

friend returnTypefriendFunction(<список параметров>);

};

   Пример 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 className

{

public:

   // конструкторы и деструктор

   // функции-элементы

   // унарная операция

   returnType operator operatorSymbol();

   // бинарная операция

   returnType operatoroperatorSymbol(operand);

   // унарная дружественная операция

   friend returnType operatoroperatorSymbol(operand);

   // бинарная дружественная операция

   friend returnType operatoroperatorSymbol(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 +(constchar* 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 <(String&sl, String& s2);

   friend int operator <=(String&sl, String& s2);

   friend int operator ==(String&s1, String& s2);

   friend int operator !=(String&sl, String& s2);

};

   Код, который вы пишете, будетиспользовать операции и дружественные операции точно так же, как ипредопределенные операции. Следовательно, вы можете создавать операции, чтобыподдерживать действия над классами, моделирующими, например, комплексные числа,строки, векторы и матрицы. Эти операции дают возможность вам записыватьвыражения в более привычной форме, чем использование вызовов функций.

Виртуальные функции

   Мы ужеупоминали о полиморфизме — важной особенности объектно-ориентированногопрограммирования. Рассмотрим следующий пример (6):

#include<iostream.h>

class X

{

public:

   doubleA(double x) { return x * x; }

   doubleB(double x) { return A(x) / 2; }

};

class Y: publicX

{

public:

   doubleA(double x) { return x * x * x; }

};

int main ()

{

  Y y;

  cout <<y.B(3) << endl;

  return 0;

}

   В классе Xобъявляются функции A и B, причем функция B вызывает функцию А. Класс Y,потомок класса X, наследует функцию B, но переопределяет функцию A. Цель этогопримера — демонстрация полиморфного поведения класса Y. Мы должны получитьследующий результат: вызов наследуемой функции X::B должен привести к вызовуфункции Y::A. Что же выдаст нам наша программа? Ответом будет 4.5, а не 13.5! Вчем же дело? Почему компилятор разрешил выражение y.B(3) как вызов наследуемойфункции X::B, которая, в свою очередь, вызывает X::A, а не функцию Y::A, чтодолжно было бы произойти в случае полиморфной реакции класса?

   Виртуальныефункции объявляются следующим образом (синтаксис):

class className1

{

  //функции-элементы

  virtual returnTypefunctionName(<список параметров>);

};

class className2: public className1

{

  //функции-элементы

  virtualreturnType functionName(<список параметров>);

};

  Пример 7,показывающий, как при помощи виртуальных функций можно реализовать полиморфноеповедение классов X и Y:

#include<iostream.h>

class X

{

public:

   virtualdouble A(double x) { return x * x; }

   double B(double x) { return A(x) / 2; }

};

class Y: publicX

{

public:

   virtualdouble A(double x) { return x * x * x; }

};

main()

{

  Y y;

  cout <<y.B(3) << endl;

  return 0;

}

   Этот примервыведет вам правильное значение 13.5, потому что в результате вызованаследуемой функции X::B, вызывающей функцию A, в качестве функции A во времявыполнения программы будет использована замещающая функция Y::A.

Правило виртуальной функции

   Правиловиртуальной функции гласит:

          «Виртуальная однажды — виртуальна всегда».

   Это означаетследующее. Если вы объявили функцию как виртуальную в некотором классе, то вклассах-потомках, переопределяющих эту функцию, она также будет виртуальной, нотолько если она имеет тот же список параметров. Если переопределенная функция вклассе-потомке имеет другой список параметров, то ее версия из базового классабудет недоступна классу-потомку (и всем его потомкам). Это может показатьсянеудобным, но только на первый  згляд.

Правило этосправедливо и для всех языков объектно-ориентированного программирования,поддерживающих виртуальные функции, но не допускающих перегрузку функций. В С++положение несколько иное. Вы можете объявлять невиртуальные перегруженныефункции, совпадающие по имени с виртуальными функциями, но имеющие другойсписок параметров. И, кроме того, вы не можете наследовать невиртуальныефункции, имя которых совпадает с виртуальными функциями.

Рассмотримпример 8, иллюстрирующий сказанное.

#include<iostream.h>

class A

{

public:

   A() {}

   virtual voidfoo(char c)

      { cout<< «virtual A::foo() returns » << c << endl; }

};

class B: publicA

{

public:

  B() {}

  void foo(constchar* s)

     { cout<< «B::foo() returns » << s << endl; }

  void foo(inti)

     { cout<< «B::foo() retuzns » << i << endl; }

  virtual voidfoo(char c)

     { cout<< «virtual B::foo() returns » << c << endl; }

};

class C: publicB

{

public:

   C() {}

   voidfoo(const char* s)

      { cout<< «C::foo() returns » << s << endl; }

   voidfoo(double x)

      { cout<< «C::foo() returns » << x << endl; }

   virtual voidfoo(char c)

      { cout<< «virtual C::foo() returns » << c << endl; }

};

int main()

{

  A Aobj;

  B Bobj;

  C Cobj;

  Aobj.foo('A');

  Bobj.foo('B');

  Bobj.foo(10);

 Bobj.foo(«Bobj»);

  Cobj.foo('C');

 Cobj.foo(144.123);

 Cobj.foo(«Cobj»);

  return 0;

}

   В этомпримере вводятся три класса — A, B и C — образующих линейную иерархию наследования.В классе A объявляется виртуальная функция foo(char).

Класс Bобъявляет свою версию виртуальной функции foo(char), но, кроме того, в классе Bобъявляются невиртуальные перегруженные функции foo(const char*) и foo(int).Класс C объявляет свою версию виртуальной функции foo(char) и невиртуальныеперегруженные функции foo(const char*) и foo(double). Обратите внимание на то,что в классе C приходится заново объявлять функцию foo(const char*), посколькув данном случае функция-элемент B::foo(const char*) не наследуется. Такимобразом, в С++ схема наследования отличается от обычной для случая виртуальнойи перегруженных функций с одинаковым именем. В функции main объявляются объектыдля всех трех классов и вызываются различные версии функции-элемента foo.

Дружественныефункции

   В С++функции-элементы имеют доступ ко всем данным-элементам своего класса. Кромеэтого, С++ предусматривает такую возможность еще и для дружественных функций.Объявление дружественной функции производится в объявлении класса и начинаетсяс ключевого слова friend. Кроме наличия спецификатора friend, объявлениедружественной функции совпадает с объявлением функции-элемента, однако прямогодоступа к классу дружественная функция не имеет, поскольку для этого необходимскрытый указатель this, который ей недоступен. Но если вы передаете такойфункции указатель на объект дружественного класса, функция будет иметь доступ кего элементам. Когда вы определяете дружественную функцию вне объявлениядружественного ей класса, вам не нужно в определении указывать имя класса.Дружественной называется обычная функция, которой открыт доступ ко всемэлементам-данным одного или нескольких классов.

   Общий вид(синтаксис) объявления дружественной функции следующий:

class className

{

public:

   className();

   // другиеконструкторы

friendreturnType friendFunction(<список параметров>);

};

   Пример 9:

class String

{

protected:

   char *str;

   int len;

public:

   String();

   ~String();

   // другиефункции-элементы

friendString& append(String &str1, String &str2);

friendString& append(const char* str1, String &str2);

friendString& append(String &str1, const char* str2);

};

   Дружественныефункции могут решать задачи, которые при помощи

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

   Рассмотримпростой пример использования дружественных функций.

Текст программыFRIEND.CPP представлен в листинге 8.5. Программа следит за памятью, отведеннойдля хранения массива символов. Эта программа — первый шаг к созданию классаstring.

Операции и дружественные операции

   Последняяпрограмма использовала функции-элементы и дружественную функцию, которыереализовали действия, выполняемые в стандартных типах с помощью операций вроде= и +. Подход типичен для языков C и Pascal, потому что эти языки неподдерживают определяемые пользователем операции. В отличии от них C++позволяет вам объявлять операции и дружественные операции. Эти операциивключают в себя: +, -, *, /, %, ==, !=, <=, <, >=, >, +=, -=, *=,/=, %=, [], (), << и >>. Обратитесь к описанию языка C++, гдеобсуждаются детали определения этих операций. С++ трактует операции идружественные операции как специальный тип функций-элементов и дружественныхфункций.

   Общийсинтаксис для объявления операций и дружественных операций:

class className

{

public:

   //конструкторы и деструктор

   //функции-элементы

   // унарнаяоперация

   returnTypeoperator operatorSymbol();

   // бинарнаяоперация

   returnTypeoperator operatorSymbol(operand);

   // унарнаядружественная операция

   friendreturnType operator operatorSymbol(operand);

   // бинарнаядружественная операция

   friendreturnType operator operatorSymbol(firstOperand, secondOperand);

};

      Пример 10:

class String

{

protected:

   char *str;

   int num;

public:

   String();

   ~String();

   // другиефункции-элементы

   // операцияприсваивания

   String&operator =(String& s);

   String&operator +=(String& s);

   // операцииконкатенации

   friendString& operator +(String& s1, String& s2);

   friend String&operator +(const char* s1, String& s2);

   friendString& operator +(String& s1, const char* s2);

   // операцииотношения

   friend intoperator >(String& s1, String& s2);

   friend intoperator =>(String& s1, String& s2);

   friend intoperator <(String& sl, String& s2);

   friend intoperator <=(String& sl, String& s2);

   friend intoperator ==(String& s1, String& s2);

   friend intoperator !=(String& sl, String& s2);

};

   Код, которыйвы пишете, будет использовать операции и дружественные операции точно так же,как и предопределенные операции. Следовательно, вы можете создавать операции,чтобы поддерживать действия над классами, моделирующими, например, комплексныечисла, строки, векторы и матрицы.

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

ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ

(Листинг 8.1.исходный текст программы RECT.CPP

// ПрограммаC++, иллюстрирующая использование класса.

// Программамоделирует прямоугольник.)

// Листинг 8.2.Исходный текст программы ARRAY.CPP

// Программадемонстрируюет использование конструкторов и деструкторов:

//  — создаетдинамический массив (объект),

//  — присваивает значения элементам динамического массива,

//  — выводитзначения элементов динамического массива,

//  — удаляетдинамический массив.

// Листинг 8.3.Исходный текст программы CIRCLE.CPP

// Простойпример иерархии классов.

// Листинг 8.4.Исходный текст программы VIRTUAL.CPP

// Программадемонстрирует использование виртуальных функций

// для моделированияквадратов и прямоугольников и вывода их

// размеров иплощади

ВОПРОСЫ И ОТВЕТЫ

Что случится,если я объявлю конструктор по умолчанию, конструктор копии и другиеконструкторы в защищенной области?

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

Могу я задатьцепочку вызовов функций-элементов ?

   Да, можете,только если указанные в цепочке функции-элементы возвращают ссылку на тот жесамый класс. Например, если в классе String объявлены  следующиефункции-элементы:

   String&upperCase();

   String&reverse();

   StringamapChar(char find, char replace);

   вы можетенаписать следующий оператор обработки объекта класса

   String:

   s.upperCase().reverse().mapChar('', '+');

Что можетслучиться, если класс полагается на конструктор копии, созданный компилятором,и при этом класс использует указатели в качестве элементов-данных?

   Этиконструкторы выполняют побитовую копию объекта. Следовательно, соответствующиеэлементы-указатели в обоих объектах будут ссылаться на те же самые динамическиеданные. Этот способ создания копии объекта — верный путь к различнымнеприятностям.

Могу ли ясоздавать массив объектов?

   Да, можете.Однако соответствующий класс должен иметь заданный по умолчанию конструктор.При создании массива используется ранее упомянутый конструктор.

Могу ли яиспользовать указатель при создании объекта класса?

   Да, можете,но в этом случае вы должны использовать операции new и delete, чтобыраспределять и освобождать память для данного объекта.

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

   Complex *pC;

   pC = newComplex;

   // операции собъектом, к которому обращаются по указателю pC

   delete pC;

   или

   Complex *pC =new Complex;

   // операции собъектом, к которому обращаются по указателю pC

   delete pC;

Контрольные вопросы

1. Найдитеошибку в следующем объявлении класса:

   class String{

      char *str;

      unsignedlen;

      String ();

     String(const String& s);

     String(unsigned size, char = ' ');

     String(unsigned size);

     String& assign(String& s);

      ~String();

      unsignedgetLen() const;

      char*getString();

      // другиефункции-элементы

   };

2. Найдитеошибку в следующем объявлении класса:

   class String{

   protected:

      char *str;

      unsignedlen;

   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

voidopen (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.

//открыть поток для ввода

fstreamf;

f.open(«simple.txt»,ios::in);

//открыть поток для вывода fstream f;

fstreamf;

f.open(«simple.txt», ios::out);

//открыть поток ввода/вывода для двоичных данных fstream f;

fstreamf;

f.open(«simple.txt»,ios::in | ios::out | ios::binary);

Внимание: Классы файловых потоков предусматриваютконструкторы, которые выполняют действия (и имеют такие же параметры)функции-компонента open.

Функция close закрывает поток и освобождаетиспользовавшиеся ресурсы. Эти ресурсы включают буфер памяти для операции потоковоговвода/вывода.

Функция-компонент close

Прототип для функции close:

voidclose();

Пример 2.

fstreamf;

//открыть поток

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.

fstreamf;

chartextLine[MAX];

f.open(«sample.txt»,ios::in);

while(!f.eof()) {

f.getline(textLine, MAX);

cout << textLine << endl;

}

f.close();

Рассмотрим пример. В листинге 10.1 приведен исходныйкод программы TRIM.CPP. Программа выполняет следующие задачи:

-     Выдает запрос на ввод именивходного текстового файла.

-     Выдает запрос на ввод именивыходного текстового файла. (Программа     проверяет имена этих файлов насовпадение, и в случае положительного результата повторяет запрос на ввод имени выходногофайла).

-     Читает строки из входного файла иудаляет из них <висящие> пробелы.

-     Записывает эти строки в выходнойфайл и также в стандартное окно вывода.

Листинг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.

constMAX = 80;

charbuff[MAX+1] = «Hello World!»;

intlen = strlen (buff) + 1;

fstreamf;

f.open(«CALC.DAT»,ios::out | ios::binary);

f.write((constunsigned char*) &len, sizeof(len));

f.write((constunsigned 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.

constMAX = 80;

charbuff [MAX+1];

intlen;

fstreamf;

f.open(«CALC.DAT»,ios::in | ios::binary);

f.read((unsignedchar*) &len, sizeof(len));

f.read((unsignedchar*) 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        С конца файла

Пример

constBLOCK SIZE = 80

charbuff[BLOCK_SIZE] = «Hello World!»;

f.open(«CALC.DAT»,ios::in | ios::out | ios::binary);

f.seekg(3* BLOCK_SIZE); // продвинутся к блоку 4

f.read((constunsigned 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:

voidcopyStream(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, используя неупорядоченные данныеинициализирующего списка. (Элементы этого списка доступны при использованииданных-указателей.)

еще рефераты
Еще работы по информатике, программированию