Создание новых типов
Вы уже познакомились с типами переменных, включая беззнаковые целые и символы. Тип переменной несет в себе немало информации. Например, если объявить переменные Height и Width как беззнаковые короткие целые (unsigned short int), то каждая из них сможет хранить целое число в диапазоне 0-65 535, занимая при этом только два байта. Если же вы попытаетесь присвоить такой переменной значение, отличное от беззнакового целого числа, то получите сообщение об ошибке. Это значит, что с помощью такой переменной вы не сможете хранить свое имя, так что даже и не пытайтесь сделать это.
Лишь объявив переменные Height и Width беззнаковыми короткими целыми, вы получаете возможность сложить их или присвоить одной из них значение другой. Итак, тип переменной определяет:
• ее размер в памяти;
• тип данных, которые она может хранить;
• операции, которые могут выполняться с ее участием.
Тип данных является категорией. К нему можно отнести автомобиль, дом, человека, фрукты, геометрическую фигуру и т.п. В языке C++ программист может создать любой нужный ему тип, и каждый из этих типов может совмещать свойства и функциональные возможности встроенных базовых типов.
Зачем создавать новый тип
Программы обычно пишут для решения таких реальных проблем, как отслеживание информации о служащих или имитация работы отопительной системы. И хотя решать сложные проблемы можно с помощью программ, написанных только с использованием одних целочисленных значений и символов, решения выглядели бы значительно проще, если бы можно было создавать уникальные типы для различных объектов. Другими словами, имитацию работы отопительной системы было бы гораздо легче реализовать, если бы можно было создавать переменные, представляющие помещения, тепловые датчики, термостаты и бойлеры. И чем ближе эти переменные соответствуют реальности, тем легче написать такую программу.
Классы и члены классов
Новый тип создается путем объявления класса. Класс - это просто коллекция переменных (причем часто различных типов), скомбинированная с набором связанных функций.
Автомобиль можно представлять себе по-разному, например как коллекцию, состоящую из колес, дверей, сидений, окон и т.д. Или же, думая об автомобиле, можно представить себе его способность двигаться, увеличивать скорость, тормозить, останавливаться, парковаться и т.д. Класс позволяет инкапсулировать различные запчасти автомобиля и его разнообразные функции в одну коллекцию, которая называется объектом.
Инкапсуляция всего, что мы знаем об автомобиле, в один класс имеет для программиста ряд преимуществ. Ведь все сведения собраны вместе в одном объекте, на который легко ссылаться, копировать и манипулировать его данными. Клиенты вашего класса, т.е. части программы, работающие с этим классом, могут использовать ваш объект, не беспокоясь о том, что находится в нем или как именно он работает.
Класс может состоять из любой комбинации типов переменных, а также типов других классов. Переменные в классе называют переменными-членами или данными- членами. Класс Car может иметь переменные-члены, представляющие сидения, радиоприемник, шины т.д.
Переменные-члены, известные также как данные-члены, принадлежат только своему классу. Переменные-члены - это такие же составные части класса, как колеса и мотор - составные части автомобиля.
Функции в классе обычно выполняют действия над переменными-членами. Они называются функциями-членами или методами класса. В число методов класса Car могут входить Start() и Break(). Класс Cat может иметь такие данные-члены, которые представляют возраст и вес животного, а функциональная часть этого класса может быть представлена методами Sleep(), Meow() и ChaseMice().
Функции-члены принадлежат своему классу, как и переменные-члены. Они оперируют переменными-членами и определяют функциональные возможности класса.
Объявление класса
Для объявления класса используйте ключевое слово class, за которым следует открывающая фигурная скобка, а за ней - список данных-членов и методов класса. Объявление завершается закрывающей фигурной скобкой и точкой с запятой. Вот, например, как выглядит объявление класса Cat:
class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
void Meow();
};
При объявлении класса Cat память не резервируется. Это объявление просто сообщает компилятору о существовании класса Cat, о том, какие данные он содержит (itsAge и itsWeight), а также о том, что он умеет делать (метод Meow()). Кроме того, данное объявление сообщает компилятору о размере класса Cat, т.е. сколько места должен зарезервировать компилятор для каждого объекта класса Cat. Поскольку в данном примере для целого значения требуется четыре байта, то размер объекта Cat составит восемь байтов (четыре байта для переменной itsAge и четыре - для itsWeight). Метод Meow() не требует выделения памяти в объекте.
Несколько слов об используемых именах
На программиста возложена ответственность за присвоение имен переменным- членам, функциям-членам и классам. Как упоминалось на занятии 3, всегда следует давать понятные и осмысленные имена. Например, Cat (Кот), Rectangle (Прямоугольник) и Employee (Служащий) - вполне подходящие имена для классов, а Meow() (Мяу), ChaseMice() (ДогониМышку) и StopEngine() (Остановка Двигателя) - прекрасные имена для методов, поскольку из их названий понятно, что они делают. Многие программисты сопровождают имена своих переменных-членов префиксами its (например, itsAge, itsWeight, itsSpeed). Это помогает отличить переменные-члены от переменных, не являющихся членами класса.
В языке C++ имеет значение регистр букв, и все имена классов должны следовать одному образцу. Исходя из этого, вам никогда не придется вспоминать, как именно пишется название вашего класса: Rectangle, rectangle или RECTANGLE. Некоторые программисты любят добавлять к имени каждого класса однобуквенный префикс с (от слова class) например, cCat или cPerson, в то время как другие используют для имени только прописные или же только строчные буквы. Я предпочитаю начинать имена классов с прописной буквы, например Cat или Person.
Также многие программисты начинают имена функций с прописных букв, а для имен всех остальных переменных используют только строчные буквы. Слова, являющиеся составными частями имен, разделяют обычно символами подчеркивания (например, Chase_Mice) или просто начинают каждое слово с прописной буквы (например, ChaseMice или DrawCircle).
Важно придерживаться одного стиля на протяжении всей программы. По мере приобретения опыта программирования ваш собственный стиль написания программ включит в себя соглашения не только по присвоению имен, но также и по отступам, выравниванию фигурных скобок и оформлению комментариев.
Примечание:Обычно солидные компании по разработке программных продуктов имеют специальные отделы, которые занимаются вопросами стандартизации, охватывающими и стилевые особенности программ. Это гарантирует, что все разработчики смогут легко читать программы, созданные их коллегами.
Определение объекта
Объект нового типа определяется таким же способом, как и любая целочисленная переменная:
unsigned int GrossWeight; // определяем беззнаковое целое Cat Frisky; // определяем объект Cat
В этих программных строках определяется переменная с именем GrossWeight, которая имеет тип unsigned int, а также определяется объект Frisky, который является объектом класса (или имеет тип) Cat.
Классы в сравнении с объектами
Вам никогда не придет в голову поиграть с кошкой как с абстрактным понятием, скорее вы приласкаете свою настоящую мурку. Не нужно много говорить о том, какая разницу между кошкой вообще и конкретным котом, от которого шерсть по всей комнате и царапины на ножках стульев. Точно такая же разница между классом Cat, представляющим собой некую абстракцию, и отдельным объектом класса Cat. Следовательно, Frisky - это объект типа Cat в том самом смысле, в котором GrossWeight - переменная типа unsigned int.
Итак, мы пришли к тому, что объект - это отдельный экземпляр некоторого класса.
Получение доступа к членам класса
После определения реального объекта класса Cat, например Frisky, у нас может возникнуть необходимость в получении доступа к членам этого объекта. Для этого используется оператор прямого доступа (.). Следовательно, чтобы присвоить число 50 переменной-члену Weight объекта Frisky, можно записать
Frisky.Weight = 50;
Аналогично, для вызова метода Meow() достаточно использовать следующую запись:
Frisky.Meow();
Когда нужно использовать некоторый метод класса, выполняется вызов этого метода. В данном примере вызывается метод Meow() объекта Frisky.
Значения присваиваются объектам, а не классам
В языке C++ нельзя присваивать значения типам данных, они присваиваются только переменным. Например, такая запись неверна:
int = 5; // неверно
Компилятор расценит это как ошибку, поскольку нельзя присваивать число типу int. Вместо этого нужно определить целочисленную переменную и присвоить число 5 этой переменной. Например:
int x; // определяем x как переменную типа int
x = 5: // присвоение переменной x значения 5
Таким образом, число 5 присваивается переменной x, которая имеет тип int. Из тех же соображений недопустима следующая запись:
Cat.itsAge=5; // неверно
???
Если Cat - это класс, а не объект, то компилятор отметит это выражение как ошибочное, поскольку нельзя присвоить число 5 переменной itsAge класса (т.е. типа) Cat. Вместо этого нужно определить объект класса Cat и присвоить число 5 соответствующей переменной-члену этого объекта. Например:
Cat Frisky; // это определение аналогично int x;
Frisky.itsAge = 5; // это присвоение аналогично x = 5;
Что объявишь, то и будешь иметь
Представьте себе, что вы гуляете со своим трехлетним ребенком, показываете ему кошку и говорите: "Это Фриски, чудесная кошка, ну-ка Фриски, залай". Даже маленький ребенок рассмеется и скажет: "Нет, кошки не умеют лаять". Если вы запишете:
Cat Frisky; // создаем кошку (объект) no имени Frisky
Frisky.Bark(); // велим Frisky залаять
то компилятор тоже сообщит вам, что даже виртуальные кошки лаять не умеют, поскольку для них не объявлен такой метод. В классе Cat есть метод Meow() (мяукать). Если же вы не определите в классе Cat метод Meow(), то компилятор не позволит вашей кошке даже мяукать.
Рекомендуется:Используйте ключевое слово class для объявления класса. Используйте оператор прямого доступа (.) для получения доступа к переменным-членам и методам класса.
Не рекомендуется:Не путайте объявление с определением. Объявление заявляет о существовании класса, а определение резервирует память для объекта. Не путайте класс с объектом. Не присваивайте значения классу. Присваивайте значения переменным-членам объекта.
Ограничение доступа к членам класса
В объявлении класса используются и другие ключевые слова. Двумя самыми важными из них являются public (открытый) и private (закрытый), определяющие доступ к членам класса.
Все члены класса - данные и методы - являются закрытыми по умолчанию. К закрытым членам можно получить доступ только с помощью методов самого класса. Открытые члены доступны для всех других функций программы. Определение доступа
к членам класса имеет очень важное значение, и именно при решении этой задачи начинающие программисты часто сталкиваются с трудностями. Чтобы прояснить ситуацию, рассмотрим пример, который уже приводился выше в этой главе:
class Cat {
unsigned int itsAge; unsigned int itsWeight; void Meow();
};
В этом объявлении переменные itsAge и itsWeight, а также метод Meow() являются закрытыми, поскольку все члены класса закрытые по умолчанию. Если требуется изменить доступ к членам класса, то это следует сделать явно.
Если в программе будет описан класс Cat, как показано выше, то обращение к переменной-члену itsAge из функции main() вызовет ошибку компиляции:
Cat Boots;
Boots.itsAge = 5; // Ошибка! Нельзя обращаться к закрытым данным
И в самом деле, сначала компилятору указывается, что члены itsAge, itsWeight и Meow() можно использовать только внутри класса Cat, а затем делается попытка использовать во внешней функции переменную-член itsAge, безраздельно принадлежащую объекту Boots класса Cat. Хотя объект Boots реально существует в программе, это не означает, что можно получать доступ к членам данного объекта, закрытым для постороннего глаза.
Именно эти моменты с определением доступа к членам класса служат источником бесконечных недоразумений у начинающих программистов. Я прямо-таки слышу ваш удивленный вопрос: "Если в программе объявлен реальный объект Boots класса Cat, почему же нельзя присвоить значение переменной-члену этого объекта, даже обратившись к ней с помощью оператора прямого доступа?"
Дело в Том, что в объявлении класса Cat ничего не говорится о ваших правах обращаться к членам этого класса, а это значит, что вы таких прав не имеете. Только собственные методы объекта Boots всегда имеют доступ ко всем данным класса, как открытым, так и закрытым. Даже несмотря на то, что вы сами создали класс Cat, это не дает вам права возвращать или изменять в программе его данные, которые являются закрытыми.
Однако из любого положения есть выход. Чтобы получить доступ к переменным- членам класса Cat, откройте их следующим способом:
class Cat {
public:
unsigned int itsAge; unsigned int itsWeight; void Meow();
};
Теперь благодаря ключевому слову public все члены класса (itsAge, itsWeight и Meow()) стали открытыми.
В листинге 8.1 показано объявление класса Cat с открытыми переменными-членами.
Листинг 8.1. Доступ к открытым членам простого класса
1: // Пример объявление класса с
2: // открытыми членами
3:
4: #include <iostream.h> // для использования cout
5:
6: class Cat // объявляем класс
7: {
8: public: // следующие члены являются открытыми
9: int itsAge;
10: int itsWeight;
11: };
12:
13:
14: int main()
15: {
16: Cat Frisky;
17: Frisky.itsAge =5; // присваиваем значение переменной-члену
18: cout << "Frisky is а cat who is ";
19: cout << Frisky.itsAge << " years old.\n";
20: return 0;
21: }
Результат:
Frisky is а cat who is 5 years old.
Анализ: В строке 6 содержится ключевое слово class. Оно сообщает компилятору о том, что следующий после него блок является объявлением класса. Имя нового класса стоит сразу после ключевого слова class. В данном случае у нас объявляется класс Cat.
Тело объявления класса начинается с открывающей фигурной скобки в строке 7 и заканчивается закрывающей фигурной скобкой и точкой с запятой в строке 11. Строка 8 содержит ключевое слово public, которое означает, что до тех пор, пока не встретится ключевое слово private или конец объявления класса, все последующие члены объявляются открытыми.
В строках 9 и 10 объявляются переменные-члены itsAge и itsWeight.
В строке 14 начинается функция main() программы. Frisky определяется в строке 16 как экземпляр класса Cat, т.е. как объект класса Cat. В строке 17 возраст объекта Frisky (значение переменной itsAge) устанавливается равным 5. А в строках 18 и 19 переменная-член itsAge используется для вывода данных на экран.
Примечание:Попробуйте заблокировать символом комментария строку 8 и перекомпилировать программу. Компилятор покажет сообщение об ошибке в строке 17, поскольку к переменной itsAge больше нет открытого доступа, ведь по умолчанию все члены класса объявляются как закрытые.
Оставьте данные класса закрытыми
Согласно общей стратегии использования классов переменные-члены класса следует оставлять закрытыми. Благодаря этому достигается инкапсуляция данных внутри класса. Доступ следует открывать только к функциям-членам класса, обеспечивающим доступ к его закрытым данным (эти функции еще называют методами доступа). Эти методы можно вызывать из любого места в программе для возвращения или установки значений закрытых переменных-членов.
Зачем же используются в программе такие посредники между закрытыми членами класса и остальной программой? Не проще ли открыть данные класса для внешнего доступа, вместо того чтобы работать с методами доступа?
Дело в том, что применение методов доступа позволяет скрыть от пользователя детали хранения данных в объектах, в то же время, снабжая их методами использования этих данных. В результате можно модернизировать способы хранения и обработки данных внутри класса, не переписывая при этом методы доступа и вызовы их во внешнем программном коде.
Если для некоторой внешней функции в программе, возвращающей возраст объекта Cat, открыть непосредственный доступ к переменной itsAge, то эту функцию пришлось бы переписывать в том случае, если автор класса Cat решит изменить способ хранения этого компонента данных. Однако если между внешней функцией и данными класса будет стоять функция-член GetAge(), то класс Cat можно будет модернизировать сколько угодно раз, что никак не повлияет на способ вызова функции GetAge() в основном коде программы. При вызове в программе метода доступа не нужно знать, хранится ли нужное значение в переменной типа unsigned integer или long либо оно вычисляется при запросе.
Такой подход облегчает эксплуатацию вашей программы и ее поддержку в будущем. Можно сказать, что он продлевает жизнь программе, поскольку, изменяя классы, можно существенно модернизировать выполнение программы, не затрагивая при этом основного кода.
В листинге 6.2 показан класс Cat, в котором в этот раз объявлены закрытые переменные-члены и открытые методы доступа к закрытым данным. Обратите внимание, что перед вами не выполняемый вариант программы, а только объявление класса.
Листинг 6.2. Объявление методов доступа к данным класса