НА ГЛАВНУЮ
Меню сайта
Категория
Ghost++ [1]
С++ [55]
Развлечение
ON - LINE
Опрос
Часто ли вы сталкиваетесь в игре с Noob'ami?
Всего ответов: 269
Оbserver Ward

Онлайн всего: 1
Гостей: 1
Пользователей: 0


Друзья сайта
Заведи себе Бота
Hаша кнопка
Для обмена банерами , наша кнопка для размещения у вас на сайте

Клансайт USSR


Главная » Статьи » Программирование » С++

2. Краткий обзор С++ (2)
2.5. Использование шаблонов

Наш класс IntArray служит хорошей альтернативой встроенному массиву целых чисел. Но в жизни могут потребоваться массивы для самых разных типов данных. Можно предположить, что единственным отличием массива элементов типа double от нашего является тип данных в объявлениях, весь остальной код совпадает буквально.
Для решения данной проблемы в С++ введен механизм шаблонов. В объявлениях классов и функций допускается использование параметризованных типов. Типы-параметры заменяются в процессе компиляции настоящими типами, встроенными или определенными пользователем. Мы можем создать шаблон класса Array, заменив в классе IntArray тип элементов int на обобщенный тип-параметр. Позже мы конкретизируем типы-параметры, подставляя вместо них реальные типы int, double и string. В результате появится способ использовать эти конкретизации так, как будто мы на самом деле определили три разных класса для этих трех типов данных.
Вот как может выглядеть шаблон класса Array:

template <class elemType>
class Array {
public:
   explicit Array( int sz = DefaultArraySize );
   Array( const elemType *ar, int sz );
   Array( const Array &iA );

   virtual ~Array() { delete[] _ia; }

   Array& operator=( const Array & );
   int size() const { return _size; }
   virtual elemType& operator[]( int ix )
      { return _ia[ix]; }

   virtual void sort( int,int );
   virtual int find( const elemType& );
   virtual elemType min();
   virtual elemType max();
protected:
   void init( const elemType*, int );
   void swap( int, int );
   static const int DefaultArraySize = 12;
   int _size;
   elemType *_ia;
};

Ключевое слово template говорит о том, что задается шаблон, параметры которого заключаются в угловые скобки (<>). В нашем случае имеется лишь один параметр elemType; ключевое слово class перед его именем сообщает, что этот параметр представляет собой тип.
При конкретизации класса-шаблона Array параметр elemType заменяется на реальный тип при каждом использовании, как показано в примере:

#include <iostream>
#include "Array.h"

int main()
{
   const int array_size = 4;
   // elemType заменяется на int

   Array<int> ia(array_size);

   // elemType заменяется на double
   Array<double> da(array_size);
   // elemType заменяется на char
   Array<char> ca(array_size);

   int ix;

   for ( ix = 0; ix < array_size; ++ix ) {
      ia[ix] = ix;
      da[ix] = ix * 1.75;
      ca[ix] = ix + 'a';
   }
   for ( ix = 0; ix < array_size; ++ix )
      cout << "[ " << ix << " ] ia: " <<
          ia[ix]
          << "\tca: " << ca[ix]
          << "\tda: " << da[ix] << endl;
   return 0;
}

Здесь определены три экземпляра класса Array:

Array<int> ia(array_size);
Array<double> da(array_size);
Array<char> ca(array_size);

Что делает компилятор, встретив такое объявление? Подставляет текст шаблона Array, заменяя параметр elemType на тот тип, который указан в каждом конкретном случае. Следовательно, объявления членов приобретают в первом случае такой вид:

// Array<int> ia(array_size);
int _size;
int *_ia;

Заметим, что это в точности соответствует определению массива IntArray.
Для оставшихся двух случаев мы получим следующий код:

// Array<double> da(array_size);
int _size;
double *_ia;

// Array<char> ca(array_size);
int _size;
char *_ia;

Что происходит с функциями-членами? В них тоже тип-параметр elemType заменяется на реальный тип, однако компилятор не конкретизирует те функции, которые не вызываются в каком-либо месте программы. (Подробнее об этом в разделе 16.8.)
При выполнении программа этого примера выдаст следующий результат:

[ 0 ] ia: 0 ca: a da: 0
[ 1 ] ia: 1 ca: b da: 1.75
[ 2 ] ia: 2 ca: c da: 3.5
[ 3 ] ia: 3 ca: d da: 5.25

Механизм шаблонов можно использовать и в наследуемых классах. Вот как выглядит определение шаблона класса ArrayRC:

#include <cassert>
#include "Array.h"

template <class elemType>
class ArrayRC : public Array<elemType> {
public:
   ArrayRC( int sz = DefaultArraySize )
      : Array<elemType>( sz ) {}
   ArrayRC( const ArrayRC& r )
      : Array<elemType>( r ) {}
   ArrayRC( const elemType *ar, int sz )
      : Array<elemType>( ar, sz ) {}
   elemType& ArrayRC<elemType>::operator[]( int ix )
   {
      assert( ix >= 0 && ix < Array<elemType>::_size );
      return _ia[ ix ];
   }
private:
   // ...
};

Подстановка реальных параметров вместо типа-параметра elemType происходит как в базовом, так и в производном классах. Определение

ArrayRC<int> ia_rc(10);

ведет себя точно так же, как определение IntArrayRC из предыдущего раздела. Изменим пример использования из предыдущего раздела. Прежде всего, чтобы оператор
// функцию swap() тоже следует сделать шаблоном

swap( ia1, 1, ia1.size() );

был допустимым, нам потребуется представить функцию swap() в виде шаблона.

#include "Array.h"
template <class elemType>
inline void
swap( Array<elemType> &array, int i, int j )
{
   elemType tmp = array[ i ];
   array[ i ] = array[ j ];
   array[ j ] = tmp;
}

При каждом вызове swap() генерируется подходящая конкретизация, которая зависит от типа массива. Вот как выглядит программа, использующая шаблоны Array и ArrayRC:

#include <iostream>
#include "Array.h"
#include "ArrayRC.h"
template <class elemType>
inline void
swap( Array<elemType> &array, int i, int j )
{
   elemType tmp = array[ i ];
   array[ i ] = array[ j ];
   array[ j ] = tmp;
}

int main()
{
   Array<int> ia1;
   ArrayRC<int> ia2;

   cout << "swap() with Array<int> ia1" << endl;
   int size = ia1.size();
   swap( ia1, 1, size );

   cout << "swap() with ArrayRC<int> ia2" << endl;
   size = ia2.size();
   swap( ia2, 1, size );

   return 0;
}

Упражнение 2.13

Пусть мы имеем следующие объявления типов:

template<class elemType> class Array;
enum Status { ... };
typedef string *Pstring;

Есть ли ошибки в приведенных ниже описаниях объектов?

(a) Array< int*& > pri(1024);
(b) Array< Array<int> > aai(1024);
(c) Array< complex< double > > acd(1024);
(d) Array< Status > as(1024);
(e) Array< Pstring > aps(1024);

Упражнение 2.14

Перепишите следующее определение, сделав из него шаблон класса:

class example1 {
public:
   example1 (double min, double max);
   example1 (const double *array, int size);

   double& operator[] (int index);
   bool operator== (const example1&) const;

   bool insert (const double*, int);
   bool insert (double);

   double min (double) const { return _min; };
   double max (double) const { return _max; };

   void min (double);
   void max (double);

   int count (double value) const;

private:
   int size;
   double *parray;
   double _min;
   double _max;
}

Упражнение 2.15

Имеется следующий шаблон класса:

template <class elemType> class Example2 {
public:
   explicit Example2 (elemType val=0) : _val(val) {};
   bool min(elemType value) { return _val < value; }
   void value(elemType new_val) { _val = new_val; }
   void print (ostream &os) { os << _val; }
private:
   elemType _val;
}

template <class elemType>
ostream& operator<<(ostream &os,const Example2<elemType> &ex)
  { ex.print(os); return os; }

Какие действия вызывают следующие инструкции?

(a) Example2<Array<int>*> ex1;
(b) ex1.min (&ex1);
(c) Example2<int> sa(1024),sb;
(d) sa = sb;
(e) Example2<string> exs("Walden");
(f) cout << "exs: " << exs << endl;

Упражнение 2.16

