Назад | Начало урока | Вперед
Содержание

Глава 17

Смотрите на мир объективно

В этой главе...

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

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

Вверх

Что такое классы и с чем их едят

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

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

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

Изменить данные, содержащиеся в классе, или способы их обработки возможно только
путем изменения элементов самого класса. Благодаря этому исключаются такие неприятные ситуации, при которых изменение одной глобальной переменной может нарушить работу всей программы. Используя классы, вы всегда сможете контролировать возможные последствия изменения отдельных переменных или их значений.

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

Например, чтобы определить координаты линии, для хранения которых используется класс LineObject, можно просто вызвать функцию Initialize. Таким образом программисту, который использует этот класс, требуется всего лишь вызвать эту функцию и совершенно не нужно разбираться, какие при этом будут задействованы переменные или как именно будет использована функция Console.

Разберемся в деталях

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

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

Вверх

Данные-члены

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

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

Функции-члены

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

Вверх

Объявление классов

Для объявления классов используется ключевое слово class:

class ClassName
{

public:
здесь описываются элементы данных и функции-члены, доступные
пользователям этого класса
} ;

Предположим, вам нужно создать класс LineObject, За основу можете взять структуру PointList (она использовалась в качестве примера в предыдущих главах), добавив к ней функции и некоторые новые переменные:

__gc class LineObject
{

public:
void Initialize();
void Draw(Graphics *poG);
private:
int m_nXFrom;
int m_nYFrom;
int m_nXTo;
int m_nYTo;
};

Позже вы сможете определить, какие действия выполняют функции Initialize и Draw.

Обратите внимание, что класс LineObject был определен как класс, для которого активирована возможность сборки мусора (эта возможность рассматривалась в главе 13). В Visual C++ .NET обязательно объявлять классы именно таким образом. Если вы этого не сделаете, компилятор выдаст сообщение об ошибке.

Вверх

Ограничение доступа

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

О том, что в классе LineObject есть такие переменные, как m_nXFrom, m_nYFroin,
m_nXTo и m_nYTo, известно только функциям-членам Initialize и Draw. Для тех, кто будет просто использовать класс LineObject, эти переменные не существуют. Только функции-члены класса LineObject имеют к ним доступ и могут изменять их значения.

Как вы смогли убедиться на примере объявления класса LineObject, ограничить доступ к членам данных очень просто. После того как вы объявили открытые члены данных и функции-члены (использовав для этого ключевое слово public), наберите ключевое слово private. Затем перечислите закрытые члены данных, которые предназначены для использования исключительно внутри данного класса. (Можно создать также закрытые функции-члены. Они могут быть вызваны только другими функциями-членами этого же класса. По сути, это будут вспомогательные функции для открытых функций-членов, действия и возвращаемые результаты которых тем не менее не предназначаются для непосредственного использования за пределами этого класса.)

Совет:

Если вы придерживаетесь соглашений о присвоении имен, принятых в данной
книге, начинайте имена закрытых членов данных символами m_.

Защищенный доступ

До настоящего времени вы сталкивались только с двумя ключевыми словами, регулирующими права доступа: public и private . Члены данных и функции-члены, объявленные после слова public , являются открытыми (т.е. они могут быть использованы за пределами класса, в котором объявляются). Открытые члены данных и функции-члены создают внешний интерфейс класса. В свою очередь, внешний интерфейс — это то, к чему обращаются при использовании класса.

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

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

Права доступа регулируются еше одним ключевым словом— protected. Члены данных и функиии-члены, объявленные после этого слова, могут быть использованы только функциями-членами этого же класса либо функциями-членами классов, производных от данного класса.

Вверх

Определение функций-членов

После того как вы объявите, из чего состоит класс, можно приступить к определению того, какие именно действия будут выполнять функции-члены. Функции-члены определяются почти точно так же как и обычные функции (что было описано в главе 32). Но, поскольку функция-член является составной частью какого-то определенного класса, указывать нужно как имя функции, так и имя этого класса. (В конце концов, ведь несколько разных классов могут иметь, например, функцию Initialize.)

