Связные списки и другие структуры

Базовые классы. Классы и члены. Особенности применения и ООП. Примеры.

2. Принципы ООП. Перечислите и дайте характеристику каждому принципу. Примеры.

3. Получение доступа к членам класса. Ограничение доступа к членам класса, зачем это необходимо. Примеры.

4. Определение методов класса. Конструкторы и деструкторы. Конструкторы-копировщики. Примеры.

5. Перегрузка функций-членов. Примеры.

6. Указатель this. Особенности существования и использования. Примеры.

7. Перегрузка операторов. Операторы преобразований. Примеры.

8. Наследование. Плюсы и минусы применения. Пути решения проблем множественного наследования. Примеры.

9. Функции — друзья. Отличия в применении и написании от функций-членов и внешних функций. Примеры.

10. Инициализация наследуемых классов. Передача параметров конструктору базового класса. . Примеры.

11. Потоки ввода-вывода. Переопределение потоков. Иерархия потоков. Примеры.

12. Управление выводом данных. Наличия функций-методов и флагов ввода-вывода. . Примеры.

13. Использование файлов для ввода- вывода информации. . Примеры.

14. Виртуальные методы. Полиморфизм как принцип ООП. Примеры.

15. Как работают виртуальные функции. Примеры.

16. Абстрактные классы. Необходимость в чисто-виртуальных функциях. Примеры.

17. Статические переменные-члены и статические функции-члены. Применение и использование. Примеры.

18. Шаблоны функций .Написание и особенности применения. . Примеры.

19. Шаблоны классов. Описание особенности применения. Примеры.

20. Связные списки и другие структуры. Примеры.

21. Указатели на классы и виртуальные функции. Примеры.

22. Экземпляризация классов и функций из шаблонов. Примеры.

23. Передача в функции объектов класса. Решение проблемы вызова конструктора и деструктора при работе локальных функций. Примеры.

 

21. Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён вклассах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен.

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

Базовый класс может и не предоставлять реализации виртуального метода, а только декларировать его существование. Такие методы без реализации называются «чистыми виртуальными» (перевод англ. pure virtual) или абстрактными. Класс, содержащий хотя бы один такой метод, тоже будет абстрактным. Объект такого класса создать нельзя (в некоторых языках допускается, но вызов абстрактного метода приведёт к ошибке). Наследники абстрактного класса должны предоставить реализацию для всех его абстрактных методов, иначе они, в свою очередь, будут абстрактными классами.

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

 

1. Пример виртуальной функции на C++

Пример на C++, иллюстрирующий отличие виртуальных функций от невиртуальных:

class Ancestor{public: virtual void function1 () { cout << "Ancestor::function1()" << endl; } void     function2 () { cout << "Ancestor::function2()" << endl; }}; class Descendant : public Ancestor{public: virtual void function1 () { cout << "Descendant::function1()" << endl; } void     function2 () { cout << "Descendant::function2()" << endl; }}; Descendant* pointer = new Descendant ();Ancestor* pointer_copy = pointer; pointer->function1 ();pointer->function2 (); pointer_copy->function1 ();pointer_copy->function2 ();

1. Указатели на функции

Начнем с краткого обзора простых указателей на функции. В С, и С++ в частности, указатель на функцию с именем my_func_ptr , указывающий на функцию, принимающую в качестве аргументов int и char*, и возвращающую float, объявляется так:

float (*my_func_ptr)(int, char*);// Для большей удобочитаемости, я очень рекомендую использовать typedef.// Особенно можно запутаться, когда указатель на функцию является аргументом// функции.// Тогда бы объявление выглядело так:typedef float (*MyFuncPtrType)(int, char*);MyFuncPtrType my_func_ptr;

Заметьте, что различным комбинациям аргументов соответствуют различные типы указателей на функцию. В MSVC, кроме этого, указатели на функцию различаются в зависимости от типа соглашения о вызове (calling conventions): __cdecl, __stdcall, __fastcall. Для того чтобы указатель на функции указывал на вашу функцию, необходимо выполнить следующую конструкцию:

my_func_ptr = some_func;

Для вызова функции через указатель:

(*my_func_ptr)(7, "Arbitrary string");

Разрешается приводить один тип указателя на функцию к другому. Но не разрешается приводить указатель на функцию к указателю на void. Остальные разрешенные операции тривиальны. Указателю на функцию можно присвоить 0 для обозначения нулевого указателя. Доступны многочисленные операторы сравнения (==, !=, <, >, <=, >=), можно также проверить на равенство 0 либо неявным преобразованием к bool. Кроме того, указатель на функцию может быть использован в качестве нетипизированного параметра шаблона. Он в корне отличается от типизированного параметра, а также отличается от интегрального нетипизированного параметра шаблона. При инстанцировании используется имя, а не тип или значение. Именные параметры шаблонов поддерживаются не всеми компиляторами, даже не всеми из тех, которые поддерживают частичную специализацию шаблонов.

Наиболее распространенное применение указателей на функции в С – это использование библиотечных функций, таких как qsort, и обратных (callback) функций в Windows. Кроме того, есть еще много вариантов их применения. Реализация указателей на функции проста: это всего лишь «указатели на код», в них содержится начальный адрес участка ассемблерного кода. Различные типы указателей существуют лишь для уверенности в корректности применяемого соглашения о вызове.

2. Указатели на функции-члены

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

float (SomeClass::*my_memfunc_ptr)(int, char*)// Для константных функций-членов используется объявлениеfloat (SomeClass::*my_const_memfunc_ptr)(int, char*) const;

Заметьте, что используется специальный оператор ( ::* ), а при объявлении используется класс SomeClass. Указатели на функции-члены имеют очень серьезное ограничение – они могут указывать лишь на функции-члены одного класса. Различным комбинациям аргументов, типам константности и различным классам соответствуют различные указатели на функции-члены. В MSVC , кроме того, указатели различаются по типу соглашения о вызове: __cdecl, __fastcall, __stdcall и __thiscall (__thiscall по умолчанию. Заметим, что документированного квалификатора __thiscall нет, но он иногда появляется в сообщениях об ошибках. Если вы попробуете использовать его явно, то получите сообщение об ошибке, информирующей о том, что использование этого квалификатор зарезервировано для будущих нужд.) При использовании указателей на функции-члены всегда следует использовать typedef, во избежание ошибок и лишних неприятностей.

Чтобы указатель на функцию-член указывал на метод класса SomeClass::some_member_func(int, char*), нужно выполнить следующую инструкцию:

my_memfunc_ptr = &SomeClass::some_member_func;

Многие компиляторы (например, NSVC) разрешат вам пропустить взятие адреса (&), но более соответствующие стандарту (например, GNU G++) потребуют этого. Так что если пишете портируемый код, то не забывайте &. Для вызова метода через указатель на функцию-член нужно предоставить объект SomeClass и воспользоваться специальным оператором (->*). Данный оператор имеет низкий приоритет, так что его следует поместить в скобки:

SomeClass *x = new SomeClass;(x->*my_memfunc_ptr)(6, "Another arbitrary parameter");// Также можно использовать оператор .* если класс создан на стекеSomeClass y;(y.*my_memfunc_ptr)(15, "Different parameter");

Прошу меня не винить в ужасном синтаксисе – похоже, кто-то из разработчиков С++ любит знаки препинания!

С++ добавил в С три специальных оператора специально для поддержки указателей на функции-члены. ::* используется при объявлении указателя, ->* и .* используются при вызовах методов, на которые указывает указатель. Похоже, очень много внимания было уделено этой неясной и редко используемой части языка (разрешено даже перегрузить оператор ->*, однако, я не представляю, для чего это может вам потребоваться; я только знаю одно применение [Meyers]).

Указатели на функции-члены могут быть установлены в 0, и предоставляют операторы ==, !=, но лишь для указателей на функции-члены одного класса. Любой указатель на функцию-член может быть проверен на равенство 0. В отличие от простых указателей на функции, операции сравнения на неравенство (<,>, <=, >=) недоступны. Как и простые указатели, они могут быть использованы как нетипизированные параметры шаблона.

22. Экземпляризация классов и функций из шаблонов. Примеры

Шаблоны функций и классов

Шаблоны представляют ещё одну реализацию полиморфизма в программах С++. Они позволяют создать универсальный фрагмент кода, а затем использовать его многократно с различными типами данных или различными объектами. С помощью шаблонов можно уменьшить объём и сложность программы.

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

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

Построение шаблонов рассмотрим на примере. Необходимо написать функцию расчёта куба для аргумента. (Cub()).

#include<iostream.h>
#include<math.h>

template <class T>
T cub(T x)
{ return x*x*x;}
template <class SwapType>
void Swap(SwapType &x,SwapType &y)
{ SwapType tmp;
tmp=x;
x=y;
y=tmp;
}
template <class T1,class T2>
T1 max(T1 x,T2 y)
{
if(x>y)return x;
else return y;
}
void main(void)
{
int i=3;
float f=3.12;
double x=3.1e2;
cout<<"//целое "<<cub(i)<<endl;
cout<<"//вещественное "<<cub(f)<<endl;
cout<<"//двойной точности "<<cub(x)<<endl;
char ch1='a',ch2='z';
cout<<"//1-- "<<ch1<<" 2-- "<<ch2<<endl;
Swap(ch1,ch2); cout<<"//переставили "<<endl;
cout<<"//1-- "<<ch1<<" 2-- "<<ch2<<endl;
int c1=0,c2=9;
cout<<"//1-- "<<c1<<" 2-- "<<c2<<endl;
Swap(c1,c2); cout<<"//переставили "<<endl;
cout<<"//1-- "<<c1<<" 2-- "<<c2<<endl;

cout<<"//max из int "<<c1<<" и int "<<c2<<" = "<<max(c1,c2)<<endl;
cout<<"//max из char "<<ch1<<" и char "<<ch2<<" = "<<max(ch1,ch2)<<endl;
cout<<"//max из int "<<i<<" и float "<<f<<" = "<<max(f,i)<<endl;
}


