Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Перегруженные операции класса vector




20.5.3.1. Операция []

Возвращает ссылку на элемент в указанной позиции.

reference operator[]( size_type _Pos );const_reference operator[](size_type _Pos) const;

Параметр

_Pos

Позиция (индекс) элемента вектора

Замечания

Если возвращаемое значение присваивается константной ссылке, то вектор не может быть модифицирован, а в противном случае – может. Если _Pos>=Size(), то возвращаемое значение не определено.

Пример

#include <vector>

#include <iostream>

 

int main( )

{

using namespace std;  

vector <int> v1;

 

v1.push_back( 10 );

v1.push_back( 20 );

 

int& i = v1[1];

cout << "The second integer of v1 is " << i << endl;

}

Вывод программы

The second integer of v1 is 20

Операции сравнения

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

template<class T, class A>

bool operator==(operator

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

template<class T, class A>

bool operator!=(

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

template<class T, class A>

bool operator< (

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

template<class T, class A>

bool operator> (

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

template<class T, class A>

bool operator<=(

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

template<class T, class A>

bool operator>=(

   const vector<T, A>& lhs,

   const vector<T, A>& rhs);

Пример

typedef vector <int> intVec;

void PrintRes(intVec &v1,intVec &v2)

{ int i;

cout<<" v1=";

for(i=0;i<v1.size();i++) cout<<' '<<v1[i];

cout<<endl;

cout<<" v2=";

for(i=0;i<v2.size();i++) cout<<' '<<v2[i];

cout<<endl;

if(v1==v2) cout<<"v1==v2"; else

   if(v1>v2) cout<<"v1>v2"; else

         if (v1<v2) cout<<"v1<v2"; else

              if(v1>=v2) cout<<"v1>=v2"; else

                   if(v1<=v2) cout<<"v1<=v2";

cout<<endl;

}

int main()

{

intVec v1,v2;

v1.push_back(10); v1.push_back(20);

v2.push_back(10); v2.push_back(20);

PrintRes(v1,v2);

 

v2[1]=22;

PrintRes(v1,v2);

 

v2[1]=2;

PrintRes(v1,v2);

 

v1.push_back(30); v2.push_back(40);

PrintRes(v1,v2);

 

v1.pop_back();

PrintRes(v1,v2);

}

 

Вывод программы

v1= 10 20

v2= 10 20

v1==v2

v1= 10 20

v2= 10 22

v1<v2

v1= 10 20

v2= 10 2

v1>v2

v1= 10 20 30

v2= 10 2 40

v1>v2

    v1= 10 20

v2= 10 2 40

v1>v2



Иллюстрация использования класса vector

Проект DemoVector.

#include <vector>

Void DemoVec()

{

vector <int> Vec;

vector <int>::iterator Iter;

vector <double>::iterator dIter;

  

for(int i=10; i<40; i+=10) Vec.push_back(i);

cout << "Vec =" ;

for ( Iter = Vec.begin( ) ; Iter != Vec.end( ) ; Iter++ )

 cout << " " << *Iter;

cout << endl; // 10 20 30

Vec.insert( Vec.begin( ) + 1, 40 );

cout << "Vec =";

for (int i=0; i<Vec.size(); i++) cout << " " <<Vec[i];

cout << endl; // 10 40 20 30

cout<<"Vec.capacity()="<<Vec.capacity()<<endl; // 4

Vec.reserve(10);

cout<<"Vec.capacity()="<<Vec.capacity()<<

" Vec.size()="<<Vec.size()<<endl; // 10 4

vector <int> Vec2;

Vec2=Vec; // копирование элементов векторов!

cout<<"Vec2= ";

for (int i=0; i<Vec2.size(); i++) cout << " " <<Vec2[i]; // 10 40 20 30

cout<<endl;

cout<<"Vec= ";

for (int i=0; i<Vec.size(); i++) cout << " " <<Vec[i]; // 10 40 20 30

cout<<endl;

Vec2.clear();

Vec2.assign(2,0);

cout<<"Vec2= ";

for (int i=0; i<Vec2.size(); i++) cout << " " <<Vec2[i]; // 0 0

cout<<endl;

// копирование элементов одного вектора в другой

Vec2.assign(Vec.begin()+1,Vec.end());

cout << "Vec2 =";

for (Iter = Vec2.begin( ); Iter != Vec2.end( ); Iter++ )

cout << " " << *Iter; // 40 20 30

cout << endl;

 //cout<<Vec2[3]; //возникает ИС

 /* Перехватить эту ИС не удается. Надо писать свой шаблонный класс

на основе шаблона vector */

 //try // Нижеследующий код не работает, так как тип vector не

       // // генерирует ИС

 // // при указаниии недействительного индекса в операции []

 //{

     // cout<<Vec2[3]<<endl;

 //}

 //catch(...)

 //{

     // cout<<"Goodness me!"<<endl;

 //}

_getch();

// освобождать память не нужно

}

 /* Для контроля выхода индекса за границу массива при использовании

операции [] надо переопределить шаблон класса */

template <class T> class MyVector : public vector<T>

{

// …

public:

T& operator[ ](int n);    // access

// …

};

template <class T> T& MyVector<T>::operator[ ](int n)

{

if (n<0 || size()<=n) throw "Out of range";

return at(n);

};

Void DemoMyVec()

{

MyVector <int> Vec;

for(int i=10; i<40; i+=10) Vec.push_back(i);

 try // Нижеследующий код работает, т.е. перехватывает ИС

 {

            cout<<Vec[3]<<endl; // Vec[3] вызывает ИС

 }

 catch(char *)

 {

            cout<<"Goodness me!"<<endl;

 }

_getch();

}

Void main()

{

     DemoVec();

     DemoMyVec();

}

 



Алгоритмы

Общие соглашения

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

В данном разделе широко использованы материалы из работ [4,6] и MSDN.

Итераторы

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

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

Рис. 1. Итераторы и элементы контейнера

 

Итератор begin() всегда указывает на первый элемент последовательности, а end() – на позицию последовательности, следующую за последним элементом. Несмотря на сходство с указателем, для итератора неприменимо значение NULL. Говорят, что итератор действителен, если он указывает на существующий элемент или на end(). Итератор недействителен (invalid), если он не инициализирован или если последовательность изменила размеры после последнего присваивания значения итератору.

Так как итераторы - обобщение указателей, их семантика - обобщение семантики указателей в C++. Это гарантирует, что каждая шаблонная функция, которая использует итераторы, работает с обычными указателями. Есть пять категорий итераторов в зависимости от операций, определённых для них:

· ввода (input iterators);

· вывода (output iterators);

· последовательные (forward iterators);

· двунаправленные (bidirectional iterators);

· произвольного доступа (random access iterators).

 

Отношения между категориями итераторов показаны в табл.1.

Таблица 1.

Отношения среди категорий итераторов

Произвольного доступа -> Двунаправленные -> Последовательные --
--> Ввода
--> Вывода

 

 

Последовательные итераторы удовлетворяют всем требованиям итераторов ввода и вывода и могут использоваться всякий раз, когда определяется тот или другой вид. Двунаправленные итераторы удовлетворяют всем требованиям последовательных итераторов и могут использоваться всякий раз, когда определяется последовательный итератор. Итераторы произвольного доступа удовлетворяют всем требованиям двунаправленных итераторов и могут использоваться всякий раз, когда определяется двунаправленный итератор. Имеется дополнительный атрибут, который могли быть иметь последовательные, двунаправленные и произвольного доступа итераторы, то есть они могут быть модифицируемые (mutable) или постоянные (constant) в зависимости от того, ведёт ли себя результат operator* как ссылка или как ссылка на константу. Постоянные итераторы не удовлетворяют требованиям итераторов вывода.

Точно так же, как обычный указатель на массив гарантирует, что имеется значение указателя, указывающего за последний элемент массива, так и для любого типа итератора имеется значение итератора, который указывает за последний элемент соответствующего контейнера. Эти значения называются законечными (past-the-end) значениями. Значения итератора, для которых operator* определён, называются разыменовываемыми (dereferenceable). Библиотека никогда не допускает, что законечные значения являются разыменовываемыми. Итераторы могут также иметь исключительные (singular) значения, которые не связаны ни с каким контейнером. Например, после объявления неинициализированного указателя x (например, int* x;), всегда должно предполагаться, что x имеет исключительное значение указателя. Результаты большинства выражений не определены для исключительных значений. Единственное исключение – присваивание неисключительного значения итератору, который имеет исключительное значение. В этом случае исключительное значение перезаписывается таким же образом, как любое другое значение. Разыменовываемые и законечные значения всегда являются неисключительными.

Итератор j называется доступным (reachable) из итератора i, если и только если имеется конечная последовательность применений операции operator++ к i, которая делает i==j. Если i и j относятся к одному и тому же контейнеру, тогда или j доступен из i, или i доступен из j, или оба доступны (i == j).

Большинство алгоритмических шаблонов библиотеки, которые работают со структурами данных, имеют интерфейсы, которые используют диапазоны. Диапазон – это пара итераторов, которые указывают начало и конец вычисления. Интервал [i,i) – пустой диапазон; вообще, диапазон [i,j) относится к элементам в структуре данных, начиная с элемента, указываемого i, и до элемента, но не включая его, указываемого j. Диапазон [i,j) допустим, если и только если j доступен из i. Результат применения алгоритмов библиотеки к недопустимым диапазонам не определён.

Более подробные сведения по категориям итераторов и особенностям их применения см. в разделе «Итераторы» [5].

 



Функциональные объекты

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

Например, если мы хотим поэлементно сложить два вектора a и b, содержащие double, и поместить результат в a, мы можем сделать зто так:

transform(a.begin(), a.end(), b.begin(), b.end(), plus<double>());

 

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

using namespace std;

template <class T>

class Print

{

public:

void operator()(T& arg1)

{ cout<< arg1<<' '; }

};

int main()

{

Print <int> DoPrint; // DoPrint – функциональный объект

for(int i=0;i<5;i++) DoPrint(i);

return 0;

}

 

Эта программа выведет на монитор числа от 0 до 4.

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

Функциональные объекты могут использоваться алгоритмами STL для выполнения различных действий над контейнерами. Существует три типа функциональных объектов:

генераторы – функциональные объекты, не имеющие аргументов. Классический пример – генератор случайных чисел;

унарные функции – функциональные объекты, имеющие один аргумент. Примером такого объекта является DoPrint;

бинарные функции – функциональные объекты, имеющие два аргумента.

 

Все типы объектов-функций могут иметь возвращаемое значение, а могут и не иметь. Если тип возвращаемого значения булевский, то функциональный объект называют предикатом.

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

argument_type и result_type для функциональных объектов, которые используют один параметр;

first_argument_type, second_argument_type и result_type для функциональных объектов, которые используют два параметра.

 

В заголовочном файле <functional> определены шаблоны стандартных классов, которые помогают стандартизировать и упростить описание функциональных объектов:

template <class Arg, class Result>

struct unary_function {

      typedef Arg argument_type;

      typedef Result result_type;

}:

 

template <class Arg1, class Arg2, class Result>

struct binary_function {

       typedef Arg1 first_argument_type;

       typedef Arg2 second_argument_type;

       typedef Result result_type;

};

 

С учетом приведенных шаблонов класс Print можно описать так:

template <class T>

class Print : public unary_function<T, void>

{

public:

void operator()(T& arg1)

{ cout<< arg1<<' '; }

};

 

Библиотека STL обеспечивает базовые классы функциональных объектов для:

· арифметических операций языка (arithmetic operations);

· операций сравнения (comparisons);

· логических операций (logical operations).

 

Их описание см. в разделе «Функциональные объекты» [5]. Шаблоны для операций сравнения позволяют стандартизировать описание функциональных объектов предикатов.

 

Распределители

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

STL принимается за эту проблему, обеспечивая стандартный набор требований для распределителей (allocators), являющихся объектами, которые инкапсулируют эту информацию. Все контейнеры в STL параметризованы в терминах распределителей. Это значительно упрощает задачу взаимодействия с многочисленными моделями памяти.

 










Последнее изменение этой страницы: 2018-04-12; просмотров: 417.

stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда...