Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Microsoft visual C++ и MFC

© Александр Фролов, Григорий Фролов
Том 24, М.: Диалог-МИФИ, 1993.

[Назад] [Содеожание] [Дальше]

Классы XE "классы"

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

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

Объявление класса имеет следующий вид:


class [<tag>]
{
	<member-list>
} [<declarators>];	

Когда вы определяете класс, то сначала указывается ключевое слово class, а затем в качестве аргумента <tag> имя самого класса. Это имя должно быть уникальным среди имен других классов, определенных в вашей программе.

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

Классы образуют собственное пространство имен. Имена элементов одного класса могут совпадать с именами элементов другого класса и даже с именами других переменных и функций определенных в программе.

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

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


[class] tag declarators;

Ключевое слово class перед именем класса можно опустить.

Для динамического создания и удаления объектов классов можно пользоваться операторами new и delete. Эти операторы позволяют легко строить списки классов, деревья и другие сложные структуры.

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

Ключевое слово this XE "this" представляет собой указатель на текущий объект класса. Методы класса могут использовать ключевое слово this чтобы получить указатель на объект для которого вызван данный метод. Указатель this представляет собой постоянную величину, вы не можете изменять его значение в своей программе.

Разграничение доступа к элементам класса

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

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

Ключевые слова public XE "public" , private XE "private" и protect XE "protect" указываются в определении класса перед элементами класса, доступом к которым они управляют. Ключевые слова, управляющие доступом, могут быть указаны несколько раз в одном классе, порядок их расположения значения не имеет. По умолчанию элементы класса являются private. Рекомендуется всегда явно определять права доступа к членам класса.

Ниже представлено определение класса Sample:


class Sample
{
	int	iX;
	void	Load();
public:
	void	SetStr();
	void	GetStr();
	char	sDataText[80];
private:
	char	sNameText[80];
	int	iIndex;
public:
	void	ConvertStr();
	int	iLevel;
};

В классе описаны элементы данных iX, sDataText, sNameText, iIndex, iLevel и методы Load, SetStr, GetStr, ConvertStr.

Элементы данных и методы SetStr, GetStr, sDataText, ConvertStr, iLevel объявлены public. К ним можно обращаться как из методов класса Sample, так и из программы. Остальные элементы класса объявлены как private. Доступ к ним открыт только для методов самого класса, а также дружественных функций и дружественных методов других классов. Дружественные функции и дружественные классы описаны в следующем разделе.

Методы, входящие в класс

Если исходный текст метода XE "методы" очень короткий, то такой метод обычно определяется непосредственно внутри класса. Вы можете указать, что вместо вызова необходимо выполнять подстановку его тела. Для этого перед ее объявлением следует указать ключевое слово inline XE "inline" . Вот пример определения методов SetWeight и GetWeight непосредственно внутри класса:


class line
{
public:
	void SetLength(int newLength) { length = newLength; }
	int  GetLength() { return length; }
private:
	int length;
};	

Если исходный код методов не такой короткий, то при определении класса указывается только объявление метода, а его определение размещается отдельно. Встраиваемые методы также можно определить вне класса. Когда вы определяете метод отдельно от класса, то имени метода должно предшествовать имя класса и оператор разрешения области видимости :: XE "оператор \:\:" .


class convert
{
public:
	void GetString()  { scanf(sText,"%s"); }
	void ShowString() { puts(sText); }
	int  ConvertString();
	void DummyString();
private:
	char sText[80];
};

void convert::ConvertString(void) 
{
	int i;

	for(i = 0; sText[i] != '\0'; i++ ) {
		sText[i] = tolower(sText[i]);
	}
	return i;
}

inline void convert::DummyString(void) 
{
	int i = 0;

	while(sText[i++]) 
		sText[i] = 0;
}

Чтобы вызвать метод, надо сначала указать имя объекта класса, для которого будет вызван метод, а затем через точку имя метода. Вместо имени объекта можно использовать указатель на объект. В этом случае вместо символа точки надо использовать оператор -> XE "оператор ->" . Если метод вызывается из другого метода этого же класса, то имя объекта и оператор выбора элемента указывать не надо.

