Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Паппас К., Мюррей У. - Visual C++ 6. Руководство разработчика - 2000

.pdf
Скачиваний:
288
Добавлен:
13.08.2013
Размер:
4.96 Mб
Скачать

тип1имя1; тип2 имя2; типЗ имяЗ;

.

.

.

тип-п имя_п; };

Обратите внимание на схожесть описания структуры и объединения:

union unmany_types { char с;

int ivalue; float fvalue;

double dvalue; } my_union;

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

имя_переменной.член_объединения

Создание простейшего объединения

Проиллюстрируем работу с объединением на следующем, довольно простом примере:

//

//union.срр

//Эта программа на языке C++ демонстрирует, как работать с объединением.

//

#include <iostream.h> union unmany_types { char с;

int ivalue; float fvalue;

double dvalue; } my_union; int main (void) {

//корректные операции ввода/вывода my_union.c = 'b';

cout << my_union.c << endl; my_union. ivalue = 1990;

cout << my_union. ivalue << endl;

my_union. fvalue =19.90; , cout << my_union. fvalue << endl; my_union. dvalue = 987654 . 32E+13; cout << my_union. dvalue << endl;

//некорректные операции ввода/вывода

cout << my_union.c << endl; cout << my_union. ivalue << endl; cout << my_union. fvalue << endl; cout << my_union. dvalue << endl;

// вычисление размера объединения

cout<< "Размер объединения составляет " << sizeof (unmany_types) << "

байт."; return(0);

}

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

b

221

1990

19.9

9.87654е+018

Ш

-154494568 -2.05461е+018 9.87654е+018

Размер объединения составляет: 8 байт.

Ключевое слово typedef

С помощью ключевого слова typedef можно создавать новые типы данных на основании уже существующих. Как правило, это необходимо для упрощения текста программы. Например, выражение

typedef struct stboat* stboatptr;

делает имя stboatptr синонимом конструкции stboat*. Теперь создать указатель на структуру stboat можно будет следующим образом:

stboatptr my_boat;

Перечисления

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

enum необязательный_тег (константа1 [= значение!],

.

.

.

,

константа-n[= значение-п]};

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

Константы, входящие в перечисление, имеют тип int. По умолчанию первая константа равна 0, следующая — 1, потом — 2 и т.д. в арифметической прогрессии. Таким образом, значения констант можно не указывать: они будут вычислены автоматически путем формирования прогрессии с шагом 1 от последнего специфицированного значения. В то же время, для каждой константы после знака равенства можно указывать собственное значение.

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

Например, в следующем фрагменте программы можно организовать цикл от 0 до 4, а можно — от понедельника до пятницы:

enum eweekdays { /*

будние дни */

Monday,/* понедельник */

 

Tuesday,/*

вторник

*/

 

Wednesday, /*

среда

*/

*/

Thursday,/*

 

четверг

 

.Friday )/*

 

пятница

*/

в языке С */

/* описание

перечисления

enumeweekdaysewToday;

 

 

/* описание

перечисления

в C++ */

eweekdaysewToday;

 

 

/* задание цикла for без

использования перечисления */

222

for(i=0;i<= 4; i++)

.

.

.

/* и с использованием перечисления */

for(ewToday = Monday; ewToday <= Friday; ewToday++)

Компилятор не видит разницы между типами данных intи enum. Поэтому переменным типа перечисления в программе могут присваиваться целочисленные значения. Но в языке C++, если такое присваивание не сопровождается явным приведением типа, компилятор выдаст предупреждение:

/* допустимо в С, но не в C++ */ ewtodау = 1;

/* устранение ошибки в C++ */ ewToday = (eweekdays) 1;

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

/*

*enum. с

*Эта программа на языке С демонстрирует работу с перечислением.

*/

#include <stdio.h> enum emonths { January = 1 , February,

March,

April,

May,

June,

July,

August,

September,

October,

November, December } months;

int main(void)

