Реферат: Основы алгоритмического языка С++ - текст реферата. Скачать бесплатно.
Банк рефератов, курсовых и дипломных работ. Много и бесплатно. # | Правила оформления работ | Добавить в избранное
 
 
   
Меню Меню Меню Меню Меню
   
Napishem.com Napishem.com Napishem.com

Реферат

Основы алгоритмического языка С++

Банк рефератов / Программирование

Рубрики  Рубрики реферат банка

закрыть
Категория: Реферат
Язык реферата: Русский
Дата добавления:   
 
Скачать
Microsoft Word, 1389 kb, скачать бесплатно
Заказать
Узнать стоимость написания уникального реферата

Узнайте стоимость написания уникальной работы

Основы алгоритмического языка С ++ Переменные и операц ии языка С ++ Здесь представлены базовые компоненты программ на С ++. В их число входят типы данных , переменные , константы и выражения. ИЗУЧАЕМЫЕ ПОНЯТИЯ - Предопределенные типы данных в С ++ включают в себя типы int, char, float, double и void. В языке С ++ гибкость типов данных увеличивается благодаря применению модификаторов типов . Эти модификаторы изменяют точность представления и диапазон значений переменных . Модификаторами типа являются signed, unsigned, sho r t и long. - Идентификаторы в С ++ могут иметь длину до 32 символов и должны начинаться с буквы или подчеркивания . Последующие символы идентификатора могут быть буквой , цифрой или подчеркиванием . Идентификаторы С ++ чувствительны к регистру . Ограничение на 3 2 символа может быть , однако , изменено путем установки опций компилятора. - Директива #include является специальной командой компилятора . Она предписывает компилятору включить в программу содержимое определенного файла , как если бы вы сами ввели его в тек ущий исходный файл. - Объявление констант предусматривает использование директивы #define для объявления констант , определенных при помощи макросов , или использование ключевого слова const для объявления формальных констант . Формальные константы требуют о т вас определения их типа (значением по умолчанию является int), имени и ассоциированного с ними значения. - Объявление переменной требует , чтобы вы задали ее тип и имя , С ++ дает вам возможность инициализировать переменную при ее объявлении . Вы можете объ явить несколько переменных в одном операторе объявления. - Арифметическими операциями являются +, -, *, / и % (деление по модулю ). - Арифметические выражения различаются по сложности . Самое простое выражение содержит единственный элемент данных (литерал, константу или переменную ). Сложные выражения включают набор операций , функции , литералы , константы и переменные. - Операции инкремента и декремента используются в префиксной и постфиксной формах . Язык С ++ дает вам возможность применять эти операции к пер еменным , в которых хранятся символы , целые числа и даже числа с плавающей точкой. - Арифметические операции присваивания дают вам возможность записывать более короткие арифметические выражения , в которых первый операнд является также переменной , принимающ ей результат вычислений. - Оператор sizeof возвращает как для типов данных , так и для переменных их размер в байтах. - Механизм приведения типа дает вам возможность форсировать преобразование типа выражения. - Операции отношений и логические операции да ют вам возможность строить логические выражения. - Булевы выражения объединяют операции отношений и логические операции для формулирования нетривиальных условий . Эти выражения позволяют программе принимать сложные решения. - Условное выражение предлагает вам короткую форму для простого оператора if-else с двумя альтернативами. - Операции манипулирования битами выполняют поразрядные операции AND, OR, XOR и NOT. Кроме того , в С ++ поддерживаются поразрядные операции сдвига << и >>. - Операции манипулирован ия битами с присваиванием предлагают короткие формы для простых операций манипулирования битами. Буквы и цифры Множество символов Си включает большие и малые буквы из английского алфавита и 10 десятичных арабских цифр : -большие английские буквы : A B C D E F G H I J K L M N O P Q R T U V W X Y Z -малые английские буквы : a b c d e f g h i j k l m n o p q r t u v w x y z -десятичные цифры : 0 1 2 3 4 5 6 7 8 9 Буквы и цифры используются при формировании констант , идентификаторов и ключевых слов . Все эти конструкции описаны ниже . Компилятор Си рассматривает одну и ту же малую и большую буквы как отличные символы . Если в данной записи использованы малые буквы , то замена малой буквы "a" на большую букву "A" сделает отличной данную з а пись от предшествующей. Пробельные символы Пробел , табуляция , перевод строки , возврат каретки , новая страница , вертикальная табуляция и новая строка - это сиволы , называемые пробельными , поскольку они имеют то же самое назначен ие , как и пробелы между словами и строками на печатной странице . Эти символы разделяют объекты , определенные пользователем , такие , как константы и идентификаторы , от других объектов программы. Символ CONTROL-Z рассматривается как индикатор конца файла . Ко мпилятор игнорирует любой текст , следующий за символом CONTROL-Z. Компилятор Си игнорирует пробельные символы , если они не используются как разделители или как компоненты константы-символа или строковых литералов . Это нужно иметь в виду , чтобы дополнитель но использовать пробельные символы для повышения наглядности программы (например,для просмотра редактором текстов ). Знаки пунктуации и специальные символы Эти символы имеют специальный смысл для компилятора Си . Их использовани е в языке Си описывается в дальнейшем содержании руководства . Знаки пунктуации из множества представимых символов , которые не представлены в данном списке , могут быть использованы только в строковых литералах , константах-символах и комментариях. ESC- последовательности ESC- последовательности - это специальные символьные комбинации , которые представляют пробельные символы и неграфические символы в строках и символьных константах. Их типичное использование связано со спецификацией таких действий , как возврат каретки и табуляция , а также для задания литеральных представлений символов , таких как символ двойная кавычка . ESC-последовательность состоит из наклонной черты влево , за которой следует буква , знаки пунктуации ' " \ или комб и нация цифр . В таблице 2.2. приведен список ESC- последовательностей языка Си. Если наклонная черта влево предшествует символу , не включенному в этот список , то наклонная черта влево игнорируется , а символ представляется как литеральный . Например , изображ ение \ c представляет символ "c" в литеральной строке или константе-символе. Последовательности \ ddd и \ xdd позволяют задать любой символ в ASCII (Американский стандартный код информационного интерфейса ) как последовательность трех восьмеричных цифр или дв ух шестнадцатеричных цифр . Например , символ пробела может быть задан как \ 010 или \ x08. Код ASCII "нуль " может быть задан как \ 0 или \ x0 . В восьмеричной ESC- последовательности могут быть использованы от одной до трех восьмеричных цифр. Например , символ пробела может быть задан как \ 10 . Точно так же в шестнадцатеричной ESC- последовательности могут быть использованы от одной до двух шестнадцатеричных цифр . Так , шестнадцатеричная последовательность для символа пробела может быть задана как \ x08 или \x8 . Замечание : Когда используется восьмеричная или шестнадцатеричная ESCпоследовательность в строках , то нужно полностью задавать все цифры ESC- последовательности (три цифры для восьмеричной и две цифры для шестнадцатеричной ESC- последовательностей ). Иначе , если символ непосредственно следующий за ESC- последовательностью , случайно окажется восьмеричной или шестнадцатеричной цифрой , то он проинтерпретируется как часть последовательности . Например , строка \ x7Bell при выводе на печать будет выглядеть как el l , поскольку \ x7B проинтерпретируется как символ левой фигурной скобки ( ) . Строка \ x07Bell будет правильным представлением символа "звонок " с последующим словом Bell. ESC- последовательности позволяют посылать неграфические управляющие символы к внешним устройствам . Например , ESC- последовательность\ 033 часто используется как первый символ команд управления терминалом и принтером . Неграфические символы всегда должны представляться ESC-последовательностями , поскольку , непосредственное использование в прог р аммах на Си неграфических символов будет иметь непредсказуемый результат. Наклонная черта влево (\ ) помимо определения ESC-последовательностей используется также , как символ продолжения строки в препроцессорных определениях. Если символ "новая строка " сл едует за наклонной чертой влево , то новая строка игнорируется и следующая строка рассматривается , как часть предыдущей строки. Операции Операции - это специальные комбинации символов , специфицирующие действия по пробразованию раз личных величин . Компилятор интерпретирует каждую из этих комбинаций как самостоятельную единицу , называемую лексемой (token). Операция условного выражения ?: -это тернарная , а не двухсимвольная операция . Формат условного выражения следующий : ? : Константы Константа - это число , символ или строка символов . Константы используются в программе как неизменяемые величины . В языке Си различают четыре типа констант : целые константы , константы с плавающе й точкой , константы-символы и строчные литералы. Целые константы Целая константа - это десятичное , восьмеричное или шестнадцатеричное число , которое представляет целую величину . Десятичная константа имеет следующий формат предста вления : , где - это одна или более десятичных цифр от 0 до 9. Восьмеричная константа имеет следующий формат представления : 0, где - это одна или более восьмеричных цифр от 0 до 7. Запись ведущего нуля необходима. Ше стнадцатеричная константа имеет один из следующих форматов представления : 0x 0X, где одна или более шестнадцатеричных цифр . Шестнадцатеричная цифра может быть цифрой от 0 до 9 или буквой (большой или малой ) от A до F. В предст авлении константы допускается "смесь " больших и малых букв . Запись ведущего нуля и следующего за ним символа x или X необходима. Пробельные символы не допускаются между цифрами целой константы . В Табл . 2.4 иллюстрируются примеры целых констант. Целые ко нстанты всегда специфицируют положительные величины . Если требуется отрицательные величины , то необходимо сформировать константное выражение из знака минус и следующей за ним константы . Знак минус рассматривается как арифметическая операция. Каждая целая константа специфицируется типом , определяющим ее представление в памяти и область значений . Десятичные константы могут быть типа int или long. Восьмеричные и шестнадцатеричные константы в зависимости от размера могут быть типа int, unsigned int, long или unsigned long. Если константа может быть представлена как int, она специфицируется типом int. Если ее величина больше , чем максимальная положительная величина , которая может быть представлена типом int, но меньше величины , которая представляется в том же с амом числе бит как и int, она задается типом unsigned int. Наконец , константа , величина которой больше чем максимальная величина , представляемая типом unsigned int, задется типом long или unsigned long, если это необходимо. Важность рассмотренных выше пра вил состоит в том , что восьмеричные и шестнадцатеричные константы не содержат "знаковых " расширений , когда они преобразуются к более длинным типам (преобразование типов смотри в разделе 5 "Выражения и присваивания "). Программист может определить для любой целой константы тип long, приписав букву "l" или "L" в конец константы . В Табл . 2.6 показаны примеры целых констант. Константы с плавающей точкой Константа с плавающей точкой - это действительное десятичное положительное число . Величина действительного числа включает целую , дробную части и экспоненту . Константы с плавающей точкой имеют следующий формат представления : [][.][E[-]], где - одна или более десятичных цифр (от 0 до 9), а E или e -симво л экспоненты . Целая или дробная части константы могут быть опушены , но не обе сразу . Десятичная точка может быть опущена только тогда , когда задана экспонента. Экспонента состоит из символа экспоненты , за которым следует целочисленная величина экспоненты, возможно отрицательная . Пробельные символы не могут разделять цифры или символы константы. Константы с плавающей точкой всегда специфицируют положительные величины . Если требуются отрицательные величины , то необходимо сформировать константное выражение из знака минус и следующей за ним константы . Знак минус рассматривается как арифметическая операция. Примеры констант с плавающей точкой и константных выражений : 15.75 1.575E1 1575e-2 -0.0025 -2.5e-3 25e-4 Целая часть константы с плавающей точкой м ожет быть опущена , например : .75 .0075e2 -.125 -.175E-2 Все константы с плавающей точкой имеют тип double. Константа-символ Константа-символ - это буква , цифра , знак пунктуации или ESC- символ , заключенные в одиночные кавычки . Величина константы-символа равна значению представляющего ее кода символа . Константа-символ имеет следующую форму представления : '', где может быть любым символом иэ множества представимых символов , включая любой ESC- символ , исключая оди ночную кавычку ('), наклонную черту влево (\ ) и символ новой строки. Чтобы использовать одиночную кавычку или наклонную черту влево в качестве константы-символа , необходимо вставить перед этими знаками наклонную черту влево . Чтобы представить символ новой строки , необходимо использовать запись '\n'. Константы-символы имеют тип int. Строковые литералы Строковый литерал - это последовательность букв , цифр и символов , заключенная в двойные кавычки . Строковый литерал рассматриваетс я как массив символов , каждый элемент которого представляет отдельный символ . Строковый литерал имеет следующую форму представления : "" , где - это нуль или более символов из множества представимых символов , исключая двойную кавы чку ("), наклонную черту влево (\ ) и символ новой строки . Чтобы использовать символ новой строки в строковом литерале , необходимо напечатать наклонную черту влево , а затем символ новой строки. Наклонная черта влево вместе с символом новой строки будут про игнорированы компилятором , что позволяет формировать строковые литералы , располагаемые более чем в одной строке . Например , строковый литерал : "Long strings can be bro\ cken into two pieces." идентичен строке : "Long strings can be brocken into two piec es." Чтобы использовать двойные кавычки или наклонную черту влево внутри строкового литерала , нужно представить их с предшествующей наклонной чертой влево , как показано в следующем примере : "This is a string literal" "First \\ Second" "\"Yes, I do,\" s he said." "The following line shows a null string:" "" Заметим , что ESC- символы (такие как \\ и \ ") могут появляться в строковых литералах . Каждый ESC- символ считается одним отдельным символом. Символы строки запоминаются в отдельных байтах памяти . С имвол null (\ 0) является отметкой конца строки . Каждая строка в программе рассматривается как отдельный объект . Если в программе содержатся две идентичные строки , то каждая из них будет храниться в отдельном месте памяти. Строчные литералы имеют тип char[ ]. Под этим подразумевается , что строка - это массив , элементы которого имеют тип char. Число элементов в массиве равно числу символов в строчном литерале плюс один , поскольку символ null (отметка конца строки ) тоже считается элементом массива. Идентификаторы Идентификаторы - это имена переменных , функций и меток , используемых в программе . Идентификатор создается объявлением соответствующей ему переменной или функции.После этого его можно использовать в последующих операторах прогр аммы . Идентификатор - это последовательность из одной или более букв , цифр или подчерков (_), которая начинается с буквы или подчерка . Допускается любое число символов в идентификаторе , однако только первые 31 символ распознаются компилятором . (Программы , и с пользующие результат работы компилятора , такие как , линкер , могут распознавать меньшее число символов ). При использовании подчерков в идентификаторе нужно быть осторожным , поскольку идентификаторы , начинающиеся с подчерка могут совпадать (войти в конфликт ) с именами "скрытых " системных программ. Примеры идентификаторов : temp1 toofpage skip12 Компилятор Си рассматривает буквы верхнего и нижнего регистров как различные символы . Поэтому можно создать отдельные независимые идентификаторы , которые совпадаю т орфографически , но различаются большими и малыми буквами . Например , каждый из следующих идентификаторов является уникальным : add ADD Add aDD Компилятор Си не допускает идентификаторов , которые имеют ту же самую орфографию , что и ключевые слова . Ключ евые слова описаны в следующем раздела Замечание : По сравнению с компилятором , сборщик может в большей степени ограничивать количество и тип символов для глобальных идентификаторов , и в отличие от компилятора не делать различия между большими и малыми б уквами . (Подробнее смотри руководство по пакету MSC). Ключевые слова Ключевые слова - это предопределенные идентификаторы , которые имеют специальное значение для компилятора Си . Их можно использовать только так как они определены . Имена объектов программы не могут совпадать с названиями ключевых слов. Список ключевых слов : auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof while do if static volatile Ключевые слова не могут быть переопределены . Тем не менее , они могут быть названы другим текстом , но тогда перед компиляцией они должны быть заменены посредством препроцессора на соответствующие ключевые с лова. Ключевые слова const и volatile зарезервированы для будущего использования. Следующие идентификаторы могут быть ключевыми словами для некоторых приложений : cdecl far fortran huge near pascal Комментарии Комментарий - это последовательность символов , которая воспринимается компилятором как отдельный пробельный символ или , другими словами , игнорируется. Комментарий имеет следующую форму представления : /**/, где может быть любой комбинацией си мволов из множества представимых символов , включая символы новой строки , но исключая комбинацию */. Это означает , что комментарии могут занимать более одной строки , но не могут быть вложенными. Комментарии допускаются везде , где разрешены пробельные симво лы . Компилятор игнорирует символы комментария , в частности , в комментариях допускается запись ключевых слов и зто не приведет к ошибке . Так как компилятор рассматривает комментарий как символ пробела , то комментарии не могут появляться внутри лексем . След ующие примеры иллюстрируют некоторые комментарии : /* Comments can separate and document lines of a program. */ /* Comments can contain keywords such as for and while */ /******************************************* Comments can occupy several lines. ** *****************************************/ Так как комментарии не могут содержать вложенных комментариев , то следующий пример будет ошибочным : /* You cannot/* nest */ comments */ Компилятор распознает первую комбинацию */ после слова nest как конец комм ентария . Затем , компилятор попытается обрабатывать оставшийся текст и выработает сообщение об ошибке . Чтобы обойти компиляцию комментариев больших размеров , нужно использовать директиву #if препроцессора. Лексемы Когда компилято р обрабатывает программу , он разбивает программу на группы символов , называемых лексемами . Лексема - это единица текста программы , которая имеет определенный смысл для компилятора и которая не может быть разбита в дальнейшем . Операции , константы , идентифик а торы и ключевые слова , описанные в этом разделе,являются примерами лексем . Знаки пунктуации , такие как квадратные скобки ([]), фигурные скобки ( ), угловые скобки (<>), круглые скобки и запятые , также являются лексемами . Границы лексем определяются пробе л ьными символами и другими лексемами , такими как операции и знаки пунктуации . Чтобы предупредить неправильную работу компилятора , запрещаются пробельные символы между символами идентификаторов , операциями , состоящими из нескольких символов и символами ключ е вых слов. Когда компилятор выделяет отдельную лексему , он последовательно объединяет столько символов , сколько возможно , прежде чем перейти к обработке следующей лексемы . Поэтому лексемы , не разделенные пробельными символами , могут быть проинтерпретирован ы неверно. Например , рассмотрим следующее выражение : i+++j В этом примере компилятор вначале создает из трех знаков плюс самую длинную из возможных операций (++), а затем обработает оставшийся знак +, как операцию сложения (+). Выражение проинтерпретиру ется как (i++)+(j), а не как (i)+(++j). В таких случаях необходимо использовать пробельные символы или круглые скобки , чтобы однозначно определить ситуацию. ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ // Программа VAR.CPP, иллюстрирующая простые п еременные #include int main() int i, j = 2; double x, y = 355.0 / 113; i = 3 * j; cout << "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 #define SEC_IN_MIN 60 #define MIN_IN_HOUR 60 int main() 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 const int SEC_IN_MIN = 60; // глобальная ко нстанта int main() 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 int main() int int1, int2; long long1, long2, long3, long4, long5; float x, y, real1, real2, real3, real4; cout << end l << "Введите первое целое число : "; 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; c out << 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 int main() 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(short int) << 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 int main() short shortInt1, shortInt2; unsigned short aByte; int anInt; long aLong; char aChar; float aReal; // присваиваются значения shortInt1 = 10; shortInt2 = 6; // действия выполняются без приведения типа aByte = s hortInt1 + shortInt2; anInt = shortInt1 - shortInt2; aLong = shortInt1 * shortInt2; aChar = aLong + 5; // автоматическое преобразование // в символьный тип aReal = shortInt1 * shortInt2 + 0.5; cout << "shortInt1 = " << shortInt1 << endl << "shortIn t2 = " << 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; /* Результаты : s hortInt1 = 10 shortInt2 = 6 aByte = 16 anInt = 4 aLong = 60 aChar is A aReal = 60.5 shortInt1 = 10 shortInt2 = 6 aByte = 16 anInt = 4 aLong = 60 aChar is A aReal = 60.5 */ /* *** ВОПРОСЫ И ОТВЕТЫ *** Существуют ли особые соглашения о присво ении имен идентификаторам ? Существует несколько стилей , которые стали популярными в последние годы . Стиль , который используется в наших занятиях , требует начинать имя переменной с символа , набранного в нижнем регистре . Если идентификатор состоит из неско льких слов , как , например , numberOfElements, набирайте первый символ каждого последующего слова в верхнем регистре. Как реагирует компилятор , если вы объявляете переменную , но никогда не присваиваете ей значения ? Компилятор выдает предупреждение , что н а переменную нет ссылок. Каково булево выражение для проверки того , что значение переменной i находится в заданном диапазоне значений (например , определяемом переменными lowVal и hiVal)? Выражением , которое определяет , находится ли значение переменной i в некотором диапазоне , является (i >= lowVal && i <= hiVal). Конструкции принятия решений и циклы ВОПРОСЫ И ОТВЕТЫ Предъявляет ли С ++ какие-либо требования на отступ операторов в предложениях операто ра ? Нет . Отступ определяется только Вами . Типовые размеры отступа составляют два или четыре пробела . Использование отступов делает ваш листинг намного более удобочитаемым. Вот пример оператора 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 fo r main function */ int z; /* Variable declarations */ int w; z = y + x; /* Executable statements */ w = y - x; printf("z = %d \nw = %d \n", z, x); Эта исходная программа определяет функцию с именем main и объявляет функцию printf. Переменные x и y зад аются своими определениями . Переменные z и w только объявляются. ОБЪЯВЛЕНИЯ В этом разделе описываются форматы и составные части объявлений переменных , функций и типов . Объявления Си имеют следующий синтаксис : [][][=] [,[=...], где : - спецификатор класса памяти ; - имя определяемого типа ; - идентификатор , который может быть модифицирован при объявлении указате ля , массива или функции ; - задает значение или последовательность значений , присваиваемых переменной при объявлении. Все переменные Си должны быть явно объявлены перед их использованием . Функции Си могут быть объявлены явно или неявно в случ ае их вызова перед определением. Язык Си определяет стандартное множество типов данных . К этому множеству можно добавлять новые типы данных посредством их объявлений на типах данных уже определенных. Объявление Си требует одного или более деклараторов . Д екларатор - это идентификатор , который может быть определен с квадратными скобками ([]), эвездочкой (*) или круглыми скобками () для объявления массива , указателя или функции . Когда об 'является простая переменная (такая как символ , целое или плавающее ), ст р уктура или совмещение простых переменных , то декларатор - это идентификатор. В Си определено четыре спецификатора класса памяти , а именно : auto, extern, register и static. Спецификатор класса памяти определяет , каким образом объявляемый объект запоминаетс я и инициализируется и из каких частей программы можно ссылаться на него . Расположение объявления внутри программы , а также наличие или отсутствие других объявлений - также важные факторы при определении видимости переменных. Объявления функций описаны в р азделе 4.4. Спецификаторы типов Перечислимые типы также рассматриваются как основные типы . Спецификаторы перечислимых типов рассмотрены в разделе 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 int signed, int signed short int short, signed short signed long int long, signed long unsigned char unsigned int unsigned unsigned short int unsignet short unsignet long int unsignet long float long float double Замечание : в этом руководстве в основном используются сокращенные формы , перечисленные в Табл . 4.2, при этом предполагается , что char по умолчанию знаковый. В табл . 4.3 для каждого типа приведены : размер распределяемой памяти и об ласти значений переменных для данного типа . Поскольку тип void не представляет переменных , он не включен в эту таблицу. int зависит от реализации short 2 байта -32768 до 32767 long 4 байта -2.147.483.648 до 2.147.483.647 unsigned char 1 байт 0 до 255 u nsigned зависит от реализации unsigned short 2 байта 0 до 65535 unsigned long 4 байта 0 до 4.294.967.295 float 4 байта IEEE стандартное соглашение double 8 байт IEEE стандартное соглашение Тип char используется для запоминания буквы , цифры или символа из множества представимых символов . Значением объекта типа char является ASCII код , соответствующий данному символу . Так как тип char интерпретируется как однобайтовая целая величина с областью значений от -128 до 127, то только величины от 0 до 127 имею т символьные эквиваленты . Аналогично , тип unsigned char может запоминать величины с областью значений от 0 до 255. Заметим , что представление в памяти и область значений для типов int и unsigned int не определены в языке Си . По умолчанию размер int (со зна ком и без знака ) соответствует реальному размеру целого на данной машине . Например , на 16-ти разрядной машине тип int всегда 16 разрядов или 2 байта . На 32-ух разрядной машине тип int всегда 32 разряда или 4 байта . Таким образом , тип int эквивалентен типа м short int или long int в зависимости от реализации. Аналогично , тип unsigned int эквивалентен типам unsigned short или unsigned long. Спецификаторы типов int и unsigned int широко используются в программах на Си , поскольку они позволяют наиболее эффектив но манипулировать целыми величинами на данной машине. Однако , размер типов int и unsigned int переменный , поэтому программы , зависящие от специфики размера int и unsigned int могут быть непереносимы . Переносимость кода можно улучшить путем включения выраж ений с sizeof операцией. Деклараторы Синтаксис : [] [constant-expression>] * () () () Си позволяет объявлять : массивы вел ичин , указатели на величины , величины возвратов функций . Чтобы объявить эти объекты , нужно использовать декларатор , возможно модифицированный квадратными скобками ([]), круглыми скобками () и звездочкой (*), что соответствует типам массива , функции или ук а зателя . Деклараторы появляются в объявлениях указателей , массивов и функций. Деклараторы массивов , функций и указателей Когда декларатор состоит из немодифицируемого идентификатора , то об 'ект , который объявляется , имеет немодифиц ированный тип . Звездочка , которая может появиться слева от идентификатора , модифицирует его в тип указателя . Если за идентификатором следуют квадратные скобки ([]), то тип модифицируется на тип массива . Если за идентификатором следуют круглые скобки , то т и п модифицируется на тип функции . Сам по себе декларатор не образует полного объявления . Для этого в объявление должен быть включен спецификатор типа . Спецификатор типа задает тип элементов массива или тип адресуемых объектов и возвратов функции. Следующие примеры иллюстрируют простейшие формы деклараторов : 1. int list[20] 2. char *cp 3. double func(void), где : 1. Массив list целых величин 2. Указатель cp на величину типа char 3. Функция func без аргументов , возвращающая величину double Составные деклараторы Любой декларатор может быть заключен в круглые скобки . Обычно , круглые скобки используются для спецификации особенностей интерпретации составного декларатора . Составной декларатор - это идентификатор , определяемый более чем одним модификатором массива , указателя или функции. С отдельным идентификатором могут появиться различные комбинации модификаторов массива , указателя или функции . Некоторые комбинации недопустимы . Например , массив не может быть композицией функций , а функция не может возвратить массив или функцию . При интерпретации составных деклараторов квадратные и круглые скобки (справа от идентификатора ) имеют приоритет перед звездочкой (слева от идентификатора ). Квадратные или круглые скобки имеют один и тот же п риоритет и рассматриваются слева направо . Спецификатор типа рассматривается на последнем шаге , когда декларатор уже полностью проинтерпретирован . Можно использовать круглые скобки , чтобы изменить порядок интерпретации на необходимый в данном случае. При и нтерпретации составных деклараторов может быть предложено простое правило , которое читается следующим образом : "изнутри - наружу ". Нужно начать с идентификатора и посмотреть вправо , есть ли квадратные или круглые скобки . Если они есть , то проинтерпретирова т ь эту часть декларатора , затем посмотреть налево , если ли звездочка . Если на любой стадии справа встретится закрывающая круглая скобка , то вначале необходимо применить все эти правила внутри круглых скобок , а затем продолжить интерпретацию . на последнем ш а ге интерпретируется спецификатор типа . В следующем примере проиллюстрированы эти правила . Последовательность шагов при интерпретации перенумерована. char *(*(*var) ()) [10]; ^ ^ ^ ^ ^ ^ ^ 7 6 4 2 1 3 5 1. Идентификатор var об 'явлен как 2. Указатель на 3. Функцию , возвращающую 4. Указатель на 5. Массив из 10 элементов , который состоит 6. Из указателей на 7. Величины типа char. В следующих примерах показывается каким образом круглые скобки могут поменять смысл объявлений. 1. int *var[5]; - массив ук азателей на величины типа int. 2. int (*var)[5]; - указатель на массив величин типа int. 3. long *var(long,long); - функция , возвращающая указатель на величину типа long. 4. long (*var) (long,long); - указатель на функцию , возвращающую величину типа long. 5. struct both int a; char b; ( *var[5] ) ( struct both, struct both); массив указателей на функции , возвращающих структуры. 6. double ( *var( double (*) [3] ) ) [3]; функция , возвращающая указатель на массив из трех величин типа double. 7. union sign int x; unsigned y; **var[5] [5]; массив массивов указателей на указатели совмещений. 8. union sign *(*var[5]) [5]; массив указателей на массив указателей на совмещения. Описание примеров : В первом примере , модификатор массива имеет высший приоритет , чем модификатор указателя , так что var объявляется массивом . Модификатор указателя определяет тип элементов массива ; элементами являются указатели на величины типа int. Во втором примере скобки меняют значение объявления первого примера. Теперь модификатор указателя имеет более высокий приоритет , чем модификатор массива , и переменная var объявляется как указатель на массив из пяти величин типа int. В третьем примере модификатор функции имеет более высокий приоритет , чем модификатор указа теля , так что переменная var объявляется функцией , возвращающей указатель на величину типа long. Функция объявлена с двумя аргументами типа long. Четвертый пример похож на второй . Скобки задают более высокий приоритет модификатору указателя , и поэтому пер еменная var объявляется как указатель на функцию , возвращающую величину типа long. По прежнему функция объявлена с двумя аргументами типа long. Элементы массива не могут быть функциями . Взамен этому в пятом примере показано , как объявить массив указателей на функции . В этом примере переменная var объявлена как массив из пяти указателей на функции , возвращающие структуры с двумя элементами . Оба аргумента функции объявлены как структуры типа both. Заметим , что круглые скобки , в которые заключено выражение * v ar[5], обязательны . Без них объявление будет неверным , поскольку будет объявлен массив функций : /* ILLEGAL */ struct both *var[5] ( struct both, struct both ); В шестом примере показано , как объявлять функцию , возвращающую указатель на массив . Здесь var объявлена функцией , возвращающей указатель на массив из трех величин типа double. Тип аргумента функции задан составным абстрактным декларатором . Круглые скобки , заключающие звездочку , требуются , так как в противном случае типом аргумента был бы массив из трех указателей на величины типа double. В седьмом примере показано , что указатель может указывать на другой указатель и массив может состоять из массивов . Здесь var- это массив из пяти элементов . Каждый элемент , в свою очередь , так же массив из пяти элем ентов , каждый из которых является указателем на указатель совмещения , состоящего из двух элементов . В восьмом примере показано , как круглые скобки изменили смысл объявления . В этом примере var- это массив из пяти указателей на массив из пяти указателей на совмещения. Объявления переменной В этом разделе дано описание синтаксиса и семантики объявлений переменной . В частности , здесь объясняется каким образом объявить следующие переменные : Массив Переменная , представляющая на бо р элементов одного типа. Указатель Переменная , которая указывает на другую переменную (содержит местоположение другой переменной в форме адреса ). Общий синтаксис объявлений переменных следующий : [] [,...], где - задает тип данных , представляемых переменной , а - это имя переменной , возможно модифицированное для объявления массива или указателя . В объявлении мо жет быть задана более чем одна переменная путем задания множе ственного объявления , в котором деклараторы разделены запятыми . задает класс памяти переменной . В некоторых случаях переменные могут быть инициализированы при их определении . Классы памяти и инициализация описаны в разделах 4.6 и 4.7 соотв е тственно. Объявление простой переменной Синтаксис : [,...]; Объявление простой переменной определяет имя переменной и ее тип ; оно может также определять класс памяти переменной , как это о писано в разделе 4.6. Имя переменной - это идентификатор , заданный в объявлении . Спецификатор типа задает имя определяемого типа данных. Можно определить имена различных переменных в том же самом объявлении , задавая список идентификаторов, разделенных запятой . Каждый идентификатор списка именует переменную . Все переменные , заданные в объявлении , имеют один и тот же тип. Примеры int x; /* Example 1 */ unsigned long reply, flag /* Example 2 */ double order; /* Example 3 */ В первом примере объявляется простая переменная x. Эта переменная может принимать любое значение из множества значений , определяемых для типа int. Во втором примере объявлены две переменные : reply и flag. Обе переменные имеют тип unsigned long. В третьем примере объявле на переменная order, которая имеет тип double. Этой переменной могут быть присвоены величины с плавающей запятой. Объявление перечисления Синтаксис : enum[] [,...]; enum[, ...]; Объявление перечисления задает имя переменной перечисления и определяет список именованных констант , называемый списком перечисления . Значением каждого имени списка является целое число . Переменная перечисления принимает значение одной и з именованных констант списка . Именованные константы списка имеют тип int. Таким образом , память соответствующая переменной перечисления - это память , необходимая для размещения отдельной целой величины . Объявление перечисления начинается с ключевого слова enum и имеет две формы представления . В первой форме представления имена перечисления задаются в списке перечисления . Опция - это идентификатор , который именует тип перечисления , определенного в . Переменную перечисления имен ует . В объявлении может быть описана более чем одна переменная перечисления. Во второй форме используется тег перечисления , который ссылается на тип перечисления . В этой форме объявления список перечисления не представлен , поскольку тип переч исления определен в другом месте . Если задаваемый тег не ссылается на уже определенный тип перечисления , или если именуемый тегом тип находится вне текущей видимости , то выдается ошибка. имеет следующий синтаксис : [=][, [= переопределяет последовательность значений , заданных по умолчанию . Идентификатор , следующий перед записью = принимает значение , задаваемое этим константным выражением . Константное выражение имеет тип int и может быть отрицательным . Следующий идентификатор в списке ассоциируется с величиной , равной +1, если он явно не задается другой величиной. Перечисление может содержать повторяющиеся значения идентификаторов, но каждый идентификатор должен быть уникальным . Кроме того , он должен быть отличным от всех других идентификаторов перечислений с той же видимостью . Например , двум различным идентификаторам null и zero может быть задано значение 0 в одном и том же перечи с лении . Идентификаторы должны быть отличны от других идентификаторов с той же самой видимостью , включая имена обычных переменных и идентификаторы других перечислений . Теги перечислений должны быть отличны от тегов перечислений , тегов структур и совмещений с той же самой видимостью. Примеры : /**************** Example 1 ***************/ enum day saturday, sunday = 0, monday, tuesday, wednesday, thursday, friday workday; /***************** Example 2 ***************/ enum day today = wednesday; В первом примере определяется тип перечисления , поименованный day и объявляется переменная workday этого типа перечисления . С saturday по умолчанию ассоциируется значение 0. Идентификатор sunday явно устанавливается в 0. Оставшиеся идентификаторы по умол ч анию принимают значение от 1 до 5. Во втором примере переменной today типа enum day присваивается значение из перечисления . Заметим , что для присваивания используется имя константы из перечисления . Так как тип перечисления day был предварительно объявлен, то достаточно сослаться только на тег перечисления. Объявления структур Синтаксис : struct[] [,...]; struct[,...]; Объявление структуры задает имя типа структуры и специфицирует последовательность переменных величин , называемых элементами структуры , которые могут иметь различные типы. Объявление структуры начинается с ключевого слова struct и имеет две формы представления , как показано выше . В п ервой форме представления типы и имена элементов структуры специфицируются в списке объявлений элементов . - это идентификатор , который именует тип структуры , определенный в списке объявлений элементов. Каждый за дает имя переменной типа структуры . Тип переменной в деклараторе может быть модифицирован на указатель к структуре , на массив структур или на функцию , возвращающую структуру. Вторая синтаксическая форма использует тег - структуры для ссылки на тип ст руктуры . В этой форме объявления отсутствует список объявлений элементов , поскольку тип структуры определен в другом месте . Определение типа структуры должно быть видимым для тега , который используется в объявлении и определение должно предшествовать объя в лению через тег , если тег не используется для объявления указателя или структурного типа typedef. В последних случаях объявления могут использовать тег структуры без предварительного определения типа структуры , но все же определение должно находиться в пр е делах видимости объявления. Список объявлений элементов - это одно или более объявлений переменных или битовых полей . Каждая переменная , объявленная в этом списке , называется элементом структурного типа . Объявления переменных спис ка имеют тот же самый синтаксис , что и объявления переменных обсуждаемых в этой главе , за исключением того , что объявления не могут содержать спецификаторов класса памяти или инициализаторов . Элементы структуры могут быть любого типа : основного , массивом, указателем , совмещением или структурой. Элемент не может иметь тип структуры , в которой он появляется . Однако , элемент может быть объявлен , как указатель на тип структуры , в которую он входит , позволяя создавать списочные структуры. Битовые поля Объявления битовых полей имеют следующий синтаксис : []:; Битовое поле состоит из некоторого числа бит , специфицированных константным выражением - . Для битового поля с пецификатор типа должен специфицировать беззнаковый целый тип , а константное выражение должно быть неотрицательной целой величиной . Массивы битовых полей , указатели на битовые поля и функции , возвращающие битовые поля не допускаются . Иде н тификатор - именует битовое поле . Неименованное битовое поле , чей размер специфицируется как нулевой , имеет специальное назначение : оно гарантирует , что память для следующей переменной объявления будет начинаться на границе int. Идентификаторы элементов внутри объявляемой структуры должны быть уникальными . Идентификаторы элементов внутри разных структур могут совпадать . В пределах той же самой видимости теги структур должны отличаться от других тегов (тегов других структур , совмещений и перечи с лений ). Переменные (элементы ) структуры запоминаются последовательно в том же самом порядке , в котором они объявляются : первой переменной соответствует самый младший адрес памяти , а последнейсамый старший . Память каждой переменной начинается на границе св ойственной ее типу . Поэтому могут появляться неименованные участки между соседними элементами. Битовые поля не располагаются на пересечении границ , обявленных для них типов . Например , битовое поле , объявленое с типом unsigned int, упаковывается или в прос транстве , оставшимся от предидущего unsigned int или начиная с нового unsigned int. Примеры /**************** Example 1 ****************/ struct float x,y; complex; /**************** Example 2 *****************/ struct employee char name[20]; int id; long class; temp; /**************** Example 3 ******************/ struct employee student, faculty, staff; /**************** Example 4 ******************/ struct sample char c; float *pf; struct sample *next; x; /***************** Ex ample 5 ******************/ struct unsigned icon : 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[] [,...]; union[,...]; Объявление совмещения определяет имя переменной совмещения и специфицирует множество переменных , называемых элементами совмещения , которые могут быть различных типов . Переменная с типо м совмещения запоминает любую отдельную величину , определяемую набором элементов совмещения. Объявление совмещения имеет тот же самый синтаксис , как и объявление структуры , за исключением того , что она начинается с ключевого слова union вместо ключевого с лова struct. Для объявления совмещения и структуры действуют одни и те же правила , за исключением того , что в совмещении не допускаются элементы типа битовых полей. Память , которая соответствует переменной типа совмещение , определяется величиной для разме щения любого отдельного элемента совмещения. Когда используется наименьший элемент совмещения , то переменная типа совмещения может содержать неиспользованное пространство . Все элементы совмещения запоминаются в одном и том же пространстве памяти переменно й , начиная с одного и того же адреса . Запомненные значения затираются каждый раз , когда присваивается значение очередного элемента совмещения. Примеры : /************** Example 1 ********************/ union sign int svar; unsigned uvar; number; /** ************ Example 2 ********************/ union char *a, b; float f[20]; jack; /*************** Example 2 *******************/ union struct char icon; unsigned color : 4; window1, window2, window3, window4; screen[25][80]; В перво м примере объявляется переменная типа совмещения , поименованная number. Список элементов совмещения состоит из двух объявлений переменных : svar типа int и uvar типа unsigned. Это объявление позволяет запоминать текущее значение number в знаковом или беззн а ковом виде . Тип совмещения поименован идентификатором sign. Во втором примере объявляется переменная типа совмещения с именем jack. Список элементов объявления состоит из трех объявлений : указателя a на величину типа char, переменной b типа char и массива f из 20 элементов типа float. Тип совмещения не поименован. Память , распределенная для переменной jack, равна памяти , распределенной под массив f, поскольку f самый большой элемент совмещения. В третьем примере объявляется двумерный массив совмещений с именем screen. Массив состоит из 2000 объектов . Каждый объектэто отдельное совмещение из четырех элементов : window1, window2, window3, window4, где каждый элемент - это структура . В любое заданное время каждый объект совмещения поддерживается одним из четы р ех возможных элементов типа структура . Таким образом , переменная screen- это композиция четырех возможных "windows". Объявление массива Синтаксис : []; [ ]; Здесь квадратные скобки - это терминальные символы . Объявление массива определяет тип массива и тип каждого элемента . Оно может определять также число элементов в массиве . Переменная типа массив рассматривается как указатель на элементы массива . Объявле ние массива может представляться в двух синтаксических формах , указанных выше . Декларатор задает имя переменной . Квадратные скобки , следующие за декларатором , модифицируют декларатор на тип массива . Константное выражение , заключенное в квадратные скобки , определяет число элементов в массиве . Каждый элемент имеет тип , задаваемый спецификатором типа , который может специфицировать любой тип , исключая void и тип функции. Во второй синтаксической форме опущено константное выражение в квадратных скобках . Эта форма может быть использована только тогда , когда массив инициализируется или объявлен как формальный параметр или объявлен как ссылка на массив , явно определенный где-то в программе. Массив массивов или мно гомерный массив определяется путем задания списка константных выражений в квадратных скобках , следущего за декларатором : [] []... Каждое константное выражение - в квадратных скобках определяет число элементов в данном измерении массива , так что объявление двумерного массива содержит два константных выражения , трехмерного - три и т.д . Если многомерный массив объявляется внутри функции или если он инициализируется либ о объявляется как формальный параметр или объявляется как ссылка на массив , явно определенный где - то в программе , то первое константное выражение может быть опущено. Массив указателей на величины , заданного типа , может быть определен посредством составног о декларатора , как было описано в разделе 4.3.2. Типу массив соответствует память , которая требуется для размещения всех его элементов . Элементы массива с первого до последнего запоминаются в последовательных возрастающих адресах памяти . Между элементами массива в памяти разрывы отсутствуют . Элементы массива запоминаются друг за другом построчно . Например , массив , содержащий две строки с тремя столбцами каждая , char A[2][3] будет запомнен следующим образом . Сначала запоминаются три столбца первой строки , з атем элементы трех столбцов второй строки . Смысл этого в том , чтобы последний индекс был более быстрым . Чтобы сослаться на отдельный элемент массива , нужно использовать индексное выражение , которое описано в разделе 5.2.5. Примеры : /*************** Exampl e 1 ******************/ int scores[10], game; /*************** Example 2 ******************/ float matrix[10][15]; /*************** Example 3 ******************/ struct float x,y; complex[100]; /*************** Example 4 *******************/ char *name[20]; В первом примере объявляется переменная типа массив с именем scores из 10 элементов типа int. Переменная с именем game объявлена как простая переменная целого типа. Во втором примере объявляется двумерный массив с именем matrix. Массив состои т из 150-ти элементов типа float. В третьем примере объявляется массив структур . Массив состоит из 100 объектов . Каждый объект массива представляет собой структуру , состоящую из двух элементов. В четвертом примере объявлен массив указателей . Массив состо ит из 20-ти элементов , каждый из которых является указателем на величину типа char. 4.4.6. Объявление указателей Синтаксис : *; Объявление указателя определяет имя переменной типа указатель и тип объекта , на который указывае т эта переменная . Декларатор - определяет имя переменной с возможной модификацией ее типа . Спецификатор типа - задает тип объекта , который может быть базового типа , типа структуры или совмещения. Переменная типа указатель може т указывать также на функции , массивы и другие указатели . Более полная информация о типах указателей дана в разделе 4.3.2. "Составные деклараторы ". Если указатель не используется до определения типа структуры или совмещения , то он может быть объявлен ране е этого определения . Такие объявления допускаются , поскольку компилятору не требуется знать размера структуры или совмещения , чтобы распределить память под переменную типа указатель . Указатель может быть объявлен посредством использования тега структуры и л и совмещения (смотри ниже пример 4). Переменная , объявленная как указатель , хранит адрес памяти . Размер памяти , требуемый для адреса , и смысл адреса зависит от данной конфигурации машины . Указатели на различные типы не обязательно имеют одну и ту же длину. Для некоторых реализаций используются специальные ключевые слова near, far и huge, чтобы модифицировать размер указателя . Объявления , использующие специальные ключевые слова , были описаны в разделе 4.3.3. Информация о смысле ключевых слов дана в системн ой документации. Примеры : char *message; /* Example 1 */ int *pointers[10]; /* Example 2 */ int (*pointer)[10]; /* Example 3 */ struct list *next, *previous; /* Example 4 */ struct list /* Example 5 */ char *token; int count; struct list *next; li ne; struct id /* Example 6 */ unsigned int id_no; struct name *pname; record; В первом примере объявляется переменная - указатель поименованная message. Она указывает на величину типа char. Во втором примере объявлен массив указателей , поименованный pointers. Массив состоит из 10 элементов . Каждый элемент - это указатель на переменную типа int. В третьем примере объявлена переменная - указатель , поименованная pointer. Она указывает на массив из 10 элементов . Каждый элемент в этом массиве имеет тип int. В четвертом примере объявлены две переменныхуказателя , которые ссылаются на величины структурного типа list (смотри следующий пример ). Определение типа с именем list должно находиться в пределах видимости объявления. В пятом примере объявляется перемен ная с именем line, структурного типа , поименованного list. Тип структуры с именем list определяется тремя элементами . Первый элементэто указатель на величину типа char, второй - на величину типа int, а третийэто указатель на следующую структуру типа list. В шестом примере объявляется переменная с именем record, имеющая тип структуры с именем id. Заметим , что третий элемент с именем pname объявлен как указатель на другой тип структуры с именем name. Это объявление может появиться перед объявление структуры с именем name. Объявление функций Синтаксис : []([])[,...]; Объявление функции определяет имя , тип возврата функции и , возможно , типы и число ее аргументов . Объявление функци и также называется forward- объявлением . Декларатор функции объявляет имя функции , а спецификатор типа задает тип возврата . Если спецификатор типа опущен в объявлении функции , то предполагается , что функция возвращает величину типа int. Объявление функции может включать спецификаторы класса памяти extern или static. Список типов аргументов. Список типов аргументов - определяет число и типы аргументов функции . Синтаксис списка аргументов следующий : [,...] Список имен типов - это список из одного или более имен типов . Каждое имя типа отделяется от другого запятой . Первое имя типа задает тип первого аргумента , второе имя типа задает тип второго аргумента и т . д . Если список имен типов заканчивается запятой с многоточием (,... ) , то это означает , что число аргументов функции переменно . Однако , предполагается , что функция будет иметь не меньше аргументов , чем имен типов , предшествующих многоточию. Если список типов аргументов - содержит только многоточие (...), то число аргументов функции является переменным или равно нулю. Замечание : Чтобы поддержать совместимость с программами предыдущих версий , компилятор допускает символ запятой без многоточия в конце списка типов аргументов для обозначения их переменного числ а . Запятая может быть использована и вместо многоточия для объявления нуля или более аргументов функции . Использование запятой поддерживается только для совместимости . Использование многоточия рекомендуется для нового представления. Имя типа - для типов структуры , совмещения или базового типа состоит из спецификатора этого типа (такого как int ). Имена типов для указателей , массивов и функций формируются путем комбинации спецификатора типа с "абстрактным декларатором ". Абстрактный декларатор - э то декларатор без идентификатора . В разделе 4.9 "Имена типов " объясняется , каким объразом формировать и интерпретировать абстрактные деклараторы. Для того чтобы объявить функцию , не имеющую аргументов , может быть использовано специальное ключевое слово vo id на месте списка типов аргументов . Компилятор вырабатывает предупреждающее сообщение , если в вызове такой функции будут специфицированы аргументы. Еще одна специальная конструкция допускается в списке типов аргументов . Это фраза void *, которая специфиц ирует аргумент типа указатель . Эта фраза может быть использована в списке типов аргументов вместо имени типа. Список типов аргументов может быть опущен . В зтом случае скобки после идентификатора функции все же требуются , хотя они и пусты . В этом случае в объявлении функции не определяются ни типы , ни число аргументов в функции . Когда эта информация опускается , то компилятор не проверяет соответствия между формальными и фактическими параметрами при вызове функции . Более подробная информация дана в разделе 7 .4 "Вызовы функций ". Тип возврата Функции могут возвращать величины любого типа за исключением массивов и функций . Для этого посредством спецификатора типа "type-specifier" в объявлении функции можно специфицировать любой тип : основной , структуру или совм ещение . Идентификатор функции может быть модифицирован одной или несколькими звездочками (*), чтобы объявить возвращаемую величину типа указателя. Хотя функции и не допускают возвратов массивов или функций , но они могут возвращать указатели на массивы или функции . Функции , которые возвращают указатели на величины типа массив или функция , объявляются посредством модификации идентификатора функции квадратными скобками , звездочкой и круглыми скобками , чтобы сформировать составной декларатор . Формирование и и н терпретация составных деклараторов рассматривались в разделе 4.3.2. Примеры : int add(int, int); /* Example 1 */ double calc(); /* Example 2 */ char *strfind(char *,...); /* Example 3 */ void draf(void); /* Example 4 */ double (*sum(double, double)) [3 ]; /* Example 5 */ int (*select(void)) (int) ; /* Example 6 */ char *p; /* Example 7 */ short *q; int prt(void *); В первом примере объявляется функция , поименованная add, которая требует два аргумента типа int и возвращает величину типа int. Во второ м примере объявляется функция , поименованная calc, которая возвращает величину типа double. Список типов аргументов не задан . В третьем примере объявляется функция , поименованная strfind, которая возвращает указатель на величину типа char. Функция требует, по крайней мере один аргументуказатель на величину типа char. Список типов аргументов заканчивается запятой с многоточием , обозначающим , что функция может потребовать большее число аргументов. В четвертом примере объявляется функция с типом возврата void (нет возвращаемой величины ). Список типов аргументов также void, означающий отсутствие аргументов для этой функции. В пятом примере sum объявляется как функция , возвращающая указатель на массив из трех величин типа double. Функция sum требует два аргумен та , каждый из которых является величиной типа double. В шестом примере функция , поименованная select, объявлена без аргументов и возвращает указатель на функцию . Указатель возврата ссылается на функцию , требующую один аргумент типа int и возвращающую вели чину типа int. В седьмом примере объявлена функция prt, которая требует аргумент - указатель любого типа , и которая возвращает величину типа int. Любой указатель p или q могли бы быть использованы как аргументы функции без выдачи при этом предупреждающего сообщения. Классы памяти Класс памяти переменной , которая определяет какой либо объект , имеет глобальное или локальное время жизни . Объект с глобальным временем жизни существует и имеет значение на протяжении всей программы . Вс е функции имеют глобальное время жизни . Переменные с локальным временем жизни захватывают новую память при каждом выполнении блока , в котором они определены . Когда управление на выполнение передается из блока , то переменная теряет свое значение. Хотя Си определяет два типа классов памяти , но , тем не менее , имеется следующих четыре спецификатора классов памяти : auto register static extern Объекты классов auto и register имеют локальное время жизни . Спецификаторы static и extern определяют объекты с гл обальным временем жизни . Каждый из спецификаторов класса памяти имеет определенный смысл , который влияет на видимость функций и переменных в той же мере , как и сами классы памяти . Термин "видимость " относится к той части программы , в которой могут ссылать с я друг на друга функции и переменные . Объекты с глобальным временем жизни существуют на протяжении выполнения исходной программы , но они могут быть видимы не во всех частях программы . Видимость и связанная с ней концепция времени жизни рассмотрена в разде л е 3.5. Месторасположение объявления переменной или функции внутри исходных файлов также влияют на класс памяти и видимость . Говорят , что объявления вне определения всех функций и переменных относятся к внешнему уровню , а объявления внутри определений функ ций относятся к внутреннему уровню. Точный смысл каждого спецификатора класса памяти зависит от того , находится ли объявление на внешнем или внутреннем уровне и от того , объявлен ли объект функцией или переменной . В следующем разделе описывается смысл спе цификаторов класса памяти в каждом случае объявления , а также объясняется режим умолчания , когда спецификатор класса памяти опущен при объявлении переменной или функции. Объявления переменной на внешнем уровне Объявления перемен ной на внешнем уровне используют спецификаторы класса памяти static и extern или совсем опускают их . Спецификаторы класса памяти auto и register не допускаются на внешнем уровне. Объявления переменных на внешнем уровне - это определения переменных или ссыл ки на определения , сделанные в другом месте . Объявление внешней переменной , которое инициализирует эту переменную (явно или неявно ), называется определением этой переменной . Определение на внешнем уровне может задаваться в следующих различных формах : -пе ременная на внешнем уровне может быть определена путем ее объявления со спецификатором класса памяти static. Такая переменная может быть явно инициализирована константным выражением . Если инициализатор отсутствует , то переменная автоматически инициализиру е тся нулем во время компиляции . Таким образом , объявления static int k = 16; и static int k; оба рассматриваются как определения ; -переменная определяется , когда она явно инициализируется на внешнем уровне . Например , int j = 3; это определение переменной. Так как переменная определяется на внешнем уровне , то она видима в пределах остатка исходного файла , от места , где она определена . Переменная не видима выше своего определения в том же самом исходном файле ни в других исходных файлах программы , если не о б ъявлена ссылка , которая делает ее видимой. Переменная может быть определена на внешнем уровне внутри исходного файла только один раз . Если задается спецификатор класса памяти static, то в других исходных файлах могут быть определены переменные с тем же им енем . Так как каждое определение static видимо только в пределах своего собственного исходного файла , то конфликта не возникнет. Спецификатор класса памяти extern используется для объявления ссылки на переменную , определенную где-то в другом месте . Такие объявления используются в случае , когда нужно сделать видимым определение переменной в других исходных файлах или выше места , где она определена в том же самом исходном файле . Так как ссылка на переменную объявлена на внешнем уровне , то переменная видима в пределах остатка исходного файла от места объявления ссылки. В объявлениях , которые используют спецификатор класса памяти extern, инициализация не допускается , так как они ссылаются на переменные , чьи величины уже определены. Переменная , на которую дела ется ссылка extern, должна быть определена на внешнем уровне только один раз . Определение может быть сделано в любом из исходных файлов , составляющих программу . Есть одно исключение из правил , описанных выше . Можно опустить из объявления переменной на вне шнем уровне спецификатор класса памяти и инициализатор . Например , объявление int n; будет правильным внешним объявлением . Это объявление имеет два различных смысла в зависимости от контекста. 1. Если где-нибудь в программе будет определена на внешнем уров не переменная с тем же именем , то объявление является ссылкой на эту переменную , как если бы был использован спецификатор класса памяти extern в объявлении. 2. Если нет такого определения , то объявленной переменной распределяется память во время линковани я и переменная инициализируется нулем . Если в программе появится более чем одно такое объявление , то память распределится для наибольшего размера из объявленных переменных . Например , если программа содержит два неинициализированных объявления переменной i на внешнем уровне int i; и char i; то память во время линкования распределится под переменную i типа int. Неинициализированные объявления переменной на внешнем уровне не рекомендуются для файлов , которые могут быть размещены в библиотеку. Пример : /***** ************************************************ SOURCE FILE ONE *****************************************************/ extern int i; /* reference to i defined below */ main() i++; printf("%d\n", i); /* i equals 4 */ next(); int i = 3; /* def inition of i */ next() i++; printf("%d\n", i); /* i equals 5 */ other(); /***************************************************** SOURCE FILE TWO *****************************************************/ extern int i; /* reference to i in first sou rce file */ other() i++; printf("%d\n", i); /* i equals 6 */ Два исходных файла в совокупности содержат три внешних объявления i. Одно объявление содержит инициализацию - int i = 3; , где глобальная переменная i определена с начальным значением ра вным 3. Самое первое объявление extern в первом файле делает глобальную переменную видимой выше ее определения в файле. Без объявления extern функция main не смогла бы сослаться на глобальную переменную i. Объявление extern переменной i во втором исходно м файле делает глобальную переменную видимой в этом исходном файле. Все три функции выполняют одну и ту же задачу : они увеличивают i на 1 и печатают получившееся значение . (Предполагается , что функция printf определена где-то еще в программе .). Печатаются величины равные 4, 5 и 6. Если бы переменная i не была бы инициализирована,она бы была автоматически установлена в 0 при линковании . В этом случае напечатанные значения были бы равны 1, 2 и 3. Объявление переменной на внутреннем уровне Любой из четырех спецификаторов класса памяти может быть использован для объявления переменной на внутреннем уровне . Если спецификатор класса памяти опускается в объявлении переменной на внутреннем уровне , то подразумевается класс памяти auto. Сп ецификатор класса памяти auto объявляет переменную с локальным временем жизни . Переменная видима только в том блоке , где она объявлена . Объявления переменных auto могут включать инициализаторы . Переменные класса памяти auto автоматически не инициализируют с я , а инициализируются явно при объявлении или присваивании начальных значений , посредством операторов внутри блока . Если нет инициализации , то величина переменной auto считается неопределенной. Спецификатор класса памяти register сообщает компилятору о то м , чтобы он распределил память под переменную в регистре , если это возможно . Использование регистровой памяти обычно приводит к более быстрому времени доступа и к меньшему размеру результирующего кода . Переменные , объявленные с классом памяти register име ю т ту же самую видимость , что и переменные auto. Число регистров , которое может быть использовано под память переменных , зависит от машины . Когда компилятор встречает спецификатор класса памяти register в объявлении , а свободного регистра не имеется , то дл я переменной распределяется память класса auto. Компилятор назначает переменным регистровую память в том порядке , в котором появляются объявления в исходном файле . Регистровая память , если она имеется , гарантирована только для целого и адресного типов. Пе ременная , объявленная на внутреннем уровне со спецификатором класса памяти static,имеет глобальное время жизни и имеет видимость только внутри блока , в котором она объявлена . В отличие от переменных auto, переменные , объявленные как static, сохраняют свое значение при завершении блока. Переменные класса памяти static могут быть инициализированы константным выражением . Если явной инициализации нет , то переменная класса памяти static автоматически устанавливается в 0. Инициализация выполняется один раз во вр емя компиляции . Инициализация переменной класса памяти static не повторяется при новом входе в блок. Переменная , объявленная со спецификатором класса памяти extern, является ссылкой на переменную с тем же самым именем , определенную на внешнем уровне в люб ом исходном файле программы . Цель внутреннего объявления extern состоит в том , чтобы сделать определение переменной внешнего уровня видимой внутри блока . Внутреннее об 'явление extern не изменяет видимость глобальной переменной в любой другой части програм мы. Пример : int i = 1; main() /* reference to i, defined above */ extern int i; /* initial value is zero; a is visible only within main */ static int a; /* b is stored in a register, if possible */ register int b = 0; /* default storage class is au to */ int c = 0; /* values printed are 1, 0, 0, 0 */ printf("%d\n%d\n%d\n%d\n", i, a, b, c); other(); other() /* i is redefined */ int i = 16; /* this a is visible only within other */ static int a = 2; a += 2; /* values printed are 16, 4 */ printf("%d\n%d\n", i, a); Переменная i определяется на внешнем уровне с инициализацией 1. В функции main объявлена ссылка extern на переменную i внешнего уровня . Переменная класса памяти static автоматически устанавливается в 0, так как инициализатор опущен . Вызов функции print (предполагается , что функция print определена в каком-то месте исходной программы .) печатает величины 1, 0, 0, 0. В функции other, переменная i переопределяется как локальная переменная с начальным значением 16. Это не влияет н а значение внешней переменной i. Переменная a объявляется как переменная класса памяти static с начальным значением 2. Она не противоречит переменной a, объявленной в функции main, так как видимость переменных класса памяти static на внутреннем уровне огр а ничена блоком , в котором она объявлена. Значение переменной увеличивается на 2 и становится равным 4. Если бы функция other была вызвана снова в той же самой программе , то начальное значение a стало бы равным 4. Внутренние переменные класса памяти static сохраняют свои значения , когда заканчивается выполнение блока , в котором они объявлены. Объявление функции на внешнем и внутреннем уровнях Функции могут быть объявлены со спецификаторами класса памяти static или extern. Функции всегда имеют глобальное время жизни. Правила видимости для функций отличаются от правил видимости для переменных . Объявления функций на внутреннем уровне имеют тот же самый смысл , что и объявления на внешнем уровне . Это значит , что функции не могут иметь блочной видимости и видимость функций не может быть вложенной . Функция объявленная как static, видима только в пределах исходного файла , в котором она определяется . Любая функция в том же самом исходном файле может вызвать функцию static, но функции stati c из других файлов нет . Функция static с тем же самым именем может быть объявлена в другом исходном файле. Функции , объявленные как extern видимы в пределах всех исходных файлов , которые составляют программу . Любая функция может вызвать функцию extern. Об ъявления функций , в которых опущен спецификатор класса памяти , считаются по умолчанию extern. Инициализация В объявлении переменной может быть присвоено начальное значение посредством инициализатора . Величина или величины инициа лизатора присваиваются переменной. Синтаксически , записи инициализатора предшествует знак равно (=) = Могут быть инициализированы переменные любого типа . Функции не инициализируются . Объявления , которые используют спецификатор класса памяти extern не могут содержать инициализатора . Переменные , объявленные на внешнем уровне , могут быть инициализированы . Если они явно не инициализированы , то они устанавливаются в нуль во время компиляции или линкования . Любая переменная , объявленная со специф икатором класса памяти static, может быть инициализирована константным выражением . Инициализация переменных класса static выполняется один раз во время компиляции . Если отсутствует явная инициализация , то переменные класса памяти static автоматически уста н авливаются в нуль. Инициализация переменных auto и register выполняется каждый раз при входе в блок , в котором они объявлены . Если инициализатор опущен в объявлении переменной класса памяти auto или register, то начальное значение переменной не определено . Инициализация составных типов auto (массив , структура , совмещение ) запрещена . Любое составное объявление класса памяти static может быть инициализировано на внешнем уровне. Начальными значениями для внешних объявлений переменной и для всех переменных st atic как внешних так и внутренних должно быть константное выражение . Автоматические и регистровые переменные могут быть инициализированы константными или переменными величинами. Базовые типы и типы указателей Синтаксис : = Величина выражения присваивается переменной . Для выражения допустимы правила преобразования. Примеры : int x = 10; /* Example 1 */ register int *px = 0; /* Example 2 */ int c = (3 * 1024); /* Example 3 */ int *b = &x; /* Example 4 */ В первом прим ере x инициализируется константным выражением 10. Во втором примере , указатель px инициализирован нулем , в результате чего получился "null" указатель . В третьем примере используется константное выражение для инициализации c. В четвертом примере инициализи р уется указатель b адресом другой переменной x. Составные типы Синтаксис : = Список инициализаторов - это последовательность инициализаторов , разделенных запятыми . Каждый инициализатор в по следовательности - это либо константное выражение , либо список инициализаторов . Поэтому , заключенный в фигурные скобки список , может появиться внутри другого списка инициализации . Эта конструкция используется для инициализации элементов составных конструкц и й. Для каждого списка инициализации значения константных выражений присваиваются в порядке следования элементов составной переменной . Когда инициализируется совмещение , то список инициализаторов представляет собой единственное константное выражение . Велич ина константного выражения присваивается первому элементу совмещения. Если в списке инициализации меньше величин , чем их имеется в составном типе , то оставшиеся памяти инициализируются нулем . Если число инициализирующих величин больше чем требуется , то вы дается ошибка. Эти правила применяются к каждому вложенному списку инициализаторов , точно так же как и ко всей конструкции в целом. Пример : int p[4] [3] = 1, 1, 1 , 2, 2, 2 , 3, 3, 3, , 4, 4, 4, , ; В примере объявляется массив p размерн ости 4 строки на 3 столбца . Элементы первой строки инициализируются 1, второй строки 2 и т . д . Заметим , что списки инициализаторов третьей и четвертой строк заканчиваются запятой . Последний список инициализаторов 4, 4, 4, также заканчивается запятой. Э ти дополнительные запятые допускаются , но не требуются . Требуются только те запятые , которые разделяют константные выражения и списки инициализации . Если список инициализаторов не структурирован под составной объект , то его величины присваиваются в том по р ядке , в котором подстыкованы элементы объекта . Поэтому вышеприведенная инициализация эквивалентна следующей : int p[4] [3] = 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4 ; Фигурные скобки могут также появляться вокруг индивидуальных инициализаторов в списке. К огда инициализируются составные переменные , то нужно позаботиться о том , чтобы правильно использовать фигурные скобки и списки инициализаторов . В следующем примере иллюстрируется более детально интерпретация компилятором фигурных скобок. typedef struct int n1, n2, n3; triplet; triplet nlist[2] [3] = 1, 2, 3 , 4, 5, 6 , 7, 8, 9 , /* Line 1 */ 10,11,12 , 13,14,15 , 15,16,17 /* Line 2 */ ; В примере nlist объявляется как массив структур , состоящий из двух строк и трех столб цов . Каждая структура состоит из трех элементов . Первая строка инициализации назначает величины первой строке массива nlist следующим образом : 1. Первая левая фигурная скобка Line 1 информирует компилятор о том , что это начало инициализации первой строки массива nlist(nlist[0]). 2. Вторая левая фигурная скобка означает то , что начинается инициализация первого элемента первой строки массива ( nlist[0] [0] ). 3. Первая правая фигурная скобка сообщает об окончании инициализации первого элемента - структуры n list[0] [0]. Следующая левая фигурная скобка сообщает о начале инициализации второго элемента первой строки nlist[0] [1]. 4. Процесс продолжается до конца Line 1 и заканчивается по последней правой фигурной скобке. Аналогично , Line 2 назначает величины в торой строке массива nlist. Заметим , что внешние фигурные скобки инициализаторов Line 1 и Line 2 требуются . Следующая конструкция , в которой внешние фигурные скобки опущены будет неверной. /* THIS CAUSES AN ERROR */ triplet nlist[2] [3] = 1, 2, 3 , 4, 5, 6 , 7, 8, 9 , /* Line 1 */ 10,11,12 , 13,14,15 , 16,17,18 /* Line 2 */ ; В этом примере первая левая фигурная скобка в Line 1 стартует инициализацию nlist[0], которая является массивом из трех структур . Величины 1, 2, 3 назначаются трем элементам первой структуры . Когда встретится правая фигурная скобка (после величины 3), инициализация nlist[0] закончится и две оставшиеся структуры автоматически инициализируются нулем . Аналогично , 4, 5, 6 инициализирует первую структуру во втор о й строке nlist, а оставшиеся две структуры nlist[1] установятся в нуль . Когда компилятор встретит следующий список инициализации 7, 8, 9 , то это приведет к попытке инициализировать nlist[2]. Так как nlist содержит только две строки , то будет выдано со о бщение об ошибке. Примеры : /******************* Example 1 *********************/ struct list int i, j, k; float n[2] [3]; x = 1, 2, 3, 4.0, 4.0, 4.0 ; /******************* Example 2 *********************/ union char x[2] [3]; int i, j, k; y = '1' , '4' ; В первом примере три элемента int структурной переменной x инициализированы 1, 2, и 3 соответственно . Три элемента первой строки массива m инициализированы как 4.0. Элементы второй строки инициализированы нулем по умолчанию. Во втором примере инициализируется переменная y типа совмещения . Первым элементом совмещения является массив , для которого требуется составной инициализатор . Список инициализации '1' задает величины для первой строки массива . Поскольку в спи ске всего одна величина , то только первый элемент строки массива инициализируется символом 1 , а оставшиеся два элемента в строке инициализируются нулем (символом \ 0) по умолчанию . Аналогично , первый элемент второй строки массива x инициализируется символ о м 4, а оставшиеся два элемента в строке инициализируются нулем. Строковые инициализаторы Массив может быть инициализирован строчным литералом. Например, char code[ ] = "abc"; инициализирует code как массив символов из четырех элементов . Четвертым элементом является символ \ 0, который завершает все строковые литералы. Если специфицируется размер массива , а строка больше чем специфицированный размер , то лишние символы отбрасываются . Следующее объявление инициализирует переменную code, как трехэлементный массив символов : char code[3] = "abcd" В примере только три первые символа инициализатора назначаются для массива code. Символ d и сивол нуль отбрасываются. Если строка короче , чем специфицированный размер массива , то оставшиес я элементы массива инициализируются нулем (символом \0). Объявления типов Объявление типа определяет имя и элементы структурного или совмещающего типов или имя и перечислимое множество перечислимого типа. Имя типа может быть ис пользовано в объявлениях переменных и функций в качестве ссылки на этот тип . Это полезно , когда многие переменные или функции имеют один и тот же тип. Объявление typedef определяет спецификатор типа для типа . Это объявление используется для того , чтобы со здавать более короткие или более осмысленные имена типов уже определенных в Си или объявленных пользователем. Типы структур , совмещений и перечислений Объявления типов структур , совмещений и перечислений имеют ту же самую общую синтаксическую форму , как и объявления переменных этих типов . В объявлении типа идентификатор переменной опущен , так как нет переменной которая объявляется . Именем структуры , совмещения или перечисления является тег. В объявлении типа может появиться спис ок объявлений элементов - или список перечисления , определяющие тип. Сокращенная форма объявления переменной , в котором tag ссылается на тип , определенный где-то еще , при объявлении типа не используется. Примеры : /**** **************** Example 1 ********************/ enum status loss = -1, bye, tie = 0, win, ; /********************* Example 2 *******************/ struct student char name[20]; int id, claas; ; В первом примере объявляется тип перечисления , поименованный status. Имя типа может быть использовано в об 'явлениях перменных типа перечисления . Идентификатор loss явно устанавливается в -1. Идентификаторы bye и tie ассоциируются со значением 0, а win принимает значение 1. Во втором примере объявляе т ся структурный тип , поименованный student. Теперь можно использовать такое объявление , как struct student employee, чтобы объявить структурную переменную employee типа student. Объявления typedef Синтаксис : typedef [,...]; Объявления typedef являются аналогом объявления переменной , за исключением того , что ключевое слово typedef заменяет спецификатор класса памяти. Объявление интерпретируется тем же самым путем , как объявления переменной и ли функции , но вместо того , чтобы стать переменной типа , специфицированного объявлением , становится синонимом имени типа . Объявление typedef не создает типов . Оно создает синонимы для существующих имен типов , которые были специфицированы друг и м способом . Любой тип может быть объявлен с typedef, включая типы указателя , функции и массива . Имя с ключевым словом typedef для типов указателя , структуры или совмещения может быть объявлено прежде чем эти типы будут определены , но в пределах видимости о бъявления. Примеры : /******************** Example 1 ********************/ typedef int WHOLE; /******************** Example 2 ********************/ typedef struct club char name[30]; int sise, year; GROUP; /******************** Example 3 ********************/ typedef GROUP *PG; /******************** Example 4 ********************/ typedef void DRAWE(int, int); В первом примере объявляется WHOLE как синоним для int . Во втором примере объ является GROUP к ак структурный тип с тремя элементами . Так как специфицирован также тег clab, то имя GROUP и тег club могу быть использованы в объявлениях. В третьем примере используется предыдущее имя typedef для объявления адресного типа . Тип PG объявляется как указате ль на тип GROUP, который в свою очередь определен как структурный тип . В последнем примере представлен тип DRAWE для функции не возвращающей значения и требующей два аргумента типа int. Это означает , например , что объявление DRAWE box; эквивалентно объявл ению void box(int, int); Имена типов Имя типа специфицирует особенности типа данных . Имена типов используются в трех контекстах : в списках типов аргументов , при объявлении функций , в вычислениях cast (преобразованиях типов ), и в sizeof операциях . Списки типов аргументов рассматривались в разделе 4.5. "Объявления функций ". Преобразования cast и операция sizeof обсуждаются в разделах 5.7.2. и 5.3.4. соответственно. Именами для основных , перечисляющих , структурных и совмещающих типо в являются спецификаторы типа для каждого из них . Имена для типов указателя , массива и функции задаются следующей синтаксической формой : Абстрактный декларатор - это декларатор без идентификатора , состоящий из одного или более модификаторов указателей , массивов и функций . Модификатор указателя (*) всегда появляется перед идентификатором в деклараторе , в то время как модификатор массива ([]) или функции ( () ) появляются после идентификатора . Таки м образом , чтобы правильно интерпретировать абстрактный декларатор , нужно начинать интерпретацию с подразумеваемого идентификатора. Абстрактные деклираторы могут быть составными . Скобки в составном абстрактном деклараторе специфицируют порядок интерпретаци и , подобно тому как это делается при интерпретации составных деклараторов объявлений . Абстрактный декларатор , состоящий из пустых круглых скобок () не допускается , поскольку это двусмысленно . В этом случае невозможно определить находится ли подразумеваемы й идентификатор внутри скобок , и в таком случае - это немодифицированный тип , или перед скобками , тогда - это тип функции . Спецификаторы типа , установленные посредством объявлений typedef, также рассматриваются как имена типов. Примеры : long * /* Example 1 */ int (*) [5] /* Example 2 */ int (*) (void) /* Example 3 */ В первом примере задано имя типа как указатель на тип long. Во втором и третьем примерах показано каким образом скобки модифицируют составные абстрактные деклараторы . В примере 2 задано имя ти па для указателя на массив иэ пяти злементов . В третьем примере именуется указатель на функцию , не требующую аргументов и возвращающую значение типа int. КОНТРОЛЬНЫЕ ВОПРОСЫ : 1. Какие ошибки содержат следующие операторы ? enum S tate on, off ; enum YesNo yes, no ; enum DiskDriveStatus on, off ; 2. Верно или нет , что объявление следующего перечислимого типа неправильно ? enum YesNo no = 0, No = 0, yes = 1, Yes = 1 ; 3. Что не так в следующей программе ? #include int main() int *p = new int; cout << "Enter а number"; cin >> *p; cout << "The square 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 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 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, int diff = 1) i = I + diff; /* 6. В этой пр ограмме есть ошибка . Что это за ошибка и как ее исправить ? */ # include 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 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 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_t nelem, 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. Сегодня мы рассмотрим следующие темы : · Форматированный потоковый вывод · Потоковый ввод · Функция pri ntf · Строки в C++ · Ввод строк · Использование стандартной библиотеки функций для работы со строками · Присвоение значений строкам · Определение длины строки · Конкатенация строк · Сравнение строк · Преобразование строк · Перестановка символов в строке в обратном порядке · Поиск символа · Поиск подстроки · Форматированный потоковый вывод C++ имеет целое семейство гибких библиотек функций ввода /вывода . Разработчикам языка было ясно , что функции ввода /вывода из STDIO.H, унаследованные из С , имеют ограничения при работе с классами (вы узнаете больше о классах в главе 8)., В результате в C++ было введено понятие потоков . Вспомним , что потоки , которые уже существовали в С , означают последовательность данных , передаваемых из одной части компьютера в другую . В программах , рассматриваемых ранее , вы видели операцию помещения в поток “ , например — в стандартный поток вывода, cout. Встречалась вам и операция извлечения из потока ” , применяемая к стандартному потоку ввода, cin. В э том разделе мы познакомимся с потоковыми функциями width и precision, используемыми при форматировании вывода . Библиотеки потоков C++ содержат большое количество таких функций , позволяющих настроить ваш вывод. Функция width задает ширину поля вывода . Общая форма использования функции width с потоком cout: cout.width (widthOf Output ); Функция precision определяет количество значащих цифр после точки для чисел с плавающей точкой . Общая форма использования функции precision с потоком cout: cout.precision(numbe rOfDigits) ; Обратимся к примеру , программе OUT1.CPP, исходный текст которой приведен в листинге 1. Программа , в которую ничего не вводится , просто выводит форматированные целые числа , числа с плавающей точкой и символы с использованием функций width и pre cision. Листинг 1. Исходный текст программы OUT1.CPP 01 // Программа иллюстрирует потоковый форматированный вывод в C++ 02 // с использованием функций width и precision 03 #include 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. Оператор вывода в строках 1 3 и 14 выводит сумму и среднее значение введенных вами величин. Оператор вывода в строке 15 предлагает вам ввести три символа . Оператор (ввода в строке 16 последовательно размещает введенные символы в переменных с 1, с 2, с 3. Использовать пробел для разделени я вводимых символов не обязательно . Например , вы можете ввести данные и таким образом : 1А 2, Bob и 1 D d. Оператор вывода в строках 17 — 19 выводит введенные вами символы , разделенные пробелами и заключенные в одинарные кавычки. Оператор вывода в строке 20 пр едлагает вам ввести число , символ и число . Оператор ввода в строке 21 помещает ваши данные в переменные х , с 1 и у . Пробел-разделитель здесь нужен только в том случае , если символ может быть интерпретирован как часть числа . Например , если вам нужно ввести ч исло 12, символ “точка” и число 55, вам нужно набрать на клавиатуре 12 . 55. Вводимый символ “точка” лучше “заключить” в пробелы , чтобы быть уверенным , что поток вода не воспримет эту точку как точку , разделяющую в вещественном числе целую и дробную части. Оператор вывода в строке 22 выводит введенные вами данные разделенные пробелами. Оператор вывода в строке 23 предлагает вам ввести символ , число и символ . Оператор ввода в строке 24 последовательно размещает введенные значения в переменных с 1, х , с 2. Проб ел-разделитель здесь нужно использовать только в том случае , если символ может быть интерпретирован как часть числа . Например , если вам нужно ввести символ “-” , число 12 и цифру 0, вам нужно набрать на клавиатуре 12 0. Оператор вывода в строке 25 выводит в веденные вами данные , разделяя их пробелами. Функция printf Просматривая программы , написанные разными людьми , вы часто можете встретить функцию printf. Этот стандартный оператор вывода пришел из языка С . Так как C++ является расш ирением С , эта функция поддерживается и в этом языке . Многие программисты до сих пор предпочитают использовать старую функцию printf, а не потоки ввода /вывода C++. Вот почему вам эта функция наверняка уже знакома . Но , помимо этого , эта функция имеет неско л ько очень мощных возможностей , и в ряде случаев она оказывается удобнее функций потоков . Прототип функций можно найти в заголовочном файле STDIO.H. Функция printf Общая форма объявления функции printf: int printf(const char *format[, argument,... ]); Параметр format является символьным массивом , содержащим выводимый текст . Кроме этого обязательного параметра , могут быть необязательные аргументы . Массив format может содержать специальные форматирующие сим волы , которые выполняют преобразование необязательных аргументов при выводе . Функция printf является очень мощной функцией с богатыми возможностями форматирования вывода . В качестве первого шага в освоении ее возможностей рассмотрим Esc-последовательности , позволяющие представлять специальные символы . Esc-последовательность начинается с символа “\” — “обратная косая черта” . Esc-коды представлены в таблице 1. Таблица 1. Е sс - последовательности Последовательность Десятичное значени е Шестнадцатеричное значение Название \ а 7 0х 07 Звонок \ b 8 0х 08 Возврат назад \ f 12 0х0С Перевод страницы \ n 10 0х0А Новая строка \ г 13 0x0D Возврат каретки \ t 9 0х 09 Табуляция \ v 11 0х0В Вертикальная табуляция \\ 92 0х 5С Обратная черта \ ' 44 0 х 2С Апостроф \ " 34 0х 22 Кавычка \ ? 63 0х 3 F Знак вопроса \0 Восьмеричное число , от 1 до 3 цифр \ XHHH и \xhhh 0xhhh Шестнадцатеричное число Функция printf имеет специальные форматирующие спецификации (символы ) для вывода переменных . Общий вид этих спецификаций таков : % [flags] [width] [.precision] [F | N | h | l | L ] <символ типа > Опции flags могут определять выравнивание , отображение знака числа при выводе , вывод десятичной точки и символов заполнения . Кроме того , эти флаги определяют префиксы для восьмеричных и шестнадцатеричных чисел . Возможные значения флагов приведены в таблице 2. Таблица 7.2. Значения флагов строки формата функции printf Символ Назначение - Выравнивать вывод по левому краю поля + Всегда выводить зна к числа Пробел Выводить пробел перед положительным числом и знак минус — перед отрицательным # Не влияет на вывод десятичных целых , для шестнадцатеричных чисел выводит префикс 0х или 0Х , перед восьмеричными целыми выводит ноль , десятичную точку для вещес твенных чисел. Спецификация width определяет минимальное количество выводимых символов . Если необходимо , используются заполнители — пробелы или нули . Когда значение для width начинается с нуля , printf использует в качестве заполнителей нули , а не пробелы . Если в качестве значения для width используется универсальный символ *, а не число , то printf подставляет на место этого символа значение , которое должно содержаться в списке аргументов . Это значение ширины поля должно предшествовать выводимому значению. Ниже приведен пример вывода числа 2, занимающего три позиции , согласно значению второго аргумента printf: printf("%*d", 3, 2); Спецификатор precision определяет максимальное количество выводимых цифр . В случае целого числа он определяет минимальное количе ство выводимых символов . Для precision также можно применить символ *, вместо которого будет подставлено значение из списка аргументов . Это значение точности представления должно предшествовать выводимому значению . Ниже приведен пример вывода числа с плав а ющей точкой 3.3244 с использованием десяти символов , как это задано вторым аргументом printf: printf("%7.*f", 10, 3.3244); Символы F, N, h, l и L являются символами размера , переопределяющими размер по умолчанию . Символы F и N применяются с указателями, fa r и near соответственно . Символы h, l , и L используются для указания соответственно типов short int, long или long double. Символам типа данных должен предшествовать форматирующий символ %. В таблице 7.2 мы показали возможные значения флагов форматирующей строки printf. Символы типов данных перечислены в таблице 7.3. Таблица 3. Символы типов данных строки формата функции printf Тип данных символ типа результат Символ c Один символ d Десятичное целое со знаком i Десятичное цело е со знаком O Восьмеричное целое без знака N Десятичное целое без знака X Шестнадцатеричное целое без знака ; набор цифр - 0123456789abcdef X Шестнадцатеричное целое без знака ; набор цифр - 0123456789ABCDEF Указатель P Для указателей near выводит т олько смещение в виде : 0000. Указатели far отображаются в виде : SSSS:0000 Указатель на целое N Вещественное F Выводит величину со знаком в формате [-]dddd.dddd E Выводит вещественную величину со знаком в экспоненциальном формате [-]d.dddde[+|-]ddd Е Выводит вещественную величину со знаком в экспоненциальном формате [-]d.ddddE[+|-]ddd G Выводит вещественную величину со знаком в формате f или е в зависимости от ее значения и заданной точности G Выводит вещественную величину со знаком в формате F ил и Е в зависимости от ее значения и заданной точности Указатель S Выводит строку символов , пока не встретит нуль-терминатор строки Разберем небольшой пример . Программа OUT2.CPP, исходный код которой приведен в листинге 3, создана на основе программы OUT1 .CPP. В этой программе используется форматированный вывод с использованием функции printf. Программа выводит те же числа , что и OUT1.CPP, используя три различных набора спецификаций преобразования. Листинг 3. Исходный текст программы OUT2.CPP в файле List7-3.CPP 01 // Программа , использующая printf для форматирования вывода 02 03 #include 04 05 int main() 06 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 a Single, aDouble, aSingle / aDouble ); 23 24 printf("Вывод использует спецификации преобра зования %%lg :\n"); 25 printf(" %6.4g / %6.4lg = %7.5lg\n", 26 a Single, 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 Вывод использует спецификации преобразования %l g : 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 an I nt int I nt %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 объявляет ся следующим образом : istreams getline( signed char *buffer, int size, char delimiter = '\n') ; istreams getline( unsigned char *buffer, int size, char delimiter = ' \ n') ; istream& getline( char *buffer, int size, char delimiter = '\n') ; Параметр buffer я вляется указателем на строку , в которую помещаются вводимые символы . Параметр size задает максимальное количество вводимых символов . Параметр delimeter определяет символ-ограничитель , при появлении которого ввод символов прекращается прежде , чем будут вве д ены все size символов . Параметр delimeter имеет аргумент по умолчанию , равный '\ n'. В случае ввода символов с клавиатуры этот символ появляется в потоке ввода при нажатии клавиши Пример #include // см . файл Ex01.cpp int main () char name [80] ; cout “ "Enter your name: "; cin.getline(name, sizeof(name) - 1); cout “ "Hello " “ name “ ", how are you?"; return 0; Функции , объявленные в STRING. H Стандартная библиотека для работы со строками содержит много полезных функций (объявляемых в STRING.H) , разработанных коллективными усилиями многих программистов на С . В файлах заголовка STDIO.H и IOS-TREAM.H также имеются прототипы строковых функций . Комитетом ANSI/ISO C++ предложен класс для работы со строками . Строки этого класса больше похожи на строк и в языках Pascal и BASIC. (Мы познакомимся с классами в День 8, а со строковым классом в День 11.) Этот раздел будет посвящен рассмотрению некоторых (ни в коей мере не всех ) функций , объявленных в STRING.H. Некоторые функции из STRING.H имеют несколько вер сий . Дополнительные версии этих функций , имеющих в имени префиксы _f, f или _ работают с указателями типа far. Этих версий вы не встретите в плоской , 32-битной модели памяти компилятора Borland. Присвоение значений строкам C++ под держивает два способа присвоения значений строкам . Вы можете присвоить строковой переменной строковую константу , произведя инициализацию при объявлении строки . Этот метод прост : требуется операция присваивания и строковая константа. Инициализа ция строки Общий метод инициализации строки : char stringVar[stringSize] = stringLiteral; Пример char a3tring[81] = "Borland C++ 5 in 21 days"; char Named = "Rene Kinner"; Второй способ присвоить значение строке — это вызвать функцию , которая копирует содержимое одной строки в другую , — не забывая при этом и нуль-символ . Эта функция называется strcpy. Она предполагает , что копируемая строка оканчивается символом NUL и прекращает копирование , как только встретит этот символ. Функция strcpy Прототип функции strcpy таков : char* strcpy(char *target, const char *source); Функция копирует строку source в строку target. Функция предполагает , что целевая строка имеет размер , достаточный для того , чтобы вместить содержимое строк и-источника . Пример char name[41] ; strcpy(name, "Borland C++ 5"); Переменная name содержит строку "Borland C++ 5". Функция strdup Функция strdup копирует одну строку в другую , при этом отводит необходимое количество памяти для целевой строки. Прототип функции strdup таков : char* strdup(const char *source); Функция копирует строку source и возвращает указатель на строку-копию. Пример char *string1 = "Монархия в Испании "; char *string2; string2 = strdup(string1); После того , как будет отведено необходимое количество памяти для строки string2, строка string 1 будет скопирована в строку string2. Функция strncpy Библиотека строковых функций предлагает также функцию strncpy, копирующую заданное количество симв олов из одной строки в другую. Прототип функции strncpy таков : char * strncpy(char *target, const char *source, size_t num); Функция копирует num символов из строки source в строку target. Функция не выполняет ни усечение , ни заполнение строки. Пример char str1[] = "Pascal"; char str2[] = "Hello there"; strcnpy(strl, str2, 5); Переменная strl содержит строку "Hellol". Заметьте , что символ ‘ l ’ строки-приемника , следующий за скопированной частью строки , сохранился. Определение длины строки При работе со строками часто бывает нужно знать длину строки . Функция strlen Функция strlen возвращает количество символов в строке , в которое не включается нуль-терминатор. Прототип функции strncpy таков : size_t st rlen (const char *st ri ng) , Функция strlen возвращает длину строки string. size_t — это имя , приписанное типу unsigned int оператором typedef. Пример char str[] = "1234567890"; size_t i; i = strlen(str) , Переменной i будет присвоено значение 10. Конкатен ация строк Операция конкатенации используется достаточно часто , когда новая строка получается объединением двух или более строк . Присоединить одну строку к другой можно функцией strcat. Функция strcat Конкатенация строк означает их последовательное присоединение друг к другу. Прототип функции strcat таков : char *strcat(char *target, const char *source) ; Функция добавляет к содержимому целевой строки содержимое строки-источника и возвращает указатель на целевую строку . Функция пре дполагает , что целевая строка может вместить содержимое объединенной строки. Пример char string [81] ; strcpy(string, "Turbo"); strcat (string, " C++"); Переменная string содержит строку "Turbo C++ ". Функция strncat Функция strncat добавляет к содержимому целевой строки указанное количество символов из строки-источника. Прототип функции strcat : char *strncat(char *target, const char *source, size_t num) ; Функция добавляет к содержимому целевой строки num символов из строки -источника и возвращает указатель на целевую строку. char strl[81] = "Hello I am "; char str2[41] = "Keith Thompson"; strncat(strl, str2, 5); Переменная strl теперь содержит строку "Hello I am Keith". Пример использования функций getline, strlen и st rcat в файле 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 больше , чем str 2. Пример 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, так как string 1 и string2 отличаются друг от друга только регистро м символов. Функция strncmp выполняет сравнение заданного количества символов двух строк с учетом регистра символов. Функция strncmp Прототип функции strncmp: int strncmp(const char *strl, const char *str2, size_t num); Функция ср авнивает первые num символов строк strl и str2. Возвращает в качестве результата сравнения целую величину : < 0 когда strl меньше , чем str2; = 0 когда strl равна str2; > 0 когда strl больше , чем str2. Пример char string1[] = "Borla nd 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 string 1 [] = "Borlan d C++"; char string2[] = "BORLAND Pascal"; i = strnicmp (string 1 , 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* s trupr(char *source) Функция преобразует символы нижнего регистра в символы верхнего регистра в строке source. Другие символы не затрагиваются . Функция возвращает указатель на строку source. Пример char str[] = " Borland C++ "; strup r(str) ; Переменная str теперь содержит строку " BORLAND С ++". Обращение строк Библиотека STRING.H предлагает функцию strrev для записи символов в строке в обратном порядке. Функция strrev Прототип функци и strrev: char* strrev(char *str) Функция обращает порядок символов в строке str и возвращает указатель на строку str. char str[] = "Hello"; strrev(str) ; cout “ str; Будет выведено "olleH". Рассмотрим программу , которая манипулирует символами в строке . List 7 _ 6 .cpp показывает исходный текст программы STRING3.CPP. Программа выполняет следующие задачи : · Запрашивает у вас ввод строки · Отображает ваш ввод · Выводит вашу строку в нижнем регистре · Выводит вашу строку в верхнем регистре · Отображает симв олы , которые вы ввели , в обратном порядке · Выводит сообщение , что ваш ввод не содержит символов верхнего регистра , если это так · Выводит сообщение , что ваш ввод не содержит символов в нижнем регистре , если это так · Выводит сообщение , что ваша строка симметрична , если это так · Поиск символов Библиотека STRING.H предлагает ряд функций для поиска символов в строках . Это функции strchr, strrchr, strspn, strcspn и strpbrk . Они осуществляют поиск в строках символов и простых симв ольных шаблонов. Функция strchr Функция strchr определяет первое вхождение символа в строку. Прототип функции strchr: char* strchr(const char *target, int c) Функция находит первое вхождение символа с в строку target. Функция воз вращает указатель на символ в строке target, который соответствует заданному образцу с . Если символ с в строке не обнаруживается , функция возвращает 0. Пример char str[81] = "Borland C++"; char *strPtr; strPtr = strchr ( str, '+'); Указатель strPtr теперь с одержит адрес подстроки "++" в строке str. Функция strrchr Функция strrchr определяет последнее вхождение символа в строке. Прототип функции strrchr: char* strrchr(const char *target, int c) Функция находит последнее вхождение си мвола с в строку target. Функция возвращает указатель на символ в строке target, который соответствует заданному образцу с . Если символ с в строке не обнаруживается , функция возвращает 0. Пример char str[81] = "Borland C++ is her e"; 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[] = "Borla nd C++ 5"; char substr[] = "narlBod" ; int index; index = strspn(str, substr); Этот оператор присваивает 8 переменной index, потому что первые восемь символов из str содержатся в подстроке substr. Функция strcspn Функция strcspn п росматривает строку и выдает число первых символов в строке , которые не содержатся в шаблоне. Прототип функции strcspn: size_t strcspn(const char* str1, const char* str2) Функция strcspn просматривает строку str1 и выдает длину подстроки , отсчитываемой с н ачала строки , символы которой полностью отсутствуют в строке str2. Пример char strng[] = "The rain in Spain"; int i = strcspn(strng, " in"); Этот пример возвращает 3 (расположение первого пробела в строке strng) переменной i. Функция strpbrk Функция strpbrk просматривает строку и определяет первое вхождение любого символа из образца. Прототип функции strpbrk: char* strpbrk(const char* target, const char* pattern) Функция strpbrk ищет в строке target пе рвое вхождение любого символа из образца pattern. Если символы из образца не содержатся в строке , функция возвращает 0. Пример char *str = "Hello there how are you"; char *substr = "hr"; char *ptr; ptr = strpbrk(str, substr); cout “ ptr “ endl; Вы увидите на экране строку "here how are you", потому что 'h' встречается в строке str раньше , чем ' r '. Поиск строк Библиотека функций STRING.H предлагает для поиска подстроки в строке функцию strstr. Функция strstr Прототип функции strstr: char* strstr(const char *str, const char *substr) ; Функция ищет в строке str первое вхождение подстроки substr. Функция возвращает указатель на первый символ найденной в строке str подстроки substr. Есл и строка substr не обнаружена в строке str, функция возвращает 0. Пример char str[] = "Hello there! how are you"; char substr [] = "how"; char *ptr; ptr = strstr (str, substr); cout “ ptr “ endl ; Это приведет к выводу строки "how are you", поскольку в строке str , была обнаружена подстрока "how". Указатель ptr содержит адрес остатка первоначальной строки , начинающегося с подстроки "how". Функция strtok Библиотека функций для работы со строками имеет функцию strtok, которая дает вам возможность разбить строку на подстроки на основании заданного набора символов-ограничителей. Подстроки иногда называются лексемами. Прототип функции strtok: char* strtok(char *target, const char * delimiters ); Функция разб ивает строку на лексемы , согласно символам-ограничителям , заданным в параметре delimeters. В следующем примере показано , как работать с этой функцией и как получать лексемы , на которые была разбита строка . Функция strtok вводит символ '\ 0' после каждой лек семы . (Опять же не забудьте сохранить копию вашей строки в другой строковой переменной .) Пример #include // см . файл Ex02.cpp #include int main () char *str = " (Base_Cost + Profit) * Margin"; char *tkn = "+*( )"; char *ptr = str; printf("%s\n", str) ; // Первый вызов функции ptr = strtok(str, tkn); printf(" Лексемы этой строки : %s", ptr); while (ptr) // Первый аргумент должен быть равен нулю if ((ptr = strtok(0, tkn)) != 0) printf (",%s", ptr) ; printf("\ n"); return 0; В результате выполнения этой программы на экран выводятся следующие строки : (Base_Cost + Profit) * Margin Лексемы этой строки : Base_Cost, Profit, Margin Рассмотрим пример программы поиска символов и строк . Листинг 7 ( List7_7.cpp ) содерж ит исходный текст программы STRING4.CPP. Программа выполняет следующие задачи : · Запрашивает у вас ввод основной строки · Запрашивает строку поиска · Предлагает вам ввести символ поиска · Выводит линейку цифр и основную строку · Выводит номер символа в основной строке , с которого начинается строка поиска * · Выводит номер символа в основной строке , совпавшего с символом поиска. Основы объектно-ориентированного программирования СИНТАКСИС ОСНОВНЫХ КОНСТРУКЦИЙ Объя вление базовых классов В С ++ мы имеем возможность объявлять классы , которые инкапсулируют элементы-данные и функции-элементы . Эти функции изменяют и позволяют обращаться к значениям данных-элементов и выполняют другие задачи. Базовый класс Базовый класс определяется следующим образом (синтаксис ): class className private: <закрытые элементы-данные > <закрытые конструкторы > <закрытые функции-элементы > protected: <защищенные элементы-данные > <защищенные конструкторы > <защищенные функ ции-элементы > public: <открытые элементы-данные > <открытые конструкторы > <открытый деструктор > <открытые функции-элементы > ; Пример 1: class point protected: double х ; double у ; public: point(double xVal, double yVal); double getX(); double getY(); void assign(double xVal, double yVal); point& assign(point &pt); ; Разделы класса Классы С ++ имеют три различных уровня доступа к своим элементам - как к данным , так и к функциям : Закрытые (частные ) элементы Защищенные элементы Открытые элем енты К данным в закрытом разделе имеют доступ только функции-элементы класса. Классам-потомкам запрещен доступ к закрытым данным своих 6азовых классов. К данным в защищенной секции имеют доступ функции-элементы класса и классов-потомков . Данные из открыто й секции находятся в области видимости функций-элементов класса , функций-элементов классов-потомков , и вообще доступны кому угодно. Существуют следующие правила для разделов класса : Разделы могут появляться в любом порядке. Один и тот же раздел можно опр еделять несколько раз. Если не определен ни один раздел , компилятор (по умолчанию ) объявляет все элементы закрытыми. Помещать данные-элементы в открытый раздел следует только в том случае , если в этом есть необходимость , например , если это упрощает вашу за дачу . Обычно элементы-данные помещаются в защищенный раздел , чтобы к ним имели доступ функции-элементы классов-потомков. Используйте для изменения значений данных и доступа к ним функции-элементы . При использовании функции вы можете осуществлять проверку д анных и , если нужно , изменять другие данные. Класс может иметь несколько конструкторов. Класс может иметь только один деструктор , который должен объявляться в открытом разделе класса. Функции-элементы (в том числе конструкторы и деструкторы ), состоящие из нескольких операторов , должны определяться вне объявления класса . Определение функции может содержаться в том же файле , в котором определяется класс . Это напоминает порядок работы с обычными функциями : задание прототипа и определение функции. Конструктор ы являются специфическим типом функций-элементов , тип возвращаемого значения для которых не указывается , а имя должно совпадать с именем класса-хозяина . Вызываются они при создании нового представителя класса . Деструктор вызывается для разрушения представи теля класса. При определении функции-элемента вы должны указать ее имя и имя ее класса . Сначала вы должны Сначала необходимо указать имя класса (т.н . квалификатор ), а затем , через два двоеточия ( :: ), имя функции . В качестве примера рассмотрим такой класс : class point protected: double x; double y; public: point(double xVal, double yVal); double getX(); // другие функции-элементы ; Определения конструктора и функций-элементов должны выглядеть так point::point (double xVal, double yVal) // опе раторы double point::getX() // операторы После того , как вы объявили класс , вы можете использовать имя класса в качестве спецификатора типа данных при объявлении представителей класса . Синтаксис объявления тот же , что и при объявлении переменной. В листинге 8.1 приведен исходный текст программы RECT.CPP . Программа предлагает вам ввести длину и ширину прямоугольника (в данном примере прямоугольник является объектом ). Затем программа выводит значения длины , ширины и площади определенного вами прямоу гольника . Конструкторы Конструкторы и деструкторы в С ++ вызываются автоматически , что гарантирует правильное создание и разрушение объектов класса. Общий вид (синтаксис ) объявления конструктора : class 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, double yVal); 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; Конструктор копии создает объект класса , копир уя при этом данные из существующего объекта класса. В С ++ имеются следующие особенности и правила работы с конструкторами : Имя конструктора класса должно совпадать с именем класса. Нельзя определять тип возвращаемого значения для конструктора , даже тип void. Класс может иметь несколько конструкторов или не иметь их совсем. Конструктором по умолчанию является конструктор , не имеющий параметров , или конструктор , у которого все параметры имеют значения по умолчанию. Рассмотрим два примера с фрагментами объявления конструкторов. // класс с конструктором без параметров class point1 protected: double x; double y; public: point1(); // другие функции-элементы ; // конструктор класса имеет параметры со значениями по умолчанию class point2 protected : double x; double y; public: point2(double xVal = 0, double yVal = 0); // другие функции-элементы ; Конструктор копии создает объект класса на основе существующего объекта. Например : class point protected: double x; double y; public: point(); point(double xVal = 0, double yVal = 0); point(const point &pt); // другие функции-элементы ; Объявление объекта класса , которое может содержать параметры и , в качестве параметра , имя уже существующего объекта , влечет за собой вызов конструктора . Но какой из конструкторов будет использоваться в каждом конкретном случае ? Ответ зависит от того , как много конструкторов вы объявили и с какими аргументами вы объявляете объект класса . Например , рассмотрим следующие объявления объектов последней версии клас с а point: point p1; // применяется конструктор по умолчанию point p2(1.1, 1.3); // используется второй по счету конструктор point p3(p2); // используется конструктор копии Поскольку объект p1 объявляется без параметров , компилятор использует конструктор по умолчанию . Объект p2 объявляется с двумя вещественными аргументами , поэтому компилятор вызовет второй конструктор . Объект p3 при объявлении имеет параметром объект p2, поэтому компилятор вызовет конструктор копии , чтобы создать новый объект из объекта p 2 . ВНИМАНИЕ : Определяйте конструктор копии , особенно для классов , моделирующих динамические структуры данных . Конструкторы копии должны выполнять т.н . глубокое копирование , которое подразумевает копирование динамических данных. Если вы не определили ко нструктор копии , компилятор создаст конструктор копии по умолчанию , который будет создавать поверхностную копию , копируя только элементы-данные . При этом будет скопировано содержимое данных-элементов , содержащих указатели на другие , данные , но сами эти да н ные скопированы не будут. Не полагайтесь на поверхностный конструктор копии для классов имеющих данные-указатели. Деструкторы Классы С ++ могут содержать деструкторы , которые автоматически разрушают объекты класса. Общий синт аксис объявления деструктора : class className public: className(); // конструктор по умолчанию // другие конструкторы ~className(); // объявление деструктора // другие функции-элементы ; Пример 3 на синтаксис обявления деструктора : class String protected: char *str; int len; public: String(); String(const String& s); ~String(); // другие функции-элементы ; Деструкторы в С ++ имеют следующие особенности и подчиняются следующим правилам : Имя деструктора должно начинаться со знака тильды (~), за которым должно следовать имя класса. Нельзя определять тип возвращаемого значения , даже тип void. Класс может иметь только один деструктор или ни одного . В последнем случае компилятор создаст деструктор по умолчанию. Деструктор не должен иметь параметров. Исполняющая система автоматически вызывает деструктор класса , когда объект класса выходит за пределы области действия и может быть удален , или удаляется явным образом. (см . 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, double wid e); double calcArea(); ; class Вох : public Rectangle protected: double height; public: Box(double len, double wide, double height); double getHeight () const; assign(double len, double wide, double height); double calcVolume(); ; ( см . LIST8-3.CPP ) Виртуальные функции Мы уже упоминали о полиморфизме - важной особенности объектноориентированного программирования . Рассмотрим следующий пример (6): #include class X public: double A(double x) re turn 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 returnType functionName(<список параметров >); ; class className2 : public className1 // функции-элем енты virtual returnType functionName(<список параметров >); ; Пример 7, показывающий , как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y: #include class X public: virtual double A(double x) return x * x; double B (double x) return A(x) / 2; ; class Y : public X public: virtual double A(double x) return x * x * x; ; main() Y y; cout << y.B(3) << endl; return 0; Этот пример выведет вам правильное значение 13.5, потому что в р езультате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции A во время выполнения программы будет использована замещающая функция Y::A. *** Правило виртуальной функции *** Правило виртуальной функции гласит : "Виртуальная однаж ды - виртуальна всегда ". Это означает следующее . Если вы объявили функцию как виртуальную в некотором классе , то в классах-потомках , переопределяющих эту функцию , она также будет виртуальной , но только если она имеет тот же список параметров . Если переоп ределенная функция в классе-потомке имеет другой список параметров , то ее версия из базового класса будет недоступна классу-потомку (и всем его потомкам ). Это может показаться неудобным , но только на первый взгляд. Правило это справедливо и для всех языков объектно-ориентированного программирования , поддерживающих виртуальные функции , но не допускающих перегрузку функций . В С ++ положение несколько иное . Вы можете объявлять невиртуальные перегруженные функции , совпадающие по имени с виртуальными функциями , н о имеющие другой список параметров . И , кроме того , вы не можете наследовать невиртуальные функции , имя которых совпадает с виртуальными функциями . Рассмотрим пример 8, иллюстрирующий сказанное. #include class A public: A() virtual void foo(char c) cout << "virtual A::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 << "virtual B::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 << "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*) не наследуется . Таким образом , в С ++ схема наследования отличается от обычной для случая виртуальной и перегруженных функций с одинаковым именем . В функции m ain объявляются объекты для всех трех классов и вызываются различные версии функции-элемента foo. Дружественные функции В С ++ функции-элементы имеют доступ ко всем данным-элементам своего класса . Кроме этого , С ++ предусматривае т такую возможность еще и для дружественных функций . Объявление дружественной функции производится в объявлении класса и начинается с ключевого слова friend. Кроме наличия спецификатора friend, объявление дружественной функции совпадает с объявлением функ ц ии-элемента , однако прямого доступа к классу дружественная функция не имеет , поскольку для этого необходим скрытый указатель this, который ей недоступен . Но если вы передаете такой функции указатель на объект дружественного класса , функция будет иметь дос т уп к его элементам . Когда вы определяете дружественную функцию вне объявления дружественного ей класса , вам не нужно в определении указывать имя класса . Дружественной называется обычная функция , которой открыт доступ ко всем элементам-данным одного или не с кольких классов. Общий вид (синтаксис ) объявления дружественной функции следующий : class className public: className(); // другие конструкторы friend returnType friendFunction(<список параметров >); ; Пример 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 operator operatorSymbol(operand); // унарная дружественная операция friend returnType operator operatorSymbol(operand); // бинарная дружественная операция friend returnType operat or operatorSymbol(firstOperand, secondOperand); ; Пример 10: class String protected: char *str; int num; public: String(); ~String(); // другие функции-элементы // операция присваивания String& operator =(String& s); String& operator +=(Strin g& s); // операции конкатенации friend String& operator +(String& s1, String& s2); friend String& operator +(const char* s1, String& s2); friend String& operator +(String& s1, const char* s2); // операции отношения friend int operator >(String& s1, String& s2); friend int operator =>(String& s1, String& s2); friend int operator <(String& sl, String& s2); friend int operator <=(String& sl, String& s2); friend int operator ==(String& s1, String& s2); friend int o perator !=(String& sl, String& s2); ; Код , который вы пишете , будет использовать операции и дружественные операции точно так же , как и предопределенные операции . Следовательно , вы можете создавать операции , чтобы поддерживать действия над классами , моде лирующими , например , комплексные числа , строки , векторы и матрицы . Эти операции дают возможность вам записывать выражения в более привычной форме , чем использование вызовов функций. Виртуальные функции Мы уже упоминали о полимо рфизме - важной особенности объектно-ориентированного программирования . Рассмотрим следующий пример (6): #include class X public: double A(double x) return x * x; double B(double x) return A(x) / 2; ; class Y : public X public: double A(double x) return x * x * x; ; int main () Y y; cout << 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 returnType functionName(<список параметров >); ; class className2 : public className1 // функции-элементы virtual returnType functionName(<список параметров >); ; Пример 7, показываю щий , как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y: #include class X public: virtual double A(double x) return x * x; double B (double x) return A(x) / 2; ; class Y : public X public: virtual double A(double x) return x * x * x; ; main() Y y; cout << y.B(3) << endl; return 0; Этот пример выведет вам правильное значение 13.5, потому что в результате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции A во время выполнения программы будет использована замещающая функция Y::A. Правило виртуальной функции Правило виртуальной функции гласит : "Виртуальная однажды - виртуальна всегда ". Это означает следующее . Если вы объявили функцию как виртуальную в некотором классе , то в классах-потомках , переопределяющих эту функцию , она также будет виртуальной , но только если она имеет тот же список параметров . Если переопределенная функция в классе-потомке имеет другой список параметров, то ее версия из базового класса будет недоступна классу-потомку (и всем его потомкам ). Это может показаться неудобным , но только на первый згляд. Правило это справедливо и для всех языков объектно-ориентированного программирования , поддерживающих виртуаль ные функции , но не допускающих перегрузку функций . В С ++ положение несколько иное . Вы можете объявлять невиртуальные перегруженные функции , совпадающие по имени с виртуальными функциями , но имеющие другой список параметров . И , кроме того , вы не можете нас л едовать невиртуальные функции , имя которых совпадает с виртуальными функциями . Рассмотрим пример 8, иллюстрирующий сказанное. #include class A public: A() virtual void foo(char c) cout << "virtual A::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 << "virtual B::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 << "virtual C::fo o() 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, который ей недоступен . Но если вы передаете такой функции указатель на объект дружественного класса , функция будет иметь доступ к его элементам . Когда вы определяете дружественную функцию вне объявления дружестве н ного ей класса , вам не нужно в определении указывать имя класса . Дружественной называется обычная функция , которой открыт доступ ко всем элементам-данным одного или нескольких классов. Общий вид (синтаксис ) объявления дружественной функции следующий : cl ass className public: className(); // другие конструкторы friend returnType friendFunction(<список параметров >); ; Пример 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 operator operatorSymbol(operand ); // унарная дружественная операция friend returnType operator operatorSymbol(operand); // бинарная дружественная операция friend returnType operator operatorSymbol(firstOperand, secondOperand); ; Пример 10: class String protected: char *str; int num; public: String(); ~String(); // другие функции-элементы // операция присваивания String& operator =(String& s); String& operator +=(String& s); // операции конкатенации friend String& operator +(String& s1, String& s2); friend String& operator +(const char* s1, String& s2); friend String& operator +(String& s1, const char* s2); // операции отношения friend int operator >(String& s1, String& s2); friend int operator =>(String& s1, String& s2); friend int operator <(S tring& sl, String& s2); friend int operator <=(String& sl, String& s2); friend int operator ==(String& s1, String& s2); friend int operator !=(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(); Stringa mapChar(char find, char replace); вы можете написать следующий оператор обработки об ъекта класса String: s.upperCase().reverse().mapChar(' ', '+'); Что может случиться , если класс полагается на конструктор копии , созданный компилятором , и при этом класс использует указатели в качестве элементов-данных ? Эти конструкторы выполняют поб итовую копию объекта . Следовательно , соответствующие элементы-указатели в обоих объектах будут ссылаться на те же самые динамические данные . Этот способ создания копии объекта - верный путь к различным неприятностям. Могу ли я создавать массив объектов ? Да , можете . Однако соответствующий класс должен иметь заданный по умолчанию конструктор . При создании массива используется ранее упомянутый конструктор. Могу ли я использовать указатель при создании объекта класса ? Да , можете , но в этом случае вы должн ы использовать операции new и delete, чтобы распределять и освобождать память для данного объекта. Вот пример , использующий класс Complex. Не забудьте , что для обращения к элементам классов или структур используется операция ->, если вы ссылаетесь на них при помощи указателей. Complex *pC; pC = new Complex; // операции с объектом , к которому обращаются по указателю pC delete pC; или Complex *pC = new Complex; // операции с объектом , к которому обращаются по указателю pC delete pC; Контрольные вопросы 1. Найдите ошибку в следующем объявлении класса : class String char *str; unsigned len; String (); String(const String& s); String(unsigned size, char = ' '); String(unsigned size); String& assign(String& s); ~S tring(); unsigned getLen() const; char* getString(); // другие функции-элементы ; 2. Найдите ошибку в следующем объявлении класса : class String protected: char *str; unsigned len; public: String(); String(const char* s); String(const String& s); String(unsigned size, char = ' '); String(unsigned size); ~String(); // другие функции-элементы 3. Верно или нет ? Следующий оператор , который создает объект s класса String, объявленного ранее , является правильным : s = Stri ng("Hello Borland C++"); 4. Если в программе OPERATOR.CPP вы следующим образом измените объявления объектов , будет ли программа компилироваться без ошибок ? String s1 = String("Kevin"); String s2 = String(" Нау "); String s3 = s1; ФАЙЛОВЫЕ ОПЕРАЦИИ ВВОДА /ВЫВОДА Сегодняшний урок посвящен файловым операциям ввода /вывода с использованием библиотеки управления потоками C++. У вас есть две возможности : либо использовать функции файлового ввода /вывода , описанные в заголовочном файле STDIO.H, либо функции stream-библиотеки C++. Каждая из этих библиотек имеет множество мощных и удобных функций . Сегодня будут представлены основные операторы , которые позволят вам читать и записывать данные в файл . Вы изучите следующие темы : Стандартные ф ункции потоков ввода /вывода Последовательный ввод /вывод потока с текстовой информацией Последовательный ввод /вывод двоичных данных Прямой доступ к потоку двоичных данных Stream-библиотека C++ Stream-библиотека (известная также как библиотека iostream) выполнена в виде иерархии классов , которые описаны в нескольких заголовочных файлах . Файл IOSTREAM.H, используемый до сих пор , - это только один из них . Другой, который будет интересен в этой главе , - FSTREAM.H. Файл IOSTREAM.H поддер живает основные классы для ввода /вывода потока . Файл FSTREAM.H содержит определения для основных классов файлового ввода /вывода. Существуют дополнительные файлы библиотеки ввода /вывода , в которых имеются более специализированные функции ввода /вывода. ОБЩИЕ ФУНКЦИИ ПОТОКОВОГО ВВОДА /ВЫВОДА В этом разделе представлены функции-элементы ввода /вывода , являющиеся общими как для последовательного , так и для прямого доступа . Эти функции включают open, close, good и fail в дополнение к опер ации !. Функция open открывает файловый поток для ввода , вывода , добавления , а также для ввода и вывода . Эта функция позволяет указывать тип данных , с которыми вы собираетесь работать : двоичные или текстовые. При работе с файловым вводом /выводом очень важ но знать различие между текстовым и двоичным режимами . Текстовый режим предназначен для текстовых файлов , в которых имеются строки обычного текста . Двоичный режим используется для любых других и особенно для файлов , которые сохраняются в форматах , неудобны х для чтения человеком. Существуют некоторые особые тонкости , связанные с файлами текстового режима , на которые следует обратить особое внимание и запомнить . Первая из них - символ EOF (26 в коде ASCII или Ctrl+Z) - представляет собой метку (символ ) конца файла . В текстовом режиме , где встречается символ EOF, система C++ низкого уровня автоматически продвигается к концу файла ; вы ничего не можете прочитать после специального символа . Это может вызвать проблемы , если такой специальный символ окажется в серед ине файла. Другая особенность текстового режима заключается в том , как интерпретируются строки текстового файла . Каждая строка заканчивается последовательностью конца строки (EOL). На компьютерах PC и совместимых с ними EOL-последовательность представлена двумя символами кода ASCII: CR (13 в коде ASCII или Ctrl+M) и LF (10 в коде ASCII или Ctrl+J). Эта CRLF-последовательность используется функциями чтения и записи текстовой строки , которые автоматически , вставляют ее в файл или удаляют из него. Заметьте , чт о на большинстве других , систем (UNIX и Macintosh) EOF просто является символом LF. Функция-компонент open Прототип функции open void open (const char* filename, int mode, int m = filebuf::openprot); Параметр filename задает им я открываемого файла . Параметр mode указывает режим ввода /вывода . Далее следует список аргументов для mode, описанных в заголовочном файле FSTREAM.H: in открыть поток для ввода , out открыть поток для вывода , ate установить указатель потока на конец файла , app открыть поток для добавления , trunk удалить содержимое файла , если он уже существует (bc++5) , nocreate инициировать ошибку , если уже не существует , noreplace инициировать ошибку , если файл уже существует , binary открыть в двоичном режиме . Пример 1. // открыть поток для ввода fstream f; f.open("simple.txt", ios::in); // открыть поток для вывода fstream f; fstream f; f.open ("simple.txt", ios::out); // открыть поток ввода /вывода для двоичных данных fstream f; fstream f; f.open("simple.txt", ios::in | ios::out | ios::binary); Внимание : Классы файловых потоков предусматривают конструкторы , которые выполняют действия (и имеют такие же параметры ) функции-компонента open. Функция close закрывает поток и освобождает использова вшиеся ресурсы . Эти ресурсы включают буфер памяти для операции потокового ввода /вывода. Функция-компонент close Прототип для функции close: void close(); Пример 2. fstream f; // открыть поток f.open ( "simple.txt", ios:: in); // работа с файлом // закрыть поток f.close (); Stream-библиотека C++ включает в себя набор основных функций , которые контролируют состояние ошибки потоковой операции . Эти функции включают следующие : Функция good() возвращает ненулевое значение , если при выполнении потоковой операции не возникает ошибки . Объявление функции good: int good(); Функция fail() возвращает ненулевое значение , если при выполнении потоковой операции возникает ошибка . Объявление функции fail: int fail(); Перегруженная операция ! пр именяется к экземпляру потока для определения состояния ошибки. Stream-библиотека C++ предоставляет дополнительные функции для установки и опроса других аспектов и типов ошибок потока. ПОСЛЕДОВАТЕЛЬНЫЙ ТЕКСТОВЫЙ ПОТОК ВВОДА /ВЫВОДА Функции и операции последовательного текстового ввода /вывода являются довольно простыми . Вы уже имели дело со многими из них в предыдущих уроках . Эти функции и операции включают : Операция извлечения из потока << записывает строки или символы в поток. Оп ерация помещения в поток >> читает символы потока. Функция getline читает строку из потока. Функция-элемент getline Прототипы функции-элемента getline: istream& getline (char* buffer, int size, char delimiter = '\n'); istream& getline (signed char* buffer, int size, char delimiter = '\n'); istream& getline (unsigned char* buffer, int size, char delimiter = '\n'); Параметр buffer - это указатель на строку , принимающую символы из потока. Параметр size задает максимальное число символов для чтения . Параметр delimiter указывает разделяющий символ , который вызывает прекращение ввода строки до того , как будет введено количество символов , указанное в параметре size. По умолчанию параметру delimiter присваивается значение '\n'. Приме р 3. fstream f; char textLine[MAX]; f.open("sample.txt", ios::in); while (!f.eof()) f.getline(textLine, MAX); cout << 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 содержимое этой строки . В теле этого цикла просто вызывается функция tr imStr, а затем line передается в потоки fout и cout. Заметьте , что команды для получения строки текста из файла и передачи ее в файл в точности совпадают с теми , которые могли бы использоваться для получения текста с экрана и передачи его на экран . Это про исходит потому , что используемые вами cout и cin - на самом деле только потоки , которые открываются автоматически и работают непосредственно с экраном. Функция main, как обычно со всеми уже описанными функциями , довольно простая . Она только объявляет перем енные файловых потоков fin, fout и inFile, outFile для сохранения имен этих потоков . Далее входной и выходной файлы открываются в функциях getInputFilename и getOutputFilename. Наконец , функция processLine приводит в порядок и копирует содержимое файла , а функция-компонент close вызывается для каждого из потоков. ПОСЛЕДОВАТЕЛЬНЫЙ ДВОИЧНЫЙ ФАЙЛОВЫЙ ВВОД /ВЫВОД Stream-библиотека C++ имеет перегруженные потоковые функции-элементы write и read для последовательного двоичного файлового ввода /вывода. Функция write посылает ряд байт в выходной поток . Эта функция может записывать любую переменную или экземпляр в поток. Функция-элемент write Прототип перегруженной функции-элемента : ostream& write(const char* buff , int num); ostream& write(const signed char* buff, int num); ostream& write(const unsigned char* buff, int num); Параметр buff - это указатель на буфер , содержащий данные , которые будут посылаться в выходной поток . Параметр num указывает число байт в б уфере, которые передаются в этот поток. Пример 4. const MAX = 80; char buff[MAX+1] = "Hello World!"; int len = strlen (buff) + 1; fstream f; f.open("CALC.DAT", ios::out | ios::binary); f.write((const unsigned char*) &len, sizeof(len)); f.write((const uns igned char*) buff, len); f.close(); В этом примере открывается файл CALC.DAT, записывается целое , содержащее число байт в строке и записывается сама строка перед тем , как файл закрывается. Функция read считывает некоторое количество байт из входного поток а . Эта функция может считывать любую переменную или экземпляр из потока. Функция-элемент read Прототип перегруженной функции-элемента read: ostream& read ( char* buff, int num); ostream& read(signed char* buff, int num); ostream& read(unsigned char* buff, int num); Параметр buff - это указатель на буфер , который принимает данные из входного потока . Параметр num указывает число считываемых из потока байт. Пример 5. const MAX = 80; char buff [MAX+1]; int len; fstream f; f.open("CALC.DAT", ios::in | ios::binary); f.read((unsigned char*) &len, sizeof(len)); f.read((unsigned char*) buff, len); f.close(); В этом примере считывается информация , записанная в предыдущем примере. Рассмотрим пример , выполняющий последовательный двоичный потоковый ввод /вывод . В листинге 10.2 представлен исходный код программы ARRAY.CPP. Эта программа объявляет класс , который моделирует численный динамический массив . Операции ввода /вывода позволяют программе читать и писать как отдельные элементы м ассива , так и целый массив в двоичный файл . Эта программа создает массивы arr1, arr2 и а rrЗ , а затем выполняет следующие задачи : Присваивает значения элементам массива arr1. (Этот массив имеет 10 элементов ). Присваивает значения элементам массива а rrЗ . (Эт от массив имеет 20 элементов ). Отображает значения массива arr1. Записывает элементы массива arr1 в файл ARRAY1.DAT (по одному за операцию ). Читает элементы массива arr1 из этого файла в массив arr2 (по одному за операцию ). (Массив arr2 имеет 10 элементов, то есть он одного размера с массивом arr1). Отображает элементы массива arr2. Отображает элементы массива а rrЗ. Записывает элементы массива а rrЗ в файл ARRAY3.DAT, все сразу. Читает (все сразу ) данные из файла ARRAY3.DAT и сохраняет их в массиве arr1. Ото бражает значения массива arr1. (Выход показывает , что массив arr1 имеет тот же размер , что и массив arr3). Листинг 10.2. Исходный код программы ARRAY.CPP // C++ демонстрация последовательного двоичного // ввода /вывода Программа листинга 10.2 объявляет ве рсию класса Array, который похож на приводимый в главе 8 в листинге 8.2. Основное отличие в том , что здесь мы использовали operator[ ] для замены и функции store, и recall. Эта операция проверяет правильность указания индекса и возвращает значение в badInd ex, если аргумент выходит за диапазон массива . В дополнение к 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, первый - для чтения элемента класса s ize, и второй - для чтения элементов динамического массива. Другим свойством функции readArray является то , что она изменяет размер экземпляра класса Array для настройки его в соответствии с данными двоичного файла , вызывая функцию-элемент resize. Это озна чает , что динамический массив , который доступен посредством экземпляра класса , может либо уменьшаться , либо расширяться в зависимости от размера массива , сохраняемого в файле. Функция-элемент resize, которая начинается в строке 65, на самом деле очень прос тая . Она проверяет , является ли требуемый размер тем же , что и установленный ранее . Если нет , то память , зарезервированная функцией dataPtr, освобождается , а затем создается новая область памяти , соответствующая новому размеру . Этот новый размер присваивае тся компоненту класса size. Функция dispArray чаще всего является функцией-элементом , но я решил сделать ее здесь обычной функцией , чтобы лучше показать , как использование функции operator[ ] позволяет тем , кто работает с классом Array, обращаться к нему т аким же способом , какой они применяют к элементам стандартного массива . В этом случае есть простой цикл for, который выполняется для каждого элемента arr и отображает его содержимое. Наконец , мы подходим к функции main (строка 104). Обычно она в основном т олько запускает функции , которые уже были созданы , для выполнения следующих задач : Объявляет (строка 108) три экземпляра класса Array с именами arr1, arr2 и а rr3. (Первые два экземпляра имеют тот же самый размер динамического массива , заданный константой S IZE1, в то время как а rr3 имеет больший размер , определенный константой SIZE2). Объявляет (строка 111) файловый поток f и открывает его (используя конструктор потока ) для доступа к файлу ARRAY1.DAT в двоичном режиме. Использует циклы for (строки с 114 по 1 16), чтобы произвольно присвоить значения экземплярам arr1 и а rr3. Отображает элементы экземпляра arr1 (строка 119). Записывает элементы массива arr1 в выходной файловый поток f, используя цикл for (строка 122) для вызова функции-компонента writeElem с вых одным файловым потоком f и переменной цикла i. Закрывает файловый поток f, вызывая функцию-элемент close этого потока. Открывает (строка 127) файловый поток f для доступа к файлу ARRAY1.DAT. (На это раз сообщение open определяет режим двоичного ввода ) Счит ывает элементы в arr2 (которому до сих пор не присваивались никакие значения ) из входного файлового потока f, используя цикл for (строка 128). Закрывает входной поток (строка 130). D Отображает элементы экземпляров arr2 и а rr3 (строки 132 и 133). Записывае т все содержимое а rr3, вызывая функцию-компонент writeArray. (Функция writeArray имеет аргумент имени файла ARRAY3.DAT). Считывает массив файла ARRAY3.DAT в экземпляр arr1, вызывая функцию-компонент readArray и передавая ей в качестве аргумента имени файла ARRAY3.DAT. Отображает новые элементы экземпляра arr1. Файловый ввод /вывод с прямым доступом Файловые операции ввода /вывода прямого доступа также используют потоковые функции-элементы read и write, представленные в предыдущем р азделе. Stream-библиотека имеет ряд функций , позволяющих вам передвигать указатель потока в любое необходимое положение . Функция-элемент seekg - одна из таких функций. Функция-элемент seekg Прототип для перегруженной функции-комп онента seekg: istream& seekg(long pos); istream& seekg(long offset, seek_dir dir); Параметр pos в первой версии определяет абсолютное положение байта в потоке . Во второй версии параметр offset определяет относительное смещение , в зависимости от аргумента dir. Аргументы для последнего параметра : ios::beg С начала файла ios::cur С текущей позиции файла ios::end С конца файла Пример const BLOCK SIZE = 80 char buff[BLOCK_SIZE] = "Hello World!"; f.open("CALC.DAT", ios::in | ios::out | ios::binary); f.seekg (3 * BLOCK_SIZE); // продвинутся к блоку 4 f.read((const unsigned char*)buff, BLOCK_SIZE); cout < buff < endl; fclose (); Виртуальный массив - это базирующийся на диске массив , который сохраняет строки фиксированного размера на диске . Вы можете рассматрив ать его как обычный массив , который вместо сохранения своих элементов в памяти записывает их в дисковый файл. Рассмотрим пример файлового ввода /вывода прямого доступа . В листинге 10.3 приведен исходный код программы VIRTUAL.CPP и реализует виртуальный масс ив . Программа выполняет следующие задачи : Использует внутренний список имен для создания объекта виртуального массива. Отображает элементы неупорядоченного объекта виртуального массива. Сортирует элементы объекта виртуального массива. Отображает элементы с ортированного объекта виртуального массива. Листинг 10.3. Исходный код прогшраммы VIRTUAL.CPP // C++ демонстрация файлового ввода /вывода прямого доступа Программа листинга 10.3 объявляет класс VmArray. Этот класс моделирует динамический базирующийся на д иске массив , который сохраняет все его элементы в двоичном файле прямого доступа . Заметьте , что в этом классе объявлен экземпляр класса fstream и что не существует указателя на динамический массив . Класс объявляет конструктор , деструктор и ряд функций-комп онентов. Конструктор класса имеет два параметра : Size и filename. Параметр Size задает размер виртуального массива . Параметр filename именует двоичный файл, который сохраняет элементы экземпляров класса . Конструктор открывает поток f, используя потоковую ф ункцию open и передавая ей в качестве аргументов filename и выражение для режима работы с файлом ios::in | ios::out | ios::binary. Это выражение указывает , что поток открывается для двоичного ввода /вывода (то есть режима прямого доступа ). Если конструктор успешно открывает файловый поток , он заполняет файл пустыми строками . Деструктор класса выполняет простую задачу закрытия файлового потока f. Функции setElem и getElem поддерживают прямой доступ к элементам массива . Эти функции используют потоковую функцию seekg, чтобы устанавливать указатель потока на соответствующий элемент массива . Затем функция setElem вызывает потоковую функцию write для сохранения элемента массива (передаваемый параметром str). Напротив , функция getElem называет потоковую функцию read , чтобы получить элемент массива (возвращаемый через аргумент str). Обе функции возвращают результат типа bad, который указывает на успешность операции ввода /вывода. Класс VmArray также объявляет функцию BubbleSort для сортировки элементов виртуального мас сива . Эта функция использует функции-элементы getElem и setElem для доступа и свопинга элементов массива . Затем , наконец, запускается последняя функция-элемент display для элементов виртуального массива , которая посылает их на экран . Функция main выполняет следующие задачи : Объявляет экземпляр arr класса VmArray. (Этот экземпляр сохраняет 10 строк в двоичном файле ARR.DAT) Присваивает случайное значение элементам экземпляра аот , используя цикл for (строки 97 и 98). Отображает несортированные элементы экзем пляра arr, вызывая функцию-элемент display. Сортирует массив , вызывая функцию BubbleSort. Отображает сортированные элементы экземпляра arr. Заключение Сегодняшний урок представил краткое введение в библиотеку ввода /вывода C++ и в ынес на обсуждение следующие вопросы : Общие функции ввода /вывода , включая open, close, good, fail и оператор !. Функция open открывает файловый поток ввода /вывода и поддерживает попеременный и множественный режимы ввода /вывода . Функция close закрывает файл овый поток . Функции good и fail индицируют успешную или ошибочную , соответственно , потоковую операцию ввода /вывода. C++ позволяет выполнять последовательный потоковый ввод /вывод для текста с использованием операций < и >, так же как и при помощи потоковой функции getline. Операция < позволяет записать символы и строки (а также и другие предопределенные типы данных ). Операция > применяется для получения символов . Функция getline позволяет вашему приложению считывать строки с клавиатуры или из текстового файл а. Последовательный потоковый ввод /вывод двоичных данных использует потоковые функции write или read для записи или считывания данных из переменных любого типа. Потоковый ввод /вывод прямого доступа для двоичных данных использует функцию seekg в объединении с функциями read и write. Функция seekg позволяет вам передвигать потоковый указатель либо в абсолютное , либо в относительное положение в потоке. Вопросы и ответы Как можно эмулировать прямой доступ к строкам в текстовом файле ? С начала считывайте строки из файла как текст , получайте длину строк (плюс два символа для конца каждой строки ) и сохраняйте накапливаемую длину в специальном массиве . (Назовите его , например , lineIndex) Этот массив сохраняет позицию байта , где начинается ка ждая строка . Последний элемент массива будет содержать размер файла . Для доступа к строке номер i, используйте функцию seek или seekg, чтобы найти смещение для lineIndex[i]. Размер строки номер i равен lineIndex[i+1] - lineIndex[i+1]. Как написать процеду ру общего назначения для копирования между входным ивыходным файловым потоком ? Вам необходимо использовать потоковую функцию gcount() для получения ряда байт фактически читаемых в последнем неформатированном потоковом вводе . Вот функция copyStream: void c opyStream(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, используя неупорядоченные данные инициализирующего списка . (Элементы этого списка доступны при использовании данных-указателей .)
1Архитектура и строительство
2Астрономия, авиация, космонавтика
 