Следующий пример демонстрирует вызов методов класса convert, исходный текст которого приведен выше:


void main() 
{
	convert ObjectA;

	ObjectA.GetString();
	ObjectA.ConvertString();
	ObjectA.ShowString();

	convert *pObjectB = new convert;

	pObjectB->GetString();
	pObjectB->ConvertString();
	pObjectB->ShowString();
}

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

Конструкторы и деструкторы класса

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

Язык С++ предоставляет удобное средство для инициализации и удаления объектов класса. Для этого предусмотрены специальные методы. Они называются конструкторами XE "конструктор" и деструкторами XE "деструктор" .

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

Класс BookList, представленный ниже, имеет два конструктора BookList. Первый конструктор не имеет параметров, второй конструктор имеет один параметр типа int:


class BookList
{
	// Конструкторы класса
	void	BookList();
	void	BookList(int);
	// Остальные члены класса 
};
// Первый конструктор класса
BookList::BookList(void)
{
}
// Второй конструктор класса
BookList::BookList(int iList)
{
}

Когда вы создаете объекты класса, вы можете указать параметры для конструктора. Ниже создаются два объекта класса BookList - FirstBook и SecondBook:


BookList FirstBook;
BookList SecondBook(100);

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

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

Ниже представлен класс Object, для которого определен деструктор ~Object:


class Object
{
	void	~Object();
	// Остальные члены класса
};

Object::~Object(void)
{
}

Методы, не изменяющие объекты класса

Если метод не изменяет объект, для которого он вызывается, такой метод можно объявить с ключевым словом const XE "const" . Ключевое слово const указывается после закрывающей скобки списка аргументов метода. Вы должны указать, что метод не изменяет объект и в объявлении и в определении метода.

Методы, объявленные как const XE "методы const" , не могут изменять элементы класса или вызывать другие методы, объявленные без ключевого слова const. Нарушение этих правил вызовет ошибку на этапе компиляции приложения.

В библиотеке классов MFC вы встретите много методов, объявленных как const. Их использование повышает надежность приложения, так как компилятор сможет обнаружить ошибки, связанные с непреднамеренным изменением элементов класса.

Ниже мы привели пример класса, для которого метод GetWeight определен как const. Если вы попытаетесь модифицировать элемент данных weight непосредственно из метода GetWeight, компилятор сообщит об ошибке.


#include 

void main(void);

// Класс ClassMen включает элемент данных и два метода для 
// обращения к нему
class ClassMen
{
public:
	void SetWeight(int newWeight);
	int  GetWeight() const;
private:
	int weight;
};	

// Метод GetWeight позволяет определить значение элемента 
// weight. Этот метод объявлен как const и не может 
// модифицировать объекты класса ClassMen
int  ClassMen::GetWeight() const
{ 
	return weight; 
}

// Метод SetWeight позволяет изменить значение weight.
// Такой метод нельзя объявлять как const
void ClassMen::SetWeight(int newWeight) 
{ 
	weight = newWeight; 
}

// Главная функция программы
void main(void)
{
	// Создаем объект класса ClassMen
	ClassMen	alex;

	// Устанавливаем значение элемента weight объекта alex
	alex.SetWeight(75);

	// Отображаем значение элемента weight объекта alex
	cout << alex.GetWeight() << "\n";
}

Статические методы

Вы можете объявить некоторые методы класса статическими методами. Для этого вы должны воспользоваться ключевым словом static. Статические методы не принимают параметр this XE "this" . На использование статических методов накладывается ряд ограничений.

·         Статические методы могут непосредственно обращаться только к статическим членам класса.

·         Статический метод не может быть объявлен как виртуальный метод.

·         Вы не можете определить нестатический метод с тем же именем и тем же набором параметров, что и статический метод класса.

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

Ниже представлен класс Circle, в котором определена статический метод GetPi. Он используется для получения значения статического элемента класса fPi.


class Circle
{
public:
  	static void GetPi()
     	{ return fPi; }

private:
	static float fPi;    
};
float Circle::fPi = 3.1415;

