Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Итог_Пособие C++.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
2 Мб
Скачать
☆

3.4 Указатель this

В каждом методе класса (кроме статических методов, которые будут рассмотрены далее) доступен специальный неявный параметр this, который содержит адрес текущего объекта (то есть объекта, для которого был вызван этот метод). Указатель this неявно добавляется при работе с любым полем − например, re и this->re в теле методов класса complex_t будут означать одно и то же.

Рассмотрим пример, в котором используется указатель this. Пусть требуется в классе complex_t реализовать метод plus1, который прибавляет единицу к действительной части числа и при этом возвращает ссылку на изменённое значение. Такую функцию можно будет использовать как часть выражения. Возвращать ссылку более эффективно, чем новое значение, так как не тратится время на создание копии.

struct complex_t {

...

complex_t& plus1() {

re++;

return *this;

}

};

int main() { // демонстрация структуры complex_t

complex_t a(2, 3), c;

c = a.plus1();

a.show(); cout << endl; c.show();

system("pause"); return 0;

}

В данном примере метод plus1 написан целиком внутри тела класса complex_t − для небольших методов это допустимо.

3.5 Перегрузка операций

При разработке классов можно переопределять стандартные операции. В этом случае для каждой операции пишут специальный метод-оператор, который называется operator операция. Например, вместо метода plus1 в предыдущем примере можно было бы переопределить операцию '++', реализовав метод operator++. Переопределение операций стоит использовать с осторожностью, чтобы не потерять общепринятый смысл знаков операций.

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

В каждом классе уже имеется операция = (операция присваивания), которая выполняет простое копирование всех полей одного объекта в другой. Если требуется при присваивании выполнить более сложные действия (обычно они требуются при работе с динамической памятью), её можно переопределить. Другие операции переопределяются, если в этом есть необходимость.

Напомним, что все операции в C++ делятся на унарные, бинарные и тернарные. Унарные операции обычно переопределяются как методы без параметров, бинарные требуют одного параметра (при этом в качестве одного из параметров предполагается текущий объект). Можно также переопределять операции в виде отдельных функций (а не методов класса) − тогда бинарная функция будет иметь один параметр, а бинарная − два. Поясним сказанное примерами.

Определим постфиксную и префиксную формы операции ++.

struct complex_t {

...

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

complex_t& operator ++() {

re++;

return *this;

}

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

complex_t operator ++(int) {

complex_t tmp(*this);

re++;

return tmp;

}

...

};

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

Реализация префиксной формы операции ++ практически совпадает с реализацией функции plus1. Поясним код для постфиксного варианта. Нам нужно, чтобы в выражении наподобие c = a++; в объект c записалось старое значение объекта a, а не новое. Для этого в операторной функции мы сохраняем старое значение во временной переменной tmp, которую и возвращаем в качестве результата. Заметим, что будет ошибкой возвращать ссылку, поскольку локальная переменная tmp уничтожается при выходе из функции.

Приведём пример перегрузки операции вычитания.

struct complex_t {

...

complex_t operator - (const complex_t &other) const {

return complex_t(re - other.re, im - other.im);

}

...

};

int main() { // демонстрация структуры complex_t

complex_t a(3, 4), b(2, 1);

complex_t c = a - b;

c.show();

system("pause"); return 0;

}

Обратите внимание на два спецификатора const в объявлении функции operator -. Первый const относится к параметру other, второй − к неявному параметру this (то есть функция не будет изменять текущий объект).

В заключение этого раздела приведём ещё один пример класса (немного посложнее) – на этот реализуем класс массива с заданными границами индекса.

Класс - массив в стиле языка Pascal (с произвольной левой границей)

//

#include <iostream>

class pas_array{

// тип int легко заменить на любой другой

typedef int type_of_data;

int left, right; // левая (нижняя) и правая (верхняя) границы индекса

type_of_data *data; // указатель на начало массива

public:

pas_array(int left, int right) : // параметры - границы индекса

left(left), right(right),

data (new elem_type [right-left+1])

{}

pas_array& operator = (const pas_array &other) { // операция =

if (this != &other) {

if (right - left < other.right - other.left) {

delete[] data;

data = new type_of_data[right - left + 1];

}

left = other.left;

right = other.right;

for (int i = 0; i <= right - left; i++)

data[i] = other.data[i];

}

return *this;

}

pas_array(const pas_array &other) : // конструктор копирования

left(other.left), right(other.right) {

data = new type_of_data[right - left + 1];

for (int i = 0; i <= right - left; i++)

data[i] = other.data[i];

}

~pas_array() {

delete[] data;

}

// операция индексирования

type_of_data & operator [] (int index) {

return data[index - left];

}

};

int main() {

pas_array a(-3, 3);

for (int i = -3; i <= 3; i++)

a[i] = i;

pas_array b(a);

pas_array c(1, 2);

c = b;

for (int i = -3; i <= 3; i++)

std::cout << c[i] << " ";

}