//целое 27
//вещественное 30.3713
//двойной точности 2.9791e+07
//1-- a 2-- z
//переставили
//1-- z 2-- a
//1-- 0 2-- 9
//переставили
//1-- 9 2-- 0
//max из int 9 и int 0 = 9
//max из char z и char a = z
//max из int 3 и float 3.12 = 3.12

 

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

Шаблон класса представляет собой скелет обобщённого класса. Ситаксис такой:

Template< список_аргументов_шаблона>
{
//тело класса
};

Каждый аргумент в списке является либо объявлением типа float a; либо идентификатором класса class T. Из-за этого определение функции-метода шаблонного класса имеет вид:
Template< список_аргументов_шаблона>
Тип_результата имя_класса < список_аргументов_шаблона>::
Имя_функции( список_аргументов_функции)
{
//тело функции.
}

объявление объекта шаблонногокласса:
имя_класса_шаблона < список_аргументов_шаблона> имя_объекта;
Пример использования шаблонов будем рассматривать на создании очереди.

 

Связные списки и другие структуры

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

Существуют три основных вида списков:

· Однонаправленные списки (очередь, стек);

· Двунаправленные списки (дек и т.д.);

· Деревья.

Названия образуются от количества связующих список указателей в каждом объекте списка.

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

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

#include <iostream.h>
#include <iomanip.h>
#include <fstream.h>
ofstream fout("rez.txt");
ifstream fin("rez.txt");

class Data
{
public:
Data ();
~Data(){delete fam;}
int Compare(const Data &);
void Show();
private:
char fam[25];
float ball;
};
Data::Data()
{
cout<<"\n Введите фамилию и средний балл :";
cin>>fam>>ball;
}
void Data::Show()
{ cout.width(25);
cout.fill(' ');
cout<<fam;
cout.width(10);
cout.fill('-');
cout.precision(3);
cout<<ball<<"\n";
fout.width(25);
fout.fill(' ');
fout<<fam;
fout.width(10);
fout.fill('-');
fout.precision(3);
fout<<ball<<"\n";
//fout<<fam<<"--"<<ball<<endl;
}
int Data::Compare(const Data &theO)
{
if(strcmp(fam,theO.fam)<0)return -1;
if(strcmp(fam,theO.fam)==0)return 0;
if(strcmp(fam,theO.fam)>0)return 1;
}
class Node;
class HeadNode;
class TailNode;
class InternalNode;

class Node
{public:

Node(){}
virtual ~Node(){}
virtual Node* Insert(Data* theData)=0;
virtual void Show()=0;
private:
};
class InternalNode:public Node
{public:
InternalNode(Data *theData, Node *next) ;
~InternalNode(){delete myNext,delete myData;}
virtual Node* Insert(Data *theData);
virtual void Show() {myData->Show();myNext->Show();}
private:
Data* myData;
Node* myNext;
};
InternalNode::InternalNode(Data *theData,Node*next):
myData(theData),myNext(next)
{}
Node* InternalNode ::Insert(Data *theData)
{
int result=myData->Compare(*theData);
switch(result)
{
case 0:
case1:{InternalNode *dataN=new InternalNode (theData,this);
return dataN;}
case-1:{myNext=myNext>Insert(theData);
return this;}
}
return this;
}
class TailNode:public Node
{
public:
TailNode(){}
~TailNode() {}
virtual Node* Insert(Data *theData);
virtual void Show(){ }
private:
};
Node* TailNode::Insert(Data *theData)
{
InternalNode *dataN=new InternalNode (theData,this);
return dataN;
}
class HeadNode:public Node
{public:
HeadNode(){myNext=new TailNode;};
~HeadNode(){delete myNext;}
virtual Node* Insert(Data *theData);
virtual void HeadNode::Show();
private:
Node* myNext;
};
void HeadNode::Show()
{
cout<<" Фамилия "<<" балл "<<endl;
fout<<" Фамилия "<<" балл "<<endl;
myNext->Show();
}
Node* HeadNode::Insert(Data *theData)
{
myNext=myNext>Insert(theData);
return this;
}

class Admin
{public:
Admin();
~Admin(){delete myHead;}
void Insert(Data* pData);
void ShowAll(){myHead->Show();}
private:
HeadNode* myHead;
};
Admin::Admin()
{
myHead=new HeadNode;
}
void Admin:: Insert(Data*pData)
{
myHead->Insert(pData);
}
int main()
{
Data* pData;
int val=1;
Admin ll;
while(val!=0)
{
pData=new Data();

ll.Insert(pData);
cout<<"хотите закончить ввод? Если да, то введите 0";
cin >>val;
}
ll.ShowAll();
cout<<"********************************\n";
fout.close();
return 0;

 

 

23. Передача в функции объектов класса. Решение проблемы вызова конструктора и деструктора при работе локальных функций. Примеры.

 


Дата добавления: 2021-01-21; просмотров: 49; Мы поможем в написании вашей работы!

Поделиться с друзьями:




Мы поможем в написании ваших работ!