В файле string8.H
class string_iterator {
public:
string_ierator (my_string& s) : cur_ind (0), ptr_s (&s) { }
bool successor ( );
char& item ( ) { return ((ptr_s -> st -> s) [cur_ind]); }
void reset (int n = 0) { cur_ind = n; }
int position ( ) { return cur_ind; }
private:
my_string* ptr_s;
int cur_ind;
};
bool string_iterator :: successor ( )
{
if (cur_ind >= ptr_s -> st -> len –1)
return false;
else {
++ cur_ind;
return true;
}
}
Используем итератор для поиска в строке следующего слова.
В файле string8.Cpp
void word (string_iterator& it_s, char* w)
{
while (isspace (it_s. item ( ) ) && it_s.successor ( ) )
; //ищем не-пробел
if (!isspace (it_s. item ( ) ) ) {
*w = it_s. item ( ); //первый символ в слове
while (it_s. successor ( ) && !isspace (it_s. item ( ) ) )
*++w = it_s. item ( ); //последующие символы
}
*++w = 0; //добавляем ‘\0’ (терминатор)
}
Эта процедура пропускает символы, для которых isspace ( ) принимает истинное значение. Затем она собирает слово как набор не- isspace ( ) символов и завершает его нулевым символом.
Обобщенное программирование с использованием void*
Тип указателя void* служит в качестве обобщенного или универсального указателя. Ему разрешается присваивать указатели любых других типов. Поэтому void* можно использовать полиморфно, разрабатывая код, косвенно управляющий объектами любого типа. Изложенное можно продемонстрировать на примере определения стандартной функции копирования памяти memcpy.
В файле memcopy.cpp
#include <stddef.h> //определяем size_t
#include <iostream.h>
void* memcpy (void* to, const void* from, size_t n_bytes)
{
const char* f = reinterpret_cast<const char*>(from);
char t = reinterpret_cast<char>(to);
for (int I – 0; i < n_bytes; ++I)
t [i] = f[i];
return to;
}
int main ( )
{
char v [4];
int w = 0x00424344;
memcpy (v, &w, 4); //полиморфный интерфейс
cout << w << “ ==” << v;
}
Функция memcpy допускает аргументы-указатели любого типа. Она используется для побайтового копирования символов начиная с адреса, заданного from. В приведенном фрагменте мы инициализировали четырехбайтовый символьный массив целым значением, хранящимся в w. кроме того, эта техника может использоваться с контейнерными классами, такими как stack, для того чтобы они могли (косвенно) хранить произвольные значения.
В файле genstack.H
//реализация обобщенного стека: genstack.h
typedef void* generic_ptr;
class stack {
public:
explicit stack (int size = 1000) : max_len (size), top (EMPTY)
{ s = new generic_ptr [size]; assert (s != 0); }
~stack ( ) { delete [ ] s ; }
void reset ( ) { top = EMPTY; }
void push (generic_ptr c) { s[++top] = c; }
generic_ptr pop ( ) { return s[top--]; }
generic_ptr top_of ( ) { return s[top]; }
bool empty ( ) const { return top == EMPTY; }
bool full ( ) const { return top == max_len – 1; }
private:
enum { EMPTY = -1};
generic_ptr* s;
int max_len;
int top;
};
Конечно, для того чтобы этот класс выполнял полезную работу, значения должны быть правильно приведены. Предположим, например, что у вас есть массив слов, хранящийся как двумерный массив символов, и вы хотите использовать алгоритм стандартного стека для вывода слов в обратном порядке.
В файле month.cpp
#include <iostream.h>
#include “genstack.h”
char* months [12] = { “январь”, “февраль”, “март”, “апрель”, “май”, “июнь”,
“июль”, “август”, “сентябрь”, “октябрь”, “ноябрь”, “декабрь”};
int main ( )
{
stack a;
int i;
for (i = 0; i<12; ++i)
-
push (months [i]);
for (i = 0; i<12; ++i)
cout << reinterpret_cast<char*> (a.pop( ) )
<< end1;
}
Написание обобщенной процедуры обращения порядка следования значений, использующей стек, мы оставим до упражнения 10 не стр. 242.
Список и итератор списка
В этом разделе мы разработаем двусвязный список, интерфейс которого близок к библиотечному типу list из STL. Двусвязный список жертвует размером в пользу эффективности. Добавляя связь, которая явно указывает на предыдущий элемент списка, мы упрощаем такие операции, как удаление; правда, за это приходится платить хранением дополнительных указателей для всех элементов списка. Рассмотрим модель такого класса и связанных с ним определений итератора.
В файле list2.h
class list {
public:
struct listelem; //предварительные объявления
class iterator;
friend iterator;
list ( ) : h (0), t(0) { } //создаем пустой список
~list ( ) { release ( ) ; }
iterator begin ( ) const {return h; }
iterator end ( ) const {return t; }
void push_front (char c);
void pop_front ( );
char& front ( ) {return h -> data;}
char& back ( ) {return t -> data;}
bool empty ( ) const { return h == 0; }
void release ( );
private:
listelem* h, *t; //голова и хвост списка
struct listelem //ячейка списка
{
char data;
listelem* next, *prev;
listelem (char c, listelem* n, listelem* p)
:data ©, next (n), prev (p) { }
};
}
Исследуем схему этого класса поэтапно, начиная со скрытой реализации. Имеется структура listelem, которая содержит член данных списка и два указателя: на следующий и предыдущий элементы. Сам список представлен в виде указателей на голову и хвост. Проход по элементам списка легко выполнить как в прямом направлении – начиная с h с помощью члена next, так и в обратном направлении – начиная с t – с помощью члена prev. Такой проход останавливается, когда значение указателя становится равным 0.
Мы добавили класс-итератор, вложив его внутрь класса списка. Этот класс
list :: iterator будет использоваться для указания на текущую позицию внутри списка. Он сродни указателю-курсору. Таким образом, мы должны также уметь перемещаться по списку с помощью операторов автоинкремента и автодекремента, определенных в классе итератора. Это можно сделать следующим образом.