Объектно-ориентированноепрограммирование на Borland C++Текстылекций, прочитанных в Московском государственном университете экономики,статистики и информатики
В текстах лекцийрассмотрены основные принципы и средства объектно-ориентированногопрограммирования с применением языка С++ и системы программирования BorlandC++. В приложении приведены задачи для самостоятельного решения.Предполагается, что читатель уже знаком с основами универсальных языковпрограммирования. Пособие предназначено для студентов специальности«Прикладная математика» и может быть использовано студентами другихспециальностей для самостоятельного изучения С++.
1. Объектно-ориентированный подход впрограммировании1.1 Технологии программирования
Технологияпрограммирования — это совокупность методов и средств разработки (написания)программ и порядок применения этих методов и средств.
На ранних этапах развитияпрограммирования, когда программы писались в виде последовательностей машинныхкоманд, какая-либо технология программирования отсутствовала. Первые шаги вразработке технологии состояли в представлении программы в виде последовательностиоператоров. Написанию последовательности машинных команд предшествовалосоставление операторной схемы, отражающей последовательность операторов ипереходы между ними. Операторный подход позволил разработать первые программыдля автоматизации составления программ — так называемые составляющие программы.
С увеличением размеровпрограмм стали выделять их обособленные части и оформлять их как подпрограммы.Часть таких подпрограмм объединялась в библиотеки, из которых подпрошраммыможно было включать в рабочие программы и затем вызывать из рабочих программ.Это положило начало процедурному программированию — большая программапредставлялась совокупностью процедур-подпрограмм. Одна из подпрограмм являласьглавной, и с нее начиналось выполнение программы.
В 1958 году былиразработаны первые языки программирования, Фортран и Алгол-58. Программа наФортране состояла из главной программы и некоторого количества процедур — подпрограмм и функций. Программа на Алголе-58 и его последующей версииАлголе-60 представляла собой единое целое, но имела блочную структуру,включающую главный блок и вложенные блоки подпрограмм и функций. Компиляторыдля Фортрана обеспечивали раздельную трансляцию процедур и последующее ихобъединение в рабочую программу, первые компиляторы для Алгола предполагали,что транслируется сразу вся программа, раздельная трансляция процедур необеспечивалась.
Процедурный подходпотребовал структурирования будущей программы, разделения ее на отдельныепроцедуры. При разработке отдельной процедуры о других процедурах требовалосьзнать только их назначение и способ вызова. Появилась возможностьперерабатывать отдельные процедуры, не затрагивая остальной части программы,сокращая при этом затраты труда и машинного времени на разработку имодернизацию программ.
Следующим шагом вуглублении структурирования программ стало так называемое структурноепрограммирование, при котором программа в целом и отдельные процедурырассматривались как последовательности канонических структур: линейныхучастков, циклов и разветвлений. Появилась возможность читать и проверятьпрограмму как последовательный текст, что повысило производительность трудапрограммистов при разработке и отладке программ. С целью повышенияструктурности программы были выдвинуты требования к большей независимостиподпрограмм, подпрограммы должны связываться с вызывающими их программамитолько путем передачи им аргументов, использование в подпрограммах переменных,принадлежащих другим процедурам или главной программе, стало считатьсянежелательным.
Процедурное и структурноепрограммирование затронули, прежде всего, процесс описания алгоритма какпоследовательности шагов, ведущих от варьируемых исходных данных к искомомурезультату. Для решения специальных задач стали разрабатываться языкипрограммирования, ориентированные на конкретный класс задач: на системыуправления базами данных, имитационное моделирование и т.д.
При разработкетрансляторов все больше внимания стало уделяться обнаружению ошибок в исходныхтекстах программ, обеспечивая этим сокращение затрат времени на отладкупрограмм.
Применение программ всамых разных областях человеческой деятельности привело к необходимостиповышения надежности всего программного обеспечения. Одним из направленийсовершенствования языков программирования стало повышения уровня типизацииданных. Теория типов данных исходит из того, что каждое используемое впрограмме данное принадлежит одному и только одному типу данных. Тип данногоопределяет множество возможных значений данного и набор операций, допустимыхнад этим данным. Данное конкретного типа в ряде случаев может бытьпреобразовано в данное другого типа, но такое преобразование должно быть явнопредставлено в программе. В зависимости от степени выполнения перечисленныхтребований можно говорить об уровне типизации того или иного языкапрограммирования. Стремление повысить уровень типизации языка программированияпривело к появлению языка Паскаль, который считается строго типизированнымязыком, хотя и в нем разрешены некоторые неявные преобразования типов, например,целого в вещественное. Применение строго типизированного языка при написаниипрограммы позволяет еще при трансляции исходного текста выявить многие ошибкииспользования данных и этим повысить надежность программы. Вместе с тем строгаятипизация сковывала свободу программиста, затрудняла применение некоторыхприемов преобразования данных, часто используемых в системном программировании.Практически одновременно с Паскалем был разработан язык Си, в большей степениориентированный на системное программирование и относящийся к слаботипизированным языкам.
Все универсальные языкипрограммирования, несмотря на различия в синтаксисе и используемых ключевыхсловах, реализуют одни и те же канонические структуры: операторы присваивания,циклы и разветвления. Во всех современных языках присутствуют предопределенные(базовые) типы данных (целые и вещественные арифметические типы, символьный и,возможно, строковый тип), имеется возможность использования агрегатов данных, втом числе массивов и структур (записей). Для арифметических данных разрешеныобычные арифметические операции, для агрегатов данных обычно предусмотренатолько операция присваивания и возможность обращения к элементам агрегата.Вместе с тем при разработке программы для решения конкретной прикладной задачижелательна возможно большая концептуальная близость текста программы к описаниюзадачи. Например, если решение задачи требует выполнения операций надкомплексными числами или квадратными матрицами, желательно, чтобы в программеявно присутствовали операторы сложения, вычитания, умножения и деления данныхтипа комплексного числа, сложения, вычитания, умножения и обращения данных типаквадратной матрицы. Решение этой проблемы возможно несколькими путями:
— Построением языкапрограммирования, содержащего как можно больше типов данных, и выбором длякаждого класса задач некоторого подмножества этого языка. Такой язык иногданазывают языком-оболочкой. На роль языка-оболочки претендовал язык ПЛ/1,оказавшийся настолько сложным, что так и не удалось построить егоформализованное описание. Отсутствие формализованного описания, однако, непомешало широкому применению ПЛ/1 как в Западной Европе, так и в СССР.
— Построениемрасширяемого языка, содержащего небольшое ядро и допускающего расширение,дополняющее язык типами данных и операторами, отражающими концептуальнуюсущность конкретного класса задач. Такой язык называют языком-ядром. Какязык-ядро были разработаны языки Симула и Алгол-68, не получившие широкогораспространения, но оказавшие большое влияние на разработку других языковпрограммирования.
Дальнейшим развитиемвторого пути явился объектно-ориентированный подход к программированию,рассматриваемый в следующем параграфе.1.2.Сущность объектно-ориентированного подхода к программированию
Основные идеи объектно-ориентированногоподхода опираются на следующие положения:
— Программа представляетсобой модель некоторого реального процесса, части реального мира.
— Модель реального мираили его части может быть описана как совокупность взаимодействующих между собойобъектов.
— Объект описываетсянабором параметров, значения которых определяют состояние объекта, и наборомопераций (действий), которые может выполнять объект.
— Взаимодействие междуобъектами осуществляется посылкой специальных сообщений от одного объекта кдругому. Сообщение, полученное объектом, может потребовать выполненияопределенных действий, например, изменения состояния объекта.
— Объекты, описанныеодним и тем же набором параметров и способные выполнять один и тот же набордействий представляют собой класс однотипных объектов.
С точки зрения языкапрограммирования класс объектов можно рассматривать как тип данного, аотдельный объект — как данное этого типа. Определение программистом собственныхклассов объектов для конкретного набора задач должно позволить описыватьотдельные задачи в терминах самого класса задач (при соответствующем выбореимен типов и имен объектов, их параметров и выполняемых действий).
Таким образом,объектно-ориентированный подход предполагает, что при разработке программыдолжны быть определены классы используемых в программе объектов и построены ихописания, затем созданы экземпляры необходимых объектов и определеновзаимодействие между ними.
Классы объектов частоудобно строить так, чтобы они образовывали иерархическую структуру. Например,класс “Студент”, описывающий абстрактного студента, может служить основой дляпостроения классов “Студент 1 курса”, “Студент 2 курса” и т.д., которыеобладают всеми свойствами студента вообще и некоторыми дополнительнымисвойствами, характеризующими студента конкретного курса. При разработкеинтерфейса с пользователем программы могут использовать объекты общего класса“Окно” и объекты классов специальных окон, например, окон информационныхсообщений, окон ввода данных и т.п. В таких иерархических структурах один классможет рассматриваться как базовый для других, производных от него классов.Объект производного класса обладает всеми свойствами базового класса инекоторыми собственными свойствами, он может реагировать на те же типы сообщенийот других объектов, что и объект базового класса и на сообщения, имеющие смыслтолько для производного класса. Обычно говорят, что объект производного классанаследует все свойства своего базового класса.
Некоторые параметрыобъекта могут быть локализованы внутри объекта и недоступны для прямоговоздействия извне объекта. Например, во время движения объекта-автомобиляобъект-водитель может воздействовать только на ограниченный набор органовуправления (рулевое колесо, педали газа, сцепления и тормоза, рычагпереключения передач) и ему недоступен целый ряд параметров, характеризующихсостояние двигателя и автомобиля в целом.
Очевидно, для того, чтобыпродуктивно применять объектный подход для разработки программ, необходимыязыки программирования, поддерживающие этот подход, т.е. позволяющие строитьописание классов объектов, образовывать данные объектных типов, выполнятьоперации над объектами. Одним из первых таких языков стал язык SmallTalk вкотором все данные являются объектами некоторых классов, а общая системаклассов строится как иерархическая структура на основе предопределенных базовыхклассов.
Опыт программированияпоказывает, что любой методический подход в технологии программирования недолжен применяться слепо с игнорированием других подходов. Это относится и кобъектно-ориентированному подходу. Существует ряд типовых проблем, для которыхего полезность наиболее очевидна, к таким проблемам относятся, в частности,задачи имитационного моделирования, программирование диалогов с пользователем.Существуют и задачи, в которых применение объектного подхода ни к чему, кромеизлишних затрат труда, не приведет. В связи с этим наибольшее распространениеполучили объектно-ориентированные языки программирования, позволяющие сочетатьобъектный подход с другими методологиями. В некоторых языках и системахпрограммирования применение объектного подхода ограничивается средствамиинтерфейса с пользователем (например, Visual FoxPro ранних версий).
Наиболее используемыми внастоящее время объектно-ориентированными языками являются Паскаль с объектамии Си++, причем наиболее развитые средства для работы с объектами содержатся вСи++.
Практически всеобъектно-ориентированные языки программирования являются развивающимисяязыками, их стандарты регулярно уточняются и расширяются. Следствием этогоразвития являются неизбежные различия во входных языках компиляторов различныхсистем программирования… Наиболее распространенными в настоящее время являютсясистемы программирования Microsoft C++, Microsoft Visual C++ и системыпрограммирования фирмы Borland International. Дальнейший материал в данномпособии излагается применительно к системе программирования Borland C++. Этосвязано прежде всего наличием в этой системе программирования развитойинтегрированной среды, объединяющей текстовый редактор, компилятор, редакторсвязей (компоновщик) и отладочные средства.
2. Начальные сведения о языке Си2.1 Назначение Си, исторические сведения
Язык Си был разработан в70-е годы как язык системного программирования. При этом ставилась задачаполучить язык, обеспечивающий реализацию идей процедурного и структурногопрограммирования и возможность реализации специфических приемов системногопрограммирования. Такой язык позволил бы разрабатывать сложные программы науровне, сравнимом с программированием на Ассемблере, но существенно быстрее.Эти цели, в основном, были достигнуты. Большинство компиляторов для Си написанына Си, операционная система UNIX
На основе Си в 80-е годыбыл разработан язык Си++, вначале названный «Си с классами». Си++практически включает язык Си и дополнен средствами объектно-ориентированногопрограммирования. Рабочая версия Си++ появилась в 1983 г. С тех пор языкпродолжает развиваться и опубликовано несколько версий проекта стандартов Си иСи++.
Рядом фирм, производящихпрограммное обеспечение, разработаны компиляторы для Си и Си++. Системыпрограммирования фирмы Borland International выделяются среди других фирмпрежде всего комплексным подходом к разработке программ, выражающимся вовключении в систему программирования интегрированной среды разработчика,объединяющей под общим управлением текстовый редактор для ввода исходныхтекстов программ, компилятор, редактор связей и набор отладочных средств. В1989 г. этой фирмой была выпущена система Turbo C++, включавшая компиляторСи++, работающий в операционной системе DOS, с 1992 г. выпускаются системыBorland C++, содержащие компиляторы Си++ для DOS и WINDOWS, с 1997 г.поставляется версия Borland C 5.0, содержащая компиляторы Си++ для WINDOWS,причем компилятор для WINDOWS теперь позволяет разрабатывать как 16-разрядные,так и 32-разрядные варианты программ для ПЭВМ с процессорами i486 и Pentium.
Программа на Си/Си++представляет собой один или несколько исходных файлов, которые могуттранслироваться раздельно. Результаты трансляции (объектные файлы) объединяютсяв исполняемый файл редактором связей (компоновщиком). Обычно различают два типаисходных файлов: файлы заголовков и программные файлы. Файлы заголовков содержатописания типов данных и прототипов функций и предназначены для включения впрограммные файлы перед их компиляцией, их имена, как правило, имеют расширение.h, например, stdio.h. Программные файлы содержат описания функций и, возможно,глобальных переменных и констант, их имена принято записывать с расширениями .cили .cpp, например, myprog.cpp. Один и тот же файл заголовков может включатьсяв несколько программных файлов
Каждый файл содержитпоследовательность так называемых «внешних определений», описывающихтипы данных, переменные, константы и функции.
В последующих параграфахэтого раздела приведен обзор средств Си/Си++, не связанных с объектнойориентацией Си++.2.2 Алфавит, базовые типы и описаниеданных.
Алфавит языка включаетпрактически все символы, имеющиеся на стандартной клавиатуре ПЭВМ:
— латинские буквы A...Z,a...z;
— цифры 0...9;
— знаки операций иразделители:
{ } [ ] ( )., ->& * + — ~! / %?:; = | # ^
Некоторые операцииобозначаются комбинациями символов, значения символов операций в ряде случаевзависят от контекста, в котором они употреблены.
Базовые(предопределенные) типы данных объединены в две группы: данные целого типа иданные с плавающей точкой (вещественные).
Данные целого типа могутбыть обычными целыми со знаком (signed) и целыми без знака (unsigned). По числуразрядов, используемых для представления данного (диапазону значений) различаютобычные целые (int), короткие целые (short int) и длинные целые (long int ).Символьные данные (char) также рассматриваются как целые и могут быть со знакоми без знака.
Константы целого типазаписываются как последовательности десятичных цифр, тип константы зависит отчисла цифр в записи константы и может быть уточнен добавлением в концеконстанты букв L или l (тип long), U или u (тип unsigned) или их сочетания:
321 — константа типа int,
5326u — константа типа unsigned int,
45637778 — константа типаlong int,
2746L — константа типаlong int.
Целые константы могутзаписываться в восьмеричной системе счисления, в этом случае первой цифройдолжна быть цифра 0, число может содержать только цифры 0… 7:
0777 — константа типаint,
0453377 — константа типаlong.
Целые константы можнозаписывать и в шестнадцатеричной системе счисления, в этом случае записьконстанты начинается с символов 0x или 0X:
0x45F — константа типаint,
0xFFFFFFFF — константа типа unsigned long.
Константы типа charвсегда заключаются в одиночные кавычки, значение константы задается либо знакомиз используемого набора символов, либо целой константой, которой предшествуетобратная косая черта: 'A', '\33', '\042', '\x1B'. Имеется также ряд специальныхсимволов, которые могут указываться в качестве значений константы типа char:
'\n' — новая строка,
'\t' — горизонтальнаятабуляция,
'\v' — вертикальнаятабуляция,
'\r' — перевод каретки,
'\f' — перевод страницы,
'\a' — звуковой сигнал,
'\'' — одиночная кавычка(апостроф),
'\"' — двойнаякавычка,
'\\' — обратная косаячерта.
Вещественные числа могутбыть значениями одного из трех типов: float, double, long double. Диапазонзначений каждого из этих типов зависит от используемых ЭВМ и компилятора.Константы вещественных типов могут записываться в естественной илиэкспоненциальной формах и по умолчанию имеют тип double, например, 15.31,1.43E-3, 2345.1e4. При необходимости тип константы можно уточнить, записав вконце суффикс f или F для типа float, суффикс l или L для типа long double.
Внешнее определение,объявляющее переменные, состоит из необязательного спецификатора класса памяти,спецификаторов типа и списка так называемых деклараторов-инициализаторов,каждый из которых объявляет идентификатор одной переменной и, возможно,значение, присваиваемое переменной при ее объявлении. Внешнее определениезаканчивается точкой с запятой:
int i, j, k; // Три переменных типа int без явной инициализации
double x=1, y=2; //Две переменных типа double с начальными значениями 1 и 2
char c1='0'; // Переменная типа char, ее значение — код литеры 0
Текст, записанный в этихпримерах после знаков //, является комментарием и служит только для документированияпрограммы. Такой комментарий может занимать только одну строку текста идопускается в текстах программ на Си++. Комментарий, занимающий несколькострок, заключается в специальные скобки /* и */.
В качестве спецификаторовкласса памяти во внешнем определении может указываться одно из ключевых словextern, static или typedef, Спецификатор extern означает, что объявляемыйобъект принадлежит другому программному файлу, а здесь дается информация о егоимени и типе и не должно присутствовать инициализирующее выражение.Спецификатор static ограничивает область действия объявляемого имени даннымфайлом или блоком, если объявление содержится в блоке.
Если объявление данногосодержится внутри тела функции (локальное объявление), то можно указывать спецификаторыкласса памяти register или auto. Спецификатор register носит рекомендательныйхарактер, компилятор пытается разместить данное этот класса в регистрепроцессора, если в данный момент имеются свободные регистры. Спецификатор autoпринимается по умолчанию и поэтому явно не указывается, он означает, что данноекласса auto должно размещаться в программном стеке при вызове функции.
Спецификатор typedefслужит для присвоения имени описываемому типу данного и будет рассмотренподробнее в следующем параграфе.
Наряду с показанными вышеконстантами-литералами, значения которых определяются их представлением впрограмме, в Си и Си++ предусмотрены константы, которым присваиваютсясобственные имена — именованные константы. В описании именованной константыприсутствует описатель const, например,
const double Pi = 3.141592653;
Переменной, идентификаторкоторой объявлен с описателем const, нельзя присвоить иное значение, чем былоустановлено при объявлении идентификатора. Инициализирующее значение приобъявлении константы является обязательным.
Наряду с базовыми целымии вещественными типами различных размеров в программе могут объявляться ииспользоваться данные типов, определяемых программистом: указатели, ссылки,агрегаты данных и данные перечислимого типа.
Перечислимый типприменяется для данных целого типа, которые могут принимать ограниченный наборзначений. Каждому значению соответствует собственное имя-идентификатор и целоечисло, значение этого имени. Объявление перечислимого типа строится по схеме:
enum идентификатор {список перечисления} деклараторы-инициализаторы;Здесь идентификатор задает имяперечислимого типа, список перечисления состоит из перечислителей, разделенныхзапятыми. Каждый перечислитель задается идентификатором и, возможно, целымзначением типа char или int, например,
enum color { RED, GREEN, BLUE } en_color;
enum lex_type { CNST, VAR, OPER=3, FUNC };Если значение перечислителя незадано, первый из них получает значение 0, а каждый следующий — значение,большее на 1. Вообще любой перечислитель по умолчанию имеет значение на 1больше предыдущего. В Си/Си++ принято записывать идентификаторы перечислителейпрописными буквами. Имена перечислителей используется либо как именованныеконстанты либо для присвапивания переменным перечислимого типа.
В Си/Си++ для ссылок напеременную того или иного типа служат указатели. Указатель — это тип данного,значением которого является адрес другого данного. При объявлении указателяперед идентификатором записывается знак *. Указатель может инициализироваться адресомданного, для получения адреса служит операция & (амперсенд):
double y;
double *px, *py = &y;
Для указателей определеныоперации сравнения, сложения указателя с целым числом, вычитание двухуказателей, а также операция индексирования (операция []).
Для обращения кпеременной по указателю выполняется операция разыменования, обозначаемая знаком* (звездочка), например, *py = 7.5; .
При объявлении указателяможет использоваться описатель const, например,
const int cc = 20;
const int *pc = &cc; // Можно инициализировать адресом константы.
double *const delta = 0.001; // Указатель — константа
Кроме обычных переменныхи указателей в Си++ имеется тип «ссылка на переменную», задающий дляпеременной дополнительное имя (псевдоним). Внутреннее представление ссылкитакое же, как указателя, т.е. в виде адреса переменной, но обращение кпеременной по ссылке записывается в той же форме, что и обращение по основномуимени. Переменная типа ссылки всегда инициализируется заданием именипеременной, к которой относится ссылка. При объявлении ссылки за именем типазаписывается знак & (амперсенд):
int ii;
int& aii = ii;
При таком описанииоператоры aii = 5; и ii = 5; эквивалентны.
Из переменных любого типамогут образовываться массивы. При объявлении массива в деклараторе-инициализатореза идентификатором массива задается число элементов массива в квадратныхскобках:
int a [ 5 ]; // Массив из пяти элементов типа int
Индексы элементов массивавсегда начинаются с 0, индекс последнего элемента на единицу меньше числаэлементов в массиве. Массив может инициализироваться списком значений вфигурных скобках:
int b [ 4 ] = { 1, 2, 3, 4 };
При наличии спискаинициализации, охватывающего все элементы массива, можно не указывать числоэлементов массива, оно будет определено компилятором:
int c [ ] = { 1, 2, 3 }; // Массив из трех элементов типа int
Массивы с размерностью 2и более рассматриваются как массивы массивов и для каждого измеренияуказывается число элементов:
double aa [ 2 ] [ 2 ] = { 1, 2, 3, 4 }; // Матрица 2 * 2
Массивы типа char могутинициализироваться строковым литералом. Строковый литерал — этопоследовательность любых символов, кроме кавычек и обратной косой черты,заключенная в кавычки. Если строковый литерал не умещается на одной строке, егоможно прервать символом "\" и продолжить с начала следующей строки. Встандарте C++ предусмотрена и другая возможность записи длинных литералов ввиде нескольких записанных подряд строковых литералов. Если между строковымилитералами нет символов, отличных от пробелов, такие литералы сливаютсякомпилятором в один.
При размещении в памяти вконце строкового литерала добавляется символ '\0', т.е. нулевой байт. Строковыйлитерал может применяться и для инициализации указателя на тип char:
char str1 [ 11 ] = «Это строка»,
str2 [ ] = " Размер этого массива определяется"
" числом знаков в литерале + 1";
char *pstr = «Указатель с инициализацией строкой»;
Имя массива в Си/Си++является указателем-константой, ссылающимся на первый элемент массива, имеющийиндекс, равный нулю. Для обращения к элементу массива указывается идентификатормассива и индекс элемента в круглых скобках, например, c[2], aa[0][1]. 2.3 Структуры и объединения
Наряду с массивами вСи/Си++ имеются агрегаты данных типа структур и объединений. Тип структурыпредставляет собой упорядоченную совокупность данных различных типов, к которойможно обращаться как к единому данному. Описание структурного типа строится посхеме:
struct идентификатор
{ деклараторы членов }деклараторы_инициализаторы;
Такое объявлениевыполняет две функции, во-первых объявляется структурный тип, во-вторыхобъявляются переменные этого типа.
Идентификатор послеключевого слова struct является именем структурного типа. Имя типа можетотсутствовать, тогда тип будет безымянный и в других частях программы нельзябудет объявлять данные этого типа. Деклараторы_инициализаторы объявляютконкретные переменные структурного типа, т.е. данные описанного типа, указателина этот тип и массивы данных. Деклараторы_инициализаторы могут отсутствовать, вэтом случае объявление описывает только тип структуры.
Структура, описывающаяточку на плоскости, может быть определена так:
struct Point_struct // Имя структуры
{ int x, y; } // Деклараторы членов структуры
point1, *ptr_to_point, arpoint [3]; // Данные структурного типа
Члены (компоненты)структуры описываются аналогично данным соответствующего типа и могут бытьскалярными данными, указателями, массивами или данными другого структурноготипа. Например, для описания структурного типа «прямоугольник состоронами, параллельными осям координат» можно предложить нескольковариантов:
struct Rect1
{Point p1; // Координаты левого верхнего угла
Point p2; // Координаты правого нижнего угла
};
struct Rect2
{Point p [ 2 ];
};
struct Rect3
{Point p; // Левый верхний угол
int width; // Ширина
int high; // Высота прямоугольника
};
Поскольку при описаниичленов структуры должны использоваться только ранее определенные имена типов,предусмотрен вариант предварительного объявления структуры, задающий только имяструктурного типа. Например, чтобы описать элемент двоичного дерева, содержащийуказатели на левую и правую ветви дерева и указатель на некоторую структурутипа Value, содержащую значение данного в узле, можно поступить так:
struct Value;
struct Tree_element
{Value * val;
Tree_element *left, *right;
};
Членами структур могутбыть так называемые битовые поля, когда в поле памяти переменной целого типа(int или unsigned int) размещается несколько целых данных меньшей длины. Пусть,например, в некоторой програме синтаксического разбора описание лексемысодержит тип лексемы (до шести значений) и порядковый номер лексемы в таблицесоответствующего типа (до 2000 значениий). Для представления значения типалексемы достаточно трех двоичных разрядов (трех бит), а для представления чиселот 0 до 2000 — 11 двоичных разрядов (11 бит). Описание структуры, содержащейсведения о лексеме может выглядеть так:
struct Lexema
{unsigned int type_lex: 3;
unsigned int num_lex :11;
};
Двоеточие с целым числомпосле имени члена структуры указывает, что это битовое поле, а целое числозадает размер поля в битах.
Объединение можноопределить как структуру, все компоненты которой размещаются в памяти с одногои того же адреса. Таким образом, объединение в каждый момент времени содержитодин из возможных вариантов значений. Для размещения объединения в памятивыделяется участок, достаточный для размещения члена объединения самогобольшого размера. Применение объединения также позволяет обращаться к одному итому же полю памяти по разным именам и интерпретировать как значения разныхтипов.
Описание объединениястроится по той же схеме, что и описание структуры, но вместо ключевого словаstruct используется слово union, например, объединение uword позволяетинтерпретировать поле памяти либо как unsigned int, либо как массив из двухэлементов типа unsigned char.
union uword
{unsigned int u;
unsigned char b [ 2 ];
};
Описания типов,объявляемых программистом, в том числе структур и объединений могут бытьдостаточно большими, поэтому в Си/Си++ предусмотрена возможность присваиваниятипам собственных имен (синонимов), достигая при этом повышения наглядностипрограммных текстов. Синоним имени типа вводится с ключевым словом typedef истроится как обычное объявление, но идентификаторы в деклараторах в этом случаеинтерпретируются как синонимы описанных типов. Синонимы имен типов принятозаписывать прописными буквами, чтобы отличать их от идентификаторов переменных.Ниже приведено несколько примеров объявления синонимов имен типов.
typedef struct { double re, im } COMPLEX;
typedef int *PINT;
После таких объявленийсиноним имени может использоваться как спецификатор типа:
COMPLEX ca, *pca; // переменная типа COMPLEX и указатель на COMPLEX
PINT pi; // указатель на int
Приведенное выше описаниеструктур и объединений в основном соответствует их построению в языке Си. ВСи++ структуры и объединения являются частными случаями объектных типов данных.Дополнительные сведения об этом будут приведены при рассмотренииобъектно-ориентированных средств Си++.2.4 Операции и выражения
Несмотря на ограниченныйнабор базовых типов данных (целые и вещественные арифметические данные истроковые литералы) в языке Си++ определен обширный набор операций над данными,часть из которых непосредственно соответствует машинным командам. Как и во всехязыках программирования, операции служат для построения выражений. Выражениепредставляет собой последовательность операндов и знаков операций и служит длявычисления некоторого значения.
В качестве операндов ввыражении выступают идентификаторы переменных, константы, и строковые литералы,являющиеся первичными выражениями. Выражение, заключенное в круглые скобки,также рассматривается как первичное. Каждая операция предполагает использованиеопределенных типов операндов (целых, вещественных, указателей). Операцияприсваивания в Си++ также является выражением, в связи с этим различаютсяоперанды, которым можно присвоить новое значение и операнды, значение которыхне может меняться. Чтобы операнду можно было присвоить значение, ему должнасоответствовать область памяти и компилятору должен быть известен адрес этойпамяти. Такие операнды называют L-выражениями (от английского left -левый), таккак они могут быть записаны в левой части оператора присваивания.
Результат вычислениявыражения зависит от приоритетов операций. В Си++ сложная система приоритетовопераций, включающая 16 уровней. В таблице 2.1 приведен перечень операций Си++с указанием их приоритетов, назначения и схемы записи.
Таблица 2.1
Приоритет
Знак операции
Назначение
Схема 1 : : Доступ к глобальному имени или имени из другой области
:: идентификатор (глобальный)
имя области:: имя_члена_структуры 1 -> Обращение к члену структуры по указателю на структуру указатель -> имя_члена_структуры 1 . Обращение к члену структуры по имени структуры имя_структуры. имя_члена_структуры 1 [ ] Обращение к элементу массива указатель [ индекс ] 1 ( ) Преобразование типа данного имя_типа (выражение ) или (тип) выражение 1 ( ) Вызов функции функция(аргументы) 2 ++ Автоувеличение
++ L-значение или
L-значение++ 2 -- Автоуменьшение
— L-значение или
L-значение-- 2 ~ Битовое инвертирование ~ целое_выражение 2 ! Логическое отрицание ! выражение 2 - Одноместный минус — выражение 2 + Одноместный плюс + выражение 2 & Получение адреса & L-значение 2 * Разыменование указателя * указатель 2 new Выделение динамической памяти new тип данного 2 delete Освобождение памяти delete указатель 2 delete [] Освобождение памяти для массива delete [] указатель 2 sizeof Размер данного sizeof выражение 2 Размер типа данного sizeof (имя типа ) 3 * Умножение выражение * выражение 3 / Деление выражение / выражение 3 % Остаток от деления нацело выражение % выражение 4 ->* Обращение к члену структуры по указателю указатель_на_структуру ->* имя_члена_структуры-указателя 4 .* Обращение к члену структуры по имени структуры
имя_структуры .*
имя_члена_структуры-указателя 5 + Сложение выражение + выражение 5 - Вычитание выражение — выражение 6 > Сдвиг вправо целое_выражение >> целое_выражение 7 Больше выражение > выражение 7 >= Больше или равно выражение >= выражение 8 == Равно выражение == выражение 8 != Не равно выражение != выражение 9 & Поразрядная конъюнкция выражение & выражение 10 ^ Отрицание равнозначности выражение ^ выражение 11 | Поразрядная дизъюнкция выражение | выражение 12 && Логическое «И» выражение && выражение 13 | | Логическое «ИЛИ» выражение | | выражение 14 ? : Условное выражение выражение? выражение1: выражение2 15 = Простое присваивание выражение = выражение 15 @= Составное присваивание, знак @ — один из знаков операций * / % + — > & ^ | выражение @= выражение 16 , Операция следования выражение, выражение
Рассмотрим особенностиприменения основных из перечисленных выше операций.
Операция ": :"(два двоеточия) применяется для уточнения имени объекта программы в случае,когда в этом месте программы известны два одинаковом имени, например, когдаодно имя объявлено глобально, а другое в теле функции. Если имени предшествуютдва двоеточия, то это глобальное имя.
Для обращения к членамструктуры или объединения можно воспользоваться либо именем структурногоданного, либо указателем на структурное данное. В первом случае полное имячлена структуры состоит из имени самой структуры и имени члена структуры,разделенных точкой. Во втором случае за именем указателя на структуру ставитсязнак -> (стрелка), а за ним имя члена структуры. Пусть в программе объявленструктурный тип AnyStruct, содержащий компоненту с именем member типа int иобъявлены
AnyStruct s1;// Данное s1 типа AnyStruct
AnyStruct *ps1 = &s1;// Указатель на данное типа AnyStruct
Тогда к члену структурыmember из s1 можно обратиться как к s1.member или как ps1->member.
Поскольку членомструктуры может быть указатель, в Си++ имеются специальные операцииразыменования такого указателя, операции .* и ->. Пусть одним из членовструктуры AnyStruct является указатель pp1 на данное типа int. Тогда выраженияs1.*pp1 и ps1->*pp1 обеспечат доступ к значению данного, на котороеуказывает pp1 из s1.
Выше отмечалось, что имямассива в Си/Си++ интерпретируется как указатель-константа на первый элементмассива. Для разыменования указателя, т.е. для доступа к данному по указателюна это данное служит операция * (звездочка). Следовательно, если в программеобъявлен массив
int Array1 [ 10 ];то выражение *Array1=0 служит дляприсвоения нулевого значения первому элементу массива. Чтобы обратиться кпроизвольному элементу массива, нужно указать индекс элемента, например, Array1[3]. Это выражение эквивалентно выражению *(Array1 + 3), т.е. требуется сначалаувеличить указатель Array1 на 3 единицы, а затем разыменовать полученныйуказатель. При сложении указателя на объект некоторого типа T с целым числом Nзначение указателя увеличивается на N, умноженное на длину данного типа T.Отметим, что индекс можно задавать не только для имен массивов, но и для любоготипа указателя, кроме указателя на тип void:
int *pint = &Array[ 4]; pint [ 2 ] =1;
В этом примере указательpint инициализирован адресом пятого элемента массива Array, а затем седьмомуэлементу этого массива присвоено значение 1.
В качестве индекса можетзадаваться любое выражение со значением целого типа.
Поскольку Си++ являетсятипизированным языком, в нем определены явные и неявные преобразования типовданных. Неявные преобразования выполняются при двуместных арифметическихоперациях и операции присваивания и называются стандартными арифметическимипреобразованиями. Эти преобразования выполняются в следующейпоследовательности:
— если один операнд имееттип long double, другой операнд преобразуется в тип long double;
— иначе, если одиноперанд имеет тип double, другой операнд преобразуется в тип double;
— иначе, если одиноперанд имеет тип float, другой операнд преобразуется в тип float;
— иначе, если одиноперанд имеет тип unsigned long int, другой операнд преобразуется в типunsigned long int;
— иначе, если одиноперанд имеет тип long int, >другой операнд преобразуется в тип long int;
— иначе, выполняютсястандартные преобразования для целых, при этом типы char, short int и битовыеполя типа int преобразуются в тип int, затем, если один операнд имеет большийразмер (больший диапазон значений), чем другой операнд, то второй операндпреобразуется к типу операнда большего размера;
— в остальных случаяхоперанды имеют тип int.
Явное преобразованиетипов может быть задано в двух формах. Первая форма совместима с Си, в ней заименем типа в круглых скобках записывается преобразуемое значение, котороеможет быть первичным выражением или выражением с одноместной операцией. Имятипа в этом случае может быть представлено последовательностью описателей,например, (long int * ) pp определеяет преобразование некоторого данного pp в типуказателя на long int. Вторая форма преобразования типа записывается как вызовфункции, при этом имя типа должно задаваться идентификатором, например, int (x). Следует отметить, что результат явного преобразования не являетсяL-значением.
Операции автоувеличения иавтоуменьшения ( ++ и — ) могут быть префиксными и постфиксными и вызываютувеличение (уменьшение) своего операнда на единицу, т.е. выражение ++xэквивалентно x = x +1, а --x эквивалентно x = x — 1. Префиксная операциявыполняется до того, как ее операнд будет использован в вычислении выражения, апостфиксная операция выполняется после того, как ее операнд будет использован ввыражении, например, в результате вычисления выражения
++x * 2 + y-- *3
переменная x сначалаувеличивается на 1, а затем умножается на 2, переменная y сначала умножается на3, затем уменьшается на 1. Если перед вычислением этого выражения x и y былиравны 1, то результат выражения будет равен 5, кроме того переменная x получитзначение 2, а переменная y — значение 0. Таким образом, операции автоувеличенияи автоуменьшения всегда дают побочный эффект, изменяют значения своихоперандов. Операнды этих операций должны быть L-значениями.
Операция ~ (тильда)применяется только к целому значению и заменяет все биты своего операнда созначением 0 на 1, а биты со значением 1 на 0.
Логическое отрицание(операция !) возвращает значение 0 целого типа, если операнд не равен нулю, илизначение 1, если операнд равен нулю.
Операции«одноместный +» и «одноместный -» имеют обычныйматематический смысл, знак + не изменяет значения операнда, знак — меняет знакоперанда на противоположный.
Для получения адресаоперанда, являющегося L-значением, применяется операция & (амперсанд).Результатом этой операции будет указатель на соответствующий тип данного.Разыменование указателя, т.е. получение значения данного по указателю на негообеспечивается операцией * (звездочка). Результат операции разыменованияявляется L-значением.
В Си++ определеныоперации размещения данных в динамической памяти и удаления динамических данныхиз памяти.
Операция new требует вкачестве операнда имени типа и предназначена для размещения данного указанноготипа в динамической памяти, результатом операции будет указатель на данное. Приневозможности выделить память операция new возвращает значение NULL — предопределенную константу, имеющую нулевое значение практически во всехкомпиляторах Си и Си++. Память, выделяемую операцией new, можноинициализировать, указав за именем типа скалярного данного начальное значение вкруглых скобках, задание начальных значений для агрегатов данных будетрассмотрено позже. Примеры применения операции new :
int *ip = new int; /* создание объекта типа int и получение указателя на него */
int *ip2 = new int(2); // то же с установкой начального значения 2
inr *intArray = new int [ 10 ]; // массив из 10 элементов типа int
double **matr = new double [ m ] [ n ]; // матрица из m строк и n столбцов
Данное, размещенное вдинамической памяти операцией new, удаляется из памяти операцией delete соперандом-указателем, значение которого получено операцией new, например,
delete intArray; delete ip2;
Операция delete толькоосвобождает динамическую память, но не изменяет значение указателя-операнда.Программист должен помнить, что после освобождения памяти использовать этотуказатель для обращения к данному нельзя.
Размер данного или типаданного в байтах можно получить по операции sizeof. Операнд может быть любоготипа, кроме типа функции и битового поля. Если операндом является имя типа, онодолжно заключаться в скобки. Возвращаемое значение имеет предопределенный типsize_t, это целый тип, размер которого определяется реализацией компилятора,обычно типу size_t соответствует unsigned int. Размер массива равен числу байт,занимаемых массивом в памяти, размер строкового литерала — это число знаков влитерале +1, т.е. завершающий нулевой байт учитывается при определении длинылитерала. Значение, возвращаемое sizeof является константой.
Двуместные арифметическиеоперации умножения ( * ), деления ( / ), получения остатка от деления нацело (% ), сложения ( + ) и вычитания ( — ) имеют обычный смысл и обычныйотносительный приоритет. Если операнды арифметической операции имеют разныетипы, предварительно выполняются стандартные арифметические преобразования итип результата операции определяется общим типом операндов после стандартныхпреобразований. Следовательно, выражение 7/2 будет иметь значение 3 типа int,так как оба опернда имеют тип int, а выражение 7.0/2 даст результат 3.5 типаdouble, поскольку этот тип имеет первый операнд.
Операции отношения двухвыражений (, >=) требуют операндов арифметического типа илиже оба операнда должны быть указателями на одинаковый тип. В случае операндоварифметического типа вычисляются значения операндов, выполняются стандартныеарифметические преобразования и возвращается 1 типа int, если отношениевыполняется (истинно), или 0, если отношение не выполняется (ложно). Когдасравниваются два указателя, результат зависит от относительного размещения впамяти объектов, на которые ссылаются указатели. Операции сравнения ( == и != )выполняются аналогичным образом, но имеют меньший приоритет.
Выражения отношений могутсоединяться логическими связками && (конъюнкция, логическое умножение)и | | (дизъюнкция, логическое сложение). В общем случае операндами логическихсвязок могут быть любые скалярные значения и операция && даетреззультат, равный 1 типа int, если оба операнда имеют ненулевые значения, аоперация | | дает результат, равный 0, если значения обоих операндов нулевые.Применяется сокращенная форма вычисления значения логических связок, если воперации && первый операнд равен нулю, то второй операнд не вычисляетсяи возвращается 0, если в операции | | первый операнд не равен нулю, то второйоперанд не вычисляется и возвращается значение 1.
Как уже отмечалось,присваивание, обозначаемое знаком = в Си/Си++ рассматривается как операция ивозвращает значение, которое было присвоено левому операнду. Операцияприсваивания вычисляется справа налево, т.е. сначала вычисляется присваиваемоезначение, затем выполняется присваивание. Это позволяет записывать выражениявида x = y = z = 1 для установки одинаковых значений нескольким переменным.Можно, хотя это и снижает наглядность программы, строить и выражения с побочнымэффектом вида (x = 2) * (y = 3) + (z = 4 ). Результатом этого выражения будет24, но одновременно переменные x, y и z получат новые значения.
Кроме простогоприсваивания имеется набор составных операций присваивания, в которыхприсваивание совмещается с указанной двуместной операцией. Запись x += yэквивалентна выражению x = x + y.
Для целых операндовопределены операции сдвига влево и вправо. При выполнении операции e1 > e2 ) если e1 имеет тип unsigned, освобождающиеся левыеразряды заполняются нулями, а при e1 типа signed в освобождающихся левыхразрядах повторяется знаковый разряд.
Над целыми операндамидопустимы операции поразрядного логического умножения, логического сложения иисключающего или (отрицания равнозначности). В этих операциях операндырассматриваются как последовательности битов и операция выполняется над каждойпарой соответствующих разрядов из обоих операндов. Например, результатомвыражения ( x >> ( p — n +1)) & ( ~(~0
В Си/Си++ имеетсяконструкция, которая называется условным выражением. Условное выражениестроится по схеме:
условие? выражение1:выражение2
В качестве условия можетвыступать любое скалярное выражение. Если результат вычисления условияненулевой, то значением всего выражения будет выражение1, при нулевом значенииусловия значение всего выражения определяется выражением2. Второй и третийоперанды условного выражения должны быть либо оба арифметического типа, либо однотипнымиструктурами или объединениями, либо указателями одинакового типа, либо один изних — указатель на какой-либо тип, а другой операнд NULL или имеет тип void*.Выражение x > 0? 1: 0 возвращает 1, если x больше 0, и 0 в противномслучае.
Выражение может бытьпредставлено последовательностью выражений, разделенных запятыми, в этом случаевычисляются все выражения слева направо и возвращается значение последнеговыражения в списке. Например в результате вычисления выражения
x = 2, e * 3, x +1 будетполучено значение 3 и попутно x получит значение 2. Результат умножения e * 3никак не может быть использован. 2.5 Операторы Си++
Операторы — этосинтаксические конструкции, определяющие действия, выполняемые программой. ВСи/Си++ имеются следующие типы операторов: операторы-выражения, операторывыбора, операторы цикла и оператор перехода. Синтаксис некоторых операторовсодержит выражения, играющие роль условий, в зависисмости от выполнения илиневыполнения которых выбирается та или иная последовательность действий.Поскольку в Си нет булевых выражений, в качестве условий используются любыевыражения, дающие скалярные значения, и условие считается выполненым, если этозначение отлично от нуля, и невыполненным, если оно равно нулю. Несколькооператоров могут быть объединены в составной оператор заключением их в фигурные(операторные) скобки. Признаком конца оператора ( кроме составного оператора)служит точка с запятой, являющаяся в этом случае частью оператора.
Перед любым операторомможет быть записана метка в виде идентификатора, отделенного от помечаемогооператора двоеточием. Метка служит только для указания ее в операторе перехода.
Наиболее простым являетсяоператор-выражение, представляющий собой полное выражение, закнчивающеесяточкой с запятой, например,
x = 3; y = (x +1) * t;i++;
Выражение, оформленноекак оператор, вычисляется, но его значение теряется и действиеоператора-выражения состоит в побочных эффектах, сопровождающих вычисление,например, при выполнении операций присваивания, автоувеличения иавтоуменьшения.
Операторы выбора вСи/Си++ представлены условным оператором и переключателем. Условный оператораналогичен условным операторам других языков программирования и можетиспользоваться в сокращенной и полной формах, которым соответствуют схемы:
if (выражение-условие)оператор
if (выражение-условие)оператор-1 else оператор-2
В сокращенной формеусловного оператора вычисляется выражение-условие и, если его значение отличноот нуля, выполняется следующий за условием оператор, в противном случае непроизводится никаких действий.
В полной форме условногооператора при ненулевом значении выражения-условия выполняется оператор-1 споследующим переходом к следующему оператору программы, а при нулевом значениивыражения условия выполняется оператор-2 с переходом к следующему операторупрограммы.
Переключатель позволяетвыбрать одну из нескольких возможных ветвей высислений и строится по схеме:
switch (целое выражение)оператор.
Оператор в этом случаепредставляет собой тело переключателя, практически всегда является составным иимеет такой вид:
{case константа-1:операторы
case константа-2:операторы
default: операторы
};
Выполнение переключателясостоит в вычислении управляющего выражения и переходе к группе операторов,помеченных case-меткой, равной управляющему выражению, если такой case-меткинет, выполняются операторы по метке default. Пункт default может отсутствоватьи тогда, если управляющему выражению не соответствуют ни одна case-метка, весьпереключатель эквивалентен пустому оператору. Следует учитывать, что привыполнении переключателя происходит переход на оператора с выбраннойcase-меткой и дальше операторы выполняются в естественном порядке. Например, в переключателе
switch (count)
{case 1: x=1;
case 2: x=2;
case 3: x=3;
default: x=4;
};если значение count равно 1, то послеперехода на case 1 будут выполнены все операторы, в результате x станет равным4. Чтобы разделить ветви переключателя, в конце каждой ветви нужно записатьоператор break, не имеющий операндов. По этому оператору происходит выход изпереключателя к следующему оператору программы:
switch (count)
{case 1: x = 1; break;
case 2: x = 2; break;
case 3: x = 3; break;
default: x = 4;
};Теперь в зависимости от значенияcount будет выполняться только одна ветвь переключателя и x будет приниматьодно из четырех предусмотренных значений.
В Си/Си++ имеется триварианта оператора цикла: цикл с предусловием, цикл с постусловием и цикл спараметром.
Цикл с предусловиемстроится по схеме
while (выражение-условие)оператор.
При каждом повторениицикла вычисляется выражение-условие и если значение этого выражения не равнонулю, выполняется оператор — тело цикла. Цикл с постусловием строится по схеме:
do оператор while(выражение-условие).
Выражение-условиевычисляется и проверяется после каждого повторения оператора — тела цикла, циклповторяется, пока условие выполняется. Тело цикла в цикле с постусловиемвыполняется хотя бы один раз.
Цикл с параметромстроится по схеме:
for (E1; E2; E3) оператор
где E1, E2 и E3 — выражения скалярного типа. Цикл с параметром реализуется по следующемуалгоритму:
1. Вычисляется выражениеE1. Обычно это выражение выполняет подготовку к началу цикла.
2. Вычисляется выражениеE2 и если оно равно нулю выполняется переход к следующему оператору программы (выход из цикла ). Если E2 не равно нулю, выполняется шаг 3.
3. Выполняется оператор — тело цикла.
4. Вычисляется выражениеE3 — выполняется подготовка к повторению цикла, после чего снова выполняетсяшаг 2.
Пусть требуетсяподсчитать сумму элементов некоторого массива из n элементов.
С использованием цикла спредусловием это можно сделать так:
int s=0;
int i=0;
while (i
Эта же задача сприменением цикла с постусловием решается следующими операторами:
int s = 0;
int i = 0;
do s +=a[ i++]; while ( i
Поскольку в данном случаеповторениями цикла управляет параметр i, эту задачу можно решить и с помощьюцикла третьего типа:
int i, s;
for ( s = 0, i = 0; i
Объявления переменныхможно внести в выражение E1 оператора for и все записать в виде одногооператора for с пустым телом цикла:
for ( int i = 0, s = 0; i
Для прерывания повторенийоператора цикла любого типа в теле цикла может быть использован оператор break.Для перехода к следующему повторению цикла из любого места тела цикла можетбыть применен оператор continue. Эти операторы по своему назаначению аналогичнысоответствующим операторам языка Паскаль.
Несмотря на то, что Си++содержит полный набор операторов для структурного программирования, в него всеже включен оператор перехода:
goto метка;
Метка задает адресперехода и должна помечать оператор в том же составном операторе, которомупринадлежит оператор goto. Вход в составной оператор, содержащий объявленияданных, не через его начало, запрещен.2.6 Функции
Любая программа наСи/Си++ содержит хотя бы одну функцию. Алгоритм решения любой задачиреализуется путем вызовов функций. Одна из функций считается главной и имеетфиксированное имя, эта функция вызывается операционной системой при запускепрограммы, а из нее могут вызываться другие функции. При работе в MS DOSглавная функция имеет имя main.
Описание функции имеетобщий синтаксис внешнего определения и отличается от описания переменногосинтаксисом декларатора-инициализатора, который содержит идентификатор (имя )функции и список параметров в круглых скобках. Тип функции — это тип значения,возвращаемого функцией. Функция может возвращать значение любого типа, кромемассива и функции, но допускается возвращать указатель на массив или указательна функцию. Если функция не возвращает никакого значения, тип функцииобозначается ключевым словом void.
В списке параметровуказываются типы и имена параметров, передаваемых в функцию при ее вызове. Еслифункция не требует передачи ей аргументов, список параметров может быть пустым.Для совместимости с программами, написанными на Си, рекомендуется в качествепустого списка параметров указывать ключевое слово void.
Полное описание функциисодержит также тело функции, представляющее собой составной оператор. Составнойоператор — это последовательность описаний внутренних данных и операторов,заключенная в фигурные скобки. Следует отметить, что в Си/Си++ описание функцийне может быть вложенным, в теле функции нельзя объявить другую функцию.
Функция, возвращающаясреднее арифметическое трех вещественных данных, может быть описана так:
double sred ( double x, double y, double z)
{ double s;
s = x + y + z;
return s / 3;
};
Для вызова такой функциипри условии, что предварительно объявлены переменные p, a, b, и c, можнозаписать оператор:
p = sred (a, b, c );
Очевидно, что функциядолжна быть описана до того, как встретится ее первый вызов. Это не всегдавозможно, например, когда две функции вызывают друг друга. Когда программасостоит из нескольких файлов, полное описание функции должно присутствоватьтолько в одном файле, но вызовы функции возможны из разных файлов программы.Чтобы разрешить подобные противоречия предусмотрены две формы описания функций,полная форма, называемая также определением функции, и сокращенная, называемаяописанием прототипа функции или просто прототипом. Прототип функции содержиттолько заголовок функции и задает таким образом имя функции, тип возвращаемогозначения и типы параметров. По этой информации при компиляции программногофайла можно проверить правильность записи вызова функции и использованиявозвращаемого значения. В Си++ требуется, чтобы вызову любой функциипредшествовало в том же файле либо полное определение функции либо описание еепрототипа.
Следует отметить ряддополнительных возможностей описания функций в Си++:
— При описании прототипафункции можно не указывать имена параметров, достаточно указать их типы.
— Для части параметровфункции можно задавать значение по умолчанию, что позволяет вызывать функцию сменьшим числом аргументов, чем предусмотрено описанием функции. Значение поумолчанию можно указывать только для последних параметров в списке. Например,функция sred могла бы быть описана так:
double sred ( double x, double y, double z = 0)
{ double s;
s = x + y + z;
return s / 3;
};
К такой функции можнообращаться с двумя и с тремя аргументами.
— Функция может иметьпеременное число параметров, для части параметров могут быть неизвестны ихтипы. Неизвестная часть списка параметров обозначается многоточием. Например,функция с прототипом
int varfunc ( int n,… );имеет один обязательный параметр типаint и неопределенное число параметров неизвестных типов. Правильнаяинтерпретация такого списка параметров в теле функции требует от программистадополнительных усилий.
При вызове функцииаргументы передаются в функцию по значениям. Это значит, что для каждогоаргумента в памяти создается его копия, которая и используется при вычислениифункции. Следовательно, любые изменения значений аргументов в теле функциитеряются при выходе из функции. Если аргумент является массивом, в функциюпередается указатель на начальный элемент массива и присваивания элементаммассива в теле функции изменяют значения элементов массива аргумента.
В С++ с объявлениемфункции связывается так называемая сигнатура функции, определяемая типомвозвращаемого значения и типами параметров. Это позволяет назначать функциям,имеющим аналогичное назначение, но использующим параметры разных типов,одинаковые имена. Например, наряду с приведенной выше функцией sred длявущественных аргументов, в той же программе может присутствовать функция
double sred ( int x, int y, int z = 0)
{int s;
s = x + y + z;
return s / 3;
};
Функция, в теле которойотсутствуют операторы цикла и переключатели, может быть объявлена сдополнительным описателем inline. В точке вызова такой функции при компиляциипросто вставляется тело функции с соответствующей заменой параметром нааргументы вызова. В результате экономится время на передачу параметров, переходна подпрограмму и организацию возврата в вызывающую программу. Функции сописателем inline называют встроенными, они реализуются как открытыепродпрограммы. Типичный пример такой функции — определение наибольшего(наименьшего) из двух чисел:
inline int max ( int x1, int x2)
{return x1 > x2? x1: x2 }
В системахпрограммирования Turbo C++ и Borland C++ главная функция (функция main) можетпринимать три параметра, что позволяет вызывать Си-программы из команднойстроки DOS с передачей в программу необходимых аргументов. Стандартный прототипфункции main имеет вид:
int main ( int argc, char *argv[ ], char *envp[ ] )
В конкретной программеможно объявлять функцию main без возвращаемого значения ( возвращающую тип void), с использованием только двух первых параметров или вообще без параметров.Параметры argc и argv служат для передачи в программу аргументов в виде массивастрок, argc содержит число элементов этого массива, а argv — это массивуказателей на элементы массива, причем первый элемент массива, на которыйуказывает argv [0], содержит имя программы (имя exe-файла программы), остальныеэлементы представляют собой аргументы из командной строки DOS. Параметр envpиспользуется для доступа к элементам текущей среды DOS.2.7 Библиотека времени выполнения
В определении языков Си иСи++ отсутствуют операторы ввода-вывода, операции над строковыми данными имногие другие средства, имеющиеся в других языках программирования. Этотнедостаток компенсируется добавлением в системы программирования Си и Си++библиотек функций, подключаемых к рабочим программам при редактировании связейи называемых библиотеками времени выполнения. Отделение этих библиотек откомпилятора позволяет в необходимых случаях использовать различные вариантыэтих библиотек, например, для различных моделей ЭВМ или операционных систем.
Часть функций избиблиотек времени выполнения стандартизована, в стандарте зафиксированы имяфункции, ее назначение, перечень и типы параметров и тип возвращаемогозначения. Другие функции ориентированы на конкретные модели ЭВМ и операционныесистемы, способы их вызова и использования могут быть различными в системахпрограммирования разных фирм.
Чтобы обеспечитьвозможность контроля правильности вызова функций при компиляции программы, всистему программирования входят файлы заголовков функций времени выполнения. Вфайлах заголовков определяются прототипы библиотечных функций, а так жеконстанты и типы данных, используемые этими функциями.
Ниже приведены именанекоторых файлов заголовков и назначение описанных в них прототипов группфункций.
Стандартом Си определеныследующие файлы заголовков:
ASSERT.H — Содержитмакросы для сообщений об ошибках при выполнении условия, задаваемогопрограммистом.
CTYPE.H — Функции дляпроверки и преобразования данных типа char.
FLOAT.H — Макросы дляопераций над числами с плавающей точкой.
LIMITS.H — Макросы,задающие диапазоны представления целых.
LOCALE.H — Представлениедаты, времени, денежных единиц.
MATH.H — Пакетстандартных математических функций.
SETJUMP.H — Имена типов ифункции для реализации операторов перехода, используется редко.
SIGNAL.H — Макросы длясигнализации об ошибках согласно стандарта ANSI.
STDARG.H — Макросы длявызова функций с переменным числом аргументов.
STDDEF.H — Определениеобщих типов для указателей, типов size_t и NULL.
STDIO.H — Стандартныефункции ввода-вывода.
STDLIB.H — Определениеобщих типов, переменных и функций.
STRING.H — Функции дляопераций над строковыми данными.
TIME.H — Структуры ифункции для операций с датами и временем.
В Си++ добавлены операциис комплексными числами и десятичными данными:
BCD.H — Данные,представленные в десятичной системе счисления
COMPLEX.H — Функции иоперации над комплексными числами.
Имеются также файлыпрототипов функций для распределения и освобождения динамической памяти,использования средств DOS и BIOS.
Чтобы использоватькакие-либо функции из библиотек времени выполнения в программу должен бытьвключен файл-заголовок с прототипами требуемых функций. Включение в программуфайла-заголовка обеспечивается препроцессорной директивой
# include
Например, для включениязаголовка с функциями ввод-вывода в стиле Си в начале программы записываетсястрока
Очевидно, любая программаиспользует какие-либо входные данные и куда-либо выводит полученные результаты,поэтому файл заголовков stdio.h присутствует почто во всех программах. Отметимнекоторые фугкции из этого файла. Функции ввода-вывода используют понятиепотока, рассматриваемого как последовательность байтов. Поток может быть связанс дисковым файлом или другим устройством ввода-вывода, в том числе с консолью,когда ввод осуществляется с клавиатуры, а вывод — на экран монитора.Предусмотрены несколько стандартных потоков:
stdin — стандартный ввод,
stdout — стандартныйвывод,
stderr — для вывода сообщенийоб ошибках,
stdprn — стандартноеустройство печати,
stdaux — стандартныйпоследовательный порт.
Потоки stdin, stdout иstderr обычно связываются с консолью, но могут быть переназначены на другиеустройства. Назначение двух последних потоков зависит от используемойаппаратуры. Стандартные потоки автоматически открываются при запускеСи-программы и закрываются при ее завершении. Потоки, создаваемыепрограммистом, открываются функцией fopen и закрываются функцией fclose.
Функции ввода-вывода изstdio.h условно можно разбить на четыре группы: ввод-вывод байтов, ввод-выводстрок, форматный ввод-вывод и так называемый прямой (бесформатный) ввод-вывод.Здесь отметим только отдельных представителей первых трех групп,предназначенных для ввода из потока stdin и вывода в поток stdout.
Функция int getchar( )служит для ввода одного символа с клавиатуры и возвращает код символа,преобразованный к типу int. Функция int putchar (int c) выводит символ c вочередную позицию на экране монитора.
Для ввода строки с клавиатурыслужит функци char * gets ( char * buf ), которая читает ввод с клавиатуры (досимвола новой строки или нажатия клавиши Enter) и помещает коды прочитанныхсимволов в буфер, адрес которого задается параметром buf, в конце строкидобавляется нулевой байт. Вывод строки выполняет функци int puts ( char *string), которая выводит строку по адресу string на экран, пока в строке невстретится нулевой байт и возвращает код последнего выведенного символа.
Функции форматноговвода-вывода принимают в качестве параметров строку с описанием форматапредставления данных на внешнем устройстве и список вводимых или выводимыхданных. Строка описания формата состоит из обычных символов, управляющихсимволов типа новой строки, возврата каретки и т.п. и спецификаций полей вводаили вывода. Каждая такая спецификация начинается символом % (процент) закоторым следуют коды флагов, размер поля ввода или вывода, число цифр в дробнойчасти числа, префикса размера данного и кода типа формата. Обязательноуказывать только тип формата, остальные компоненты спецификации форматазадаются по необходимости. Отметим некоторые коды типа формата:
d — для представленияцелого со знаком в десятичной системе счисления,
i — для представленияцелого без знака в десятичной системе счисления,
f — для представлениячисла с плавающей точкой в естественной форме,
e или E — представлениечисла с плавающей точкой в экспоненциальной форме,
s — ввод-вывод строковыхданных,
c — ввод-вывод символов.
Для форматного выводаслужит функция int printf ( char *format,… ), имеющая список параметровпеременной длины, количество дополнительных параметров должно соответствоватьчислу спецификаций формата в форматной строке, данные для вывода могутзадаваться выражениями, ответственность за правильное задание спецификацииформата для каждого выводимого данного полностью лежит на программисте. Примерприменения функции printf :
printf ( "\n x = %d, y = %f %s", x, y, st);
При выполнении этойфункции просматривается строка формата слева направо и символы не являющиесяспецификациями формата копируются на выводное устройство, управляющий символ \nпереводит курсор на экране к началу следующей строки, когда встречаетсяспецификация формата, выбирается очередной аргумент из списка вывода,преобразуется в в соответствии с форматом и выводится в очередную позициюэкрана. Для правильного вывода требуется, чтобы переменная x была типа int, y — типа float, а переменная st — типа char*.
Форматный ввод выполняетфункция int scanf ( char *format, ...) в которой список ввода должен задаватьуказатели на вводимые переменные. Если в строке формата присутствуют символы,не входящие в спецификации форматов и не являющиеся пробельными, то во входномпотоке должны присутствовать те же символы. Пробельными символами считаютсязнаки пробела, табуляции, новой строки, они считываются из потока ввода, но неучаствуют в формировании входных данных. Когда в форматной строке встречаетсяспецификация формата, во входном потоке проаускаются пробельные символы, апоследующие символы интерпретируются в соответствии с типом формата,преобразуются во внутреннее представление и полученное значение записывается впамять по адресу очередного элемента списка ввода. Например, для ввода двухпеременных целого типа и одной вещественного типа можно применить операторавызова функции
scanf ( "%d %d %f", &x1, &x2, &y );
Здесь x1 и x2 должны бытьтипа int, а y — типа float. Во входном потоке вводимые значения должныразделяться хотя бы одним пробелом.
Более полную информацию офункциях ввода-вывода в стиле Си можно получить в справочной системеинтегрированной среды Borland C++.
Недостатком рассмотренныхвыше функций ввода-вывода является отсутствие контроля соответствия типовформатов и типов вводимых (выводимых) данных, часто приводящее к ошибкам впрограммах. В Си++ включены собственные средства потокового ввода-вывода,обеспечивающие жесткий контроль типов в операциях ввода-вывода. Для этогоопределены четыре новых стандартных потока:
cin — для ввода данных,
cout — для вывода данных,
cerr — вывод сообщений обошибках без буферизации вывода,
clog — вывод сообщений обошибках с буферизацией вывода.
В качестве знака операциивывода определены знаки >, теже, что и для операций сдвига. Компилятор по контексту определяет, какую операциюзадают эти символы, ввод-вывод или сдвиг.
Чтобы использоватьсредства ввода-вывода Си++ в программу должен быть включен файл-заголовокiostream.h :
# include
В операциях вывода левымоперандом должен быть поток вывода, правым операндом — выводимое данное.Результатом операции вывода является поток вывода, что позволяет записыватьвывод в виде цепочки операций
cout
Для базовых типов данныхопределены их преобразования при выводе и точность представления на устройствевывода. Эти характеристики можно менять, применяя специальные функции,называемые манипуляторами.
В операции ввода левымоперандом должен быть поток ввода, а правым операндом — имя вводимого данногодля арифметических данных или указатель типа char* для ввода строк, например,
cin >> x1 >> x2 >>st;
Операции ввода-выводавыполняются слева направо и последний оператор эквивалентен оператору ((cin>>x1) >> x2) >> st; или трем операторам
cin >> x1; cin >> x2; cin >> st;
В заключение приведемпример простой программы, запрашивающей у пользователя два целых числа ивыводящей на экран их сумму:
# include
int x, y ;
int main ( )
{cout > x; // Запрос и ввод значения x
cout > y; // Запрос и ввод значения y
cout
return 0;
}.