Вы можете вызвать метод GetPi следующим образом:


class Circle
{
public:
  	static void GetPi()
     	{ return fPi; }

private:
	static float fPi;    
};
float Circle::fPi = 3.1415;

Обратите внимание, что объект класса Circle не создается.

Общие члены объектов класса

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

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


class CWindow 
{
public:
	int xLeftTop, xRightBottom;
	int yLeftTop, yRightBottom;
 	static char title[80];

	void SetTitle(char*);
};

char Cwindow::title[80] = "заголовок окна";

Каждый объект класса Cwindow будет иметь уникальные координаты, определяемые элементами данных xLeftTop, xRightBottom, yLeftTop, yRightBottom и одинаковый заголовок, хранимый элементом данных title.

Общие элементы данных находятся в области действия своего класса. Методы класса могут обращаться к общим элементам точно так же, как к остальным данным из класса:


void SetTitle(char* sSource) 
{
	strcpy(title, sSource);
}

Чтобы получить доступ к общим элементам из программы, надо объявить их как public XE "public" . Для обращения к такой переменной перед ее именем надо указать имя класса и оператор :: XE "оператор \:\:" .


printf(Cwindow::title);

Дружественные функции и дружественные классы

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

Дружественные функции

В Си++ вы можете определить для класса так называемую дружественную функцию, воспользовавшись ключевым словом friend. В классе содержится только объявление дружественной функции. Ее определение расположено вне класса. Вы можете объявить дружественную функцию в любой секции класса - public, private или protect.

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

В следующем примере определена функция Clear, дружественная для класса point. Дружественная функция Clear используется для изменения значения элементов данных m_x и m_y, объявленных как private:


//==========================================================
// Класс point
class point
{
public:
	// Функция Clear объявляется дружественной классу point
	friend void point::Clear(point*); 

	// Интерфейс класса...
private:
	int	m_x;
	int	m_y;
};

//==========================================================
// Функция Clear
void Clear(point* ptrPoint) 
{
	// Обращаемся к элементам класса, объявленным как private
	ptrPoint->m_x = 0;
	ptrPoint->m_y = 0;
	return;
}

//==========================================================
// Главная функция
void main() 
{
	point pointTestPoint;

	// Вызываем дружественную функцию
	Clear(&pointTestPoint);
}

С помощью ключевого слова friend вы можете объявить некоторые методы одного класса дружественными для другого класса. Такие методы могут обращаться ко всем элементам класса, даже объявленным как private и protect, несмотря на то, что сами они входят в другой класс.

В следующем примере мы определяем два класса - line и point. В классе point определяем метод Set и объявляем его в классе line как дружественный. Дружественный метод Set может обращаться ко всем элементам класса line:


// Предварительное объявление класса line
class line;

//==========================================================
// Класс point
class point
{
public:
	// Метод Set класса point 
	void Set(line*); 

	// ...
};

//==========================================================
// Класс line 
class line
{
public:
	// Метод Set класса point объявляется дружественной 
	// классу point
	friend void point::Set(line*); 

private:
	int	begin_x, begin_y;
	int	end_x, end_y;
};

//==========================================================
// Функция Clear
void point::Set(line* ptrLine) 
{
	// Обращаемся к элементам класса line, объявленным как 
	// private
	ptrLine->begin_x = 0;
	ptrLine->begin_y = 0;
	// ...
	
	return;
}

//==========================================================
// Главная функция
void main() 
{
	point		pointTestPoint;
	line		lineTestPoint;

	// Вызываем дружественный метод
	pointTestPoint.Set(&lineTestPoint);
}

Дружественные классы

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

Так, например, в предыдущем примере вы могли бы определить, что класс point является дружественным классу line. Все методы класса point могут обращаться к любым элемента класса line.


//==========================================================
// Класс point
class point
{
	// ...
};

//==========================================================
// Класс line 
class line
{
public:
	// Класс point объявляется дружественным классу line
	friend class point; 
};
[Назад] [Содеожание] [Дальше]