- •1. Понятие объектно-ориентированного программирования
- •2.Основные принципы ооп. Инкапсуляция
- •4. Конструкторы
- •Конструктор без параметров
- •Конструктор копирования
- •Деструкторы
- •Массивы объектов
- •Виртуальные методы
- •Использование классов функциональных объектов для настройки шаблонных классов
- •2. Стандартные потоки
- •3. Форматирование при вводе/выводе
- •4. Методы обмена с потоками
- •5. Ошибки потоков
- •6. Файловые потоки
- •7. Потоки и типы определяемые пользователем
- •Стандартная библиотека шаблонов stl (4 часа)
- •2. Контейнеры
- •3. Итераторы
Виртуальные методы
Доступ к объектам иерархии лучше всего осуществляется через указатели на базовый тип. При наследовании со спецификатором public такому указателю можно присваивать адрес любого производного класса. Проблема заключается в том, что компилятор не будет знать, с каким именно классом связан указатель.
class person
{…};
class employee
{…};
void main()
{
person*p;
cin>>k;
if(k==1)
p=new employee;
else p=new student;
p->input();
}
Во время компиляции неизвестно на какой объект указывает p, следовательно, класс будет выбираться по типу указателя (person::input()).
В C++ реализован механизм позднего связывания, когда разрешение ссылок на метод происходит на этапе выполнения программы в зависимости от конкретного типа объекта, вызвавшего метод. Этот механизм реализован с помощью виртуальных методов и рассмотрен в следующем разделе.
Для определения виртуального метода используется спецификатор virtual, например:
virtual void show(); Рассмотрим правила описания и использования виртуальных методов.
Если в базовом классе метод определен как виртуальный, метод, определенный в производном классе с тем же именем и набором параметров, автоматически становится виртуальным, а с отличающимся набором параметров — обычным.
Виртуальные методы наследуются, то есть переопределять их в производном классе требуется только при необходимости задать отличающиеся действия. Права доступа при переопределении изменить нельзя.
Если виртуальный метод переопределен в производном классе, объекты этого класса могут получить доступ к методу базового класса с помощью операции доступа к области видимости.
Виртуальный метод не может объявляться с модификатором static, но может быть объявлен как дружественный.
Если в классе вводится описание виртуального метода, он должен быть опре делен хотя бы как чисто виртуальный.
Чисто виртуальный метод содержит признак = 0 вместо тела, например:
virtual void f(int) = 0;
Чисто виртуальный метод должен переопределяться в производном классе (возможно, опять как чисто виртуальный).
Механизм позднего связывания
Для каждого класса (не объекта!), содержащего хотя бы один виртуальный метод, компилятор создает таблицу виртуальных методов (vtbl), в которой для каждого виртуального метода записан его адрес в памяти. Адреса методов содержатся в таблице в порядке их описания в классах. Адрес любого виртуального метода имеет в vtbl одно и то же смещение для каждого класса в пределах иерархии.
Каждый объект содержит скрытое дополнительное поле ссылки на vtbl, называемое vptr. Оно заполняется конструктором при создании объекта (для этого компилятор добавляет в начало тела конструктора соответствующие инструкции).
На этапе компиляции ссылки на виртуальные методы заменяются на обращения к vtbl через vptr объекта, а на этапе выполнения в момент обращения к методу его адрес выбирается из таблицы. Таким образом, вызов виртуального метода, в отличие от обычных методов и функций, выполняется через дополнительный этап получения адреса метода из таблицы. Это несколько замедляет выполнение программы.
Рекомендуется делать виртуальными деструкторы для того, чтобы гарантировать правильное освобождение памяти из-под динамического объекта, поскольку в этом случае в любой момент времени будет выбран деструктор, соответствующий фактическому типу объекта. Деструктор передает операции delete размер объекта, имеющий тип size_t. Если удаляемый объект является производным и в нем не определен виртуальный деструктор, передаваемый размер объекта может оказаться неправильным.
Четкого правила, по которому метод следует делать виртуальным, не существует. Можно только дать рекомендацию объявлять виртуальными методы, для которых есть вероятность, что они будут переопределены в производных классах. Методы, которые во всей иерархии останутся неизменными или те, которыми производные классы пользоваться не будут, делать виртуальными нет смысла. С другой стороны, при проектировании иерархии не всегда можно предсказать, каким образом будут расширяться базовые классы (особенно при проектировании библиотек классов), а объявление метода виртуальным обеспечивает гибкость и возможность расширения.
Для пояснения последнего тезиса представим себе, что вызов метода draw осуществляется из метода перемещения объекта. Если текст метода перемещения не зависит от типа перемещаемого объекта (поскольку принцип перемещения всех
объектов одинаков, а для отрисовки вызывается конкретный метод), переопределять этот метод в производных классах нет необходимости, и он может быть описан как невиртуальный. Если метод draw виртуальный, метод перемещения сможет без перекомпиляции работать с объектами любых производных классов — даже тех, о которых при его написании ничего известно не было.
Виртуальный механизм работает только при использовании указателей или ссылок на объекты. Объект, определенный через указатель или ссылку и содержащий виртуальные методы, называется полиморфным. В данном случае полиморфизм состоит в том, что с помощью одного и того же обращения к методу выполняются различные действия в зависимости от типа, на который ссылается указатель в каждый момент времени.
Лекция 4
Абстрактные классы
Класс, содержащий хотя бы один чисто виртуальный метод, называется абстрактным. Абстрактные классы предназначены для представления общих понятий, которые предполагается конкретизировать в производных классах. Абстрактный класс может использоваться только в качестве базового для других классов — объекты абстрактного класса создавать нельзя, поскольку прямой или косвенный вызов чисто виртуального метода приводит к ошибке при выполнении.
При определении абстрактного класса необходимо иметь в виду следующее:
абстрактный класс нельзя использовать при явном приведении типов, для описания типа параметра и типа возвращаемого функцией значения;
допускается объявлять указатели и ссылки на абстрактный класс, если при инициализации не требуется создавать временный объект;
если класс, производный от абстрактного, не определяет все чисто виртуальные функции, он также является абстрактным.
Таким образом, можно создать функцию, параметром которой является указатель на абстрактный класс. На место этого параметра при выполнении программы может передаваться указатель на объект любого производного класса. Это позволяет создавать полиморфные функции, работающие с объектом любого типа в пределах одной иерархии.
Пример:
Написать программу, которая создает список объектов из иерархии классов. (Лаб.раб 2, организация группы другим способом)
Иерархия классов:
object
person
student employee
Иерархия объектов:
person student student employee
class object//абстрактный класс
{
protected:
static object*beg;//начало списка
object*next;//указатель на следующий элемент списка
public:
void add()//добавление элемента в список
{
this->next=beg;
beg=this;
}
virtual void show()=0;//чисто виртуальная функция для просмотра элемента списка
static void print();//функция для печати списка
};
class person: public object
{
protected:
char* name;
int age;
public:
person();//конструктор без параметров
person(char*,int);//конструктор с параметрами
person(const person&);//консруктор копирования
~person();//деструктор
void init(char*,int);//устанавливает новые значения
void show();//печатает
};
class student: public person
{
protected:
char* group;//
float rating;//
public:
student();
student(char*,int,char*,float);
student(const student&);
void init(char*,int,char*,float);
void show();
};
class employee:public person
{
protected:
char* post;
float rate;
public:
employee();
employee(char*,int,char*,float);
employee(const employee&);
void init(char*,int,char*,float);
void show();
};
////////////////////////////////////////////////////////////////////////////////////////////////////
void object::print()
{
object*p=beg;
while(p!=0)
{
p->show();
p=p->next;
}
}
////////////////////////////////////////////////////////
person::person()
{
name=new char[1];
strcpy(name,"");
age=0;
}
person::person(char* Name,int A)
{
name=new char[strlen(Name)+1];
strcpy(name,Name);
age=A;
}
person::person(const person&A)
{
name=new char[strlen(A.name)+1];
strcpy(name,A.name);
age=A.age;
}
person::~person()
{
if (name!=0) delete[]name;
}
void person::init(char*Name,int A)
{
if (name!=0) delete[]name;
name=new char[strlen(Name)+1];
strcpy(name,Name);
age=A;
}
void person::show()
{
cout<<"Name:"<<name<<endl;
cout<<"Age:"<<age<<endl;
cout<<"\n\n";
}
/////////////////////////////////////////////////////
student::student()
{
name=new char[1];
strcpy(name,"");
group=new char[1];
strcpy (group,"");
age=0;
rating=0;
}
student::student(char*Name,int Age,char* Group,float Rating):person(Name,Age)
{
group=new char[strlen(Group)+1];
strcpy (group,Group);
rating=Rating;
}
student::student(const student&A)
{
name=new char[strlen(A.name)+1];
strcpy(name,A.name);
group=new char[strlen(A.group)+1];
strcpy(group,A.group);
age=A.age;
rating=A.rating;
}
void student::init(char*Name,int Age,char*Group,float Rating)
{
if (name!=0) delete[]name;
name=new char[strlen(Name)+1];
strcpy(name,Name);
if(group!=0) delete[]group;
group=new char[strlen(Group)+1];
strcpy(group,Group);
age=Age;
rating=Rating;
}
void student::show()
{
cout<<"Name:"<<name<<endl;
cout<<"Group:"<<group<<endl;
cout<<"Age:"<<age<<endl;
cout<<"Rating:"<<rating<<endl;
cout<<"\n\n";
}
////////////////////////////////////////////////////////////////
employee::employee()
{
name=new char[1];
strcpy(name,"");
post=new char[1];
strcpy (post,"");
age=0;
rate=0;
}
employee::employee(char*Name,int Age,char* Post,float Rate):person(Name,Age)
{
post=new char[strlen(Post)+1];
strcpy (post,Post);
rate=Rate;
}
employee::employee(const employee&A)
{
name=new char[strlen(A.name)+1];
strcpy(name,A.name);
post=new char[strlen(A.post)+1];
strcpy(post,A.post);
age=A.age;
rate=A.rate;
}
void employee::init(char*Name,int Age,char*Post,float Rate)
{
if (name!=0) delete[]name;
name=new char[strlen(Name)+1];
strcpy(name,Name);
if(post!=0) delete[]post;
post=new char[strlen(Post)+1];
strcpy(post,Post);
age=Age;
rate=Rate;
}
void employee::show()
{
cout<<"Name:"<<name<<endl;
cout<<"Post:"<<post<<endl;
cout<<"Age:"<<age<<endl;
cout<<"Rate:"<<rate<<endl;
cout<<"\n\n";
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
object* object::beg=0;//инициализация статической компоненты
int main(int argc, char* argv[])
{
student a("Ivanov",17,"ASU",5);
student b("Petrov",20,"EVT",3);
employee c("Sidorov",25,"engineer",3000);
a.add();
b.add();
c.add();
object::print();
return 0;
}
Не рассмотрели:
Множественное наследование.
Локальные классы
Шаблоны классов
Вспомним сначала, что такое шаблоны функций
Шаблоны вводятся для того, чтобы автоматизировать создание функций, обрабатывающих разнотипные данные. Например, алгоритм сортировки можно использовать для массивов различных типов. При перегрузке функции для каждого используемого типа определяется своя функция. Шаблон функции определяется один раз, но определение параметризируется, т. е. тип данных передается как параметр шаблона. Формат шаблона:
template <параметры_шаблона>
заголовок_функции
{тело функции}
Таким образом, шаблон семейства функций состоит из 2 частей – заголовка шаблона: template<список параметров шаблона> и обыкновенного определения функции, в котором вместо типа возвращаемого значения и/или типа параметров, записывается имя типа, определенное в заголовке шаблона.
Пример 1.
//шаблон функции, которая находит абсолютное значение числа любого типа
template<class type>//type – имя параметризируемого типа
type abs(type x)
{
if(x<0)return -x;
else return x;
}
Шаблон служит для автоматического формирования конкретных описаний функций по тем вызовам, которые компилятор обнаруживает в программе. Например, если в программе вызов функции осуществляется как abs(-1.5), то компилятор сформирует определение функции double abs(double x){. . . }.
Шаблоны классов
Шаблоны классов так же как и шаблоны функций поддерживают парадигму обобщенного программирования, т. е. программирования с использованием типов в качестве параметров. Механизм шаблонов в С++ допускает применение абстрактного типа в качестве параметра при определении класса. После того как шаблон класса определен, он может использоваться для определения конкретных классов. Процесс генерации компилятором определения конкретного класса по шаблону класса и аргументам шаблона называется инстанцированием шаблона.
Определение шаблонного (родового, обобщенного) класса имеет вид:
template <параметры шаблона>
class имя_класса
{…};
Пример:
template<classT>
class Point
{
T x,y;//координаты точки
public:
Point(T X=0,T Y=0):x(X),y(Y){}
void Show () const;
};
template<classT>
void Point::Show()
{
cout<<”(“<<x<<” , ”<<y<<”)”;
}
};
При включении шаблона класса в программу никакие классы на самом деле не генерируются до тех пор, пока не будет создан экземпляр шаблонного класса, в котором вместо параметра шаблона указывается конкретный тип. Экземпляр создается либо объявлением объекта, либо объявлением указателя на инстанцированный шаблонный тип с присваиванием ему адреса с помощью операции new.
Point <int> a(13,15);
Point <float>*pa=new Point<float>(10.1,0.55);
Встретив такие объявления компилятор генерирует код исходного класса.
В проекте, состоящем из нескольких файлов, определение шаблона класса обычно выносится в отдельный файл. Но для того, чтобы инстанцировался конкретный экземпляр шаблона класса необходимо, чтобы определение шаблона находилось в одной единице трансляции с этим экземпляром. Поэтому все определение шаблонного класса размещается в заголовочном файле, а затем этот файл подключается к нужным файлам с помощью директивы include. Чтобы этот файл не включался повторно используется директива ifndef.
Пример.
//Point.h
#ifndef POINT_H
#define POINT_H
template<classT>
class Point
{
T x,y;//координаты точки
public:
Point(T X=0,T Y=0):x(X),y(Y){}
void Show () const;
};
template<classT>
void Point::Show()
{
cout<<”(“<<x<<” , ”<<y<<”)”;
}
};
#endif
//////////////////////////////////////////
//main.cpp
#include “Point.h”
…..
void main()
{
Point<double>p1;
Point<int>p2(1,1);
p1.Show();p2.Show();
}
Правила описания шаблонов
шаблоны методов (функций) не могут быть виртуальными;
шаблоны классов могут содержать статические элементы, дружественные функции и классы;
шаблоны могут быть производными как от шаблонов, так и от обычных классов, а также являться базовыми и для шаблонов, и для обычных классов.
Пример:
Создать шаблон класса Vector.
#ifndef VECTOR_H
#define VECTOR_H
#include <string.h>
#include <iostream.h>
const int MAX=100;
template <class Data>//Data - параметр шаблона – тип элементов массива
class Vector
{
int size;//текущий размер очереди
int max;//максимальный размер
Data *beg;//указатель на начало массива
public:
Vector();
Vector(int);
Vector(const Vector&);
~Vector();
int GetSize()
{
return size;
}
Data GetElem(int );//получить элемент с номером
int FindElem(Data);//найти заданный элемент
void Input();
void Print();
const Vector&operator=(const Vector&);//присваивание
const Vector&operator+(Data );//добавление
const Vector&operator--( );//удаление
operator bool();//проверка пустой вектор или нет
};
///////////////////////////////////////////////////////////////
class Person
{
char* name;
int age;
public:
Person();
Person(char *, int );
Person(Person&);
~Person();
char* Get_name()const
{
return name;
}
int Get_age()const
{
return age;
}
void Set_name(char*);
void Set_age(int);
void Show()const;
////////////////////////////////
Person&operator=(const Person&);
Для ввода и вывода используются перегруженные для всех стандартных типов операции помещения в поток << и извлечения из потока >>. Выбор конкретной операции определяется типом фактических параметров. Это бинарные операции, левым операндом которых является объект-поток, а правым - объект, который требуется извлечь или поместить в поток. Возвращаемое значение должно быть ссылкой на поток.
friend ostream& operator<<(ostream& out, Person&S)
{
return out<<S.name<<" "<<S.age<<endl;
}
friend istream&operator>>(istream&in,Person&S)
{
char Name[20];
cout<<"\nEnter the name: ";in>>Name;
S.Set_name(Name);
cout<<"\nEnter kurs:"; in>>S.age;
return in;
}
};
/////////////////////////////////////////////////////////////////////////////////
Person::Person()
{
name=0;
//strcpy(name,"");
age=0;
}
Person::Person(char*Name,int Age)
{
name=new char[strlen(Name)+1];
strcpy(name,Name);
age=Age;
}
Person::Person(Person&p)
{
name=new char[strlen(p.name)+1];
strcpy(name,p.name);
age=p.age;
}
Person::~Person()
{
if (name!=0)delete [] name;
}
void Person::Show()const
{
cout<<"\nName : "<<name<<"\tage : "<<age<<"\n";
}
void Person::Set_age(int Age)
{
age=Age;
}
void Person::Set_name(char* Name)
{
if (name!=0)delete[]name;
name=new char[strlen(Name)+1];
strcpy(name,Name);
}
Person&Person::operator =(const Person&p)
{
if(this==&p) return *this;
if(name!=0)delete [] name;
name=new char[strlen(p.name)+1];
strcpy(name,p.name);
age=p.age;
return *this;
}
/////////////////////////////////////////////////////////////////////////////////
template <class Data>
Vector<Data>::Vector()
{
max=MAX;
size=0;
beg=NULL;
}
template <class Data>
Vector<Data>::Vector(int n)
{
max=MAX;
if (n>max)n=max;
size=n;
beg=new Data[n];//выделеяем память
}
template <class Data>
Vector<Data>::Vector(const Vector &V)
{
max=V.max;
size=V.size;
if (beg!=NULL) delete []beg;
if(V.beg==NULL){beg=NULL;return;}
beg=new Data [size];
for(int i=0;i<size;i++)
beg[i]=V.beg[i];
}
template <class Data>
Vector<Data>::~Vector()
{
if (beg!=NULL) delete []beg;
}
template <class Data>
Data Vector<Data>::GetElem(int i )
{
if(i>size)
{
cout<<"\nThere is not such element!";
return -1;
}
if(size==0)
{
cout<<"\nThe array is empty!";
return-2;
}
return beg[i];
}
template <class Data>
int Vector<Data>::FindElem(Data a)
{
if (size==0)return -1;
for(int i=0;i<size;i++)
if(beg[i]==a) return i;
return -2;
}
template <class Data>
void Vector<Data>::Input()
{
if(size==0)
{
cout<<"\nArray is empty";
return;
}
for(int i=0;i<size;i++)
{
cout<<"\nEnter the element ";
cin>>beg[i];
}
}
template <class Data>
void Vector<Data>::Print()
{
if (size==0)
{
cout<<"\nArray is empty";
return;
}
for(int i=0;i<size;i++)
cout<<beg[i]<<" ";
cout<<"\n";
}
template <class Data>
const Vector<Data>& Vector<Data>::operator=(const Vector& V)
{
if(&V==this) return *this;
size=V.size;
if (beg!=NULL) delete []beg;
if (V.beg!=NULL)
{
beg=new Data[size];
for(int i=0;i<size;i++)
beg[i]=V.beg[i];
}
else
beg=NULL;
return *this;
}
template <class Data>
const Vector<Data>&Vector<Data>::operator+(Data a )
{
Data *temp=new Data[size];
for(int i=0;i<size;i++)
temp[i]=beg[i];
delete []beg;
beg=new Data [size+1];
for (i=0;i<size;i++)
beg[i]=temp[i];
delete []temp;
beg[size]=a;
size++;
return *this;
}
template <class Data>
const Vector<Data>&Vector<Data>::operator--( )
{
if(size==0) return*this;
if(size==1)
{
size=0;
delete []beg;
beg=NULL;
return *this;
}
Data *temp=new Data[size];
for(int i=0;i<size;i++)
temp[i]=beg[i];
delete []beg;
beg=new Data [size-1];
int j=0;
for (i=1;i<size;i++)
{
beg[j]=temp[i];j++;
}
delete []temp;
size--;
return *this;
}
template <class Data>
Vector<Data>::operator bool()
{
if(size==0)return 0;
else return 1;
}
#endif
////////////////////////////////////////////////////////////////////////////////
#include "vector.h"
#include <iostream.h>
int main(int argc, char* argv[])
{
int n;
cout<<"\nTest for type INT\n";
cout<<"\nEnter the size of array";
cin>>n;
Vector <int> ai(n);
ai.Input();
ai.Print();
int ki;
cout<<"\nOperation + :";
cout<<"\nEnter the element: ";
cin>>ki;
ai=ai+ki;
ai.Print();
cout<<"\nOperation -- :";
--ai;
ai.Print();
cout<<"\nOperation bool() :";
cout<<bool(ai)<<endl;
//////////////////////////////////////////////
*/
cout<<"\nTest for type Person\n";
cout<<"\nEnter the size of array";
cin>>n;
Vector <Person> as(n);
as.Input();
as.Print();
Person ks;
cout<<"\nOperation + :";
cout<<"\nEnter the element: ";
cin>>ks;
as=as+ks;
as.Print();
cout<<"\nOperation -- :";
--as;
as.Print();
cout<<"\nOperation bool() :";
cout<<bool(as)<<endl;
return 0;
}
Параметрами шаблона могут быть абстрактные типы (см. выше) или переменные встроенных типов. Второй вид параметров используется, если шаблон должен настраиваться некоторой константой. Например, можно создать шаблон, который содержит n элементов типа T:
template <class T, int n> class Vector{…};
Vector<Person, 20> v;//вектор из 20 элементов
Пример:
void f1(){cout<<”F1()”;}
void f2(){cout<<”F2()”;}
template<void(*pf)()>
struct A
{
void Show()
{pf();}
};
void main()
{
A<&f1>a1;
a1.Show();
A<&f2>a2;
a2.Show();
}
Здесь параметр шаблона имеет тип указателя на функцию. При инстанцировании класса в качестве аргумента подставляется адрес конкретной функции.
Специализация
Иногда возникает необходимость определить специализированную версию шаблона для какого-то конкретного типа его параметра (или одного из параметров).
Пример.
Рассмотрим отношение T a<T b, где Т – параметр шаблона. Строки char* сраниваются не так как числа, поэтому имеет смысл применить специализацию шаблона:
template <class T> class Sample
{
bool Less(T) const;
……
};
//специализация для char*
template < > class Sample<char*>
{
bool Less(T) const;
……
};