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

hkjCJgcQqF

.pdf
Скачиваний:
2
Добавлен:
15.04.2023
Размер:
810.94 Кб
Скачать

//использование библиотечной функции C++ strrchr. возвращает

//указатель на последнее вхождение символа с в строке

p = strrchr(str,c);

 

if (p != NULL)

 

 

ret = int(p-str);

// вычисление индекса

else

 

 

ret = -1;

 

// возвратить -1 при неудаче

return ret;

}

// возвращает подстроку с позиции index и длиной count MyString MyString::Substr(int index, int count) const {

//число символов от index до конца строки int charsLeft = size-index-1,i;

//создать подстроку в temp

MyString temp; char *p, *q;

// возвратить null строку , если index слишком велик if (index >= size-1)

return temp;

// если count > charsLeft , возвращать оставшиеся символы if (count > charsLeft)

count = charsLeft;

// удалить NULL -строку, созданную при объявлении temp delete [] temp.str;

// выделить память для подстроки temp.str = new char [count+1]; if (temp.str == NULL) Error(outOfMemory);

// копировать count символов из str в temp.str for(i=0,p=temp.str,q=&str[index];i < count;i++) *p++ = *q++;

// последний NULL -символ *p = 0;

temp.size = count+1; return temp; }

void MyString::Insert(const MyString& s, int index) { int newsize, length_s = s.size-1, i;

char *newstr, *p, *q; newsize = size + length_s; newstr = new char [newsize]; if (newstr == NULL) Error(outOfMemory);

for(i=0,p = newstr, q = str; i <= index-1;i++) *p++ = *q++;

63

strcpy(p,s.str); p += length_s;

strcpy(p,&str[index]);

delete [] str;

// delete old string

size = newsize; // новый размер строки

str = newstr;

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

void MyString::Insert(char *s, int index) {

int newsize, length_s = strlen(s), i;

char *newstr, *p, *q; newsize = size + length_s; newstr = new char [newsize]; if (newstr == NULL)

Error(outOfMemory);

for(i=0,p = newstr, q = str;i <= index-1;i++) *p++ = *q++;

strcpy(p,s);

p += length_s;

strcpy(p,&str[index]);

delete [] str;

// delete old string

size = newsize; // new string size

str = newstr;

// new string pointer }

void MyString::Remove(int index, int count) { int charsLeft = size-index-1, newsize, i; char *newstr, *p, *q;

if (index >= size-1)

return; // возвратить, если индекс слишком велик if (count > charsLeft)

count = charsLeft; newsize = size - count;

newstr = new char [newsize]; if (newstr == NULL)

Error(outOfMemory); for(i=0,p=newstr,q=str;i <= index-1;i++)

*p++ = *q++; q += count; strcpy(p,q); delete [] str; size = newsize; str = newstr; }

MyString index operator

char& MyString::operator[] (int n) { if (n <0 || n >= size-1) Error(indexError,n);

return str[n]; }

64

pointer conversion operator

MyString::operator char* ( ) const { return str; }

перегруженные операторы ввода/вывода

istream& operator>> (istream& istr, MyString& s) {

char tmp[256];

 

if (istr >> tmp)

// eof?

{

 

delete [] s.str;

// delete existing string

s.size = strlen(tmp) + 1; s.str = new char [s.size]; if (s.str == NULL) s.Error(outOfMemory); strcpy(s.str,tmp); }

return istr; }

ostream& operator<< (ostream& ostr, const MyString& s)

{

ostr << s.str; return ostr;

}

int MyString::ReadString (istream& istr, char delimiter)

{

// read line into tmp char tmp[256];

if (istr.getline(tmp, 256, delimiter))

{

// delete string and allocate memory for new one delete [] str;

size = strlen(tmp) + 1; str = new char [size]; if (str == NULL)

Error(outOfMemory);

// copy tmp. return number of chars read

strcpy(str,tmp);

 

return size-1;

}

else

 

return -1; // return -1 on end of file

}

 

int MyString::Length(void) const

{

return size-1; }

 

int MyString::IsEmpty( ) const

{

return size == 1; }

 

void MyString::Clear(void) {

 

65

delete [] str; size = 1;

str = new char [size]; // allocate space for NULL char if (str == NULL)

Error(outOfMemory); str[0] = 0; }

Операторные функции

Можно описать функции, определяющие интерпретацию следующих

операций:

 

 

 

 

 

 

 

 

 

+

-

*

/

%

^

&

|

~

!

 

=

<

>

 

+= -= *= /=

%=

^=

&=

|=

<<

>>

>>= <<= ==

!=

<=

>= &&

||

++

--

->* ,

->

[]

()

new delete

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

На применение перегруженных операторов налагается несколько ограничений:

нельзя изменить приоритеты этих операций;

невозможно изменить количество операндов оператора;

операторную функцию нельзя вызывать с аргументами, значения которых заданы по умолчанию (за исключением оператора вызова функции);

нельзя перегружать следующие операторы “::” , “.*”, “.”, “? :”. Внутри операторной функции можно программировать любые операции. За исключением оператора “ = “ , операторные функции наследуются производными классами. Однако в производном классе каждый из этих операторов снова можно перегрузить.

Бинарные и унарные операции

Бинарную операцию можно определить как функцию-член с одним параметром или как глобальную функцию с двумя параметрами. Значит, для любой бинарной операции @ выражение aa @ bb интерпретируется либо как aa.operator(bb), либо как operator@(aa,bb). Если определены обе функции, то выбор интерпретации происходит по правилам сопоставления параметров. Префиксная или постфиксная унарная операция может определяться как функция-член без параметров или как глобальная функция с одними параметром. Для любой префиксной унарной операции @ выражение @aa интерпретируется либо как aa.operator@(), либо как operator@(aa). Если определены обе функции, то выбор интерпретации происходит по правилам сопоставления параметров. Для любой постфиксной унарной операции @ выражение @aa интерпретируется либо как aa.operator@(int), либо как operator@(aa,int). Если определены обе функции, то выбор интерпретации происходит по правилам сопоставления

66

параметров. Операцию можно определить только в соответствии с синтаксическими правилами, имеющимися для нее в грамматике С++. В частности, нельзя определить % как унарную операцию, а + как тернарную. Например:

class X {

//члены (неявно используется указатель `this'):

//префиксная унарная операция & (взятие адреса) X* operator&( );

//бинарная операция & (И поразрядное)

Xoperator&(X);

//постфиксный инкремент

Xoperator++(int);

// X operator&(X,X);

// ошибка: & не может быть тернарной

//X operator/();

// ошибка: / не может быть унарной

}; // глобальные функции (обычно друзья)

X operator-(X);

// префиксный унарный минус

X operator-(X,X);

// бинарный минус

X operator--(X&,int); // постфиксный инкремент

X operator-();

// ошибка: нет операнда

X operator-(X,X,X);

// ошибка: тернарная операция

X operator%(X);

// ошибка: унарная операция %

Предопределенные свойства операций

Используется только несколько предположений о свойствах пользовательских операций. В частности, operator=, operator[], operator() и operator-> должны быть нестатическими функциями-членами. Этим обеспечивается то, что первый операнд этих операций является адресом.

Операции = (присваивание), &(взятие адреса) и , (операция запятая) обладают предопределенными свойствами для объектов классов. Но можно закрыть от произвольного пользователя эти свойства, если описать эти операции как частные:

class X {

//...

private:

void operator=(const X&); void operator&();

void operator,(const X&);

//...

};

void f(X a, X b) {

a= b;

// ошибка: операция = частная

&a;

// ошибка: операция & частная

67

a,b // ошибка: операция, частная

}

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

Операторные функции и пользовательские типы

Операторная функция должна быть либо членом, либо иметь по крайней мере один параметр, являющийся объектом класса (для функций, переопределяющих операции new и delete, это необязательно). Это правило гарантирует, что пользователь не сумеет изменить интерпретацию выражений, не содержащих объектов пользовательского типа. В частности, нельзя определить операторную функцию, работающую только с указателями. Операторная функция, имеющая первым параметр основного типа, не может быть функцией-членом. Так, если прибавить комплексную переменную comVar к целому 2, то при подходящем описании функциичлена comVar+2 можно интерпретировать как comVar.operator+(2), но 2+ comVar так интерпретировать нельзя, поскольку не существует класса int, для которого + определяется как 2.operator+( comVar). Даже если бы это было возможно, для интерпретации comVar+2 и 2+comVar пришлось иметь дело с двумя разными функциями-членами. Этот пример тривиально записывается с помощью функций, не являющихся членами. Каждое выражение проверяется для выявления неоднозначностей.

Решение, делать ли оператор членом класса или членом пространства имен принимается на основе следующих положений:

если перегруженный оператор является членом класса, то он вызывается лишь при условии, что левым операндом служит член этого класса. Если же левый операнд имеет другой тип, оператор обязан быть членом пространства имен;

язык требует, чтобы операторы присваивания ("="), взятия индекса ("[]"), вызова ("()") и доступа к членам по стрелке ("->") были определены как члены класса. В противном случае выдается сообщение об ошибке компиляции.

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

Друзья

Перегруженные операторы равенства для класса MyString, выглядят следующим образом:

//определенные в области видимости пространства имен. bool operator= =( const MyString &str1, const MyString &str2 )

{

if ( str1.size() != str2.size() ) return false;

68

return strcmp( str1.c_str(), str2.c_str() ) ? false : true;}

Сравните это определение с определением того же оператора как функции-члена:

bool MyString::operator= =( const MyString &rhs ) const

{

if ( _size != rhs._size ) return false;

return strcmp( _string, rhs._string ) ? false : true;}

Пришлось модифицировать способ обращения к закрытым членам класса MyString. Поскольку новый оператор равенства – это глобальная функция, а не функция-член, у него нет доступа к закрытым членам класса MyString. Для получения размера объекта MyString и лежащей в его основе C-строки символов используются функции-члены size() и c_str().

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

Объявление друга (оно начинается с ключевого слова friend) встречается только внутри определения класса. Поскольку друзья не являются членами класса, объявляющего дружественные отношения, то безразлично, в какой из секций – public, private или protected – они объявлены. Например, подобные объявления сразу после заголовка класса:

class MyString {

friend bool operator==( const MyString &, const MyString & ); friend bool operator==( const char *, const MyString & ); friend bool operator==( const MyString &, const char * );

public:

// ... остальная часть класса MyString };

В этом примере, три строчки перегруженных операторов сравнения принадлежат глобальной области видимости, они объявляются друзьями класса MyString, а следовательно, в их определениях можно напрямую обращаться к закрытым членам данного класса:

bool operator==( const MyString &str1, const MyString &str2 )

{

if ( str1._size != str2._size ) return false;

return strcmp( str1._string, str2._string ) ? false : true;

}

inline bool operator==( const MyString &str, const char *s )

{

return strcmp( str._string, s ) ? false : true;

69

}

// и т.д.

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

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

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

Объявление функции другом двух классов должно выглядеть так:

Функция-член одного класса не может быть объявлена другом второго, пока компилятор не увидел определения ее собственного класса.

Дружественная функция получает доступ к членам класса только при передаче ей объектов как параметров и использовании записи с точкой для ссылки на члена.

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

friend Vec2d operator-(Vec2d X); //унарный «минус»

friend Vec2d operator+(Vec2d X, Vec2d Y); // бинарное сложение operator =

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

//общий вид копирующего оператора присваивания

className& className:: operator=( const className &rhs )

{

// не надо присваивать самому себе if ( this != &rhs )

70

{

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

}

// вернуть объект, которому присвоено значение return *this;}

Здесь условная инструкция if ( this != &rhs ) предотвращает присваивание объекта класса самому себе, что плохо в ситуации, когда копирующий оператор присваивания сначала освобождает некоторый ресурс, ассоциированный с объектом в левой части, чтобы назначить вместо него ресурс, ассоциированный с объектом в правой части. Копирующий конструктор и копирующий оператор присваивания обычно рассматривают вместе. Если необходим один, то, как правило, необходим и другой. Если запрещается один, то, вероятно, следует запретить и другой.

Если объектам класса надо присваивать значения типа, отличного от этого класса, то разрешается определить такие операторы, принимающие подобные параметры. Например, чтобы поддержать присваивание C- строки объекту MyString:

MyString car ("Volga") car = "Ford";

предоставляется оператор, принимающий параметр типа CONST CHAR*. Эта операция была объявлена в классе MyString:

class MyString { public:

//оператор присваивания для char* MyString& operator=( const char * );

//...

private: int _size;

char *string; };

Такой оператор реализуется следующим образом. Если объекту MyString присваивается нулевой указатель, он становится “пустым”. В противном случае ему присваивается копия C-строки:

MyString& MyString::operator=( const char *sobj ) { // sobj - нулевой указатель

if (! sobj ) { _size = 0; delete[] _string; _string = 0; }

else {

_size = strlen( sobj ); delete[] _string;

71

_string = new char[ _size + 1 ]; strcpy( _string, sobj );

}

return *this; }

_string ссылается на копию той C-строки, на которую указывает sobj. На копию потому, что непосредственно присвоить sobj члену _string нельзя:

_string = sobj; // ошибка: несоответствие типов

sobj– это указатель на const и, следовательно, не может быть присвоен указателю на “не-const”. Изменим определение оператора присваивания:

MyString& MyString::operator=( const *sobj ) { // ... }

Теперь _string прямо ссылается на C-строку, адресованную sobj. Однако при этом возникают другие проблемы. Напомним, что C-строка имеет тип const char*. Определение параметра как указателя на не-const делает присваивание невозможным:

car = "Studebaker"; // недопустимо с помощью operator=( char *) ! Итак, выбора нет. Чтобы присвоить C-строку объекту типа

MyString, параметр должен иметь тип const char*.

Обратите внимание, что в операторе присваивания используется delete. Член _string содержит ссылку на массив символов, расположенный в куче. Чтобы предотвратить утечку, память, выделенная под старую строку, освобождается с помощью delete до выделения памяти под новую. Поскольку _string адресует массив символов, следует использовать версию delete для массивов. И последнее замечание об операторе присваивания. Тип возвращаемого им значения – это ссылка на класс MyString. Почему именно ссылка? Дело в том, что для встроенных типов операторы присваивания можно сцеплять.

Оператор взятия индекса

Оператор взятия индекса operator[]() можно определять для классов, представляющих абстракцию контейнера, из которого извлекаются отдельные элементы. Примерами таких контейнеров могут служить класс MyString, класс IntArray или шаблон класса vector, определенный в стандартной библиотеке C++. Оператор взятия индекса обязан быть функцией-членом класса.

У пользователей MyString должна иметься возможность чтения и записи отдельных символов члена-данных _string. Например, используется следующий способ применения объектов данного класса:

MyString str1( "строка" ); MyString mycopy;

for ( int ix = 0; ix < str1.size(); ++ix ) mycopy[ ix ] = str1 [ ix ];

В определении оператора проверяется выход индекса за границы массива. Для этого используется библиотечная C-функция assert(). Можно

72

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]