Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
267
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

720

Hacih iV # Расширенное использование C^^

void printString(const char clata[]) { const char *p = data;

while (*p ! = 0) { cout « *p;

++p; }

cout « endl; }

/ /

текст не изменяется

/ /

указывает на начало данных

/ /

выполняется до конца данных

/ /

вывод на печать текущего символа

/ /

вывод на печать следующего символа

В данном примере массив символов передается глобальной функции как кон­ станта, и каждый символ отображается по очереди. Указатель р устанавливается вначале на первый символ массива data[ ], а затем увеличивается, пока не укажет на оконечный ноль. Даже если это выглядит как логическая структура очень низ­ кого уровня, увеличивающая адрес памяти, в действительности такая операция скорее абстрактная, потому что она не показывает реальные детали управления памятью — насколько изменяется адрес и увеличивается или уменьшается он фактически.

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

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

Объединение данных и операций в одном классе защищает данные от выполне­ ния нелепого доступа клиентскими программистами и предоставляет программи­ стам инструмент для обработки объектов, который защищает от возникновения ошибок. Ниже приведен пример класса String, подобный уже рассмотренному в главе 11.

class

String

{

 

 

 

int

size;

 

 

 

 

char

*str;

 

 

 

 

void set(const char* s) ;

public:

 

 

 

 

String (const

char*

s = "")

{

set(s);

}

 

 

 

String (const

String

&s)

{

set(s . str);

}

 

"StringO

 

 

 

 

{ delete [ ]

str;

}

 

String& operator = (const String& s); int getSizeO const;

char* resetO const; } ;

/ /

размер строки

 

/ /

начало внутренней

строки

/ /

выделение закрытой

строки

/ /

по умолчанию и преобразование

/ /

конструктор копирования

/ /

деструктор

 

/ /

присваивание

 

/ /

длина текущей строки

/ /

установка на начало строки

Элемент данных класса str указывает на динамически выделенный массив, размер которого сохраняется в элементе данных size. Закрытая функция-член set() используется конструкторами класса и оператором присваивания. Она при­ нимает массив функций как параметр и динамически выделяет память из дина­ мически распределяемой области памяти. Затем она устанавливает указатели

Глава 16 • Расширенное использование перегрузки операций

721

элемента данных str на эту заново выделенную память и динамически инициали­ зирует выделенную память, используя текстовый массив, предоставленный как аргумент.

void

String::set(Gonst char* s)

 

 