Пример из предыдущего упражнения накладывает определенные ограничения на типы данных, которые могут быть подставлены вместо elemType. Так, параметр конструктора имеет по умолчанию значение 0:

explicit Example2 (elemType val=0) : _val(val) {};

Однако не все типы могут быть инициализированы нулем (например, тип string), поэтому определение объекта

Example2<string> exs("Walden");

является правильным, а

Example2<string> exs2;

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

Как было показано в предыдущем упражнении, попытка использовать шаблон Example2 с типом, для которого не определена операция меньше, приведет к синтаксической ошибке. Однако ошибка проявится только тогда, когда в тексте компилируемой программы действительно встретится вызов функции min(), в противном случае компиляция пройдет успешно. Как вы считаете, оправдано ли такое поведение? Не лучше ли предупредить об ошибке сразу, при обработке описания шаблона? Поясните свое мнение.
2.6. Использование исключений

Исключениями называют аномальные ситуации, возникающие во время исполнения программы: невозможность открыть нужный файл или получить необходимое количество памяти, использование выходящего за границы индекса для какого-либо массива. Обработка такого рода исключений, как правило, плохо интегрируется в основной алгоритм программы, и программисты вынуждены изобретать разные способы корректной обработки исключения, стараясь в то же время не слишком усложнить программу добавлением всевозможных проверок и дополнительных ветвей алгоритма.
С++ предоставляет стандартный способ реакции на исключения. Благодаря вынесению в отдельную часть программы кода, ответственного за проверку и обработку ошибок, значительно облегчается восприятие текста программы и сокращается ее размер. Единый синтаксис и стиль обработки исключений можно, тем не менее, приспособить к самым разнообразным нуждам и запросам.
Механизм исключений делится на две основные части:
точка программы, в которой произошло исключение. Определение того факта, что при выполнении возникла какая-либо ошибка, влечет за собой возбуждение исключения. Для этого в С++ предусмотрен специальный оператор throw. Возбуждение исключения в случае невозможности открыть некоторый файл выглядит следующим образом:

if ( !infile ) {
   string errMsg("Невозможно открыть файл: ");
   errMsg += fileName;
   throw errMsg;
}

Место программы, в котором исключение обрабатывается. При возбуждении исключения нормальное выполнение программы приостанавливается и управление передается обработчику исключения. Поиск нужного обработчика часто включает в себя раскрутку так называемого стека вызовов программы. После обработки исключения выполнение программы возобновляется, но не с того места, где произошло исключение, а с точки, следующей за обработчиком. Для определения обработчика исключения в С++ используется ключевое слово catch. Вот как может выглядеть обработчик для примера из предыдущего абзаца:

catch (string exceptionMsg) {
   log_message (exceptionMsg);
   return false;
}

Каждый catch-обработчик ассоциирован с исключениями, возникающими в блоке операторов, который непосредственно предшествует обработчику и помечен ключевым словом try. Одному try-блоку могут соответствовать несколько catch-предложений, каждое из которых относится к определенному виду исключений. Приведем пример:

int* stats (const int *ia, int size)
{
   int *pstats = new int [4];
   try {
      pstats[0] = sum_it (ia,size);
      pstats[1] = min_val (ia,size);
      pstats[2] = max_val (ia,size);
   }
   catch (string exceptionMsg) {
       // код обработчика
   }
   catch (const statsException &statsExcp) {
      // код обработчика
   }

   pstats [3] = pstats[0] / size;
   do_something (pstats);

   return pstats;
}

В данном примере в теле функции stats() три оператора заключены в try-блок, а четыре – нет. Из этих четырех операторов два способны возбудить исключения.

1) int *pstats = new int [4];

Выполнение оператора new может окончиться неудачей. Стандартная библиотека С++ предусматривает возбуждение исключения bad_alloc в случае невозможности выделить нужное количество памяти. Поскольку в примере не предусмотрен обработчик исключения bad_alloc, при его возбуждении выполнение программы закончится аварийно.

2) do_something (pstats);

Мы не знаем реализации функции do_something(). Любая инструкция этой функции, или функции, вызванной из этой функции, или функции, вызванной из функции, вызванной этой функцией, и так далее, потенциально может возбудить исключение. Если в реализации функции do_something и вызываемых из нее предусмотрен обработчик такого исключения, то выполнение stats() продолжится обычным образом. Если же такого обработчика нет, выполнение программы аварийно завершится.
Необходимо заметить, что, хотя оператор

pstats [3] = pstats[0] / size;

может привести к делению на ноль, в стандартной библиотеке не предусмотрен такой тип исключения.
Обратимся теперь к инструкциям, объединенным в try-блок. Если в одной из вызываемых в этом блоке функций – sum_it(), min_val() или max_val() –произойдет исключение, управление будет передано на обработчик, следующий за try-блоком и перехватывающий именно это исключение. Ни инструкция, возбудившая исключение, ни следующие за ней инструкции в try-блоке выполнены не будут. Представим себе, что при вызове функции sum_it() возбуждено исключение:

throw string ("Ошибка: adump27832");

Выполнение функции sum_it() прервется, операторы, следующие в try-блоке за вызовом этой функции, также не будут выполнены, и pstats[0] не будет инициализирована. Вместо этого возбуждается исключительное состояние и исследуются два catch-обработчика. В нашем случае выполняется catch с параметром типа string:

catch (string exceptionMsg) {
   // код обработчика
}

После выполнения управление будет передано инструкции, следующей за последним catch-обработчиком, относящимся к данному try-блоку. В нашем случае это

pstats [3] = pstats[0] / size;

(Конечно, обработчик сам может возбуждать исключения, в том числе – того же типа. В такой ситуации будет продолжено выполнение catch-предложений, определенных в программе, вызвавшей функцию stats().)
Вот пример:

catch (string exceptionMsg) {
   // код обработчика
   cerr << "stats(): исключение: "
        << exceptionMsg
        << endl;
   delete [] pstats;
   return 0;
}

В таком случае выполнение вернется в функцию, вызвавшую stats(). Будем считать, что разработчик программы предусмотрел проверку возвращаемого функцией stats() значения и корректную реакцию на нулевое значение.
Функция stats() умеет реагировать на два типа исключений: string и statsException. Исключение любого другого типа игнорируется, и управление передается в вызвавшую функцию, а если и в ней не найдется обработчика, – то в функцию более высокого уровня, и так до функции main().При отсутствии обработчика и там, программа аварийно завершится.
Возможно задание специального обработчика, который реагирует на любой тип исключения. Синтаксис его таков:

catch (...) {
   // обрабатывает любое исключение,
   // однако ему недоступен объект, переданный
   // в обработчик в инструкции throw
}

(Детально обработка исключительных ситуаций рассматривается в главах 11 и 19.)
Упражнение 2.18

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

int *alloc_and_init (string file_name)
{
   ifstream infile (file_name)
   int elem_cnt;
   infile >> elem_cnt;
   int *pi = allocate_array(elem_cnt);
   int elem;
   int index=0;
   while (cin >> elem)
      pi[index++] = elem;

   sort_array(pi,elem_cnt);
   register_data(pi);

   return pi;
}

Упражнение 2.19

В предыдущем примере вызываемые функции allocate_array(), sort_array() и register_data() могут возбуждать исключения типов noMem, int и string соответственно. Перепишите функцию alloc_and_init(), вставив соответствующие блоки try и catch для обработки этих исключений. Пусть обработчики просто выводят в cerr сообщение об ошибке.
Упражнение 2.20

Усовершенствуйте функцию alloc_and_init() так, чтобы она сама возбуждала исключение в случае возникновения всех возможных ошибок (это могут быть исключения, относящиеся к вызываемым функциям allocate_array(), sort_array() и register_data() и какими-то еще операторами внутри функции alloc_and_init()). Пусть это исключение имеет тип string и строка, передаваемая обработчику, содержит описание ошибки.
2.7. Использование пространства имен

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

class Cplusplus_Primer_Third_Edition_Array { ... };

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

namespace Cplusplus_Primer_3E {
   template <class elemType> class Array { ... };
}

Ключевое слово namespace задает пространство имен, определяющее видимость нашего класса и названное в данном случае Cplusplus_Primer_3E. Предположим, что у нас есть классы от других разработчиков, помещенные в другие пространства имен:

namespace IBM_Canada_Laboratory {
  template <class elemType> class Array { ... };
  class Matrix { ... };
}

namespace Disney_Feature_Animation {
  class Point { ... };
  template <class elemType> class Array { ... };
}

По умолчанию в программе видны объекты, объявленные без явного указания пространства имен; они относятся к глобальному пространству имен. Для того чтобы обратиться к объекту из другого пространства, нужно использовать его квалифицированное имя, которое состоит из идентификатора пространства имен и идентификатора объекта, разделенных оператором разрешения области видимости (::). Вот как выглядят обращения к объектам приведенных выше примеров:

Cplusplus_Primer_3E::Array<string> text;
IBM_Canada_Laboratory::Matrix mat;
Disney_Feature_Animation::Point origin(5000,5000);

Для удобства использования можно назначать псевдонимы пространствам имен. Псевдоним выбирают коротким и легким для запоминания. Например:

// псевдонимы
namespace LIB = IBM_Canada_Laboratory;
namespace DFA = Disney_Feature_Animation;

int main()
{
   LIB::Array<int> ia(1024);
}

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

namespace LIB = Cplusplus_Primer_3E;
int main()
{
   LIB::Array<int> ia(1024);
}

Конечно, чтобы это стало возможным, необходимо точное совпадение интерфейсов классов и функций, объявленных в этих пространствах имен. Представим, что класс Array из Disney_Feature_Animation не имеет конструктора с одним параметром – размером. Тогда следующий код вызовет ошибку:

namespace LIB = Disney_Feature_Animation;

int main()
{
  LIB::Array<int> ia(1024);
}

Еще более удобным является способ использования простого, неквалифицированного имени для обращения к объектам, определенным в некотором пространстве имен. Для этого существует директива using:
#include "IBM_Canada_Laboratory.h"

using namespace IBM_Canada_Laboratory;

int main()
{
   // IBM_Canada_Laboratory::Matrix
   Matrix mat(4,4);

   // IBM_Canada_Laboratory::Array
   Array<int> ia(1024);

   // ...
}

Пространство имен IBM_Canada_Laboratory становится видимым в программе. Можно сделать видимым не все пространство, а отдельные имена внутри него (селективная директива using):

#include "IBM_Canada_Laboratory.h"
using namespace IBM_Canada_Laboratory::Matrix;
// видимым становится только Matrix

int main()
{
  // IBM_Canada_Laboratory::Matrix
  Matrix mat(4,4);
  // Ошибка: IBM_Canada_Laboratory::Array невидим
  Array<int> ia(1024);

  // ...
}

Как мы уже упоминали, все компоненты стандартной библиотеки С++ объявлены внутри пространства имен std. Поэтому простого включения заголовочного файла недостаточно, чтобы напрямую пользоваться стандартными функциями и классами:

#include <string>
// ошибка: string невидим
string current_chapter = "Обзор С++";

Необходимо использовать директиву using:

#include <string>
using namespace std;
// Ok: видим string
string current_chapter = "Обзор С++";

Заметим, однако, что таким образом мы возвращаемся к проблеме "засорения” глобального пространства имен, ради решения которой и был создан механизм именованных пространств. Поэтому лучше использовать либо квалифицированное имя:

#include <string>
// правильно: квалифицированное имя
std::string current_chapter = "Обзор С++";
либо селективную директиву using:
#include <string>
using namespace std::string;

// Ok: string видим
string current_chapter = "Обзор С++";

Мы рекомендуем пользоваться последним способом.
В большинстве примеров этой книги директивы пространств имен были опущены. Это сделано ради сокращения размера кода, а также потому, что большинство примеров были скомпилированы компилятором, не поддерживающим пространства имен – достаточно недавнего нововведения С++. (Детали применения using-объявлений при работе с стандартной библиотекой С++ обсуждаются в разделе 8.6.)
В нижеследующих главах мы создадим еще четыре класса: String, Stack, List и модификацию Stack. Все они будут заключены в одно пространство имен – Cplusplus_Primer_3E. (Более подробно работа с пространствами имен рассматривается в главе 8.)
Упражнение 2.21

Дано пространство имен

namespace Exercize {
   template <class elemType>
      class Array { ... };

   template <class EType>
      void print (Array< EType > );

   class String { ... }
   template <class ListType>
      class List { ... };
}

и текст программы:

int main() {
   const int size = 1024;
   Array<String> as (size);
   List<int> il (size);

   // ...

   Array<String> *pas = new Array<String>(as);
   List<int> *pil = new List<int>(il);

   print (*pas);
}

Программа не компилируется, поскольку объявления используемых классов заключены в пространство имен Exercise. Модифицируйте код программы, используя
(a) квалифицированные имена
(b) селективную директиву using
(c) механизм псевдонимов
(d) директиву using
2.8. Стандартный массив - это вектор

Хотя встроенный массив формально и обеспечивает механизм контейнера, он, как мы видели выше, не поддерживает семантику абстракции контейнера. До принятия стандарта C++ для программирования на таком уровне мы должны были либо приобрести нужный класс, либо реализовать его самостоятельно. Теперь же класс массива является частью стандартной библиотеки C++. Только называется он не массив, а вектор.
Разумеется, вектор реализован в виде шаблона класса. Так, мы можем написать

vector<int> ivec(10);
vector<string> svec(10);

Есть два существенных отличия нашей реализации шаблона класса Array от реализации шаблона класса vector. Первое отличие состоит в том, что вектор поддерживает как присваивание значений существующим элементам, так и вставку дополнительных элементов, то есть динамически растет во время выполнения, если программист решил воспользоваться этой его возможностью. Второе отличие более радикально и отражает существенное изменение парадигмы проектирования. Вместо того чтобы поддержать большой набор операций-членов, применимых к вектору, таких, как sort(), min(), max(), find()и так далее, класс vector предоставляет минимальный набор: операции сравнения на равенство и на меньше, size() и empty(). Более общие операции, перечисленные выше, определены как независимые обобщенные алгоритмы.
Для использования класса vector мы должны включить соответствующий заголовочный файл.

#include <vector>
// разные способы создания объектов типа vector
vector<int> vec0; // пустой вектор

const int size = 8;
const int value = 1024;

// вектор размером 8
// каждый элемент инициализируется 0
vector<int> vec1(size);

// вектор размером 8
// каждый элемент инициализируется числом 1024
vector<int> vec2(size,value);

// вектор размером 4
// инициализируется числами из массива ia
int ia[4] = { 0, 1, 1, 2 };
vector<int> vec3(ia,ia+4);

// vec4 - копия vec2
vector<int> vec4(vec2);

Так же, как наш класс Array, класс vector поддерживает операцию доступа по индексу. Вот пример перебора всех элементов вектора:

#include <vector>
extern int getSize();

void mumble()
{
   int size = getSize();
   vector<int> vec(size);

   for (int ix=0; ix<size; ++ix)
      vec[ix] = ix;
   // ...
}

Для такого перебора можно также использовать итераторную пару. Итератор – это объект класса, поддерживающего абстракцию указательного типа. В шаблоне класса vector определены две функции-члена – begin() и end(), устанавливающие итератор соответственно на первый элемент вектора и на элемент, который следует за последним. Вместе эти две функции задают диапазон элементов вектора. Используя итератор, предыдущий пример можно переписать таким образом:

#include <vector>
extern int getSize();

void mumble()
{
   int size = getSize();
   vector<int> vec(size);

   vector<int>::iterator iter = vec.begin();

   for (int ix=0; iter!=vec.end(); ++iter, ++ix)
      *iter = ix;

   // ...
}

Определение переменной iter

vector<int>::iterator iter = vec.begin();

инициализирует ее адресом первого элемента вектора vec. iterator определен с помощью typedef в шаблоне класса vector, содержащего элементы типа int. Операция инкремента

++iter

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

*iter

В стандартной библиотеке С++ имеется поразительно много функций, работающих с классом vector, но определенных не как функции-члены класса, а как набор обобщенных алгоритмов. Вот их неполный перечень:
алгоритмы поиска: find(), find_if(), search(), binary_search(), count(), count_if();
алгоритмы сортировки и упорядочения: sort(), partial_sort(), merge(), partition(), rotate(), reverse(), random_shuffle();
алгоритмы удаления: unique(), remove();
численные алгоритмы: accumulate(), partial_sum(), inner_product(), adjacent_difference();
алгоритмы генерации и изменения последовательности: generate(), fill(), transform(), copy(), for_each();
алгоритмы сравнения: equal(), min(), max().
В число параметров этих обобщенных алгоритмов входит итераторная пара, задающая диапазон элементов вектора, к которым применяется алгоритм. Скажем, чтобы упорядочить все элементы некоторого вектора ivec, достаточно написать следующее:

