Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #58, ОКТЯБРЬ 2003 г.

Сколько плюсов у C++?

AvaLANche

Xakep, номер #058, стр. 058-090-4


MyString mystr; // пустая строка

MyString mystr2 ("C++ рулит");

mystr2 = mystr + MyString ("C тоже ничего");

mystr = mystr2; // теперь обе строки равны

Удобно? Вполне! Пользовательские типы предоставляют огромные возможности и сильно упрощают нелегкий труд программиста при решении самых разных задач, ведь операции над их объектами ничем не отличаются от операций над переменными встроенных типов (int, char и т.д.). Типы, подобные MyString, принято называть конкретными типами.

Однако в типе MyString потеряно одно свойство, которым обладал модуль MyString - реализация не отделена от интерфейса. Конечно, представление строки закрыто (private), но, тем не менее, оно "видно" пользователю. И при изменении реализации строки, программеру-юзеру класса придется перекомпилировать весь свой код. Это не есть гуд. Но что поделаешь, с конкретными типами мы хотим работать как со встроенными, и тут по-другому никак.

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

class MyString {

virtual int length () = 0;

virtual char* c_str () = 0;

// и дальше в том же духе...

}

Модификатор "virtual" означает "может быть переопределено в производном классе", а "=0" значит, что эта функция ДОЛЖНА быть переопределена в будущем. Конечно, я привел совсем бредовый пример: идея сделать виртуальной функцию, возвращающую длину строки, может родиться только в воспаленном мозгу :). Но смысл, думаю, ты уловил. Фишка в том, что производными классами можно пользоваться, не зная конкретных деталей их реализации:

void f (MyString& mystr)

{

cout << mystr.c_str ();

}

При этом функция f () проглатывает объект любого класса, производного от нашего полиморфного (т.е. предоставляющего интерфейс для множества других) MyString. Например, BigStr:

class BigStr: public MyString {

int length () { return 100; }

// и так далее...

}

Ориентируемся на объекты

Механизм наследования из предыдущего примера приводит нас еще к одной технике - объектно-ориентированному программированию. Его основы - абстракция данных и иерархия классов. Последняя представляет собой различные проявления множественного наследования. Например, класс A, класс B, производный от A, класс С, производный от B, и класс D, производный от A и B, представляют собой несложную иерархию. Производный (дочерний) класс B наследует все члены базового (родительского) класса A - это главная идея наследования.

Теперь принцип написания программы звучит так: "Реши, какие понадобятся классы, обеспечь полный набор операций над ними и вырази общность через наследование". Последнее - довольно непростая задача. Тому, кто ее решит (на этапе проектирования программы), не придется все переделывать в самый ответственный момент.

Обобщенное программирование

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

Назад на стр. 058-090-3  Содержание  Вперед на стр. 058-090-5