Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Книга C++.doc
Скачиваний:
24
Добавлен:
10.11.2019
Размер:
2.48 Mб
Скачать

Пример строкового класса с перегруженными операторами и дружественными функциями

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

Чтобы решить данную задачу, воспользуемся знаниями, полученными на этом занятии. Подробные пояснения к примеру приводятся в коментариях.

#include <string.h>

#include <iostream.h>

class string // Объявление строкового класса

{

private:

char* S; // Строка

int len; // Длина строки

public:

string(); // Конструктор по умолчанию

string(const char* s); // Перегруженный конструктор

string(const string& s); // Конструктор копирования

~string() { delete [] S; }// Деструктор

// Дружественные функции

// Перегрузка бинарного оператора

// Функция реализует сцепление строк

friend string operator+(const string&, const string&);

// Перегрузка бинарного оператора

// Функция реализует пересечение строк

friend string operator*(const string&, const string&);

// Перегрузка унарного оператора

// Функция реализует поиск уникальных элементов строки

friend string operator!(const string&);

// Перегрузка бинарного оператора

// Функция реализует ввод объектов класса с клавиатуры

friend istream& operator>>(istream&, string&);

// Перегрузка бинарного оператора

// Функция реализует вывод объектов класса на экран

friend ostream& operator<<(ostream&, string&);

string& operator=(const string&);

// Перегрузка типа

// Функция реализует преобразование объекта класса к типу char*

operator char*() { return S; }

void Sort(string s[], int n); // Функция сортировки

int GetLen() { return len; } // Функция возвращает длину строки

};

string::string()

{

S = new char[81]; // Выделение памяти под 81 элемент

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

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

S[i] = 0;

len = 80;

}

string::string(const char* s)

{

len = strlen(s);

S = new char[len + 1];

strcpy(S, s); // Инициализация строкой, переданной

// пользователем

}

string::string(const string& s)

{

len = s.len;

S = new char[len + 1]; // Безопасное копирование

strcpy(S, s.S);

}

void string::Sort(string s[], int n)

{ // Сортировка строк

bool flag = true;

string temp;

for(int j = 1; ; j++)

{

for(int i = 0; i < n - j; i++)

if(strcmp(s[i], s[i + 1]) > 0) // Происходит обращение к

{ // строкам напрямую, благодаря

// неявному вызову функции класса string

// operator char*()

temp = s[i]; // Вызов функции operator=(s[i])

s[i] = s[i + 1]; // Вызов функции operator=(s[i + 1])

s[i + 1] = temp; // Вызов функции operator=(temp)

flag = false;

}

if(flag)

break;

flag = true;

}

}

string operator+(const string &str1, const string &str2)

{ // Функция сцепления строк

string s; // Создание временного объекта

delete [] s.S; // Удаление старой строки

s.len = str1.len + str2.len;// Вычисление новой длины строки

s.S = new char[s.len + 1]; // Выделение памяти под новую строку

strcpy(s.S, str1.S); // Инициализация первой части строки

strcat(s.S, str2.S); // Инициализация второй части строки

return s; // Возврат нового объекта

}

string operator*(const string &str1, const string &str2)

{ // Функция сцепления строк

string s; // Создание временного объекта

delete [] s.S; // Удаление старой строки

s.len = (str1.len > str2.len)? str1.len : str2.len;

// Вычисление новой длины строки

s.S = new char[s.len + 1]; // Выделение памяти под новую строку

int s1 = str1.len; // Длина первой строки

int s2 = str2.len; // Длина второй строки

int k = 0; // Индекс результирующей строки

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

for(int j = 0; j < s2; j++)

if(str1.S[i] == str2.S[j])

{

s.S[k] = str1.S[i];

// Помещаем найденный символ,

k++; // увеличиваем индекс,

break; // выходим из цикла

}

s.S[k] = 0; // Вставляем завершающий символ \0

s.len = strlen(s.S);

if(s.len > 1)

return !s; // Возврат нового объекта

// Вызов функции operator !(s)

return s;

}

string operator!(const string& str)

{ // Функция нахождения уникальных

// элементов строк

string s;

delete [] s.S;

int l = s.len = str.len;

s.S = new char[s.len + 1];

s.S[0] = str.S[0]; // Копирование первого символа

for(int n = 1; n < l; n++)

s.S[n] = 0; // Обнуление остальных элементов

int k = 1; // Индекс для новой строки

bool flag = true;

for(int i = 1; i < l; i++) // Поиск уникальных элементов

{

for(int j = 0; j < l; j++)

if(s.S[j] == 0)

break;

else if(s.S[j] == str.S[i])

{

flag = false;

break;

}

if(flag)

{

s.S[k] = str.S[i]; // Добавление уникального символа

// в строку

k++; // Увеличение индекса

}

flag = true;

}

s.S[k] = 0;

return s; // Возврат полученной строки

}

istream& operator>>(istream& is, string& str)

{

char temp[256];

is.getline(temp, 256); // Запрос строки

delete [] str.S;

str.len = strlen(temp); // Выделение памяти под новую строку

str.S = new char[str.len + 1];

strcpy(str.S, temp); // Копирование строки

return is; // Возврат объекта ввода

}

ostream& operator<<(ostream& os, string& str)

{

os << str.S; // Вывод строки

return os; // Возврат объекта вывода

}

string& string::operator=(const string &str)

{ // Функция, реализующая безопасное присваивание

delete [] S; // Удаление старой строки

len = str.len; // Вычисление новой длины строки

S = new char[len + 1]; // Выделение памяти под новую строку

strcpy(S, str.S); // Инициализация строки

return *this; // Возврат ссылки на "самого себя"

// Благодаря этому возможно многократное

// присваивание объектов друг другу

// например, string a, b, c; a = b = c;

}

void main(void)

{

string a, b, c;

cout << "Input the first part of string:\t\t";

cin >> a;

cout << "Input the second part of string:\t";

cin >> b;

// Использывались неявные вызовы функций

// operator >>(cin, a) и operator >>(cin, b)

c = a + b + "\nThat's all !\n";

// Произошел вызов конструктора преобразования

// для строки "\nThat's all !\n"

cout << c; // Вывод результирующей строки

// Неявный вызов функции operator <<(cout, c)

c = a * b + "\nThat's all !\n";

cout << c;

}

Перегрузка инкремента (++) и декремента (--)

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

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

Предположим, мы хотим прибавить 1 к объекту с0 класса С. Когда компилятор встречает выражение с префиксным инкрементом ++c0, он генерирует вызов функции-элемента c0.operator++(), прототип которой должен иметь вид:

C& operator++ ();

Если префиксная форма инкремента реализуется как функция, которая не является функцией-членом класса, то, когда компилятор встречает выражение ++c0, он генерирует вызов функции operator++(c0). Прототип такой функции должен быть объявлен в классе С как дружественный:

friend C& operator++(C&);

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

C& operator++(int).

Нуль (0) в генерируемом вызове функции явялется чисто формальным значением, введенным для того, чтобы сделать список аргументов функции operator++, используемой для постфиксной формы инкремента, отличным от списка агументов функции operator++, используемых для префиксной формы инкремента. Фиктивный параметр int никогда не используется, а лишь служит признаком того, что функция вызывается для выполнения операции ++ или -- в постфиксном варианте.

Если постфиксная форма инкремента реализуется как функция, которая не является функцией-членом класса, то, когда компилятор встречает выражение c0++, он генерирует вызов функции operator++(c0, 0). Прототип такой функции должен быть объявлен в классе С как дружественный:

friend C& operator++(C&, int);

Опять формальный аргумент 0 используется компилятором только для того, чтобы список аргументов функции operator++, которая используется для постфиксной формы инкремента, отличался от списка аргументов функции operator++, используемой для префиксной формы инкремента.

Все рассмотренное в этом разделе по отношению к перегрузке операций инкремента в префиксной и постфиксной формах, применимо и к перегрузке операций декремента.

Следующая программа просто иллюстрирует возможности применения разных операций-функций для постфиксной и префиксной операций ++ и --.

#include <iostream.h>

class pair

{

int N;

double x;

friend pair& operator ++(pair&);

friend pair& operator ++(pair&, int);

public:

pair (int n, double xn)

{

N = n;

x = xn;

}

void display()

{

cout << "Координаты: N = " << N

<< "\tx = " << x << endl;

}

pair& operator --()

{

cout << "prefix --" << endl;

N /= 10; x /= 10;

return *this;

}

pair& operator --(int k)

{

cout << "postfix --" << endl;

N/=2; x /= 2.0;

return *this;

}

};

pair& operator ++(pair& P)

{

cout << "prefix ++" << endl;

P.N *= 10;

P.x *= 10;

return P;

}

pair& operator ++(pair& P, int k)

{

cout << "postfix ++" << endl;

P.N = P.N * 2+ k;

P.x = P.x * 2 + k;

return P;

}

void main ()

{

pair Z(10, 20.0);

Z.display();

++Z;

Z.display();

--Z;

Z.display();

Z++;

Z.display();

Z--;

Z.display();

}

Для демонстрации полной независимости смысла перегруженной операции от ее традиционного (стандартного) значения в операциях-функциях для префиксных операций ++ соответствуют увеличению в 10 раз, а -- - уменьшению в 10 раз. Для постфиксных операций ++ определено как увеличение в 2 раза, а -- как уменьшение в 2 раза. Попытки использовать в постфиксных операциях-функциях значение дополнительного параметра int k подтверждает его равенство 0.

Результаты выполнения программы:

Координаты: N =10 x = 20

prefix ++

Координаты: N = 100 x = 200

prefix --

Координаты: N = 10 x = 20

postfix ++

Координаты: N = 20 x = 40

postfix --

Координаты: N = 10 x = 20