sort ( ivec.begin(), ivec.end() );

Чтобы применить алгоритм sort() только к первой половине вектора, мы напишем:

sort ( ivec.begin(), ivec.begin() + ivec.size()/2 );

Роль итераторной пары может играть и пара указателей на элементы встроенного массива. Пусть, например, нам дан массив:

int ia[7] = { 10, 7, 9, 5, 3, 7, 1 };

Упорядочить весь массив можно вызовом алгоритма sort():

sort ( ia, ia+7 );

Так можно упорядочить первые четыре элемента:

sort ( ia, ia+4 );

Для использования алгоритмов в программу необходимо включить заголовочный файл

 #include <algorithm>

Ниже приведен пример программы, использующей разнообразные алгоритмы в применении к объекту типа vector:

#include <vector>
#include <algorithm>
#include <iostream>

int ia[ 10 ] = {
   51, 23, 7, 88, 41, 98, 12, 103, 37, 6
};

int main()
{
   vector< int > vec( ia, ia+10 );
   vector<int>::iterator it = vec.begin(), end_it = vec.end();
   cout << "Начальный массив: ";

   for ( ; it != end_it; ++ it ) cout << *it << ' ';
   cout << "\n";

   // сортировка массива
   sort( vec.begin(), vec.end() );

   cout << "упорядоченный массив: ";
   it = vec.begin(); end_it = vec.end();
   for ( ; it != end_it; ++ it ) cout << *it << ' ';
      cout << "\n\n";
   int search_value;
   cout << "Введите значение для поиска: ";
   cin >> search_value;
  
   // поиск элемента
   vector<int>::iterator found;
   found = find( vec.begin(), vec.end(), search_value );

   if ( found != vec.end() )
      cout << "значение найдено!\n\n";
   else cout << "значение найдено!\n\n";
   // инвертирование массива
   reverse( vec.begin(), vec.end() );
   cout << "инвертированный массив: ";
   it = vec.begin(); end_it = vec.end();

   for ( ; it != end_it; ++ it ) cout << *it << ' ';
      cout << endl;
}

Стандартная библиотека С++ поддерживает и ассоциативные массивы. Ассоциативный массив – это массив, элементы которого можно индексировать не только целыми числами, но и значениями любого типа. В терминологии стандартной библиотеки ассоциативный массив называется отображением (map). Например, телефонный справочник может быть представлен в виде ассоциативного массива, где индексами служат фамилии абонентов, а значениями элементов – телефонные номера:

#include <map>
#include <string>
#include "TelephoneNumber.h"
map<string, telephoneNum> telephone_directory;

(Классы векторов, отображений и других контейнеров в подробностях описываются в главе 6. Мы попробуем реализовать систему текстового поиска, используя эти классы. В главе 12 рассмотрены обобщенные алгоритмы, а в Приложении приводятся примеры их использования.)
В данной главе были очень бегло рассмотрены основные аспекты программирования на С++, основы объектно-ориентированного подхода применительно к данному языку и использование стандартной библиотеки. В последующих главах мы разберем эти вопросы более подробно и систематично.
Упражнение 2.22

Поясните результаты каждого из следующих определений вектора:

string pals[] = {
"pooh", "tiger", "piglet", "eeyore",    "kanga" };

(a) vector<string> svec1(pals,pals+5);
(b) vector<int> ivec1(10);
(c) vector<int> ivec2(10,10);
(d) vector<string> svec2(svec1);
(e) vector<double> dvec;

Упражнение 2.23

Напишите две реализации функции min(), объявление которой приведено ниже. Функция должна возвращать минимальный элемент массива. Используйте цикл for и перебор элементов с помощью
индекса
template <class elemType>
итератора
elemType min (const vector<elemType> &vec);
Категория: С++ | Добавил: r2d2 (29.09.2011)
Просмотров: 1078 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Born in Ussr
Залогиниться
Турниры

/j clan ussr /j clan cccp