Лекция 11. Объектно-ориентированный подход к программированию


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

Тип есть конкретное представление некоторой концепции (понятия). Например, имеющийся в C++ тип float с его операциями +, -, * и т.д. обеспечивает ограниченную, но конкретную версию математического понятия действительного числа. Новый тип создается для того, чтобы дать специальное и конкретное определение понятия, которому ничто прямо и очевидно среди встроенных типов не отвечает. Например, в программе, которая работает с телефоном, можно было бы создать тип trunk_module (элемент линии), а в программе обработки текстов — тип list_of_paragraphs (список параграфов). Как правило, программу, в которой создаются типы, хорошо отвечающие понятиям приложения, понять легче, чем программу, в которой это не делается. Хорошо выбранные типы, определяемые пользователем, делают программу более четкой и короткой. Это также позволяет компилятору обнаруживать недопустимые использования объектов, которые в противном случае останутся необнаруженными до тестирования программы.

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

struct date { int month, day, year; };

// дата: месяц, день, год }

date today;

void set_date(date*, int, int, int);

void next_date(date*);

void print_date(date*);

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

struct date {

int month, day, year;

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};

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

date today; // сегодня

date my_burthday; // мой день рождения

my_burthday.set(30,12,1950);

today.set(18,1,1985);

my_burthday.print();

today.next();

Поскольку разные структуры могут иметь функции члены с одинаковыми именами, при определении функции члена необходимо указывать имя структуры:

void date::next()

{

if ( day > 27 ) {

// делает сложную часть работы

}

else day++;

}

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

Классы

Описание date в предыдущем подразделе дает множество функций для работы с date, но не указывает, что эти функции должны быть единственными для доступа к объектам типа date. Это ограничение можно наложить, используя вместо struct class:

class date {

int month, day, year;

public:

void set(int, int, int);

void get(int*, int*, int*);

void next();

void print();

};

Метка public делит тело класса на две части. Имена в первой, закрытой части, могут использоваться только функциями членами. Вторая, открытая часть, составляет интерфейс к объекту класса. Struct — это просто class, у которого все члены общие, поэтому функции члены определяются и используются точно так же, как в предыдущем случае. Например:

void date::print()

{

printf("%d.%d.%d.", day, month, year);

}

Однако функции не члены отгорожены от использования закрытых членов класса date. Например:

void backdate()

{

today.day--; // ошибка

}

В том, что доступ к структуре данных ограничен явно описанным списком функций, есть несколько преимуществ. Любая ошибка, которая приводит к тому, что дата принимает недопустимое значение (например, Декабрь 36, 1985) должна быть вызвана кодом функции члена, поэтому первая стадия отладки, локализация, выполняется еще до того, как программа будет запущена. Это частный случай общего утверждения, что любое изменение в поведении типа date может и должно вызываться изменениями в его членах. Другое преимущество — это то, что потенциальному пользователю такого типа нужно будет только узнать определение функций членов, чтобы научиться им пользоваться.
Защита закрытых данных связана с ограничением использования имен членов класса. Это можно обойти с помощью манипуляции адресами, но это уже, конечно, жульничество.

Ссылки на Себя

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

class x {

int m;

public:

int readm() { return m; }

};

x aa;

x bb;

void f()

{

int a = aa.readm();

int b = bb.readm();

// ...

}

В первом вызове m относится к aa.m, а во втором — к bb.m. Указатель на объект, для которого вызвана функция член, является скрытым параметром функции. На этот неявный параметр можно ссылаться явно как на this. В каждой функции класса x указатель this неявно описан как

x* this;

и инициализирован так, что он указывает на объект, для которого была вызвана функция член. this не может быть описан явно, так как это ключевое слово. Класс x можно эквивалентным образом описать так:

class x {

int m;

public:

int readm() { return this->m; }

};

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

class dlink {

dlink* pre; // предшествующий

dlink* suc; // следующий

public:

void append(dlink*);

// ...

};

void dlink::append(dlink* p)

{

p->suc = suc; // то есть, p->suc = this->suc

p->pre = this; // явное использование this

suc->pre = p; // то есть, this->suc->pre = p

suc = p; // то есть, this->suc = p

}

dlink* list_head;

void f(dlink*a, dlink *b)

{

// ...

list_head->append(a);

list_head->append(b);

}

Чтобы присоединить звено к списку необходимо обновить объекты, на которые указывают указатели this, pre и suc (текущий, предыдущий и последующий). Все они типа dlink, поэтому функция член dlink::append() имеет к ним доступ.

Инициализация

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

class date {

// ...

date(int, int, int);

};

Когда класс имеет конструктор, все объекты этого класса будут инициализироваться. Если для конструктора нужны параметры, они должны даваться:

date today = date(23,6,1983);

date xmas(25,12,0); // (xmas - рождество)

date my_burthday; // недопустимо, опущена инициализация

Часто бывает хорошо обеспечить несколько способов инициализации объекта класса. Это можно сделать, задав несколько конструкторов. Например:

class date {

int month, day, year;

public:

// ...

date(int, int, int); // день месяц год

date(char*); // дата в строковом представлении

date(int); // день, месяц и год сегодняшние

date(); // конструктор по умолчанию: сегодня

};

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

date today(4);

date july4("Июль 4, 1983");

date guy("5 Ноя");

date now; // инициализируется по умолчанию

Размножение конструкторов в примере с date типично. При разработке класса всегда есть соблазн обеспечить «все», поскольку кажется проще обеспечить какое-нибудь средство просто на случай, что оно кому-то понадобится или потому, что оно изящно выглядит, чем решить, что же нужно на самом деле. Последнее требует больших размышлений, но обычно приводит к программам, которые меньше по размеру и более понятны. Один из способов сократить число родственных функций — использовать параметры по умолчанию. В случае date для каждого параметра можно задать значение по умолчанию, интерпретируемое как «по умолчанию принимать: today» (сегодня).

class date {

int month, day, year;

public:

// ...

date(int d =0, int m =0, int y =0);

date(char*); // дата в строковом представлении

};

date::date(int d, int m, int y)

{

day = d ? d : today.day;

month = m ? m : today.month;

year = y ? y : today.year;

// проверка, что дата допустимая

// ...

}

Когда используется значение параметра, указывающее «брать по умолчанию», выбранное значение должно лежать вне множества возможных значений параметра. Для дня day и месяца mounth ясно, что это так, но для года year выбор нуля неочевиден. К счастью, в европейском календаре нет нулевого года. Сразу после 1 г. до н.э. (year==-1) идет 1 г. н.э. (year==1), но для реальной программы это может оказаться слишком тонко.

Объект класса без конструкторов можно инициализировать путем присваивания ему другого объекта этого класса. Это можно делать и тогда, когда конструкторы описаны. Например:

date d = today; // инициализация посредством присваивания

По существу, имеется конструктор по умолчанию, определенный как побитовая копия объекта того же класса. Если для класса X такой конструктор по умолчанию нежелателен, его можно переопределить конструктором с именем X(X&). Это будет обсуждаться позднее.



Дата добавления: 2016-07-27; просмотров: 1365;


Поиск по сайту:

Воспользовавшись поиском можно найти нужную информацию на сайте.

Поделитесь с друзьями:

Считаете данную информацию полезной, тогда расскажите друзьям в соц. сетях.
Poznayka.org - Познайка.Орг - 2016-2024 год. Материал предоставляется для ознакомительных и учебных целей.
Генерация страницы за: 0.019 сек.