Создание функций преобразования



 

Функция преобразования автоматически преобразует тип класса в другой тип.

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

 

 

Здесь элемент type – новый тип, который является целью нашего преобразования, а элемент value – значение после преобразования. Функция преобразования должна быть членом класса, для которого она определяется.

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

 

 

Теперь рассмотрим программу, которая иллюстрирует работу функции преобразования.

 

 

Эта программа генерирует такие результаты.

 

 

Как подтверждают результаты выполнения этой программы, если в таком выражении целочисленного типа, как cout<<b+100 , используется объект типа three_d , к этому объекту применяется функция преобразования. В данном случае функция преобразования возвращает значение 24 , которое затем участвует в операции сложения с числом 100 . Но когда в преобразовании нет необходимости, как при вычислении выражения а=а+b , функция преобразования не вызывается.

Если функция преобразования создана, то она будет вызываться везде, где требуется преобразование, включая ситуации, когда объект передается функции в качестве аргумента. Например, если объект класса three_d передать стандартной функции abs() , также будет вызвана функция, выполняющая преобразование объекта типа three_d в значение типа int , поскольку функция abs() должна принимать аргумент целочисленного типа.

Узелок на память. Для различных ситуаций можно создавать различные функции преобразования. Например, можно определить функции, которые преобразуют объекты типа three_d в значения типа double или long, при этом созданные функции будут применяться автоматически. Функции преобразования позволяют интегрировать новые типы классов, создаваемые программистом, в С++‑среду программирования.

 

Глава 21: Введение в стандартную библиотеку шаблонов

 

Материал этой главы составляет то, что многие считают самым важным добавлением в C++ за последние годы. Речь идет о стандартной библиотеке шаблонов (Standard Template LibrarySTL ). Именно включение библиотеки STL в C++ было основным событием, которое обсуждалось в период стандартизации C++. Библиотека STL предоставляет шаблонные классы и функции общего назначения, которые реализуют многие популярные и часто используемые алгоритмы и структуры данных. Например, она включает поддержку векторов, списков, очередей и стеков, а также определяет различные функции, обеспечивающие к ним доступ. Поскольку библиотека STL состоит из шаблонных классов, алгоритмы и структуры данных могут быть применены к данным практически любого типа.

Библиотека STLэто набор шаблонных классов и функций общего назначения.

Библиотека STL – это результат разработки программного обеспечения, который вобрал в себя одни из самых сложных средств языка C++. Чтобы понимать содержимое библиотеки STL и уметь им пользоваться, необходимо освоить весь материал, изложенный в предыдущих главах. Особенно хорошо нужно ориентироваться в шаблонах. Откровенно говоря, шаблонный синтаксис, который описывает STL, может поначалу испугать, хотя он выглядит сложнее, чем есть на самом деле. Несмотря на то что материал этой главы не труднее остального в этой книге, не следует огорчаться, если что‑то на первый взгляд вам покажется непонятным. Немного терпения при рассмотрении примеров – и вскоре вы поймете, что за непривычным синтаксисом скрывается строгая простота STL.

STL – довольно большая библиотека, и все ее средства невозможно изложить в одной главе. Полное описание библиотеки STL со всеми ее нюансами и методами программирования заняло бы целую книгу. Цель представленного здесь обзора – познакомить вас с основными операциями, принципами проектирования и основами STL‑программирования. Освоив материал этой главы, вы сможете легко изучить остальную часть библиотеки STL самостоятельно.

В этой главе также описан еще один важный класс C++ – класс string . Он предназначен для определения строкового типа данных, который позволяет обрабатывать символьные строки во многом подобно тому, как обрабатываются данные других типов: с помощью операторов. Класс string тесно связан с библиотекой STL .

 

Обзор STL

 

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

Ядро стандартной библиотеки шаблонов включает три основных элемента: контейнеры, алгоритмы и итераторы . Они работают совместно один с другим, предоставляя тем самым готовые решения различных задач программирования.

Контейнерыэто объекты, которые содержат другие объекты.

Контейнеры – это объекты, содержащие другие объекты. Существует несколько различных типов контейнеров. Например, класс vector определяет динамический массив, класс queue создает двустороннюю очередь, а класс list обеспечивает работу с линейным списком. Эти контейнеры называются последовательными контейнерами и являются базовыми в STL. Помимо базовых, библиотека STL определяет ассоциативные контейнеры , которые позволяют эффективно находить нужные значения на основе заданных ключевых значений (ключей). Например, класс map обеспечивает хранение пар "ключ‑значение" и предоставляет возможность находить значение по заданному уникальному ключу.

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

Алгоритмы обрабатывают содержимое контейнеров.

Алгоритмы обрабатывают содержимое контейнеров. Их возможности включают средства инициализации, сортировки, поиска и преобразования содержимого контейнеров. Многие алгоритмы работают с заданным диапазоном элементов контейнера.

Итераторы подобны указателям.

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

 

 

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

Итераторы обрабатываются аналогично указателям. Их можно инкрементировать и декрементировать. К ним можно применять оператор разыменования адреса * . Итераторы объявляются с помощью типа iterator , определяемого различными контейнерами.

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

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

 

 

STL опирается не только на контейнеры, алгоритмы и итераторы, но и на другие стандартные компоненты. Основными из них являются распределители памяти, предикаты и функции сравнения.

Распределитель памяти управляет выделением памяти для контейнера.

Каждый контейнер имеет свой распределитель памяти (allocator). Распределители управляют выделением памяти для контейнера. Стандартный распределитель – это объект класса allocator , но при необходимости (в специализированных приложениях) можно определять собственные распределители. В большинстве случаев стандартного распределителя вполне достаточно.

Предикат возвращает в качестве результата значение ИСТИНА/ЛОЖЬ.

Некоторые алгоритмы и контейнеры используют специальный тип функции, называемый предикатом (predicate). Существует два варианта предикатов: унарный и бинарный. Унарный предикат принимает один аргумент, а бинарный – два. Эти функции возвращают значения ИСТИНА /ЛОЖЬ , но точные условия, которые заставят их вернуть истинное или ложное значение, определяются программистом. В остальной части этой главы, когда потребуется унарная функция‑предикат, на это будет указано с помощью типа UnPred . При необходимости использования бинарного предиката будет применяться тип BinPred . В бинарном предикате аргументы всегда расположены в порядке первый , второй относительно функции, которая вызывает этот предикат. Как для унарного, так и для бинарного предикатов аргументы должны содержать значения, тип которых совпадает с типом объектов, хранимых данным контейнером.

Функции сравнения сравнивают два элемента последовательности.

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

Помимо заголовков, требуемых различными классами STL, стандартная библиотека C++ включает заголовки <utility> и <functional> , которые обеспечивают поддержку STL. Например, в заголовке <utility> определяется шаблонный класс pair , который может хранить пару значений. Мы будем использовать класс pair ниже в этой главе.

Шаблоны в заголовке <functional> позволяют создавать объекты, которые определяют функцию operator() . Эти объекты называются объектами‑функциями, и их во многих случаях можно использовать вместо указателей на функции. Существует несколько встроенных объектов‑функций, объявленных в заголовке <functional> . Приведем здесь некоторые из них.

 

 

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

Материал этой главы не предусматривает использования объектов‑функций, и мы не будем применять их напрямую. (Подробное описание объектов‑функций можно найти в моей книге Полный справочник по C++ , 4‑е издание, М. : Издательский дом "Вильямс".)

 

Контейнерные классы

 

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

 

 

 

 

Поскольку имена типов в объявлениях шаблонных классов произвольны, контейнерные классы объявляют typedef ‑версии этих типов, что конкретизирует имена типов. Некоторые из наиболее популярных typedef ‑имен приведены ниже.

 

 

Поскольку в одной главе невозможно рассмотреть все контейнеры, в следующих разделах мы представим только три из них: vector , list и map . Если вы поймете, как работают эти три контейнера, у вас не будет проблем с использованием остальных.

 

Векторы

 

Векторы представляют собой динамические массивы.

Одним из контейнеров самого широкого назначения является вектор. Класс vector поддерживает динамический массив, который при необходимости может увеличивать свой размер. Как вы знаете, в C++ размер массива фиксируется во время компиляции. И хотя это самый эффективный способ реализации массивов, он в то же время является и самым ограничивающим, поскольку размер массива нельзя изменять во время выполнения программы. Эта проблема решается с помощью вектора, который по мере необходимости обеспечивает выделение дополнительного объема памяти. Несмотря на то что вектор – это динамический массив, тем не менее, для доступа к его элементам можно использовать стандартное обозначение индексации массивов.

Вот как выглядит шаблонная спецификация для класса vector:

 

 

Здесь T – тип сохраняемых данных, а элемент Allocator означает распределитель памяти, который по умолчанию использует стандартный распределитель. Класс vector имеет следующие конструкторы.

 

 

Первая форма конструктора предназначена для создания пустого вектора. Вторая создает вектор, который содержит num элементов со значением val , причем значение val может быть установлено по умолчанию. Третья форма позволяет создать вектор, который содержит те же элементы, что и заданный вектор ob . Четвертая предназначена для создания вектора, который содержит элементы в диапазоне, заданном параметрами‑итераторами start и end .

Ради достижения максимальной гибкости и переносимости любой объект, который предназначен для хранения в векторе, должен определяться конструктором по умолчанию. Кроме того, он должен определять операции "<" и "==" Некоторые компиляторы могут потребовать определения и других операторов сравнения. (В виду существования различных реализаций для получения точной информации о требованиях, предъявляемых вашим компилятором, следует обратиться к прилагаемой к нему документации.) Все встроенные типы автоматически удовлетворяют этим требованиям.

Несмотря на то что приведенный выше синтаксис шаблона выглядит довольно "массивно", в объявлении вектора нет ничего сложного. Рассмотрим несколько примеров.

 

 

Для класса vector определены следующие операторы сравнения:

 

 

Для вектора также определен оператор индексации "[]" , который позволяет получить доступ к элементам вектора с помощью стандартной записи с использованием индексов. Функции‑члены, определенные в классе vector , перечислены в табл. 21.2. Самыми важными из них являются size() , begin() , end() , push_back() , insert() и erase() . Очень полезна функция size() , которая возвращает текущий размер вектора, поскольку она позволяет определить размер вектора во время выполнения программы. Помните, что векторы при необходимости увеличивают свой размер, поэтому нужно иметь возможность определять его величину во время работы программы, а не только во время компиляции.

 

 

 

 

 

 

 

 

Функция begin() возвращает итератор, который указывает на начало вектора. Функция end() возвращает итератор, который указывает на конец вектора. Как уже разъяснялось, итераторы подобны указателям, и с помощью функций begin() и end() можно получить итераторы для начала и конца вектора соответственно.

Функция push_back() помещает заданное значение в конец вектора. При необходимости длина вектора увеличивается так, чтобы он мог принять новый элемент. С помощью функции insert() можно добавлять элементы в середину вектора. Кроме того, вектор можно инициализировать. В любом случае, если вектор содержит элементы, то для доступа к ним и их модификации можно использовать средство индексации массивов. А с помощью функции erase() можно удалять элементы из вектора.

Рассмотрим короткий пример, который иллюстрирует базовое поведение вектора.

 

 

Результаты выполнения этой программы таковы.

 

 

Рассмотрим внимательно код этой программы. В функции main() создается вектор v для хранения int ‑элементов. Поскольку при его создании не было предусмотрено никакой инициализации, вектор v получился пустым, а его емкость равна нулю. Другими словами, мы создали вектор нулевой длины. Это подтверждается вызовом функции‑члена size() . Затем, используя функцию‑член push_back() , в конец этого вектора мы помещаем 10 элементов, что заставляет вектор увеличиться в размере, чтобы разместить новые элементы. Теперь размер вектора стал равным 10. Обратите внимание на то, что для отображения содержимого вектора v используется стандартная запись индексации массивов. После этого в вектор добавляются еще 10 элементов, и вектор v автоматически увеличивается в размере, чтобы и их принять на хранение. Наконец, используя опять‑таки стандартную запись индексации массивов, мы изменяем значения элементов вектора v .

Обратите также внимание на то, что для управления циклами, используемыми для отображения содержимого вектора v и его модификации, в качестве признака их завершения применяется значение размера вектора, получаемое с помощью функции v.size() . Одно из преимуществ векторов перед массивами состоит в том, что у нас есть возможность узнать текущий размер вектора, что в определенных ситуациях является очень полезным средством.

 


Дата добавления: 2018-09-22; просмотров: 392; Мы поможем в написании вашей работы!

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






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