{

size = strlen(s);

 

/ /

оценка размера

 

str

= new char[size

+ 1 ] ;

/ /

запрос памяти динамически

 

 

 

 

 

/ /

распределяемой области

 

i f

(str == 0)

{ cout

« "Out of

memory\n"; exit(O); }

 

strcpy(str,s);

}

 

/ /

копирование клиентских данных в "кучу"

Это типичная структура динамического управления памятью. Такая закрытая функция удобна, потому что она инкапсулирует операции, обилие д/1Я конструкто­ ров и оператора присваивания.

Как подобает классу, который динамически управляет своей памятью, класс String предоставляет конструктор преобразования (он дублируется как конструк­ тор по умолчанию), конструктор копирования, оператор присваивания и деструк­ тор. Конструктор по умолчанию передает set() пустую строку. Конструктор преобразования отправляет set() как параметр в свой массив символов. Кон­ структор копирования передает set() в динамически распределяемую область памяти своего объекта-параметра. Деструктор возвращает память динамически распределяемой области памяти, которую конструктор или оператор присваива­ ния выделил для объекта String.

Такое добавление функций-членов поддерживает использование объектов String в разнообразных контекстах. Клиентская программа может определить объект String как неинициализированную переменную (вызывается конструктор по умолчанию), как объект, инициализированный значением символьного массива (применяется конструктор преобразования), или как объект, инициализирован­ ный данными другого существующего объекта String (вызывается конструктор копирования).

Оператор присваивания поддерживает присваивание объекта для самого себя. Он освобождает существующую память динамически распределяемой области памяти и выделяет и инициализирует новую память динамически распределяемой области памяти с помощью set(). Он поддерживает клиентскую программу, ко­ торая использует синтаксис выражения для нескольких последовательных при­ сваиваний (возвращая ссылку на целевой объект String). Обратите внимание, что хотя тело присваивания возвращает полный объект String (в данном случае разыменованный указатель), это только ссылка на возвращаемый объект — копирование отсутствует.

String&

String::operator

= (const

String& s)

{ i f (this

== &s) return

*this;

/ /

ничего, если поддерживается

 

 

 

 

/ /

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

delete

[

] str;

 

/ /

возвращение существующей памяти

set(s . str);

 

/ /

выделение/установка новой памяти

return

*this; }

 

/ /

для поддержки цепочечного присваивания

В листинге 16.3 показана полная реализация класса String вместе с встроен­ ными функциями-членами getSizeO и reset(). Первая функция возвращает максимальное количество символов, которые может содержать объект String. Вторая функция возвращает указатель на внутреннюю строку так, что клиентская программа (функции printStringO и modifyStringO) может инициализироват-. внешний указатель, который ссылается на внутреннюю строку.

Эти две клиентские функции используют операцию инкремента для поис:. • и замены символов внутри их параметра объектов String. Цикл в printString, продолжается до тех пор, пока во внутренней строке параметра String не встр.. тится оконечный нуль. (Указатель р на символ внутренней строки должен бьпь

722

Часть IV # Расширенное использование С4-4«

Hello World!

How is the weather?

Рис. 16.3.

Пример нарушения целостности информации в памяти программой из листинга 16.3

разыменован.) Цикл в modifyStringO работает до тех пор, пока все символы в параметре массива символов text[] не будут скопированы.

Функция mainO создает и инициализирует объект String, выводит на печать его содержимое, затем изменяет его и осуществляет вывод на печать. Поскольку цикл в modifyStringO не принимает во внимание текущий объем памяти динамически распределяемой области памяти, вы­ деленный для параметра String, это приводит к нарушению целостности информации в памяти. Вывод программы представлен на рис. 16.3.

Листинг 16.3. Пример использования операции инкремента

 

с указателем на внутренние данные

#include <iostream>

 

 

using namespace std;

 

 

class String {

 

 

// размер строки

int

size;

 

 

char

*str;

 

 

// начало внутренней строки

void set(const char* s);

// выделение закрытой строки

public:

 

 

// поумолчанию и преобразование

String (const char* s = "")

{ set(s); }

 

// конструктор копирования

String (const String &s)

{ set(s.str); }

 

// деструктор

"StringO

 

 

{ delete [ ]str; }

 

// присваивание

String& operator = (const String& s);

int getSizeO

const;

 

// длина текущей строки

char* resetO

const; };

 

// сброс в начало строки

void String::set(const char* s)

// оценка размера

{ size = strlen(s);

1];

str = new char[size +

// запрос динамически распределяемой области памяти

if (str == 0) {cout «

"Out ofmemory\n"; exit(O); }

 

strcpy(str,s); }

 

// копирование клиентских данных

 

 

 

 

// вдинамически распределяемую область памяти

String& String::operator = (const String& s)

{ if (this ==&s) return *this;

// ничего, если поддерживается самоприсваивание

delete [ ]str;

 

// возвращение существующей памяти

set(s.str);

}

 

// выделение/установка новой памяти

return *this;

 

// для поддержки цепочечного присваивания

int String::getSize() const { return size; }

char* String:: resetO const

{ return str; }

void printString(const String&: data)

{ char *p = data. resetO; while (*p != 0)

{ cout « *p;

++p; }

cout « endl; }

//нет изменений объекта String

//нет изменений объекта String

//возвращение указателя к началу

//нет изменений в строке

//указатель на первый символ

//выполнение дообнаружения конца символов

//вывод на печать текущего символа

//указатель наследующий символ

void modifyString(const String& data, const char text[])

// плохой

{ char *p = data. resetO;

// указатель на первый символ

int len = strlen(text) + 1 ;

// установка предела итерации

printString(data); return 0;
/ / просмотр каждого символа / / копирование текущего символа
/ / указатель на следующий символ
/ / хороший вывод

Глава 16 • Расширенное использование перегрузки операций

723 р

for (int 1=0: i < len; i++) { *p = t e x t [ i ] ;

++p; } }

int mainO

{

String data = "Hello World!"; printString(data);

modifyString(data, "How is the weather?");

/ / память искажается

Эту проблему совсем несложно исправить. Клиентская программа modifyStringO должна проверять доступное пространство и прекращать перекачивание данных в объект при достижении границ.

void

modifyString(const String&: data,

const char t e x t [ ] )

//ok

{ char *p = data. resetO;

/ /

указатель на первый символ

int

len

= strlen(text) + 1;

/ /

установка предела итерации

i n t

size = data.getSizeO;

/ /

установка другого

предела итерации

for

( i n t i=0; i<len && Ksize; i++)

/ /

просмотр каждого символа

{ *p = t e x t [ i ] ;

/ /

копирование текущего символа

++p;

} }

/ /

указатель на следующий символ

Эта функция modifyStringO исключает проблему нарушения целостности ин­ формации в памяти, однако сохраняются дефекты проектирования. Клиентская программа должна понимать детали структуры сервера (в данном случае String). Решение этой проблемы с использованием функций перегруженных операций может быть полезным.

Структура String обязана отражать изменение отношения. Чтобы быть в со­ стоянии заш,итить объект от неверного использования клиентами, класс String должен сохранить состояние своих объектов. В этом случае состояние должно включать указатель на текуш.ий символ, выводимый на печать или изменяемый.

Рассматривался важный метод проектирования в C++. Когда будет принято решение, какие данные должен сохранять класс, включайте элементы данных, ко­ торые отражают состояние объекта для клиента. Данный метод освободит объек­ ты от этой зависимости (что не было сделано в листинге 16.3). Покажем лучший вариант класса String.

class

String {

i n t

size;

char

*str;

char

*ptr;

void

set(const char* s);

public:

 

 

 

String

(const

char*

s = "")

{ set(s); }

 

 

String

(const

String

&s)

{ set(s . str); }

 

-StringO

{delete [ ] str; }

String& operator = (const String&; s); char* operator++();

int getSizeO const; char* resetO; } ;

//размер строки

//начало внутренней строки

//указатель натекущий символ

//выделение закрытой строки

//поумолчанию и преобразование

//конструктор копирования

//деструктор

//присваивание

//префиксная операция инкремемтс;

//длина текущей строки

//не константа: объект измег '= :f!

CI242J Часть !V # Расширенное исоол^

Здесь указатель ptr ссылается на текущий символ. Указатель изменяется по памяти динамически распределяемой области в операции инкремента, которая возвращает адрес текущего символа клиенту для доступа или изменения. Опера­ ция инкремента проверяет, смещает ли запрос клиента указатель ptr за пределы динамически распределяемой области памяти символьного массива. Если смеща­ ет, то операция не увеличивает указатель. Вместо этого она изменяет символ, на который указывает указатель, в "\0", чтобы обеспечить правильное завершение символьного массива. Снова используется выражение ptr-str<size, но это не означает, что значение адреса в элементе данных ptr действительно больше, чем значение адреса в элементе набора данных str. Это хороший способ показать перемещение указателя.

char*

String::operator ++()

/ /

приращение, затем доступ

{ i f ( p t r - s t r < size)

/ /

проверка,

достаточно ли памяти

return

++ptr;

/ /

указатель

на следующий символ

else

 

 

 

 

 

{

*ptr

= 0;

/ /

установить оконечный нуль

 

return ptr; } }

/ / н е пересылать его, если конец

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

char*

String::resetO

/ /

не константа: объект

изменяется

{ ptr

= str;

/ /

установить текущий указатель на начало

return str; }

/ /

возвращение указателя

на начало

Некоторые программисты ощущают неудобство, когда элемент данных не ини­ циализирован в конкретное значение при создании объекта. Такие программисты также инициализировали бы элемент данных ptr при каждом вызове конструк­ тора. В структуре, где каждый конструктор вызывает закрытую функцию set(.), инициализация может быть выполнена в данной функции.

void

String::set(const char*

s)

 

 

 

 

{

size = strlen(s);

 

 

/ /

оценка размера

 

 

str

= new char[size

+ 1 ] ;

/ /

запрос динамически

распределяемой

 

 

 

 

 

/ /

области

памяти

 

 

i f

(str == 0) { cout

«

"Out

of memoryXn";

exit(O);

}

 

strcpy(str,s);

 

 

/ /

копирование клиентских данных в "кучу"

 

ptr

= str; }

 

 

/ /

инициализация рабочего указателя

Что достигается при такой структуре? С момента, когда рождается объект, он готов к итерации. Не требуется явно отправлять ему сообщение reset(). Обязан­ ности перемещаются от клиентской программы, которая должна в предыдущей структуре вызывать reset(), к конструктору объекта.

Эта идея хорошо работает при осуществлении доступа только по чтению к дан­ ным объекта String. Функция printStringO останавливает выполнение итера­ ций, когда обнаруживается ограничивающий нуль. Операция инкремента может распознать это и установить текущий указатель обратно в начало строки.

char*

String::operator ++()

/ /

приращение, затем доступ

{ i f

( p t r - s t r < size)

/ /

проверка,

достаточно ли памяти

 

return ++ptr;

/ /

указатель

на следующий символ

Глава 16 • Расширенное использование перегрузки операций

725

else

 

 

 

 

*ptr

= 0;

/ /

установить оконечный нуль

 

ptr

= str;

/ /

снова указатель на начало данных

 

return ptr;

/ /

не пересылать его, если конец

 

Hello World!

How is the w

Рис. 16.4.

Вывод программы из листинга 16.4

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

void

printString(String&

data)

/ / не константа: строка изменяется

{ char

*р = data. reset();

/ /

действительно ли нужен этот вызов?

while

(*р

!= 0)

/ /

 

выполнение, пока не обнаружится

конец символов

{

cout

«

*р;

 

/ /

вывод текущего символа

 

 

р = ++data;

}

/ /

замечательный синтаксис: объект

изменяется

cout

«

endl;

}

 

 

 

 

Однако в этой структуре функция reset() все еиде необходима, так как клиент­ ская функция modifyStringC) может попытаться осуществить доступ к строковым данным за пределами обозначенных границ. Поскольку операция инкремента не знает, как долго клиентская программа будет предоставлять новые данные для копирования в строку, она не может переместить указатель в начало данных. Все проблемы возникают из-за "высокомерного" характера функции modifyStringO, которая закачивает данные в параметр объекта String независимо от наличия в памяти свободного пространства.

void

modifyString(String& data,

const char t e x t [ ] )

/ / не

константа

{ char

*p = data. resetO;

/ /

указывает на первый символ

int

len = strlen(text)

+1;

/ /

устанавливает

предел итераций

for

(int i=0; i

< len;

i++)

/ /

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

каждый символ

{

*p = t e x t [ i ] ;

 

 

/ /

копирует

текущий символ

 

р = ++data; }

}

 

/ /

указатель на следующий символ

Обратите внимание на то, что данные объекта параметра не содержат моди­ фикатор const, но не потому, что функция modifyStringO записывает новое содержание в память своей динамически распределяемой области памяти. В листинге 16.3 функция modifyStringO выполняет то же самое, поданные пара­ метра помечаются как const. Язык СН-+ не обращает внимания на изменения в памяти динамически распределяемой области памяти объекта, но он учитывает

изменения элементов данных объекта. Операция инкремента (и функция reset О) вызывает изменения в элементе данных ptr и, следовательно, предотвращает использование модификатора const с параметром String.

В листинге 16.4 показана полная программа, которая реализует опе­ рацию инкремента для класса String. Вывод программы представлен на рис. 16.4. Проблема искажения информации в памяти исчезает.

Листинг 16.4. Пример использования операции инкремента как сообщения объекту.

#include <iostream>

 

 

 

 

 

using

namespace std;

 

 

 

 

 

class

String {

 

 

 

 

 

 

int

 

size;

 

 

/ /

размер

строки

 

char

*str;

 

 

/ /

начало

внутренней

строки

char

*ptr;

 

 

/ /

указатель на текущий символ

void

set(const

char*

s);

/ /

выделение закрытой

строки

hlic:

 

 

 

 

 

 

 

^ring (const

char*

s = "")

/ /

по умолчанию и преобразование

1 set(s); }

 

 

 

 

 

 

726

Часть IV # Расширенное использование Сннни

String (const String &s)

{ set(s.str); } "StringO

{ delete [ ]str; }

String& operator = (const String&s); char* operator++();

int getSizeO const; char* resetO; } ;

//конструктор копирования

//деструктор

//присвоение

//префиксная операция инкремента

//длина текущей строки

//неконстанта: объект изменяется

void String::set(const char* s)

// оценка размера

 

 

 

 

{ size = strlen(s);

1];

 

 

 

 

str = new char[size +

// запрос памяти динамически распределяемой области

 

if (str ==0) {cout «

"Out of memory\n"; exit(O); }

 

 

 

 

 

strcpy(str,s);

 

// копирование клиентских данных в "кучу"

 

ptr = str; }

 

// инициализация

рабочего указателя

String& String::operator

= (const

String& s)

 

 

 

 

{

if (this ==&s) return *this;

// ничего, если обеспечивается самоприсваивание

 

delete

[ ] str;

 

// возвращение существующей

памяти

 

set(s.str);

 

// выделение/установка

новой

памяти

 

return

*this; }

 

// для поддержки

цепочечного

присваивания

int String::getSize() const

// объект String

не изменяется

{

return size; }

 

 

 

 

 

 

char* String::reset ()

 

// не константа: объект изменяется

{

ptr = str;

 

// установить текущий указатель в начало

 

return str; }

 

// возвращение указателя в начало

char* String::operator ++()

// приращение, затем доступ

 

{

if (ptr-str < size)

 

// проверка, достаточно ли памяти

 

return ++ptr;

 

// указатель наследующий символ

 

else

 

 

// установить оконечный нуль

 

 

{ *ptr = 0;

 

 

 

return ptr; } }

 

// непересылать его, если конец

void printString(String&

data)

// не константа: строка

изменяется

{

char *p = data. resetO;

// указатель на первый

символ

 

while (*p != 0)

 

// переход, пока необнаружится конец символов

 

{ cout « *p;

 

// копировать текущий символ

 

 

p = ++data; }

 

// указатель наследующий символ

cout «

endl; }

 

 

 

 

 

 

void modifyString(String& data, const char text[])

 

 

 

 

{

char *p = data. resetO;

// указатель на первый символ

 

int len = strlen(text) + 1;

// установить предел

итерации

for (int i=0; i < len; i++)

// просмотр каждого

символа

 

{ *p = text[i];

 

// копировать текущий символ

 

 

p = ++data; } }

 

// указатель наследующий символ

int

mainO

 

 

 

 

 

 

 

String data = "Hello World!";

// хороший вывод

 

 

 

 

printString(data);

 

 

 

 

 

modifyString(data, "How is the weather?");

 

 

 

 

printString(data);

 

// память HE искажается

 

 

return 0;

 

 

 

 

 

 

}