Чтобы определить функцию-член, наберите имя ее класса, далее два двоеточия (::) и затем имя самой функции. Эти два двоеточия называются квалифицирующим оператором. Этот оператор означает, что указанная функция-член (или член данных) является частью указанного класса. Например, код LineObject: : Draw указывает на функцию-член Draw класса LineObject, а код LineObject: :m_nXFroin ссылается на член данных m_nXFrom этого же класса.

Ниже приведен код, которым определяется функция-член Draw класса LineObject. Эта
функция отображает линию на экране.

viod LineObject::Draw(Graphics *poG)
{

//Создание пера для рисования
Pen *poPen = new Pen(Color::Red);
//Отображение линии
poG->DrawLine(poPen, m_nXFrcm, nwiYFrom, m_nXTo, m_nYTo);

}

Обратите внимание, что при вызове функции DrawLine вам не нужно набирать
LineObject::m_nXFrom, поскольку внутри функции-члена не требуется использовать
квалифицирующий оператор (::) при указании на члены данных. Внутри класса принадлежность члена данных к этому классу определяется автоматически. Таким образом, использование названия m_nXFrom внутри функции-члена LineObject::Draw равнозначно использованию кода SongList: :m_nXFrom (SongList— название экземпляра класса LineObject). Также обратите внимание на то, что, поскольку Draw является функцией-членом класса LineObject, она имеет доступ к закрытым членам данных этого класса. (В то же время функции-члены других классов, например Circle::Draw, не могут получить непосредственный доступ к закрытым членам данных класса LineObject.)

Что делать с готовыми классами?

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

//Статически создаваемый класс
LineObject oFoo;
//Динамически создаваемый класс
LineObject *poBar = new SongList;

Обычно классы создаются динамически. (Классы .NET CLR всегда создаются динамически.)
Те же правила и принципы, которые относятся к статическим и динамическим перемен
ным, распространяются и на статические и динамические классы. Создание классов обычно называют созданием экземпляров классов или созданием объектов.

Вверх

Доступ к элементам класса

Классы — это те же структуры данных. Если нужно получить доступ к значению, сохраненному как член данных класса, используйте точку (.). Точно так же можно получить доступ и к функции-члену:

//Вызов функции Draw класса LineObject
LineObject oFirst;
oFirst.Exaw();

Если вы используете указатель на экземпляр класса, функция вызывается несколько иначе:

LineObject *poFirst = new LineObject();
poFirst->Draw();

Подобно переменным, экземпляры классов (или объекты) имеют свое имя и тип.

Вверх

Статическое соответствие

В главе 14 отмечалось. Что можно использовать классы .NET ArrayList и Stack для хранения любых объектов, для которых включена возможность сборки мусора. Следовательно, если вы создадите класс и активизируете для него возможность сборки мусора, в последующем можно будет использовать ArrayList и Stack для хранения экземпляров этого класса. Это очень удобно, если необходимо работать с произвольными наборами элементов (вспомните набор линий, отображаемых на экране).

Ниже показано, как можно использовать ArrayList для хранения объектов LineObject.

LineObject *poLine = new LineObject();
ArrayList *paLines = new ArrayList();
//Сохранение объекта LineObject в структуре ArrayList
paLine->Add (poLine) ;

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

Предположим, например, что необходимо вызвать функцию-член Draw объекта LineObject, только что сохраненного в структуре ArrayList. Если вы непосредственно обратитесь к этому объекту и попробуете вызвать функцию Draw (как показано ниже), то получите сообщение об ошибке.

(poEnumerator->Current)->Draw(poG);

Причина в том, что компьютер не знает, что собой представляет элемент структуры ArrayList, к которому вы обращаетесь, и тем более не знает, что у этого элемента есть функция-член Draw. Чтобы объяснить ему, чем является этот элемент, нужно использовать ключевое слово static_cast (в переводе это означает установление статического соответствия). Перед тем как указать на элемент, наберите это слово и за ним в угловых скобках укажите тип элемента. Если вернуться, например, к сохраненному в структуре ArrayList объекту LineObject, то, чтобы вызвать функцию Draw, нужно набрать такой код:

static__cast(poEnumerator->Current)->Draw(poG);

Этот код как бы говорит компьютеру: "Возьми текущий элемент структуры ArrayList, рассматривай его как указатель на объект LineObject и, исходя из этого, вызови функцию Draw, которая является функцией-членом этого объекта".

Начинающие программисты часто путают названия классов и названия объектов.
Если нужно сослаться на функцию-член Draw объекта oFirst , который является
экземпляром класса LineObject, наберите код

oFirst.Draw()

Но ни в коем случае не набирайте

LineObject.Draw()

Другими словами, используйте название объекта, но не самого класса.

Имена, используемые функциями-членами

При написании кодов функций-членов не нужно набирать точку или символы ->, чтобы
получить доступ к членам данных или к другим функциям-членам этого же объекта. Сама функция принадлежит объекту, поэтому на нее распространяется область видимости других элементов этого объекта. Следовательно, если внутри данного объекта объявлено имя х, любая функция-член может просто ссылаться на это имя.

Вверх

Немного практики

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

gc class LineObject
{

public:
void Initialize();
void Draw(Graphics *poG);
private:
int m_nXFrom;
int m_nYFrom;
int m_nXTo;
int m_nYTo;
};

С этим классом вы уже встречались ранее. Второй класс, DisplayObject, сохраняет информацию обо всех отображаемых объектах. В частности, он содержит в себе структуру ArrayList, состоящую из объектов LineObject. Также у него есть функции-члены, предназначенные для добавления новых объектов LineObject и для отображения линий на экране.

gc class DisplayObject
{

public:
void Add();
void Draw(Graphics *poG);
void Initialize();
private:
ArrayList *m_paLines;
};

Одной из наиболее интересных функций этого объекта является функция Draw. Она по порядку извлекает из списка ArrayList все объекты LineObject и для каждого из них вызывает функцию Draw (ту, которая принадлежит классу LineObject). Таким образом она использует класс LineObject для выполнения всей тяжелой работы.

