А.В. Столяров - Введение в язык Си++ (1114949), страница 3
Текст из файла (страница 3)
Сам термин «парадигма программирования» означаетспособ восприятия человеком (программистом) компьютерной программы и её процесса исполнения. Надо заметить, что парадигмы относятсяскорее к мышлению программиста, чем к средствам языков программирования, но язык при этом может стимулировать, поощрять, а в некоторых случаях — и навязывать определённый стиль мышления, поэтому,конечно, игнорировать изобразительные средства избранного языка программирования при разговоре о парадигмах было бы неправильно.Объектно-ориентированное программирование представляет собойещё одну парадигму программирования.
Осмысливая свою программу всоответствии с этой парадигмой, мы прежде всего представляем данные ввиде некоторых о б ъ ек то в — «чёрных ящиков». Внутреннее устройствообъекта извне недоступно (и в ряде случаев может быть просто неизвест8но например, если данный объект реализован другим программистом).Всё, что можно сделать с объектом это послать ему сообщение и получить ответ. Так, операция 2 + 3 в терминах объектно-ориентированногопрограммирования выглядит как «мы посылаем двойке сообщение прибавь к себе т р о й к у , а она отвечает нам, что получилось 5».
Вполне возможно, что, получив сообщение, объект произведет ещё и какие-то действия, сменит свое внутреннее состояние или пошлет сообщение другомуобъекту.Объекты, внутреннее устройство которых одинаково, образуют классы : в прагматическом смысле класс есть ни что иное, как описание внутреннего устройства объекта, и при создании нового объекта указывается, к какому классу он принадлежит (объектом какого класса он будетявляться). Имея описание класса, можно создать произвольное количество1 объектов этого класса. Более того, можно взять имеющийся класси на его основе создать новый класс, в чём-то похожий на старый и проявляющий его свойства, но при этом всё-таки отличающийся от него,как правило, в сторону усложнения (хотя и не всегда).
Эта возможностьназывается наследованием .Недоступность деталей реализации объекта за пределами его описания позволяет снизить общую сложность программы по принципу «разделяй и властвуй». В применении к программированию этот принципназывается инкапсуляцией и состоит в разделении всей программы наподсистемы, каждая из которых реализуется более-менее независимо иможет разрабатываться без учёта деталей реализации других подсистем:учитывать приходится лишь то, как подсистемы «выглядят извне», тоесть какие функции и прочие возможности в них предусмотрены длявзаимодействия с другими подсистемами.
Конечно, инкапсуляция присутствует не только в объектно-ориентированных языках, но если в других языках основной единицей инкапсуляции обычно является м одуль,то в ООП мы используем инкапсуляцию на уровне отдельных объектовили (как в Си--- ) классов, что в ряде случаев позволяет резко снизитьобщую сложность многих модулей.Часто бывает так, что несколько различных классов способны обрабатывать некоторый общий для них всех набор сообщений.
В таком случаеговорят, что эти классы поддерживают общий и н те р ф е й с: во многихобъектно-ориентированных языках программирования интерфейсы описываются в виде классов специального вида, есть такая возможность ив Си--- .1 С трого говоря, иногда можно встрети ть класс, объект которого мож ет сущ ествов ат ь только в единственном экзем пляре, и д аж е такие классы , для которы х вообщ енельзя со зд авать объекты , но это скорее исключение из правил.91.2. Я з ы к Си | | и его совм естим ость с СиЯзык С и + + был предложен Бьёрном Страуструпом в начале 80х годов прошедшего столетия в качестве ответа на назревшую в индустриипотребность в индустриальном объектно-ориентированном языке.
С и + +представляет собой расширение языка Си с объектно-ориентированнымивозможностями. Иначе говоря, при работе на языке С и + + можно использовать как традиционное императивное, так и объектно-ориентированноепрограммирование.Большинство программ, написанных на языке Си, будет корректно сточки зрения языка С и ++. Некоторые программы на языке Си не могутбыть откомпилированы транслятором С и ++. Так, в С и + + присутствует некоторое количество ключевых слов, которых не было в языке Си.Поэтому, например, программа, содержащая такое описание:in t tr y ;не является корректной с точки зрения языка С и + + , поскольку словоtry — ключевое в С и ++.
Кроме того, в языке С и + + нет отдельногопространства имён для тегов структур; так, описаниеstru c t mystruct {in t a , b;>;в программе на С и + + является описанием полноценного типа mystruct,тогда как на Си такое же описание означало бы лишь введение тегаструктуры. В результате, например, часто встречающиеся в старых программах на Си описания видаtypedef stru c t mystruct {in t a , b;} m ystruct;являются с точки зрения С и + + некорректным из-за возникающего конфликта имён (в языке Си такого конфликта не возникало, т.
к. тегиструктур и имена типов относились к различным пространствам имён).Заметим, что в С и + + при описании структуры нет необходимостииспользовать typedef, поскольку идентификатор, стоящий после словаstru c t, уже сам по себе представляет собой имя нового типа (в отличиеот Си, где этот идентификатор считался тегом структуры и превращалсяв имя типа только в сочетании со словом stru c t). Так, после описанияstru c t s i {in t х, у;>;10в языке С и + + мы можем сразу описывать переменные типа s i:s i а;тогда как в языке Си мы вынуждены были использовать слово stru c t:stru c t s i а;(впрочем, компилятор С и + + такое тоже понимает).В ряде ситуаций компилятор СиН—Ь ведёт себя строже, чем компилятор чистого Си, выдавая, например, ошибку там, где в Си возниклобы как максимум предупреждение.
К таким ситуациям относятся, вопервых, вызовы необъявленных функций: в Си эта ситуация считается«нехорошей, но допустимой», так что, если забыть подключить какойнибудь заголовочный файл, программа может благополучно оттранслироваться, хотя и с предупреждениями. В С и + + этот номер не проходит: если функция не описана, её вызов рассматривается как ошибка.Во-вторых, ошибкой будет неявное преобразование адресных выраженийнесовместимых типов: например, присваивание выражения типа in t * переменной типа double* вызовет ошибку в С и + + , тогда как в чистом Сивыдавалось только предупреждение.
Ошибкой в С и + + будет и неявноепреобразование выражения типа void * в выражение другого адресноготипа, в то время как компилятор чистого Си не выдаёт в такой ситуациидаже предупреждения.В сравнении с чистым Си язык Си-|—Ь имеет ряд дополнительных (идовольно приятных) возможностей, которые можно заметить ещё до введения средств, имеющих отношение к ООП. Отметим только два из них.Во-первых, в С и + + описание переменной стало о п ер ато р о м , что позволяет вставлять его в произвольное место программы, а не только в начало блока (напомним, что в чистом Си нельзя описать переменную послетого, как в блоке встретился хотя бы один оператор).
В ряде случаев этоудобно. Во-вторых, в С и + + наряду с привычными «блочными» комментариями, которые заключаются в знаки « / * » и « * /» , введены стро ч н ы ек о м м е н та р и и , обозначаемые знаком « / / » ; компилятор проигнорирует весь текст, написанный после такого знака, вплоть до ближайшегосимвола перевода строки.Есть и другие отличия, о которых вы постепенно узнаете.И2.
Абстрактные типыданных и инкапсуляция§2.1. М етоды , о бъ екты и защ и таВ этой части курса мы будем отталкиваться от уже известных намсредств, пришедших в С и + + из языка Си, постепенно вводя новые (специфические для С и ++) возможности.§ 2.1.1. Функции-члены (методы)Для поддержки объектно-ориентированного программирования вС и + + вводятся понятия функций-членов и классов. О классах мырасскажем позднее, а пока попытаемся понять, что же такое «функциячлен».Пусть нам потребовалась структура для представления комплексногочисла через действительную и мнимую части:stru c t str_complex {double r e , im;>;Введем теперь операцию вычисления модуля комплексного числа.
Наязыке Си нам пришлось бы описать примерно такую функцию:double m odulo(struct str_complex *c){return sq rt(c - > re *c -> r e + c->im *c->im );}Язык С и + + позволяет сделать то же самое несколько более элегантно, подчеркнув непосредственное отношение функции modulo () к сущности комплексного числа. Функцию мы внесём в н у т р ь структуры, сделав«как будто бы её частью»:12stru c t str_complex {double r e , im;double modulo() { return s q r t( r e *r e + im *im ); }>;Обратите внимание, что в теле функции встречаются обращения к полямструктуры прямо по именам, без уточнения, откуда такое имя берётся,как если бы это были простые переменные, а не поля. Теперь мы можемнаписать, например, такой код:str_complex z ;double mod;z .r e = 2 .7 ;z .
im = 3 .8 ;mod = z.m oduloQ;Функция modulo () называется ф ункцией-членом или м е т о д о м структуры str_complex. Иногда речь идет о м е т о д е о б ъ е к т а (в данном случае — объекта z), при этом имеется в виду метод того типа, к которомупринадлежит объект. Как видно из примера, метод вызывается не сампо себе, а для к о н к р етн ого о б ъ е к т а , и как раз из этого объекта берутсяполя, когда в теле метода имеет место обращение к полю структуры поимени. В данном случае метод вызван для объекта z, так что в его телеупоминания идентификаторов ге и im будут соответствовать полям z .
reи z . im.Вызов метода — это именно то, что в теории объектно-ориентированного программирования понимается под о т п р а в к о й сообщ ения об ъ ек ту .Иначе говоря, т е р м и н ы «вы зо в м е т о д а » и «п ередач а сообщ ения» я в л я ю т с я синоним ам и. Мы можем, следовательно, сказать, что в последнейстрочке вышеприведённого фрагмента кода мы передали о б ъ е к т у z сообщ ение modulo, означающее «посчитай свой модуль»; объект ответил нанаше сообщение, причём в ответе содержалось число типа double, равное искомому модулю; получив этот ответ, мы присвоили его переменнойmod.§ 2.1.2.
У казатель th isНа самом деле, вызов функции-члена (метода) объекта с точки зрения реализации представляет собой абсолютно то же самое, что и вызовобычной функции, первым параметром которой является адрес объекта.В частности, в предыдущем параграфе мы заменили внешнюю функцию modulo, получавшую адрес структуры, на метод modulo, описанныйвнутри структуры, а вызов с явной передачей адреса — на вызов методадля объекта; при этом на уровне м аш и н н ого кода н икаких изм енений не13произош ло. Говорят, что функциям-членам при вызове их для объектапередаётся неявный п а р а м е т р — адрес объекта, для которого функция вызывается.К этому параметру при необходимости можно обратиться; его именемявляется ключевое слово th is .
Можно воспринимать th is как локальную константу, имеющую тип А*, где А — имя описываемой структуры.Например, описанную в предыдущем параграфе версию структуры можно было бы переписать и так:stru c t str_complex {double r e , im;double modulo() {return s q r t( th is - > r e *th is - > r e + th is-> im *th is-> im );}>;В данном конкретном случае это не имеет особого смысла, однако бывают и такие ситуации, в которых использование th is оказывается необходимым; достаточно представить себе, что из метода нужно вызватькакую-либо функцию (обычную или метод другого объекта), аргументом которой должен стать как раз объект, для которого нас вызвали.§ 2.1.3. Защ ита. Понятие конструктораЧтобы стать полноценным объектом, нашей структуре не хватаетсвойства закрытости.