Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
84
Добавлен:
10.12.2013
Размер:
856.06 Кб
Скачать

Виртуальные методы

Доступ к объектам иерархии лучше всего осуществляется через указатели на базовый тип. При наследовании со спецификатором 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;

}

Не рассмотрели:

  1. Множественное наследование.

  2. Локальные классы

Шаблоны классов

Вспомним сначала, что такое шаблоны функций

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

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;

……

};