//Отображение всех линий на экране
void DisplayObject::Draw(Graphics *poG)
{

//Прссмотр всех объектов, представляющих линии
IEnumerator *poEnumerator = m_paLines->GetEnumerator();
while (poEnumerator->MoveNext())
{
//Вызов функции Draw для объекта
static_cast(LineObject* poEnumerator->Current)->Draw(poG);

Сама функция main этой программы очень проста. Она создает новый класс
DisplayObject, инициализирует его, и вызывает метод Draw. Итак, вся программа выглядит следующим образом:


// Draw4
// Introduces class structures

#include "stdafx.h"

#using
#using
#using
#using

using namespace System;
using namespace System::Drawing;
using namespace System::Windows::Forms;
using namespace System::Collections;

//Stores line information
__gc class LineObject
{

public:
void Initialize();
void Draw(Graphics *poG);
private:
int m_nXFrom;
int m_nYFrom;
int m_nXTo;
int m_nYTo;
};

//Initialize values for this line object
void LineObject::Initialize()
{

//Read the x and y positions and store them in the structure
Console::WriteLine(S"What is the starting x position?");
m_nXFrom = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the starting y position?");
m_nYFrom = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the ending x position?");
m_nXTo = Int32::Parse(Console::ReadLine());
Console::WriteLine(S"What is the ending y position?");
m_nYTo = Int32::Parse(Console::ReadLine());
}

//Draws the line
void LineObject::Draw(Graphics *poG)
{

//Create a pen with which to draw
Pen *poPen = new Pen(Color::Red);
//Draw the line
poG->DrawLine(poPen, m_nXFrom, m_nYFrom, m_nXTo, m_nYTo);
}

//Stores all of the lines to draw
__gc class DisplayObject
{

public:
void Add();
void Draw(Graphics *poG);
void Initialize();
private:
ArrayList *m_paLines;
};

//Initialize the DisplayObject structures
void DisplayObject::Initialize()
{

m_paLines = new ArrayList();
}

//Add a new object
void DisplayObject::Add()
{

bool fFinished = false;
String *pszMore;

while (!fFinished)
{

//Create a new line object
LineObject *poLine = new LineObject();
//Initialize it
poLine->Initialize();
//Store it in the structure
m_paLines->Add(poLine);

//Should we add another one?
Console::WriteLine("Hit y to enter a new line");
pszMore = Console::ReadLine();
if (!pszMore->Equals(S"y"))
{

fFinished = true;
}
}
}

//Draw all of the lines
void DisplayObject::Draw(Graphics *poG)
{

//Go through each of the line objects
IEnumerator *poEnumerator = m_paLines->GetEnumerator();
while (poEnumerator->MoveNext())
{
//Call the draw method for the object
static_cast(poEnumerator->Current)->Draw(poG);
}
}

// This is the entry point for this application
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{

//Structures for drawing graphics
Form *poForm = new Form();
Graphics *poGraphics = poForm->CreateGraphics();
DisplayObject *poDisplay = new DisplayObject();

//Initialize the display object
poDisplay->Initialize();

//Add the lines
poDisplay->Add();

//Show the display surface
poForm->Show();

//Draw the lines
poDisplay->Draw(poGraphics);

//Free up the graphics surface
poGraphics->Dispose();

//Hang out until the user closes the form
Application::Run(poForm);

}

Вверх

Управление памятью в неуправляемых программах

При написании программы Draw4 вам не нужно было беспокоиться об освобождении памяти, выделяемой для всех создаваемых объектов. Среда .NET делала
это автоматически. Если вы создаете аналогичную неуправляемую программу
(используя обычный C++), вам придется самим позаботиться о том, чтобы динамически выделяемая память по завершении выполнения программы была освобождена. Сделать это можно путем создания для каждого класса функции-члена
Delete. (Другой возможный вариант рассматривается в главе 18.)

Кроме того, поскольку вы не сможете использовать класс .NET ArrayList, вам придется самостоятельно определить класс, задачей которого будет создание связанного списка для сохранения последовательности объектов. Приведенная ниже программа Draw5 является неуправляемой версией программы Draw4. Она содержит класс LineObjectContainer, предназначенный для создания связанного списка из объектов LineObject:

//Класс, представляющий отдельный элемент связанного списка
c l a s s LineObjectContainer
{

public:
LineObjееt *poLine;
LineObjectContainer *poNext;
void Delete();

}

Обратите внимание, что этот класс содержит в себе функцию-член Delete, предназначенную для освобождения памяти. Эта функция вызывается тогда, когда список, состоящий из объектов LineObject становится более не нужным.
Поскольку класс LineObjectContainer использует для хранения информации связан-
ный список, функция Delete находит каждый элемент этого списка и освобождает выделенную для него память:

//Освобождение памяти, выделенной для всего списка
void LineObjectContainer::Delete()
{

LineObjectContainer *poCur, poTemp;
poCur = this;
while (poCur|
{
pcTemp = poCur;
//Освобождение памяти, выделенной для объекта LineObject
delete poCur->poLine;
poCur = poCur->poNext;
//Освобождение памяти, выделенной
//для объекта LineObjectContainer
delete poTemp;
}
}

Обратите внимание, что необходимо освобождать память, выделенную как для объекта
LineObject (предназначен для хранения информации о координатах линии), так и для объ-
екта LineObjectContainer (предназначен для создания связанного списка из объектов
LineObject).

Ниже показана неуправляемая версия объектно-ориентированной программы, рисующей
на экране линии. Поскольку здесь не могут быть использованы возможности .NET, автоматически создающие связанные списки и освобождающие динамически выделяемую память, неуправляемая версия будет несколько сложнее, чем управляемая.


// Draw5
// Introduces class structures
// Unmanaged

#include "stdafx.h"
#include

//Stores line information
class LineObject
{

public:
void Initialize();
void Draw();
private:
int m_nXFrom;
int m_nYFrom;
int m_nXTo;
int m_nYTo;
};

//Initialize values for this line object
void LineObject::Initialize()
{

//Read the x and y positions and store them in the structure
cout << "What is the starting x position?";
cin >> m_nXFrom;
cout << "What is the starting y position?";
cin >> m_nYFrom;
cout << "What is the ending x position?";
cin >> m_nXTo;
cout << "What is the ending y position?";
cin >> m_nYTo;
}

//Draws the line. We'll write out the contents
void LineObject::Draw()
{

cout << "From " << m_nXFrom << "," << m_nYFrom << " to " << m_nXTo << "," << m_nYTo << endl;
}

//Class for constructing a linked list
class LineObjectContainer
{

public:
LineObject *poLine;
LineObjectContainer *poNext;
void Delete();
};

//Clean up the list
void LineObjectContainer::Delete()
{

LineObjectContainer *poCur, *poTemp;

poCur = this;
while (poCur)
{

poTemp = poCur;
//Clear out the line object
delete poCur->poLine;
poCur = poCur->poNext;
//Delete the container object
delete poTemp;
}
}

//Stores a linked list of objects
class ArrayList
{

public:
void Add(LineObject *poLine);
LineObject *MoveNext();
void Delete();
void Initialize();
private:
LineObjectContainer *m_poFirst;
LineObjectContainer *m_poCur;
LineObjectContainer *m_poLast;
};

//Initialize the structures
void ArrayList::Initialize()
{

m_poFirst = 0;
m_poCur = 0;
m_poLast = 0;
}

//Adds a new object to the list
void ArrayList::Add(LineObject *poLine)
{

//Create a new container
LineObjectContainer *poLOC = new LineObjectContainer();
//Set its value
poLOC->poLine = poLine;
poLOC->poNext = 0;

//Tie it in
if (!m_poFirst)
{

m_poFirst = poLOC;
m_poCur = m_poFirst;
}
else
m_poLast->poNext = poLOC;
//Advance the end pointer
m_poLast = poLOC;
}

//Returns the next LineObject item
LineObject *ArrayList::MoveNext()
{

LineObject *poNext;

//If there are no items, just return null
if (!m_poCur)
return 0;

//Find the next line object
poNext = m_poCur->poLine;
//Advance the pointer
m_poCur = m_poCur->poNext;
return poNext;

}

//Clean up memory
void ArrayList::Delete()
{

//Delete the list of line objects
m_poFirst->Delete();
}

//Stores all of the lines to draw
class DisplayObject
{

public:
void Add();
void Draw();
void Initialize();
void Delete();
private:
ArrayList *m_paLines;
};

//Initialize the DisplayObject structures
void DisplayObject::Initialize()
{

m_paLines = new ArrayList();
m_paLines->Initialize();
}

//Clean it up
void DisplayObject::Delete()
{

m_paLines->Delete();
}

//Add a new object
void DisplayObject::Add()
{

bool fFinished = false;
char cMore;

while (!fFinished)
{

//Create a new line object
LineObject *poLine = new LineObject();
//Initialize it
poLine->Initialize();
//Store it in the structure
m_paLines->Add(poLine);

//Should we add another one?
cout << "Hit y to enter a new line" << endl;
cin >> cMore;
if (cMore != 'y')
{

fFinished = true;
}
}
}

//Draw all of the lines
void DisplayObject::Draw()
{

LineObject *poLine;

//Go through each of the line objects
while (poLine = m_paLines->MoveNext())
{

//Call the draw method for the object
poLine->Draw();
}
}

int _tmain(int argc, _TCHAR* argv[])
{

//Structure for the display
DisplayObject *poDisplay = new DisplayObject();

//Initialize the display object
poDisplay->Initialize();

//Add the lines
poDisplay->Add();

//Draw the lines
poDisplay->Draw();

//Clean up
poDisplay->Delete();
delete poDisplay;

return 0;

}


Вверх

Функции доступа

Вы только что увидели, насколько удобно держать члены данных закрытыми от остальной программы и использовать специальные функции для доступа к ним и для изменения их значений. Visual C++ .NET оптимизирует этот процесс, позволяя автоматически создавать функции доступа. (Это такие функции, которые присваивают значения членам данных и считывают их.) Вот как это делается:
property int get_X();
property void ser:__X(int i) ;
Этот код говорит о том, что у объекта есть свойство X. Если нужно прочитать значение этого свойства, можно вызвать функцию get_X () или набрать такой код:
n = foo.X;
Если же нужно присвоить свойству X какое-то значение, можно вызвать функцию
set_X() или набрать код foo.X ~ п;
Если вы объявите только функцию get, свойство станет доступным только для чтения.

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


// Accessors
// Provides private access functions

#include "stdafx.h"

#using

using namespace System;

//This class uses properties for accessors
__gc class PointObject
{

public:
__property int get_X();
__property void set_X(int i);
__property int get_Y();
__property void set_Y(int i);
private:
int m_nX;
int m_nY;
};

//Here we implement what will occur when a programmer references
//the X and Y properties of the object. Note how we use private
//members for storing the real values
int PointObject::get_X()
{

return m_nX;
}

int PointObject::get_Y()
{

return m_nY;
}

void PointObject::set_X(int i)
{

m_nX = i;
}

void PointObject::set_Y(int i)
{

m_nY = i;
}

//Hang out until the user is finished
void HangOut()
{

Console::WriteLine(L"Hit the enter key to stop the program");
Console::ReadLine();
}

// This is the entry point for this application
#ifdef _UNICODE
int wmain(void)
#else
int main(void)
#endif
{

PointObject *poPoint = new PointObject();
//Set the x and y values
poPoint->X = 3;
poPoint->Y = 2;

//Now spit them back
Console::WriteLine(S"The point is {

0
}, {
1
}", poPoint->X.ToString(), poPoint->Y.ToString());

HangOut(); return 0;

}

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

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


// Accessors2
// Provides private access functions
// Unmanaged

#include "stdafx.h"
#include

//This class uses properties for accessors
class PointObject
{

public:
int get_X();
void set_X(int i);
int get_Y();
void set_Y(int i);
private:
int m_nX;
int m_nY;
};

//Here we implement what will occur when a programmer references
//the X and Y properties of the object. Note how we use private
//members for storing the real values
int PointObject::get_X()
{

return m_nX;
}

int PointObject::get_Y()
{

return m_nY;
}

void PointObject::set_X(int i)
{

m_nX = i;
}

void PointObject::set_Y(int i)
{

m_nY = i;
}

int _tmain(int argc, _TCHAR* argv[])
{

PointObject *poPoint = new PointObject();
//Set the x and y values
poPoint->set_X(3);
poPoint->set_Y(2);

//Now spit them back
cout << "The point is " << poPoint->get_X() << "," << poPoint->get_Y() << endl;
char enterchar;
cout <<"Hit the enter key to stop the program";
cin >>enterchar;

return 0;

}


Вверх

Заголовочные файлы

Если программа состоит из нескольких исходных файлов, вам нужно будет объявить классы в заголовочном файле. Если класс необходимо будет использовать в каком-то исходном файле, наберите в нем директиву #include и укажите название заголовочного файла, который должен быть включен в этот исходный файл.
Если вы добавляете в класс или удаляете из класса члены данных «ли функции-члены, не забудьте обновить заголовочный файл. В противном случае вы получите сообщение наподобие такого: 'fоо': is not a member of 'baz '. Расшифровывается оно приблизительно так: "Вы забыли обновить заголовочный файл для того, чтобы функция f o o была включена в класс baz". Или так: "Вы указали неверный параметр, т.е. то, что было перечислено при объявлении класса, не совпадает с тем, на что вы ссылаетесь при его использовании".

Вверх

Общие рекомендации

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

При создании классов придерживайтесь приведенной ниже последовательности действий.

  1. Проанализируйте проблему.
  2. Отдельно выделите данные, которыми нужно будет манипулировать.
    Что это за данные? Как вы будете их обрабатывать?
  3. Разбейте данные и функции на группы, чтобы определить, из чего будет состоять каждый объект.
  4. Скройте детали выполняемых операций.
Для управления объектом используйте функции высокого уровня, так чтобы пользователь не знал, например, что имена сохраняются в массиве или что записи извлекаются из связного списка.
Приведем некоторые общие рекомендации, которыми следует руководствоваться приступая к проектированию классов.


Назад | Начало урока | Вверх | Вперед
Содержание