3Безопасность жизнедеятельности
4Биология
 
5Военная кафедра, гражданская оборона
 
6География, экономическая география
7Геология и геодезия
8Государственное регулирование и налоги
 
9Естествознание
 
10Журналистика
 
11Законодательство и право
12Адвокатура
13Административное право
14Арбитражное процессуальное право
15Банковское право
16Государство и право
17Гражданское право и процесс
18Жилищное право
19Законодательство зарубежных стран
20Земельное право
21Конституционное право
22Конституционное право зарубежных стран
23Международное право
24Муниципальное право
25Налоговое право
26Римское право
27Семейное право
28Таможенное право
29Трудовое право
30Уголовное право и процесс
31Финансовое право
32Хозяйственное право
33Экологическое право
34Юриспруденция
 
35Иностранные языки
36Информатика, информационные технологии
37Базы данных
38Компьютерные сети
39Программирование
40Искусство и культура
41Краеведение
42Культурология
43Музыка
44История
45Биографии
46Историческая личность
47Литература
 
48Маркетинг и реклама
49Математика
50Медицина и здоровье
51Менеджмент
52Антикризисное управление
53Делопроизводство и документооборот
54Логистика
 
55Педагогика
56Политология
57Правоохранительные органы
58Криминалистика и криминология
59Прочее
60Психология
61Юридическая психология
 
62Радиоэлектроника
63Религия
 
64Сельское хозяйство и землепользование
65Социология
66Страхование
 
67Технологии
68Материаловедение
69Машиностроение
70Металлургия
71Транспорт
72Туризм
 
73Физика
74Физкультура и спорт
75Философия
 
76Химия
 
77Экология, охрана природы
78Экономика и финансы
79Анализ хозяйственной деятельности
80Банковское дело и кредитование
81Биржевое дело
82Бухгалтерский учет и аудит
83История экономических учений
84Международные отношения
85Предпринимательство, бизнес, микроэкономика
86Финансы
87Ценные бумаги и фондовый рынок
88Экономика предприятия
89Экономико-математическое моделирование
90Экономическая теория

 Анекдоты - это почти как рефераты, только короткие и смешные Следующий
При анархии можно задавать любые вопросы и давать любые ответы.
В правовом государстве за некоторые ответы могут привлечь к ответственности.
При диктатуре преследуют уже только за одни вопросы.
Anekdot.ru

Узнайте стоимость курсовой, диплома, реферата на заказ.

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

Смотрите также:


Банк рефератов - РефератБанк.ру
© РефератБанк, 2002 - 2016
Рейтинг@Mail.ru