Глава 16 • Расширенное использование перегрузки операций

727

Использование операции инкремента обеспечивает прекрасный синтаксис. Далее приводятся дополнительные комментарии по поводу примера. Во-первых, перегруженная операция инкремента проверяет граничные условия для индекса, что не может выполнить встроенная операция. При выполнении этого в данной операции обязанности переносятся в серверный класс. Однако нужным этот при­ мер делает именно проверка границ, а не операция инкремента, которая обеспечи­ вает прекрасный синтаксис в printStringO и modifyStringO. Кроме того, такая проверка границ может выполняться, если имя функции было movePointerO или nextO, а не operator++().

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

Операции декремента подобны операциям инкремента. В их реализацию не во­ влекаются новые принципы или идеи.

Постфиксные перегруженные операции

в C + + встроенные префиксные и постфиксные операции различаются своими позициями относительно операнда выражения. Если выражение записывается как ++clata (данные), это префиксная операция. Если выражение записывается как data++, это постфиксная операции. Важно различать их.

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

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

В соответствии с правилами C + + имя такой отдельной функции должно со­ ставляться из зарезервированного слова operator и символа, который входит в состав операции (в данном случае либо ++ или --). Предположительно имя постфиксного перегруженной операции инкремента должно быть operator++(), а имя постфиксной перегруженной операции декремента — operate г--().

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

