В двух предыдущих статьях я обсуждал, как создавать и использовать шаблоны функций в C ++. В этой и следующей статьях я расскажу, как создавать и использовать шаблоны классов. Шаблоны позволяют нам создавать общие программы, которые могут работать с различными типами данных без необходимости перегружать и / или копировать программный код. Обобщенное программирование лежит в основе стандартной библиотеки шаблонов (STL) C ++, и понимание того, как работать с шаблонами функций и классов, является основополагающим для эффективной работы с STL.

Определение шаблона класса - класс стека

Перефразируя покойного программиста Сеймура Паперта, трудно говорить о программировании, не запрограммировав что-либо. Для моих примеров шаблонов классов я сначала определю Stack класс, а затем Pair класс, который имитирует pair структуру C ++.

Как многие из вас знают, стек - это структура данных, в которой данные вводятся в верхнюю часть стека (с помощью функции-члена push), а данные удаляются только с вершины стека (с помощью функции-члена pop). Единственная другая необходимая первичная функция-член - это функция top, которая проверяет и возвращает элемент данных в верхней части стека.

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

Я начну определение класса с демонстрации того, как вы определяете класс как шаблон класса. Это очень похоже на то, как вы сначала определяете шаблон функции. Вот как я запускаю класс Stack:

template <typename T>
class Stack {
  // declarations and definitions
};

С этого момента, когда нам нужно обратиться к типу данных, который либо ссылается на данные, поступающие в класс, либо на данные, отправляемые из класса, мы будем вводить данные с помощью заполнителя T. Вот полное объявление класса Stack:

template <typename T>
class Stack {
  private:
    vector<T> data;
  public:
    void push(T element);
    bool pop();
    T top();
};

Вы можете видеть, что вектор, который будет содержать данные стека, имеет тип T, как и параметр функции-члена push и тип возвращаемого значения функции-члена top. Вы также должны заметить, что я не определял функцию-конструктор для класса или какие-либо служебные функции ради экономии места, а не потому, что я не думаю, что они необходимы для полного определения класса стека.

Теперь давайте посмотрим на определение класса Stack:

template <typename T>
void Stack<T>::push(T const& element) {
  data.push_back(element);
}
template <typename T>
bool Stack<T>::pop() {
  if (!data.empty()) {
    data.pop_back();
    return true;
  }
  return false;
}
template <typename T>
T const& Stack<T>::top() {
  return data.back();
}

Обратите особое внимание на то, где используется заполнитель параметра шаблона в разделе определения. Его следует помещать в скобки каждый раз, когда упоминается имя класса, и всякий раз, когда он должен использоваться в качестве заполнителя для типа параметра или типа возвращаемого значения.

Вот программа, которая использует класс Stack с целыми числами:

int main()
{
  Stack<int> numbers;
  numbers.push(1);
  numbers.push(2);
  cout << "The top of the stack" " << numbers.top() << endl;
  numbers.push(3);
  cout << "The top of the stack: " << numbers.top() << endl;
  numbers.pop();
  cout << "The top of the stack: " << numbers.top() << endl;
  return 0;
}

Обратите внимание: если вы не укажете аргумент шаблона, программа не скомпилируется. В отличие от шаблонов функций, где компилятор может вывести тип данных из аргументов, с шаблонами классов компилятор не может вывести тип для класса, поскольку нет аргументов для вывода.

Второй пример: парный класс

Pair - это класс, содержащий два элемента данных. Эти элементы данных могут быть любого типа и могут быть разных типов. Мое определение будет имитировать структуру pair, найденную в STL и используемую с контейнерами STL, такими как карта.

Двумя переменными-членами класса являются first и second. Чтобы более точно имитировать версию STL, они будут объявлены в разделе public, чтобы к ним можно было получить доступ непосредственно из объекта pair. (Я мог бы также реализовать этот класс как структуру из-за открытого доступа к переменным-членам.)

Поскольку это простой класс, объявление также является определением для Pair class:

template <typename T1, typename T2>
class Pair {
  public:
    T1 first;
    T2 second;
};

В структуре pair есть функция, которая помогает создавать пары объектов, make_pair. Вот определение моей версии makePair:

template <typename T1, typename T2>|
Pair<T1, T2> makePair(T1 first, T2 second) {
  Pair<T1, T2> p;
  p.first = first;
  p.second = second;
  return p;
}

Это должен быть шаблон функции, чтобы мы могли при необходимости изменять типы для первого и второго.

Вот один пример программы, которая использует класс Pair и функцию makePair:

template <typename T1, typename T2>
class Pair {
  public:
    T1 first;
    T2 second;
};
template <typename T1, typename T2>
Pair<T1, T2> makePair(T1 first, T2 second) {
  Pair<T1, T2> p;
  p.first = first;
  p.second = second;
  return p;
}
int main()
{
  Pair<string, int> nameAge;
  string name = "Mary";
  int age = 23;
  nameAge = makePair<string, int>(name, age);
  cout << "Name: " << nameAge.first << endl;
  cout << "Age: " << nameAge.second << endl;
  return 0;
}

Вот результат этой программы:

Name: Mary
Age: 23

Чтобы продемонстрировать, почему шаблоны классов полезны, вот еще одна программа, которая использует этот класс и функцию:

int main()
{
  Pair<double, string> ratios;
  double r = 3.14159;
  string name = "pi";
  ratios = makePair(r, name);
  cout << "Value: " << ratios.first << endl;
  cout << "Name: " << ratios.second << endl;
  return 0;
}

Вот результат этой программы:

Value: 3.14159
Name: pi

Ценность шаблонов классов

Как и шаблоны функций, шаблоны классов делают возможным универсальное программирование на C ++. Универсальные программы более гибкие, чем другие парадигмы программирования, потому что, как показывает Стандартная библиотека шаблонов, мы можем смешивать и сопоставлять контейнеры (структуры данных) и алгоритмы (функции) различными способами, что невозможно в других парадигмах.

Есть другие темы, касающиеся шаблонов курсов, и я расскажу о некоторых из них в своей следующей статье.

Спасибо за чтение, напишите мне с комментариями и предложениями.