{

int ipresent_month; int idiff;

{

printf("Введите номер текущего месяца (от1 до 12): ") ; scanf("%d",&ipresent_month);

months = December;

idiff = (int)months - ipresent_month;

printf("\nfloконца года осталось %dмесяца(ев)An", idiff); return (0);

}

Вданной программе перечисление в действительности представляет собой ряд целых чисел от 1 до 12. Например, значение переменной months, после того как ей была присвоена константа December, стало равным 12. Поскольку названию каждого месяца соответствует определенное числовое значение, то элементы перечисления могут участвовать в арифметических операциях.

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

223

Введите номер текущего месяца (от 1 до 12): 4 До конца года осталось 8 месяца(ев).

224

Глава 13. Основы ООП

Все новое — это хорошо забытое старое

C++ и ООП

Основная терминология

o Инкапсуляция

oИерархия классов

Первое знакомство с классом

oСтруктура в роли примитивного класса

o Синтаксис описания классов

oПростейший класс

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

Все новое — это хорошо забытое старое

Специалисты по маркетингу хорошо знают, что товар продается лучше, если где-нибудь на упаковке вставить слово "новинка". Поэтому не стоит полагать, что ООП является такой уж новой концепцией. Скотт Гудери (ScottGuthery) вообще утверждал в 1989, что ООП берет свое начало с появления подпрограмм в 40-х годах (см. статью "Является ли новое платье короля объектно-ориентированным?" ("Are the Emperor's New Clothes Object Oriented?"), Dr. Dobb's Journal, декабрь, 1989 г.).

В этой же статье утверждалось, что объекты, как основа ООП, появились еще в языке

FORTRAN II.

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

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

В 60-х годах была реализована методика структурного программирования, подразумевавшая использование переменных с осмысленными именами, существование глобальных и локальных переменных и процедурное проектирование приложений по принципу "сверху вниз". Благодаря внедрению в жизнь этих концепций тексты программ стали понятнее и читабельнее, в результате чего упростился процесс отладки программ. Стало проще сопровождать программы, так как появилась возможность контролировать выполнение не отдельных команд, а целых процедур. Примерами языков, ориентированных на подобный процедурный подход, могут быть Ada, С и Pascal.

225

Бьярна Страуструпа (Bjarne Stroustrup) можно считать отцом ООП в том виде, в каком эта концепция представлена в языке C++, разработанном им в начале 80-х в компании BellLabs. С точки зрения Джеффа Дантеманна (JeffDuntemann), "ООП представляет собой то же структурное программированное, но с еще большей степенью структуризации. Это вторая производная от исходной теории построения программ." (См. статью "Лавирование среди айсбергов" ("DodgingSteamships"), Dr. Dobb'sJournal, июль, 1989 г.) Действительно, как вы убедитесь, ООП в C++ в значительной степени основано на концепциях и средствах структурного программирования языка С. И хотя язык C++ ориентирован на работу с объектами, при желании на нем можно писать как традиционные процедурные приложения, так и неструктурированные программы. Выбор за вами.

C++ и ООП

Все предыдущие главы книги были посвящены изучению методик традиционного структурного программирования. Структура процедурных программ в общем такова: имеется функция main(), а из нее вызываются другие функции программы. Тело функции main(), как правило, невелико. Ее роль обычно сводится к распределению задач между остальными функциями, которые описывают действия, выполняемые над данными.

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

В основу ООП заложены совершенно иные принципы и другая стратегия написания программ, что часто становится камнем преткновения для многих разработчиков, привыкших к традиционному программированию. Теперь программы представляют собой алгоритмы, описывающие взаимодействие групп взаимосвязанных объектов. В C++ объекты создаются путем описания класса как нового типа данных. Класс содержит ряд констант и переменных (данных), а также операций (функций-членов, или методов), выполняемых над ними. Чтобы произвести какое-либо действие над данными объекта, ему необходимо, выражаясь в новых терминах, послать сообщение, т.е. вызвать один из его методов. Подразумевается, что к данным, хранящимся в объекте, нельзя получить доступ иначе, как путем вызова того или иного метода. Таким образом, программный код и оперируемые данные объединяются в единой "виртуальной" структуре.

ООП дает программистам три важных преимущества. Первое состоит в упрощении программного кода и улучшении его структуризации. Программы стали проще для чтения и понимания. Код описания классов, как правило, отделен от кода основной части программы, благодаря чему над ними можно работать по отдельности. Второе преимущество заключается в том, что модернизация программ (добавление и удаление программных блоков) становится несравнимо более простой задачей. Чаще всего она сводится к добавлению нового класса, который наследует все свойства одного из имеющихся классов и содержит требуемые дополнительные методы. Третье преимущество состоит в том, что одни и те же классы можно много раз использовать в разных программах. Удачно созданный класс можно сохранить отдельно в библиотечном файле, и его добавление в программу, как правило, не требует внесения серьезных изменений в ее текст.

При изучении предыдущих глав книги у вас могло сложиться впечатление, что преобразование текстов программ с языка С на C++ и обратно не составляет труда. Достаточно только поменять некоторые ключевые слова, например printf() на cout. В действительности различия между этими языками гораздо более глубоки и лежат как раз в области ООП. Например, язык С не поддерживает классы. Поэтому преобразовать процедурную программу в объектноориентированную намного труднее, чем может показаться. Зачастую проще выбросить старую программу и написать новую с нуля. В отсутствии преемственности между двумя подходами заключается определенный недостаток ООП.

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

226

Основная терминология

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

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

Класс языка C++ является расширенным вариантом структуры и служит для создания объектов. Он содержит функции-члены (методы), связанные некоторыми общими атрибутами. Объект — это экземпляр класса, доступный для выполнения над ним требуемых действий.

Инкапсуляция

Под инкапсуляцией понимается хранение в одной структуре как данных (констант и переменных), так и функций их обработки (методов). Доступ к отдельным частям класса регулируется с помощью специальных ключевых слов public(открытая часть), private(закрытая часть) и protected(защищенная часть). Методы, расположенные в открытой части, формируют интерфейс класса и могут свободно вызываться всеми пользователями класса. Считается, что переменные-члены класса не должны находиться в секции public, но могут существовать интерфейсные методы, позволяющие читать и модифицировать значение каждой переменной. Доступ к закрытой секции класса возможен только из его собственных методов, а к защищенной — также из методов классов-потомков.

Иерархия классов

В C++ класс выступает в качестве шаблона, на основе которого создаются объекты. От любого класса можно породить один или несколько подклассов, в результате чего сформируется иерархия классов. Родительские классы обычно содержат методы более общего характера, тогда как решение специфических задач поручается производным классам.

Наследование

Под наследованием понимают передачу данных и методов от родительских классов производным. Если класс наследует свои атрибуты от одного родительского класса, то такое наследование называется одиночным. Если же атрибуты наследуются от нескольких классов, то говорится о множественном наследовании. Наследование является важнейшей концепцией программирования, поскольку позволяет многократно использовать одни; и те же классы, не переписывая их, а лишь подстраивая для решения конкретных задач и расширяя их возможности.

Полиморфизм и виртуальные функции

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

Благодаря полиморфизму можно делать в программе запрос к объекту, даже если тип его не известен заранее. В C++ эта возможность реализуется за счет подсистемы позднего связывания, под которым понимается динамическое определение адресов функций во время выполнения программы в противоположность традиционному статическому (раннему)

227

связыванию, осуществляемому во время компиляции. В процессе связывания имена функций заменяются их адресами.

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

Первое знакомство с классом

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

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

Структуры в C++ можно рассматривать как примитивные классы, поскольку они могут содержать не только данные, но и функции. Рассмотрим следующую программу:

//

//sqroot.cpp

//В этой программе на языке C++ создается структура,

//содержащая, кроме данных, также функции.

//

#include <iostream.h>

# include <math.h>

struct math_operations { double data_value;

void set_value (double value) { data_value = value; } double get_square (void)

{ return (data_value * data_value) ; } double get_square_root (void) {

return (sqrt (data_value) }; } } math; main ()

{

// записываем в структуру число 35.63 math.set_value (35.63);

cout << "Квадрат числа равен " << math.get_square () << endl;

cout << "Корень числа равен " << math.get_square_root () << endl; return (0); }

В первую очередь обратите внимание на то, что помимо переменной структура содержит также несколько функций. Это первый случай, когда внутри структуры нам встретились описания функций. Такая возможность существует только в C++. Функции-члены структуры могут выполнять операции над переменными этой же структуры. Неявно подразумевается, что все члены структур являются открытыми, как если бы они были помещены в секцию public.

При выполнении программы на экране отобразится следующая информация:

Квадрат числа равен 1269.5 Корень числа равен 5.96909

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

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

//

//trigon.cpp

228

//В этой программе на языке C++ создается структура,

//содержащая тригонометрические функции.

//

#include <iostream.h> #include <math.h>

const double.DEG_TO_RAD = 0.0174532925; struct degree {

double data value;

void set_value (double angle); double get_sine (void) ; double get_cosine (void) ; double get_tangent (void) ; double get_cotangent (void) ; double get_secant (void) ;

double get_cosecant (void) ; ) deg; void degree: :set_value (double angle) { data_value = angle;

}

double degree: :get_sine (void) { return (sin(DEG_TO_RAD * data_value) )

}

double degree::get_cosine(void) { return(cos(DEG_TO_RAD * data_value)); } double degree : : get_tangent (void) { return ( tan (DEG_TO_RAD * data_value) ) ; } double degree: :get_secant (void). {

return (1.0/ sin(DEG_TO_RAD * data_value) ) ; } double degree: :get_cosecant (void) (

return (1.0/ cos(DEG_TO_RAD * data_value) ) ; ) double degree : : get_cotangent (void) <

return (1.0/ tan(DEG_TO_RAD * data_value) ) ; } main() {

// устанавливаем значение угла равным 25 градусов deg . set_value ( 25.0 ) ;

cout << "Синус угла равен " << deg.get_sine () << endl; cout << "Косинус угла равен " << deg.get_cosine () << endl; cout << "Тангенс угла равен " << deg.get_tangent () << endl; cout << "Секанс угла равен " << deg.get_secant () << endl;

cout << "Косеканс угла равен " << deg.get_cosecant () << endl; cout << "Котангенс угла равен " << deg.get_cotangent () << endl; return (0 ); }

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

void degree::set_value(double angle)

Имя функции состоит из имени структуры, за которым расположен оператор : :..

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

Синтаксис описания классов

229

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

class имя {

тип1 переменная1 тип2 леременная2 типЗ переменнаяЗ public: метод1; метод2; методЗ;

};

По умолчанию все члены класса считаются закрытыми и доступ к ним могут получить только функции-члены этого же класса. Это именно то, что в C++ отличает классы от структур: все члены структур по умолчанию являются открытыми. Если необходимо изменить тип доступа к членам класса, перед ними следует указать один из спецификаторов доступа: public, protected

или private.

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

class degree (

double data_value; public: void set_value(double); double get_sine(void); double get_cosine(void); double get_tangent(void); double get_secant(void); double get_cosecant(void); double get_cotangent(void); } deg;

Здесь создается новый тип данных degree. Закрытая переменная-член data_value доступна только функциям-членам класса. Одновременно создается и объект данного класса — переменная deg. He показалось ли вам описание класса знакомым? Это, по сути, та же самая структура, которую мы рассматривали в предыдущем примере, лишь ключевое слово class превращает структуру в настоящий класс.

Простейший класс

Рассмотрим следующий пример программы:

//

//class.срр

//В этой программе на языке C++ создается простейший

//класс, имеющий открытые и закрытые члены.

//

#include <iostream.h> #include <math.h>

const double DEG_TO_RAD = 0.0174532925; class degree (

double data_value; public:

void set_value(double angle); double get_sine(void) ; double get_cosine(void) ; double get_tangent(void) ; double get_secant(void) ; double get_cosecant(void) ;

double get_cotangent(void); ) deg; void degree::set_value(double angle) < data_value = angle;

I

230

Соседние файлы в предмете Программирование на C++