Как упоминалось ранее, количество параметров для перегруженных операций не может быть выбрано произвольно. Если бинарная операция реализована как функция-член, то ее первый операнд является целью сообщения, а второй опе­ ранд — параметром сообщения. Если унарная операция реализована как функциячлен, то ее единственный операнд должен использоваться как цель сообщения. Такая перегруженная операция не может иметь параметров.

Итак, нам требуется реализовать в одном и том же классе две перегруженные операции с одним именем (например, operator++) и одинаковыми сигнатурами (без параметров). В этом случае компилятор может пожаловаться, что их невоз­ можно различить.

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

char*

String::operator ++(int)

/ /

сначала доступ, потом приращение

{ i f

( p t r - s t r < size)

/ /

проверка наличия места

return ptr++;

/ /

указатель на следующий символ

г 728

Чость IV # Расширенное использование С+Ф

 

 

else

 

/ /

установка оконечного

нуля

 

*ptr = 0;

 

 

return ptr;

} }

/ /

если конец, завершить выполнение

 

Роль фиктивного параметра весьма ограниченная. Он должен сообщить ком­

 

пилятору, что эта функция совершенно другая, а не перегрузка операции инкре­

 

мента (или декремента) без параметров. С другой стороны, этот параметр не имеет

 

значения в теле самой функции. Именно поэтому можно опустить имя парамет­

 

