Воспользуемся для создания линейного списка шаблон структурой node
из 7 лекции:
template<typename T>
struct node
{
T data;
node<T>* next;
node(T data, node<T>* next)
{
// this указатель на себя
this->data = data;
this->next = next;
}
};
При объявлении нового экземпляра структуры node
, как это описано
в предыдущей лекции, этот объект создается в статической памяти.
То есть объект n1
будет храниться на стеке:
node<int> n1(5, nullptr);
В реальных программах, ввиду сильной ограниченности размера стека, объекты размещают в динамической памяти.
node<int>* pn = new node<int>(5, nullptr);
В C++ динамическую память выделяет не конструктор, а оператор new
.
Конструктор только создает объект в выделенной памяти.
В отличии от .NET в C++ нет сборщика мусора, и ответственным за удаление объекта из динамической памяти, является программист.
В C++ размерная модель объектов, а ссылочную можно моделировать с помощью указателей.
node<int>* pn = nullptr;
pn = new node<int>(5, pn);
Операцию добавления первого элемента в односвязный список мы оформим в виде отдельной функции. Создадим шаблон такой функции.
tempate <typename T>
void add_first(node<T>* &pn, T x)
{
pn = new node<T>(x, pn);
}
Запись node<T>* &pn
означает, что pn
это ссылка на указатель
типа node<T>
, и изменения происходящие с ней внутри фунцкии
повлияют и на изменение фактического параметра.
node<int>* pn = nullptr;
add_first(pn, 5);
add_first(pn, 3);
...
Надо обратить внимание на то, что в отличии от шаблона структуры, в шаблоне функции указывать тип не надо, он автоматически выводится по типам фактических параметров.
В результате компиляции шаблона генерируется 0 байт, поскольку
конкретный тип не указан. Если описать шаблон функции в одном
*.cpp
файле, то при многофайловой компоновке программы эта функция
не будет доступна в другом файле.
Решение. Все шаблоны функций, классов и структур должны быть помещены в заголовочные файлы.
Для шаблонов функций конкретный код генерируется при вызове функции,
когда становится известен конкретный тип Т
. Подстановка конкретного
типа в шаблон называется инстанцированием шаблона.
Количество инстанций зависит от количества используемых типов.
В C++ шаблоны компилируются в два этапа:
И на каждом этапе могут возникнуть ошибки.
В С++ компиляция шаблонов проходит в два этапа, а в .NET обобщения компилируются 1 раз.
В С++ в результате компиляции шаблона получается исполняемый код инстанцированных функций, структур и классов. В .NET в результате компиляции обобщения создается исполняемый код самого обобщения т.е в .NET можно создать dll с обобщенным классом.
template<typename T>
T inc(T t)
{
return t + 1;
}
Student s(...);
inc(s); // В C++ произойдет ошибка на этапе компиляции
// А в динамических языках это ошибка времени исполнения
template <typename T>
void print(node<T>* p)
{
while(p)
{
cout << p -> data << ' ';
p = p -> next;
}
}
В PascalABC.NET работа с указателями на функции осуществляется следующим образом:
type BitOp = function (a, b: real): real;
var op: BinOp;
write(op(3, 5));
op := mult;
write(op(3, 5));
Аналогичный код на C++ выглядит так:
// Указатель на функцию с таким прототипом
typedef double (*BinOp) (double, double);
BinOp bop = &add;
(*bop)(3, 5);
// Или как и описание переменной
double (*op)(double, double)
op = mult;
op(3, 5); // Так тоже можно вызывать template <typename T>
template <typename T>
// action — переменная типа "указатель на функцию"
void for_each(node<T>* p, void (*action)(T&))
{
while(p)
{
action(p -> data);
p = p -> next;
}
}
void print(int &x)
{
cout << x << ' ';
}
void inc(int &x)
{
x++;
}
for_each(pn, print);
for_each(pn, inc);
for_each(pn, print);
В языке C/C++ структурная эквивалентность типов, а не именная.