- •Ю.Е. Алексеев, а.В. Куров
- •Часть 5
- •Алексеев Юрий Евтихович Куров Андрей Владимирович
- •14. Классы
- •15. Создание проекта
- •15.1 Помещение компонент на форму
- •15.2 Окно сведений об объекте
- •15.3 Редактор кода
- •15.4 Основные компоненты визуальной среды программирования
- •15.5 Лабораторная работа №1. Освоение среды визуального программирования на примере создания приложения для вычисления значения определенного интеграла
- •15.6 Задания для самостоятельной работы
- •15.7 Лабораторная работа n2. Работа с текстовым файлом с использованием многострочного текстового редактора
- •Многострочный текстовый редактор RichTextBox
- •Компонент OpenFileDialog
- •Компонент SaveFileDialog
- •Компонент главное меню MenuStrip
- •Окно сообщений MessageBox
- •Порядок выполнения лабораторной работы.
- •15.8 Задания на обработку текстовых файлов Общее указание для всех вариантов
- •Д обавляемые подпункты меню Работа1 и Работа2 по вариантам
- •15.9 Лабораторная работа n3. Работа с бинарным файлом с использованием таблицы DataGridView
- •Порядок выполнения лабораторной работы.
- •Примеры обработчиков событий.
- •15.10 Задания на обработку бинарных файлов Общее указание для всех вариантов
- •Д обавляемые подпункты меню Работа1 и Работа2 по вариантам
- •Список литературы
- •Алфавитный указатель
- •Вопросы для самопроверки
- •Заключение
14. Классы
Класс можно рассматривать как тип данных, но в отличие от обычного типа, определяющего набор значений, класс объединяет в себе данные и подпрограммы (функции) обработки этих данных. Класс можно рассматривать как дальнейшее развитие типа данных структура. В классе, как и в структуре, объединяются данные различных типов. Эти данные принято называть полями. Помимо этого, класс содержит функции, которые называют методами. Для объявления класса используется ключевое слово class, за которым следует имя типа (класса), а затем в фигурных скобках следуют объявления типа каждого поля и метода. Объявление завершается точкой с запятой.
class [имя типа (класса)]
{
[спецификатор доступа] тип1 поле1;
[спецификатор доступа] тип2 поле2;
…..
[спецификатор доступа] типN полеN;
[спецификатор доступа] заголовок метода1;
[спецификатор доступа] заголовок метода2;
……
[спецификатор доступа] заголовок методаM;
};
Например, объявление класса точка на плоскости с методами, позволяющими инициализировать поля, являющиеся координатами, и считывать значения этих полей, выглядит следующим образом:
class point
{
public:
float x,y;
void initx(float ix);
void inity(float iy);
float getx();
float gety();
};
Поля класса могут иметь любой тип, кроме типа этого же класса (могут быть указателями или ссылками на этот тип). Они могут объявляться с модификатором const, при этом они инициализируются только один раз и не могут изменяться. Инициализация полей при описании не разрешается.
Поля и методы класса объявляются с использованием спецификаторов доступа private, public, protected. Элементы, описанные после служебного слова private, доступны только внутри этого класса и недоступны вне этого класса. Этот вид доступа принят в классе по умолчанию. Элементы, описанные с использованием спецификатора public, доступны вовне этого класса (в области видимости объекта этого класса) и определяют интерфейс класса. Элементы, описанные в секции protected (защищенный), рассматриваются как открытые в производных классах (рассматриваются дальше в наследовании) и закрытые в остальной части программы.
Секции могут располагаться в произвольном порядке, причем каждая секция может создаваться любое количество раз. Действие очередного спецификатора распространяется до следующего спецификатора или до конца объявления класса. Рекомендуется использовать не более одного раздела каждого вида, группируя данные соответствующего раздела.
Конкретные переменные типа класс называют экземплярами класса, или объектами. Время жизни и видимость объектов зависят от вида и места их описания и подчиняются общим правилам языка.
Каждый объект получает уникальный набор полей, но общий для всех объектов класса набор методов. Поскольку данные (поля) и методы инкапсулированы в одном обекте, то все данные (поля) глобальны по отношению к любым его методам и могут использоваться с указанием простого имени. При этом формальные параметры методов совпадать с именами полей не должны.
Функции-члены класса имеют доступ ко всем членам класса, объявленным в классе. Функции-члены класса могут вызывать друг друга напрямую. Это становится возможным благодаря наличию специального параметра this, который хранит адрес области памяти переменной типа класс и неявно передается методам при вызове.
Методы класса могут размещаться внутри описания класса. Однако при создании сложных классов методы выносятся за пределы кдасса. При этом заголовок метода (прототип) указывается внутри класса. Для определения функции – члена класса вне класса используется оператор разрешения области видимости :: (два двоеточия). При этом перед кодом метода указывается имя класса, которому он принадлежат.
Программа 1. Программа, демонстрирующая работу с классом точка
class point
{
public:
float x,y;
void initx(float ix);
void inity(float iy);
float getx();
float gety();
};
int _tmain(int argc, _TCHAR* argv[])
{
point p1;
p1.initx(5.5);
p1.inity(-2.2);
printf("x=%5.1f y=%5.1f",p1.getx(),p1.gety());
getch();
return 0;
}
void point::initx(float ix)
{ x=ix; }
void point::inity(float iy)
{ y=iy; }
float point::getx()
{ return x; }
float point::gety()
{ return y; }
Классы могут быть глобальными (объявляются вне какого-либо блока) и локальными (объявляются внутри функции или другого класса).
В состав любого класса входят специальные методы – конструктор и деструктор, которые объявляются в классе или наследуются от базового класса. Конструктор используется для создания объекта (выделения памяти под него), деструктор предназначен для освобождения памяти. Конструктор вызывается каждый раз, когда создается объект: при объявлении экземпляра переменной автоматически вызывается конструктор для создания экземпляра. Если конструктор явно не определен, то вызывается конструктор по умолчанию без параметров. Конструктор определяет необходимый размер памяти под размещение объекта в динамической памяти, осуществляет выделение памяти и распределение объекта в памяти, помещает адрес этой памяти в экземпляр – переменную типа класса. Деструктор освобождает динамическую память и разрушает объект.
Класс может иметь несколько конструкторов для разной инициализации данных членов.
Существует ряд правил объявления конструктора и деструктоа. Имена конструктора и деструктора должны совпадать с именем класса, перед именем деструктора ставится знак тильда ~. Конструктор и деструктор не имеют типа возвращаемого результата. Если не указывается ни одного конструктора, то он создается компилятором автоматически. Конструкторы используются в основном для инициализации полей.
Доступ к полям и методам объекта, объявленным со спецификатором public, извне класса выполняется с использованием операции прямого выбора (используется точка), например, p.x= 9.7; p.y=-3.4; p.getx(); Однако такой способ нежелателен. К закрытым полям доступ осуществляется с помощью вызова соответствующих методов.
Основные принципы построения классов
Основные принципы построения классов – инкапсуляция, наследование, полиморфизм.
Инкапсуляция означает объединение в виде одного целого (конструкции) данных и программ обработки этих данных. Этот принцип позволяет максимально изолировать объект класса от внешнего воздействия, что повышает надежность программ, использующих объекты. Создание библиотек классов значительно повышает производительность труда программистов по аналогии с использованием библиотек подпрограмм в прошлые времена.
Наследование означает возможность построения иерархии классов, т.е. порождать классы, являющиеся потомками родительских классов. При этом дочерний класс наследует поля и методы родительского класса, а также имеется возможность добавлять новые поля и методы и переопределять родительские поля и методы.
Принцип наследования позволяет реализовать последовательный процесс разработки классов. В результате использования принципа наследования получается часто ветвящееся дерево классов. Очередной потомок дополняется новыми полями и методами и передает их потомкам. В C++ реализована концепция множественного наследования – создание класса, имеющего несколько базовых классов.
При описании класса в его заголовке перечисляются классы, являющиеся для него базовыми. Возможность обращения к элементам этих классов регулируется с помощью ключей доступа private, protected, public:
Class имя_класса :[private| protected | public] базовый класс
{тело класса};
По умолчанию для классов используется ключ доступа private.
Доступ к полям базового класса в зависимости от спецификатора доступа к базовому классу и спецификаторов полей базового класса определяется следующей таблицей.
Таблица 1. Ключи доступа к полям базового класса
-
Ключ доступа
Спецификатор в базовом классе
Доступ в производном классе
private
private
protected
public
нет
private
private
protected
private
protected
public
нет
protected
protected
public
private
protected
public
нет
protected
public
Следующий пример демонстрирует наследование. Сначала рассматривается базовый класс прямоугольник, имеющий следующие поля: основание, высота, периметр и площадь, а также методы инициализации двух первых полей и вычисления значений двух последних полей. Классом-наследником является тругольник, имеющий дополнительное поле – длина третьей стороны. Класс-треугольник наследует поля прямоугольника и методы инициализации и считывания значений полей класса-наследника. Для инициализации и считывания дополнительного поля добавляются новые методы в классе-наследнике. Методы вычисления периметра и площади необходимо переопределять в классе-наследнике, т.к. эти параметры вычисляются в соответствии с другими выражениями.
#include "stdafx.h"
#include <math.h>
#include <stdlib.h>
#include <conio.h>
class TPram
{
public:
double fa,fb,fper,fpl;
TPram();
void Seta(double ia);
void Setb( double ib);
double Geta(void);
double Getb(void);
double Getper(void);
double Getpl(void);
};
TPram::TPram()
{
fa=0;
fb=0;
fper=0;
fpl=0;
}
void TPram::Seta( double ia)
{ fa=ia; }
void TPram::Setb( double ib)
{ fb=ib; }
double TPram::Geta()
{ return fa; }
double TPram::Getb()
{ return fb; }
double TPram::Getper()
{
fper=2.0*(fa+fb);
return fper;
}
double TPram::Getpl()
{
fpl=fa*fb;
return fpl;
}
class TTreug: public TPram
{ public:
double fc;
TTreug();
void Setc(double ic);
double Getc(void);
double Getpl(void);
double Getper(void);
};
TTreug::TTreug()
{
fa=0;
fb=0;
fc=0;
fper=0;
fpl=0;
}
void TTreug::Setc(double ic)
{ fc=ic; }
double TTreug::Getc()
{ return fc; }
double TTreug::Getper()
{
fper=(fa+fb+fc);
return fper;
}
double TTreug::Getpl()
{
double p2;
p2=Getper()/2.0;
fpl=sqrt(p2*(p2-fa)*(p2-fb)*(p2-fc));
return fpl;
}
int _tmain(int argc, _TCHAR* argv[])
{
TPram pr;
TTreug tr;
pr.Seta(5.5);
pr.Setb(4.0);
printf("a=%5.1f b=%5.1f perim=%5.1fpl=%5.1f",
pr.Geta(),pr.Getb(),pr.Getper(),pr.Getpl());
tr.Seta(4.4);
tr.Setb(8.5);
tr.Setc(6.0);
printf("\na=%5.1f b=%5.1f c=%5.1f perim=%5.1f pl=%5.1f",
tr.Geta(),tr.Getb(),tr.Getc(),tr.Getper(),tr.Getpl());
getch();
return 0;
}
Полиморфизм заключается в том, что методы классов на различных уровнях иерархии могут иметь одинаковые имена, но различное содержание. Одно и то же имя метода связывается с различными его реализациями на каждом уровне иерархии.Это действие используется всеми объектами иерархии, но в зависимости от типа объекта используется по-разному. Использование конкретного метода при обрашении к методу с заданным именем определяется типом объекта.
Использование полиморфизма демонстрируется на следующем примере. В качестве базового класса взят прямой цилиндр, имеющий поля радиус основания и высота, а также методы установки и считывания этих полей и вычисления периметра основания, площади основания, площади полной поверхности и объема. Классом-наследником является прямоугольный параллелепипед, имеющий дополнительное поле – высота основания. Следующим классом-наследником (от параллелепипеда) является треугольная призма, имеющая дополнительное поле-третья сторона основания. Методы вычисления периметра основания и площади основания в классах-наследниках необходимо переопределять, т.к. они вычисляются в соответствии с разными выражениями.
Методы вычисляения площади полной поверхности и объема желательно унаследовать от базового класса. Однако, если не переопределять эти методы, то при вычислении полной поверхности или объема прямоугольного параллелепипеда или призмы будет производиться обращение к методам базового класса при вычислении площади основания и периметра основания. Чтобы обеспечить обращение к методу, соответствующему объекту, необходимо как раз использовать свойство полиморфизма. Это означает, что методы вычисления площади основания и периметра необходимо объявить как виртуальные. В этом случае выбор необходимого метода будет осуществляться на этапе выполнения программы в соответствии с типом объекта, для которого вычисляются значения площади полной поверхности или объема. Виртуальные методы объявляются с атрибутом virtual перед указанием типа возвращаемого результата.
#include "stdafx.h"
#define _USE_MATH_DEFINES
#include "conio.h"
#include "stdio.h"
#include "math.h"
//прямой цилиндр
class Pramcil
{
protected:
float a,h,per,s,spov,v;
public:
Pramcil();
void set(float ia);
void seth(float ih);
float geta();
float geth();
virtual float getss();
virtual float getper();
float getspov();
float getv();
};
Pramcil::Pramcil()
{ a=0; };
void Pramcil::set(float ia)
{ a=ia; }
void Pramcil::seth(float ih)
{ h=ih; }
float Pramcil::geta()
{ return a; }
float Pramcil::geth()
{ return h; }
float Pramcil::getss()
{ return (float) a*a*M_PI; }
float Pramcil::getper()
{ return (float)2*a*M_PI; }
float Pramcil::getspov()
{ return (float)getper()*h; }
float Pramcil::getv()
{ return (float)getss()*h; }
//прямоугольная призма
class Prampr:public Pramcil
{
protected:
float b;
public:
Prampr();
void setb(float ib );
float getb();
float getss();
float getper();
};
Prampr::Prampr()
{a=0;b=0;};
void Prampr::setb(float ib)
{ b=ib; }
float Prampr::getb()
{ return b; }
float Prampr::getss()
{ return (float) a*b; }
float Prampr::getper()
{ return (float)2*(a+b); }
//треугольная призма
class Treugpr:public Prampr
{
protected:
float c;
public:
Treugpr();
void setc(float ic );
float getc();
float getss();
float getper();
};
Treugpr::Treugpr()
{
a=0;
b=0;
c=0;
}
void Treugpr::setc(float ic)
{ c=ic; }
float Treugpr::getc()
{ return c; }
float Treugpr::getss()
{
float p;
p=(a+b+c)/2.0f;
s=sqrt(p*(p-a)*(p-b)*(p-c));
return s;
}
float Treugpr::getper()
{ return (float)(a+b+c); }
int _tmain(int argc, _TCHAR* argv[])
{
Pramcil p;
p.set(10.0f);
p.seth(5.0f);
printf("a=%5.1f h=%5.1f s=%5.1f\n perim=%5.1f pover=%5.1f ob=%5.1f",
p.geta(),p.geth(),p.getss(),p.getper(),p.getspov(),p.getv());
Prampr pr;
pr.set(10.0f);
pr.setb(4.0f);
pr.seth(5.0f);
printf("\na=%5.1f b=%5.1f h=%5.1f s=%5.1f\n perim=%5.1f pover=%5.1f
ob=%5.1f",pr.geta(),pr.getb(),pr.geth(),pr.getss(),pr.getper()
,pr.getspov(),pr.getv());
Treugpr t;
t.set(4.0f);
t.setb(5.0f);
t.setc(3.0f);
t.seth(5.0f);
printf("\na=%5.1f b=%5.1f c=%5.1f h=%5.1f s=%5.1f\n perim=%5.1f
pover=%5.1fob=%5.1f",t.geta(),t.getb(),t.getc()
,t.geth(),t.getss(),t.getper(),t.getspov(),t.getv());
getch();
return 0;
}
В соответствии с учебным планом проведения занятий по Информатике во втором семестре предполагается работа непосредственно в среде визуального программирования. При создании приложений используется общеязыковая среда выполнения (Common Language Runtime – CLR). CLR это реализация, независимого от платформы, стандарта промежуточного универсального языка CLI (Common Language Infrastructure), дополненная библиотеками базовых классов. В CLI определена общая система типов (Common Type System – CTS).
Язык C++ для CLR разработан для интегрирования в общеязыковую среду выполнения CLR и называется C++/ CLI. Код C++, выполняемый под управлением CLR, называется управляемым C++, т.к. память для управлеяемых данных распределяется и освобождается автоматически (сборка мусора).
Код C++, который выполняется вне CLR, называется неуправляемым C++ (родной C++). Он компилируется непосредственно в родной машинный код, выделение и очистка памяти во время выполнения программы реализуется в коде. Все данные являются неуправляемыми (unmanaged data) и хранятся в стеке или неуправляемой куче. Неуправляемые данные, которые размещаются с помощью операции new в неуправляемой куче, управляются с помощью операции delete.
В языке C++/ CLI неуправляемые указатели, указывающие на неуправляемые данные, размещенные в неуправляемой куче, помечаются символом *, а управляемые указатели, ссылающиеся на управляемые данные, размещенные в управляемой куче, помечаются символом ^. Неуправляемые указатели называются указателями, а управляемые указателями называются дескрипторами.
Для описания классов объектов, размещаемых в управляемой куче, используется ссылочный класс ref class, а при создании объектов – утилита gcnew и дескриптор. Управляемый указатель (отслеживаемый дескриптор) начинается с символа ^ и хранит адрес объекта, созданного в управляемой куче CLR, которая автоматически обновляется сборщиком мусора. Программа, реализующую работу со ссылочным классом точка (аналог программы 1), имеет следующий вид
#include "stdafx.h"
#include "conio.h"
#include "stdio.h"
ref class point
{
public:
float x,y;
void initx(float ix);
void inity(float iy);
float getx();
float gety();
};
int main(array<System::String ^> ^args)
{
point ^p1=gcnew point;
p1->initx(5.5);
p1->inity(-2.2);
printf("x=%5.1f y=%5.1f",p1->getx(),p1->gety());
getch();
return 0;
}
void point::initx(float ix)
{ x=ix; }
void point::inity(float iy)
{ y=iy; }
float point::getx()
{ return x; }
float point::gety()
{ return y; }
Рассмотренные основы объектно-ориентированного программирования важны для понимания программирования в среде визуального программирования, т.к. используемым при этом элементам интерфейса соответствуют классы, включающие необходимые поля и методы, использование которых существенным образом облегчает создание пользовательских приложений.
Разработка приложения в среде визуального программирования предполагает решение двух задач: конструирование формы и написание кода. Программирование в среде визуального программирования является событийно-ориентированным в отличие от процедурно-ориентированного, которое рассматривалось в первых частях пособия.
Процедурное программирование предполагает использование функций, реализующих определенные процедуры (действия) обработки данных. При этом каждая функция представляет собой последовательность операторов, реализующих определенный жесткий алгоритм.
В случае событийно-ориентированного программирования программист по сути должен разработать функции, реализующие алгоритмы обработки различных событий. События в программе могут наступать в результате действий пользователя таких, как нажатие клавиш клавиатуры, кнопки мыши, выбора пункта меню, так и в результате функционирования объектов программы. В этом случае программист должен разработать и реализовать обработчики событий – функции, которые реализуют алгоритм обработки соответствующего события (реакцию на это событие).
Разработчик приложения должен заранее на этапе проектирования формы создать необходимые события.
Разработчик приложения должен заранее на этапе проектирования формы создать необходимые события.