ра — компилятор не покажет, что имя не определено.

 

 

В присутствии двух функций компилятор найдет ++clata в клиентской програм­

 

ме, интерпретирует это KaKClata.operator++() и выполнит префиксную операцию.

 

Когда в клиентской программе компилятор находит ++data, он интерпретирует

 

ее как data. operator++(0) и выполняет постфиксную операцию. Префиксное зна­

 

чение operator++()

и постфиксное значение operator++

(int) не навязываются

 

языком. За них отвечает проектировидик класса.

 

 

В листинге 16.5 показана программа из листинга 16.4 с постфиксной перегру­

 

женной операцией, которая вызывается из modifyString(). Постфиксная опера­

 

ция возвращает указатель на текущий символ в памяти динамически распределяемой

 

области. Чтобы изменить этот символ, клиентская программа должна разымено­

 

вать значение, возвращенное функцией. Получится четкий синтаксис присваива­

 

ния, который имеет такой же вид, как если бы пepeмeннaяdata была указателем.

 

*data++ = t e x t [ i ] ;

/ /

копирование символа

 

Вывод из этой версии программы такой же, как и для листинга 16.4 (см. рис. 16.4).

Листинг 16.5. Пример использования префиксных и постфиксных операций инкремента

#include <iostream> using namespace std;

class

String {

 

 

 

int

size;

 

 

 

char

*str;

 

 

 

char

*ptr;

 

 

 

void

set(const

char*

s);

public:

 

 

 

 

String (const

char*

s = "")

{

set(s); }

 

 

 

String (const

String

&s)

{

set(s . str);

}

 

"StringO

 

 

 

{ delete [ ] str;

}

 

String& operator = (const String& s);

char*

operator++();

 

char*

operator++(int);

int

getSizeO

const;

 

char*

resetO;

}

;

 

/ /

размер

строки

 

 

/ /

начало

внутренней строки

/ /

указатель на текущий символ

/ /

выделение закрытой строки

/ /

по умолчанию и преобразование

/ /

конструктор копирования

/ /

деструктор

 

 

/ /

присваивание

 

 

/ /

префиксная операция

инкремента

/ /

постфиксная операция

инкремента

/ /

длина текущей

строки

 

/ /

не.константа:

объект

изменяется

void

String::set(const

char* s)

 

 

{

size = strlen(s);

 

/ /

оценка размера

 

str

= new char[size

+ 1 ] ;

/ /

запрос памяти динамически распределяемой области

 

i f

(str ==0) { cout

« "Out of

memory\n"

; exit(O); }

 

strcpy(str,s);

 

/ /

копирование данных в "кучу"

 

ptr

= str; }

 

