Основы программирования
Глава 5. Введение в язык C++
Глава 6. Работа с данными
Глава 7. Инструкции
Глава 8. Функции
Глава 9. Массивы
Глава 10. Указатели
Глава 11. Основы ввода-вывода в языке С++
Глава 12. Структуры, объединения и дополнительные типы данных
Глава 13. Дополнительные средства программирования
Глава 14. Программирование: основные библиотеки С/С++
Глава 5
Введение в язык C++
Знакомство с языком программирования C++ мы начнем с исторической справки. Знание истории поможет уяснить, почему С и C++ на протяжении нескольких десятилетий остаются столь популярным среди программистов, а также, что самое главное, понять концепции, лежащие в основе этих языков.
Примеры программ, рассматриваемые в дальнейшем, следует вводить и запускать в среде Microsoft Visual C++. Повторите материал, изложенный в предыдущих главах, где описаны элементарные операции по компиляции и отладке программ.
ПРИМЕЧАНИЕ. При компиляции некоторых программ, представленных в примерах, возможен вывод сообщений об усечении данных, возникшем во время преобразования из типа double в тип float. В процессе работы над примерами настоящей главы такие предупреждения можно оставлять без внимания.
Из истории языка C
История языка C восходит к операционной системе UNIX, поскольку она сама и большинство программ для нее написаны на C. Однако это не означает, что С предназначен исключительно для среды UNIX. Благодаря популярности системы UNIX язык С был признан в среде программистов языком системного программирования, который можно использовать для написания компиляторов и операционных систем. В то же время он удобен для создания разнообразных прикладных программ.
Операционная система UNIX разработана в 1969 г. на маломощном, по современным представлениям, компьютере DEC PDP-7 компанией Bell Laboratories (город Мюррей Хилл, штат Нью-Джерси). Система была полностью написана на языке ассемблера для PDP-7 и претендовала на звание "дружественной для программистов", поскольку содержала довольно мощный набор инструментов разработки и являлась достаточно открытой средой. Вскоре после создания системы UNIX Кен Томпсон (Ken Thompson) написал компилятор нового языка B.
С этого момента можно проследить историю языка C, поскольку язык B Кена Томпсона является его предшественником. Итак, вот "родословная" языка С:
Язык Год и место создания
Algol 60 Разработан международным комитетом в 1960 г.
CPL Combined Programming Language - комбинированный язык программирования. Разработан в 1963 г. группой программистов из Кембриджского и Лондонского университетов
BCPL Basic Combined Programming Language - базовый комбинированный язык программирования. Разработан в Кембридже Мартином Ричардсом (Martin Richards) в 1967 г.
B. Разработан Кеном Томпсоном, компания Bell Labs, в 1970 г.
С. Разработан Деннисом Ритчи (Dennis Ritchie), компания Bell Labs, в 1972 г.
Позднее, в 1983 г., при Американском институте национальных стандартов (American National Standards Institute - ANSI) был учрежден специальный комитет с целью стандартизации языка C, что обусловило разработку стандарта ANSI C.
Язык Algol 60 появился всего на несколько лет позже языка FORTRAN. Это был значительно более мощный язык, который во многом предопределил пути дальнейшего развития большинства последующих языков программирования. Его создатели уделили много внимания логической целостности синтаксиса команд и модульной структуре программ, с чем, собственно, впоследствии и ассоциировались все языки высокого уровня. К сожалению, Algol 60 не стал популярным в США. Многие считают, что причиной тому была определенная его абстрактность.
Разработчики языка CPL попытались приблизить "возвышенный" Algol 60 к реалиям конкретных компьютеров. Однако этот язык остался таким же трудным для изучения и практического применения, как и Algol 60, что предопределило его судьбу. В наследство от CPL остался язык BCPL - упрощенная версия языка CPL, сохранившая лишь основные его функции.
Когда Кен Томпсон взялся за разработку языка B для ранней версии системы UNIX, он попытался еще больше упростить язык CPL. Ему удалось создать довольно интересный язык, который эффективно работал на том оборудовании, для которого был спроектирован. Языки B и BCPL, очевидно, были упрощены больше, чем нужно: их применение ограничивалось решением весьма специфических задач.
Например, когда Кен Томпсон завершил работу над языком B, появился новый компьютер PDP-11. Система UNIX и компилятор языка B были сразу же модернизированы в соответствии с характеристиками новой машины. Хотя PDP-11 по мощности превышал своего предшественника PDP-7, возможности этого компьютера оставались все еще слишком скромными по сравнению с современными стандартами. Он имел только 24 Кб оперативной памяти, из которых 16 Кб отводились операционной системе, емкость жесткого диска составляла 512 Кб. Возникла идея переписать систему UNIX на языке B. Но B работал слишком медленно, поскольку оставался языком интерпретирующего типа. Существовала и другая проблема: язык B ориентировался на работу со словами, тогда как компьютер PDP-11 оперировал байтами. Стала очевидной необходимость усовершенствования языка B. Работа над более совершенной версией, которую назвали языком C, началась в 1971 г.
Деннис РитчиДеннис Ритчи, создатель языка C, частично восстановил такое свойство языка, как независимость от конкретного оборудования, которым практически не обладали языки BCPL и B. За счет этого были успешно введены в эксплуатацию типы данных и в то же время сохранена возможность прямого доступа к оборудованию - идея, заложенная еще в языке CPL.
Многие языки программирования, разработанные одним автором (C, Pascal, Lisp и APL), отличались большей целостностью, чем те, над которыми трудились группы разработчиков (Ada, PL/I и Algol 60). Кроме того, для языков первой категории характерна определенная ориентация на решение тех вопросов, в которых автор наиболее компетентен. Деннис Ритчи - признанный специалист в области системного программирования, а именно в области языков программирования, операционных систем и генераторов программ. Учитывая профессиональную направленность автора, нетрудно понять, почему C был признан прежде всего разработчиками системных программ. Язык C представлял собой язык программирования относительно низкого уровня, что обеспечивало контроль каждой мелочи в работе алгоритма и достижение максимальной эффективности. Но в то же время в C были заложены принципы языка высокого уровня, что позволяло избежать зависимости программ от особенностей архитектуры конкретного компьютера. Это повышало эффективность процесса программирования.
Отличия языка C от ранних языков высокого уровня
Вам, конечно, интересно узнать, какое место занимает C в ряду языков программирования. Чтобы ответить на этот вопрос, рассмотрим иерархию языков, где точками представлены промежуточные этапы развития (рис. 4.1). К примеру, давние предки компьютеров, такие как станок Джакарда (Jacquard's loom, 1805 г.) и счетная машина Чарльза Беббиджа (Charles Babbage, 1834 г.), программировались механически для выполнения строго определенных функций. Не исключено, что в скором будущем мы сможем управлять компьютерами, посылая нейронные импульсы непосредственно в кору головного мозга, например, подключив воспринимающие контакты к височной области мозга (участвующей в запоминании слов) или к области Брока (управляющей функцией речи).
Рис. 5.1. Теоретическая эволюция языков программирования
Первые языки ассемблера, которые появились вместе с электронными вычислительными машинами, позволяли работать непосредственно со встроенным набором команд компьютера и были достаточно простыми для изучения. При их использовании проблема рассматривалась с точки зрения работы оборудования. Поэтому программисту постоянно приходилось заниматься перемещением байтов между регистрами, осуществлять их суммирование, сдвиг и, наконец, запись в нужные области памяти для хранения результатов вычислений. Это была очень утомительная работа с высокой вероятностью допуска ошибок.
Первые языки высокого уровня, например FORTRAN, разрабатывались как альтернатива языкам ассемблера. Они обеспечивали определенный уровень абстрагирования от аппаратной среды и позволяли строить алгоритм с точки зрения решаемой задачи, а не ресурсов компьютера. К сожалению, их создатели не учли динамики развития компьютерной техники и в погоне за упрощением процесса программирования упустили ряд существенных моментов. Языки FORTRAN и Algol оказались слишком абстрактными для системных программистов. Эти языки были проблемно-ориентированными, рассчитанными на решение общих инженерных, научных и экономических задач. Программистам, занимающимся разработкой новых системных продуктов, по-прежнему приходилось полагаться только на старые языки ассемблера.
Чтобы разрешить эту проблему, разработчикам языков пришлось сделать шаг назад и создать класс машинно-ориентированных языков. К таким языкам низкого уровня относились BCPL и B. Но при работе с ними возникла другая проблема: они были приспособлены только для компьютеров определенной архитектуры. Язык C оказался намного прогрессивнее по сравнению с машинно-ориентированными языками и при этом сохранил достаточно "низкий" уровень программирования. Этот язык позволяет контролировать процесс выполнения программы компьютером, игнорируя в то же время особенности аппаратной среды. Вот почему C рассматривается одновременно как язык программирования высокого и низкого уровней.
Достоинства языка C
Часто для определения языка программирования достаточно просто взглянуть на исходный текст программы. Так, текст программы на языке APL ассоциируется с иероглифами, текст на языке ассемблера представляет собой столбцы мнемоник, а язык Pascal характеризуется своим удобочитаемым синтаксисом. А что можно сказать о языке C? Многие программисты, впервые столкнувшиеся с ним, находят его слишком замысловатым. Конструкции, напоминающие выражения на английском языке, которые характерны для многих языков программирования, в C встречаются довольно редко. Вместо этого программист сталкивается с необычного вида операторами и обилием указателей. Многие возможности языка С реализованы в соответствии с методами программирования на компьютерах, существовавших в момент его появления. Далее мы поговорим о сильных сторонах языка C.
Оптимальный размер программы
В C имеется значительно меньше синтаксических правил, чем в других языках программирования. Для эффективной работы компилятора достаточно всего 256 Kб оперативной памяти. Список операторов и их комбинаций в языке C обширнее, чем список ключевых слов.
Сокращенный набор ключевых слов
Первоначально, в том виде, в каком его создал Деннис Ритчи, язык C содержал всего 27 ключевых слов. Когда появился стандарт ANSI C, было добавлено несколько новых зарезервированных слов. В Microsoft C набор ключевых слов расширен и их количество теперь превышает 70.
Многие функции, представленные в большинстве других языков программирования, не включены в язык C. Например, в C нет встроенных функций ввода/вывода, отсутствуют математические функции (за исключением базовых арифметических операций) и функции работы со строками. Но если для большинства языков отсутствие таких функций стало бы недостатком, с C этого не случилось, поскольку он предоставляет доступ к богатым библиотекам функций. Обращения к библиотечным функциям в программах на языке C происходят столь часто, что эти функции можно считать составной частью языка. В то же время их легко переписать без ущерба для структуры программы, в чем состоит бесспорное преимущество C.
Быстрое выполнение программ
Программы, написанные на языке C, отличаются высокой эффективностью. Благодаря небольшому размеру исполняемых модулей, а также тому, что C представляет собой язык достаточно низкого уровня, скорость выполнения программ на языке C соизмерима со скоростью работы их ассемблерных аналогов.
Упрощенный контроль за типами данных
В отличие от языка Pascal, в котором ведется строгий контроль типов данных, в C понятие типа данных трактуется несколько шире. Это унаследовано от языка B, где практиковалось такое же свободное обращение с данными разных типов. Язык C позволяет в одном месте программы рассматривать значение переменной как символ, а в другом - как ASCII-код этого символа, от которого можно отнять 32, чтобы перевести символ в верхний регистр.
Реализация принципа проектирования "сверху вниз"
Язык C содержит все управляющие конструкции, характерные для современных языков программирования, в том числе инструкции for, if/else, switch/case, while. На момент появления языка это было очень большим достижением. Язык С также позволяет создавать изолированные программные блоки, в пределах которых переменные имеют собственную область видимости. Разрешается создавать локальные переменные и передавать в подпрограммы значения параметров, а не сами параметры, чтобы защитить их от модификации.
Модульная структура
Язык C поддерживает модульное программирование, суть которого состоит в возможности раздельной компиляции и компоновки разных частей программы. Например, вы можете выполнить компиляцию только той части программы, которая была изменена в ходе последнего сеанса редактирования. Это значительно ускоряет процесс разработки крупных и средних проектов, особенно на машинах с ограниченными ресурсами. Если бы язык C не поддерживал модульное программирование, то после внесения небольших изменений в программный код пришлось бы компилировать всю программу целиком, что может занять слишком много времени.
Возможность использования кодов ассемблера
Большинство компиляторов C позволяет обращаться к подпрограммам, написанным на ассемблере. В сочетании с возможностью раздельной компиляции и компоновки это позволяет с легкостью создавать приложения, в которых используется код как высокого, так и низкого уровня. Кроме того, в большинстве систем из ассемблерных программ можно вызывать подпрограммы, написанные на C.
Возможность управления отдельными битами данных
Очень часто в системном программировании возникает необходимость управления переменными на уровне отдельных битов. Поскольку своими корнями язык C прочно связан с операционной системой UNIX, в нем представлен довольно обширный набор операторов побитовой арифметики.
Наличие указателей
Язык, используемый для системного программирования, должен предоставлять возможность обращаться к области памяти с определенным адресом, что значительно повышает скорость выполнения программы. В С эта возможность реализуется за счет использования указателей. Хотя указатели применяются и во многих других языках программирования, только C позволяет выполнять по отношению к указателям арифметические операции. Например, если переменная student_record_ptr указывает на первый элемент массива student_records, то выражение student_record_ptr + 1 будет указывать на второй элемент массива.
Возможность гибкого управления структурами данных
Все массивы данных в языке C одномерны. Но C позволяет создавать конструкции из одномерных массивов, получая таким образом многомерные массивы.
Эффективное использование памяти
В случае программирования на языке C достаточно эффективно используется память компьютера, а за счет отсутствия встроенных функций в программу загружаются только те библиотеки функций, которые ей действительно необходимы.
Возможность кросс-платформенной переносимости
Важной характеристикой любой программы является возможность ее запуска на разных платформах. В современном компьютерном мире программы, написанные на языке C, в наибольшей степени независимы от платформы. Это особенно справедливо в отношении персональных компьютеров.
Мощные библиотеки готовых функций
Имеется множество специализированных коммерческих библиотек функций, доступных любому компилятору C. Это библиотеки для работы с графикой, базами данных, окнами, сетевыми ресурсами и т. д. При использовании библиотек функций значительно сокращается время, необходимое для разработки приложений.
Недостатки языка С
Не существует абсолютно совершенных языков программирования. Дело в том, что каждая задача решается особым образом. При выборе языка программист должен исходить из того, какие именно задачи он собирается решать с помощью своей программы. Это первый вопрос, на который необходимо ответить еще до того, как начнется работа над программой, поскольку, если впоследствии выяснится, что выбранный язык не подходит для решения поставленных задач, то все придется начинать сначала. От правильного выбора языка программирования в конечном счете зависит успех всего проекта. Следующая тема, которую мы затронем, - слабые стороны языка C.
Упрощенный контроль за типами данных
То, что язык C не обеспечивает строгого контроля за типами данных, можно считать как достоинством, так и недостатком. В некоторых языках программирования присвоение переменной, имеющей один тип, значения другого типа считается ошибкой, если при этом явно не указана функция преобразования. Это предотвращает возникновение ошибок, связанных с неконтролируемым округлением значений.
Как было сказано выше, язык C позволяет присваивать целое число в качестве значения символьной переменной и наоборот. Это не проблема для опытных программистов, но для новичков данная особенность может оказаться одним из источников побочных эффектов. Побочными эффектами называются неконтролируемые изменения значений переменных. Поскольку C не осуществляет строгого контроля за типами данных, это обеспечивает большую гибкость при манипулировании переменными. Так, в одном выражении оператор присваивания (=) может использоваться несколько раз. Но это также означает, что в программе могут встречаться выражения, тип результата которых неясен или трудно определим. Если бы C требовал однозначного определения типа данных, то это устранило бы появление побочных эффектов и неожиданных результатов, но в то же время сильно уменьшило бы мощь языка, сведя его к обычному языку высокого уровня.
Ограниченные средства управления ходом выполнения программы
Вследствие того что выполнение программ на языке C слабо контролируется, многие их недостатки могут остаться незамеченными. Например, во время выполнения программы не поступит никаких предупреждающих сообщений, если осуществлен выход за границы массива. Это та цена, которую пришлось заплатить за упрощение компилятора ради его быстродействия и эффективности использования памяти.
Язык C - не для любителей!
Огромный набор предоставляемых возможностей (от побитового управления данными до форматированного ввода/вывода), а также относительная независимость и стабильность выполнения программ на разных машинах сделали язык С чрезвычайно популярным в среде программистов. Во многом благодаря тому, что операционная система UNIX написана на языке C, она получила такое широкое распространение во всем мире и используется на самых разных компьютерах.
Тем не менее, как и всякое другое мощное средство программирования, язык C требует ответственности от программистов, использующих его. Программист должен строго соблюдать правила и следовать соглашениям, принятым в этом языке, тщательно документировать программу, чтобы в ней мог разобраться другой программист, а также сам автор по прошествии некоторого времени.
Стандарт ANSI C
Специальный комитет Американского института национальных стандартов (ANSI) занимался разработкой стандартов языка C. Давайте рассмотрим, какие изменения были предложены и внесены в язык программирования в результате работы этого комитета. Основные цели внесения изменений заключались в том, чтобы повысить гибкость языка и стандартизировать предлагаемые различными компиляторами возможности.
Ранее единственным источником, содержащим информацию о стандарте языка C, была книга Б. Кернигана и Д. Ритчи "Язык программирования С" (Prentice Hall, 1978 г.). В этой книге не описывались отдельные технические детали языка, что не гарантировало однотипности компиляторов C, создаваемых разными фирмами. Стандарт ANSI должен был устранить эту неоднозначность. Хотя некоторые из предложенных изменений могли привести к возникновению проблем при выполнении ранее написанных программ, ожидалось, что негативный эффект не будет существенным.
Введение стандарта ANSI должно было в еще большей степени, чем раньше, обеспечить совместимость языка C с различными компьютерными платформами. Хотя, конечно, внесенные изменения не привели к устранению всех противоречий, возникающих в процессе использования C-программ на разных компьютерах. По-прежнему в большинстве случаев для эффективного использования программы на компьютере с другой архитектурой в код требовалось внести ряд изменений.
Комитет ANSI также подготовил в виде меморандума ряд положений, которые выражали "дух языка C". Вот некоторые из них.
Доверяйте программистам.
Не ограничивайте возможность программиста делать то, что должно быть сделано.
Язык должен оставаться компактным и простым.
Дополнительно были предприняты меры для того, чтобы обеспечить соответствие стандарта ANSI (американского) стандарту, предложенному ISO (International Standard Organization - Международная организация по стандартизации). Благодаря этому язык C оказался, пожалуй, единственным языком программирования, который эффективно применяется в разноязычных средах с различными кодовыми таблицами. В табл. 5.1. перечислены некоторые аспекты языка C, стандартизированные комитетом ANSI.
От С к С++ и объектно-ориентированному программированию
Язык C++ сочетает все возможности, предоставляемые языком C, и средства объектно-ориентированного программирования. Он позволяет решать задачи на достаточно высоком уровне абстракции, превосходя в этом отношении даже язык Ada, поддерживает модульное программирование, как в Modula-2, и сохраняет при этом простоту, компактность и эффективность языка C.
В этом языке органично сочетаются стандартные процедурные подходы, хорошо знакомые большинству программистов, и объектно-ориентированные методики, позволяющие находить объектные решения поставленных задач. На практике в одном приложении на языке C++ можно использовать одновременно как процедурные, так и объектно-ориентированные принципы. Этот дуализм языка C++ представляет собой особую проблему для начинающих программистов, поскольку от них требуется не только изучить новый язык программирования, но и освоить новый стиль мышления и новые подходы к решению задач.
Из истории языка C++
Вряд ли вас удивит тот факт, что языки C++ и C имеют общие корни. В то же время C++ впитал в себя многие идеи, реализованные не только в языках BCPL и Algol 60, но и в Simula 67. Возможность перегрузки операторов и объявления переменных непосредственно перед их первым использованием сближает C++ с языком Algol 60. Концепция подклассов (или производных классов) и виртуальных функций заимствована из Simula 67. Впрочем, все популярные языки программирования представляют собой набор усовершенствованных средств и функций, взятых из других, более ранних, языков программирования. Но, безусловно, язык C++ наиболее близок к языку C.
Язык C++ разработан в начале 80-х Бьярном Страуструпом (Bjarne Stroustrup), сотрудником компании Bell Labs. Сам доктор Страуструп утверждает, что название C++ было предложено Риком Масситти (Rick Mascitti). C++ изначально предназначался для разработки некоторых высокоточных моделей событий, поскольку необходимый уровень эффективности не мог быть достигнут с помощью других языков программирования. Впервые C++ был использован вне стен лаборатории доктора Страуструпа в 1983 г., но еще до лета 1987 г. шли работы по отладке и совершенствованию этого языка.
При создании языка C++ особое внимание уделялось сохранению совместимости с языком С. Необходимо было сохранить работоспособность миллионов строк программных кодов, написанных и скомпилированных на C, а также доступ к множеству разработанных библиотек функций и средств программирования языка C. Следует отметить, что в этом направлении были достигнуты значительные успехи. Во всяком случае многие программисты утверждают, что преобразование программных кодов от C к C++ выполняется значительно проще, чем это происходило ранее, например, при переходе от языка FORTRAN к C.
С помощью C++ можно создавать широкомасштабные программные проекты. Благодаря тому, что в языке C++ усилен контроль за типами данных, удалось преодолеть многие побочные эффекты, характерные для языка C.
Но наиболее важным приобретением языка C++ все-таки является объектно-ориентированное программирование (ООП). Чтобы воспользоваться всеми преимуществами C++, пришлось изменить привычные подходы к решению задач. Основная проблема заключается теперь в определении объектов и связанных с ними операций, а также в формировании классов и подклассов.
Эффективность объектно-ориентированного подхода
Остановимся ненадолго на объектно-ориентированном программировании, чтобы на примере показать, как использование абстрактных объектов языка C++ может облегчить решение прикладных задач. Предположим, нужно написать программу на языке FORTRAN для оперирования таблицей успеваемости студентов. Для решения этой задачи потребуется создать ряд массивов, представляющих различные поля таблицы. Все массивы должны быть связаны общим индексом. Для создания таблицы из десяти полей необходимо запрограммировать доступ к десяти массивам с помощью единого индекса, чтобы данные из разных массивов возвращались как единая запись таблицы.
В C++ решение этой задачи сводится к объявлению простого объекта student_database, способного принимать сообщения add_student, delete_student, access_student и display_student для оперирования данными, содержащимися в объекте. Обращение к объекту student_database реализуется очень просто. Допустим, нужно добавить новую запись в таблицу. Для этого достаточно ввести в программу следующую строку:
student_database.add_student(new_recruit)
В данном примере функция add_student() является методом класса, связанного с объектом student_database, а параметр new_recruit представляет собой набор данных, добавляемый в таблицу. Обратите внимание на то, что класс объектов student_database не является встроенным типом данных языка C++. Наоборот, мы расширили язык программирования собственными средствами, предназначенными для решения конкретной задачи. Возможность создавать новые классы или модифицировать существующие (порождать от них подклассы) позволяет программисту естественным образом устанавливать соответствие между понятиями предметной области и средствами языка программирования.
Незаметные различия между языками C и C++
Ниже мы рассмотрим некоторые отличия языка C++ от C, не связанные с объектным программированием.
Синтаксис комментариев
В C++ комментарии могут вводиться с помощью конструкции //, хотя старый метод маркирования блока комментариев посредством символов /* и */ по-прежнему допустим.
Перечисления
Имя перечисления является названием типа данных. Это упрощает описание перечислений, поскольку устраняет необходимость в указании ключевого слова enum перед именем перечисления.
Структуры и классы
Имена структур и классов являются названиями типов данных. В языке C понятие класса отсутствует. В C++ нет необходимости перед именами структур и классов указывать ключевое слово struct или class.
Область видимости в пределах блока
Язык C++ дает возможность объявлять переменные внутри блоков программного кода, то есть непосредственно перед их использованием. Переменная цикла может объявляться даже внутри инициализатора цикла, как в следующем примере:
// Объявление переменной непосредственно в месте ее использования
for(int index = 0; index < MAX_ROWS; index++)
Оператор расширения области видимости
Для разрешения конфликтов, связанных с именами переменных, введен оператор ::. Например, если некоторая функция имеет локальную переменную vector_location и существует глобальная переменная с таким же именем, то выражение ::vector_location позволит обратиться к глобальной переменной в пределах указанной функции.
Ключевое слово const
С помощью ключевого слова const можно запретить изменение значения переменной, данных, адресуемых указателем, и даже значения адреса, хранящегося в самом указателе.
Безымянные объединения
Безымянные объединения могут быть описаны всюду, где допускается объявление переменной или поля. Эта возможность обеспечивает экономное использование памяти, поскольку в одной и той же области могут храниться значения из нескольких полей одной структуры.
Явные преобразования типов
Существуют правила присвоения имен переменных, применение которых позволяет неявно преобразовывать типы данных. В некоторых случаях удобнее явное преобразование, чем обычная операция приведения типов.
Уникальные особенности функций
Язык C++ должен понравиться программистам, работавшим ранее с языками Pascal, Modula-2 и Ada, поскольку позволяет задавать имена и типы параметров функций непосредственно внутри круглых скобок, следующих за именем функции. Например:
void* dupmen(void *dest, int c, unsigned count)
{
.
.
.
}
В языке С после принятия стандарта ANSI также появилась возможность использования таких выражений.
Транслятор языка C++ проверяет соответствие фактических типов значений, переданных в функцию, формальным типам аргументов функции. Также выполняется проверка соответствия типа возвращаемого значения типу переменной, которой присваивается это значение. Подобная проверка типов не предусмотрена в большинстве версий языка C.
Значения параметров функций по умолчанию
В C++ можно присваивать параметрам функций значения по умолчанию. В этом случае параметры, для которых значения не заданы явно, получают их автоматически в соответствии с установками по умолчанию.
void function_with_default_argument_values( char cValue,
int iValue = 0)...
Функция может быть вызвана двумя способами:
function_with_default_argument_values('A'); // или
function_with_default_argument_values(cVariable); // или
function_with_default_argument_values('A', 10); // или
function_with_default_argument_values(cVariable, iVariable);
В первых двух строках вызова функции формальный аргумент iValue будет иметь значение по умолчанию, то есть 0, тогда как в последних двух строках вызова функции для аргумента iValue применяются фактические значения. Значение по умолчанию, присваиваемое формальному аргументу, позволяет гарантировать, что вызов подпрограммы не приведет к аварийной ситуации в тех случаях, когда в вызывающей программе содержатся критические логические элементы.
Списки аргументов переменного размера
В C++ с помощью многоточия (...) могут быть описаны функции с неопределенным набором параметров. Контроль за типами параметров таких функций не ведется, что повышает гибкость их использования.
Использование ссылок на аргументы функций
С помощью оператора & можно задать передачу аргументов функции по ссылке, а не по значению. Например:
int i;
increment(i);
.
.
.
void increment(int &variable_reference)
{
variable_reference++;
}
Поскольку параметр variable_reference определен как ссылка, его адрес присваивается адресу переменной i при вызове функции increment(). Последняя выполняет приращение значения параметра, записывая его в переменную i. При этом, в отличие от языка C, нет необходимости в явной передаче адреса переменной i функции increment().
Макроподстановка функций!
Ключевое слово inline говорит о том, что при раскрытии вызова функции компилятор должен не записывать ссылку на нее, а выполнять подстановку ее кода целиком, что повышает быстродействие программы, если объем функций невелик.
Операторы new и delete
Новые операторы new и delete языка C++ позволяют выделять и удалять в программе динамические области памяти.
Указатели типа void
В языке C++ тип void используется для обозначения того, что функция не возвращает никаких значений. Указатель, имеющий тип void, может быть присвоен любому другому указателю базового типа.
Ключевые различия между C и C++
Наиболее существенное отличие C++ от C состоит в использовании концепции объектно-ориентированного программирования. Ниже рассмотрены связанные с этим новые средства языка C++.
Классы и инкапсуляция данных
Классы являются основой объектно-ориентированного программирования. Определение класса включает в себя объявления всех полей, возможно, с начальными значениями, а также описания функций, предназначенных для манипулирования значениями полей. Такие функции называются методами. Объекты являются переменными типа класса. Каждый объект может содержать собственные наборы закрытых и открытых данных.
Структуры в роли классов
Структура представляет собой класс, все члены которого являются открытыми, то есть закрытые и защищенные разделы отсутствуют. Такой класс может содержать как поля данных (что предполагается в стандарте ANSI C), так и функции.
Конструкторы и деструкторы
Конструкторы и деструкторы являются специальными методами, предназначенными для создания и удаления объектов класса. При объявлении объекта вызывается конструктор инициализации. Деструктор автоматически удаляет из памяти указанный объект, когда тот выходит за пределы своей области видимости.
Сообщения
Основным средством манипуляции объектами являются сообщения. Сообщения посылаются объектам (переменным типа класса) посредством особого механизма, по действию напоминающего вызов функции. Набор сообщений, которые могут посылаться объектам класса, задается при описании этого класса. Каждый объект отвечает на полученное сообщение, выполняя определенные действия. Например, если есть объект Palette_Colors, для которого задан метод SetNumColors_Method, требующий указания одного целочисленного параметра, то передача объекту сообщения будет выглядеть следующим образом:
Palette_Colors.SetNumColors_Method(16);
Дружественные функции
Концепция инкапсуляции данных в классе делает невозможным доступ к внутренним данным объекта извне. Другими словами, закрытые члены классов недоступны функциям, не являющимся членами этого класса. Но в C++ предусмотрена возможность объявления внешних функций и классов "друзьями" определенного класса. Дружественные функции, не являясь членами класса, получают доступ к описанным в нем переменным и методам.
Перегрузка операторов
Уникальной особенностью C++ является возможность изменения смысла большинства базовых операторов и функций языка, что позволяет применять их к объектам различных классов, а не только к данным стандартных типов. Например, обычно разные функции имеют разные имена, однако функции, выполняющие одни и те же действия, но по отношению к объектам различных типов, иногда удобнее называть одним именем. Компилятор находит нужную для вызова функцию на основе списка аргументов вызова. Ниже представлен пример вызова трех функций с общим именем total(), перегружаемых для массивов типов int, float и double.
int total(int isize, int iarray[]);
float total(int isize, float farray[]);
double total(int isize, double darray[]);
.
.
.
Поскольку три различные функции были объявлены под одним именем, компилятор выбирает нужную функцию на основании соответствия списка аргументов в вызывающем операторе списку параметров функции:
total(isize, iarray);
total(isize, farray);
....total(isize, darray);
Производные классы
Производный класс можно рассматривать как подкласс некоторого базового класса. Возможность порождения новых классов позволяет формировать сложные иерархические модели. Объекты производного класса наследуют все переменные и методы родительского класса, но в дополнение к ним могут содержать собственные переменные и методы.
Полиморфизм и виртуальные функции
Чтобы понять смысл термина полиморфизм, рассмотрим древовидную иерархию родительского класса и порожденных от него подклассов. Каждый подкласс в этой структуре может получать сообщения с одинаковым именем. Когда объект какого-либо подкласса принимает такое сообщение, он на основании типа и количества переданных параметров определяет, к какому классу относится данное сообщение, и предпринимает соответствующие действия. Если сообщения сразу нескольких классов в иерархии имеют одинаковый формат, считается, что сообщение относится к ближайшему классу в иерархии. Методы родительского класса, которые могут переопределяться в подклассах, называются виртуальными и создаются с указанием ключевого слова virtual.
Потоковые классы
Язык C++ содержит дополнительные средства ввода/вывода. Три объекта cin, cout и cerr, подключаемые к программе посредством файла iostream.h, служат для выполнения операций консольного ввода и вывода. Все операторы классов этих объектов могут быть перегружены в производных классах, создаваемых пользователем. Благодаря этой возможности операции ввода/вывода легко настраивать в соответствии с особенностями работы конкретного приложения.
Основные компоненты программ, созданных на языках C/C++
Возможно, вам приходилось слышать, что язык С очень трудно изучать. Действительно, первое знакомство с программой на языке C может поставить в тупик, но виной тому не сложность языка, а его несколько необычный синтаксис. Информации, изложенной в этой главе, достаточно, для того чтобы легко разбираться в синтаксисе языка C и даже создавать небольшие работоспособные программы. В следующем параграфе вы узнаете о пяти основных компонентах программы на С/С++.
Пять основных свойств хорошей программы на С/С++
Возможно, читатель знаком с технологией разработки программного обеспечения методом структурного программирования, предусматривающим составление IPO-диаграмм (Input/Process/Output - Ввод/Обработка/Вывод). IPO-диаграммы предназначены для решения классических задач программирования по вводу, обработке и выводу данных. Приведенный ниже список свойств программы сформирован в соответствии с указанной моделью и охватывает полный цикл разработки приложения. Программа должна:
получать информацию из какого-либо источника;
автоматически определять способ классификации и хранения полученной информации;
содержать набор инструкций для обработки исходных данных. Эти инструкции делятся на четыре категории: одиночные операторы, условные операторы, циклы и подпрограммы;
возвращать результат обработки данных;
иметь хорошую модульную структуру, самодокументированный код, переменные с понятными именами и четкую систему отступов.
ПРИМЕЧАНИЕ. В данной главе все программы на C используются только для демонстрации различий в синтаксисе между языками С и С++, а также для сопоставления старого стиля программирования с современными нормами ANSI/ISO C++.
Простейшая программа на языке C
Ниже показан пример простейшей программы на языке C, написанной в соответствии со старым стилем оформления кода.
/*
* simple.c
* Ваша первая программа, написанная на
* языке C.
* (c) К. Паппас и У. Мюррей, 2001
*/
#include <stdio.h>
int main()
{
printf(" Здравствуй, мир! ");
return(0);
}
В этом маленьком тексте программы скрыто много интересного. Начнем с блока комментариев.
/*
* simple.c
* Ваша первая программа написанная на
* языке C.
* (c) К. Паппас и У. Мюррей, 2001
*/
Любую программу, написанную профессиональным программистом, всегда открывают комментарии. В языке C блок комментариев начинается символами /*, а заканчивается символами */. Все, что находится между ними, игнорируется компилятором.
Следующая строка, называемая директивой препроцессора, характерна для языка C:
#include <stdio.h>
Директивы препроцессора - это своего рода команды компилятору. В данном случае компилятор получает указание поместить в этом месте программы код, хранящийся в библиотечном файле stdio.h. Файлы с расширением .h называются заголовочными файлами и обычно содержат объявления различных констант и идентификаторов, а также прототипы функций. Хранение такого рода информации в отдельном файле облегчает доступ к ней из разных программ и улучшает структурированность программы.
Вслед за директивой препроцессора расположен блок описания функции:
int main()
{
.
.
.
return(0); /* или return 0; */
}
Все программы на языке C обязательно содержат функцию main(). С нее начинается выполнение программы, которое завершается вызовом инструкции return. Ключевое слово int слева от имени функции указывает на тип возвращаемых ею значений. В нашем случае возвращается целое число. Значение 0 в инструкции return будет воспринято как признак успешного завершения программы. Допускается использование инструкции return без круглых скобок.
Тело функции main() расположено между символами { и }, называемыми фигурными скобками. Фигурные скобки широко применяются для выделения в программе блоков инструкций. Это может быть тело функции, как в данном примере, тело цикла, например for или while, либо операторная часть условных конструкций if/else или switch/case.
В нашем случае тело функции main() помимо стандартного вызова инструкции return состоит из единственной команды, осуществляющей вывод на экран строки приветствия:
printf(" Здравствуй, мир! ");
Прототип функции printf() описан в файле stdio.h. Поскольку другие параметры не указаны, предложение будет выведено на экран монитора.
Простейшая программа на языке С++
В следующем примере выполняются те же действия, но на этот раз средствами языка C++.
//
// simple2.cpp
// Ваша первая программа,написанная на C++.
// (c) К. Паппас и У. Мюррей, 2001
//
#include "stdafx.h"
#include <iostream>
using namespace std;
int main(int argc, char* ardv[])
{
cout << " Здравствуй, мир! ";
return 0;
}
Между двумя предложенными вашему вниманию программами существует пять основных различий.
Первое различие связано с символами комментариев: во втором коде вместо пары символов /* */ употребляются символы //, расположенные в каждой строке комментария. В отличие от С-кода, где комментарии, состоящие из нескольких строк, могут быть заключены в единственную пару символов /* */, в коде на С++ символ комментария (//) действителен только для одной строки. Следовательно, каждая строка комментария должна начинаться с двух символов косой черты.
Перейдем ко второму различию. Имя файла в директиве #include было изменено на stdafx.h. Этот файл используется для создания предварительно скомпилированных заголовочного файла projname.pch и файла типов stdafx.obj. Файл stdafx.h необходим для включаемых системных файлов и файлов проекта. Файл stdafx.cpp содержит директиву препроцессора #include "stdafx.h", которая добавляет включаемые файлы для предварительно скомпилированных типов. Предварительное компилирование файлов любого типа, в том числе заголовочных, ускоряет компилирование программы, поскольку в дальнейшем компилируются только те файлы, по отношению к которым должна быть выполнена эта процедура. Каждая последующая компоновка проекта происходит быстрее, так как предварительно скомпилированные заголовочные файлы уже присутствуют.
Во втором коде для директивы #include указано имя файла iostream вместо stdio.h. Заголовочный файл stdio.h содержал специфические для С определения, необходимые для осуществления стандартных операций ввода/вывода. Синтаксис .h характерен для старого стиля. Файл iostream содержит специфические для С++ определения, необходимые для выполнения простых операций ввода/вывода. Имя файла без расширения .h соответствует последним требованиям стандарта ANSI/ISO С++.
Третье различие связано с использованием оператора объявления (добавления) пространства имен std, что также относится к разряду новых требований, сформулированных в стандарте ANSI/ISO С++ (мы еще вернемся к его обсуждению). Объявление пространства имен позволяет инкапсулировать определения, оно необходимо для предотвращения конфликтов имен переменных в программах большого объема. Все, что рассматривается как стандартная часть С++, входит в пространство имен std. Оператор using логически функционирует, как и директива #include, давая возможность использовать имена переменных без явного указания названия пространства имен.
Четвертое различие состоит в изменении списка аргументов функции main(). Аргументы этой функции - argc и argv[] - позволяют программе принимать аргументы во время загрузки или из командной строки.
Наконец, пятое различие заключается в использовании другого оператора вывода - cout.
Рассматривая следующие примеры, мы продолжим обсуждать различия между кодами на C++ и C.
Переход от main к _tmain и от char* к _Tchar
В среде разработки Visual Studio .NET 7.0 компанией Microsoft введено понятие программного отображения общего текста (generic-text routine mappings). В заголовочном файле tchar определены макросы, или встраиваемые функции, отображения кодов символов для моделей MBCS (MultiByte Character Set - набор символов, каждый из которых представлен однобайтовым или двухбайтовым значением), SBCS (Single-Byte Character Set - набор однобайтовых символов) и Unicode. Эти функции задают представление символьных данных как значений ANSI ASCII с помощью одиночного байта или двух байтов в стандарте Unicode. При написании алгоритмов, предназначенных для работы со стандартом Unicode, все функции main() должны быть представлены в виде _tmain(), а указатели символов char* - в виде_TCHAR*.
Последние обновления в С++, сделанные комитетом ANSI/ISO
В процессе разработки стандартной библиотеки шаблонов STL комитет ANSI/ ISO нашел возможность внести некоторые изменения в стандарт языка С++. Эти изменения касаются в основном возможностей, которые присущи и другим языкам и до сих пор не были реализованы в традиционном С++. Указанные изменения содержатся в расширениях языка и не повлияют на программы, написанные ранее на С++.
Использование пространства имен
Пространство имен определяет область видимости констант, переменных, функций, классов и т. д. Идентификаторы пространства имен самого низкого уровня объявляются в пределах функции. С этим уровнем ассоциируются функции-члены и объявления методов. Более высокий уровень имеет пространство имен в пределах класса.
Область видимости файлов определяется пространством имен файлов. Например, если для создания исполняемого файла 123.exe необходимы файлы 1.cpp, 2.cpp и 3.cpp, то идентификаторы, объявленные в файле 1.cpp, будут невидимы (по умолчанию) для кода в файлах 2.cpp и 3.cpp.
Наиболее высокий уровень имеет пространство имен программы или рабочей области. Долгие годы такая иерархия позволяла создавать корректно работающие программы без конфликтов имен. Современные программы представляют собой комбинацию исходных файлов, созданных автором, компилятором, а также другими разработчиками. При таком положении вещей определения области видимости программы будет недостаточно для предотвращения конфликтов имен между категориями. Пространства имен позволяют защитить все идентификаторы в программе, что предотвращает разнообразные коллизии.
Опасность коллизии обычно существует для внешних глобальных идентификаторов, используемых повсеместно в программе. Эти глобальные имена являются видимыми для всех объектных модулей прикладной программы, внешних классов и библиотек, а также системных библиотек компилятора. Если две переменные в глобальной области видимости имеют одинаковые имена, компоновщик выдает сообщение об ошибке.
Многие разработчики компиляторов решают эту проблему за счет присваивания каждой переменной уникального идентификатора. Например, стандартный компилятор С добавляет знак подчеркивания в качестве префикса к каждому имени глобальной переменной, а программистам рекомендуется избегать такой формы написания для предотвращения конфликта имен.
Некоторые производители во избежание конфликтов снабжают имена глобальных переменных мнемоническим префиксом. Но даже такой метод окажется неэффективным, если два разработчика употребляют один и тот же префикс. Проблема в том, что язык не имеет встроенного механизма, который отделял бы глобальные идентификаторы от идентификаторов других библиотек, подключенных к приложению.
В распоряжении программиста имеются три способа предотвращения коллизий: модифицировать исходный код; убедить авторов кода, с которым сопряжены конфликты, изменить объявляемые имена; выбрать альтернативный код, выполняющий те же функции. Не слишком приятные перспективы!
Ключевое слово namespace в С++ позволяет ограничить область видимости переменных пространством имен. Если ссылки на глобальные имена, объявленные внутри блока, расположены за пределами этого блока, то они должны содержать идентификатор пространства имен. Фактически, это равноценно применению префиксов. Однако идентификаторы пространства имен, в отличие от двух- и трехсимвольных префиксов, состоят из большего числа символов, благодаря чему гарантия их корректной работы намного выше.