/ /

инициализация рабочего указателя

 

Глава 16 • Расширенное использование перегрузки операций

729

String& String::operator = (const String& s)

 

{

if (this ==&s) return *this;

// ничего, если самоприсваивание

 

 

delete [ ] str;

 

// возвращение существующей памяти

 

 

set (s.str);

 

// выделить/установить новую память

 

 

return *this; }

 

// для поддержки цепочечных присваиваний

 

int String::getSize() const

// нет изменений вобъекте String

 

{

return size; }

 

// не константа: объект изменяется

 

char* String::resetO

 

 

{

ptr = str;

 

// установить текущий указатель наначало

 

 

return str; }

 

// возвратить указатель на начало

 

char* String::operator ++()

// приращение, затем доступ

 

{

if (ptr-str < size)

// проверка наличия памяти

 

 

return ++ptr;

 

// указатель наследующий символ

 

 

else

 

// установить оконечный нуль

 

 

{ *ptr = 0;

}

 

 

return ptr; }

// не передавать, если конец

 

char* String::operator ++(int)

// доступ, затем приращение

 

{

if (ptr-str < size)

// проверка наличия памяти

 

 

return ptr++;

 

// указатель наследующий символ

 

 

else

 

// установить оконечный нуль

 

 

{ *ptr = 0;

}

 

 

return ptr; }

// не передавать, если конец

 

void printString(String& data)

// не константа: строка изменяется

 

{

char *p =data.resetO;

// указатель на первый символ

 

 

while (*p != 0)

 

// переход, пока необнаружится конец символов

 

{ cout « *p;

 

// вывод на печать текущего символа

 

 

p =++data; }

 

// указатель наследующий символ

 

 

cout « endl; }

 

 

 

 

void modifyString(String& data, const char text[])

 

{

data.resetO;

 

// указатель на первый символ

 

 

int len = strlen(text) + 1 ;

// установить предел итерации

 

 

for (int i=0; i < len; i++)

//просмотр каждого символа

 

 

*data++ =text[i]; }

//замечательный синтаксис: копирование символа

int mainO

 

 

 

 

{

String data = "Hello World!";

 

 

 

 

/ /

хороший вывод

 

 

printString(data)

 

 

 

modifyString(data

How is the weather?");

 

 

 

printString(data)

 

/ /

память HE искажается

 

 

return .0;

 

 

 

 

 

}

 

 

 

 

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

Операции преобразования

Приведение значения одного типа к значению другого типа достигается за счет применения имени целевого типа для значения (для имени переменной) исходного типа. Синтаксис бывает двух видов: традиционный и новый функционально-подоб­ ный. В традиционном синтаксисе имя типа цели (в скобках) используется перед

Соседние файлы в предмете Программирование на C++