ООП. Указатель this

Подробнее

Размер

800.32K

Добавлен

13.11.2020

Скачиваний

37

Добавил

Natallia

Предмет

Тип работы

Вуз

Преподаватель

Текстовая версия:

Указатель this

Проведем с изученным в прошлых параграфах классом Stock некоторые изменения. До сих пор каждая функция-член класса имела дело только с одним объектом: тем, который ее вызывал. Однако иногда методу может понадобиться иметь дело с двумя объектами и для этого необходимо обращаться к указателю по имени this. Давайте посмотрим, когда это может понадобиться.

Нашему классу Stock недостает аналитических возможностей. Например, если вы взглянете на вывод функции show(), то сможете сказать, какая из ваших долей обладает наибольшим пакетом акций, но программа не сможет дать ответ на этот вопрос, поскольку не имеет прямого доступа к total_val. Самый простой способ сообщить программе о хранимых данных — это предусмотреть методы, возвращающие эти данные. Обычно для этого применяется встроенный код, как в следующем примере:

Это определение делает total_val доступным в программе только для чтения. То есть метод total () можно использовать для получения этого значения, но класс не предоставляет метода для его переустановки. Другие методы, такие как buy(), sell () и update (), модифицируют total_val в качестве побочного эффекта от переустановки значений членов shares и share_val.

За счет добавления этой функции к объявлению класса можно позволить программе исследовать последовательности пакетов акций для поиска наиболее крупного из них. Однако можно воспользоваться другим подходом, в котором применяется указатель this. Подход заключается в определении функции-члена, которая будет просматривать два объекта Stock и возвращать ссылку на больший из них. Попытка реализовать эту идею вызывает некоторые интересные вопросы, которые мы сейчас рассмотрим.

Во-первых, как написать функцию, работающую с двумя объектами с целью их сравнения? Предположим, что ее решено назвать topval(). После этого вызов stock1.topval() обращается к данным объекта stock1, в то время как stock2.topval() — к данным объекта stock2. Если нужно, чтобы метод сравнивал два объекта, второй объект потребуется передать в виде аргумента. Для эффективности его можно передавать по ссылке. Это значит, метод topval () должен принимать аргумент типа const Stock &.

Во-вторых, как результат метода будет передаваться в вызывающую программу? Самый прямой путь — заставить метод возвращать ссылку на объект, который имеет большее значение totalval. Таким образом, метод сравнения двух объектов будет иметь следующий прототип:

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

Предположим, что вы хотите сравнить два объекта Stock — stock1 и stock2 — и присвоить объекту top тот из них, который имеет большее значение total_val. Для этого можно воспользоваться любым из следующих двух операторов:

Первая форма обращается к stock1 неявно, a к stock2 — явно, в то время как вторая — наоборот (см. рис.). В любом случае метод сравнивает два объекта и возвращает ссылку на тот, который имеет большее значение total_val.

В действительности такая нотация несколько запутывает. Было бы понятнее, если бы удалось каким-то образом использовать операцию > для сравнения двух объектов. Это можно сделать с помощью перегрузки операций, которая будет обсуждаться позже.

Между тем, пока рассмотрим реализацию topval (). Она порождает небольшую проблему. Вот часть реализации, иллюстрирующая ее:

Здесь s.total_val — это суммарное значение объекта, переданное в виде аргумента, a total_val — суммарное значение объекта, которому сообщение передается. Если s.total_val больше total_val, то функция возвращает s. В противном случае она возвратит объект, использованный для вызова метода. В терминологии ООП — это объект, которому передано сообщение topval. Существует одна проблема: на чем вызывать объект? Если сделать вызов stock1.topval(stock2), то s — это ссылка на stock2 (т.е. псевдоним для stock2), но псевдонима для stock1 не существует.

Решение этой проблемы, которое предлагает C++, заключается в применении специального указателя this. Он указывает на объект, который использован для вызова функции-члена. (Обычно this передается методу в виде скрытого аргумента.) Таким образом, вызов stock1.topval(stock2) устанавливает значение this равным адресу объекта stock1 и делает его доступным методу topval(). Аналогичным образом, вызов функции stock2.topval(stock1) устанавливает значение this равным адресу объекта stock2. Вообще все методы класса получают указатель this, равный адресу объекта, который вызвал метод. Фактически total_val внутри total() является сокращенной нотацией this->total_val. Вспомните, что операция -> (стрелка) используется для доступа к членам структуры через указатель на нее. То же самое верно и для членов класса. Обратите внимание на рис.

Замечание. Каждая функция-член, включая конструкторы и деструкторы, имеет указатель this. Специфическим свойством this является то, что он указывает на вызывающий объект. Если метод нуждается в получении ссылки на вызвавший объект в целом, он может использовать и выражение *this. Применение квалификатора const после скобок с аргументами заставляет трактовать this как указатель на const; в этом случае вы не можете использовать this для изменения значений объекта.

Однако то, что необходимо вернуть из метода — это не this, поскольку this представляет собой адрес объекта. Вам нужно вернуть сам объект, а это обозначается выражением *this. Вспомните, что применение операции разыменования * к указателю дает значение, на которое он указывает.

Теперь можно завершить определение метода, используя *this в качестве псевдонима вызвавшего объекта:

Тот факт, что возвращаемое значение представляет собой ссылку, означает, что возвращаемый объект является тем же самым объектом, который вызвал данный метод, а не копией, переданной механизмом возврата. В примере 1.1 приведен новый заголовочный файл.

Пример 1.1 (stock20.h).

В примере 1.2 показан измененный файл с методами класса. Он включает в себя новый метод topval().

Пример 1.2 (stocks20.cpp).

Массив объектов

Для того чтобы проверить работу указателя this, лучше всего использовать в программе массив объектов. Часто, как и в примере со Stock, требуется создавать несколько объектов одного класса. Массив объектов объявляется таким же способом, как и массивы любых стандартных типов:

Вспомните, что программа всегда вызывает конструктор по умолчанию, когда создает объекты класса без явной инициализации. Такое объявление требует: либо отсутствия у класса явно определенных конструкторов (при этом используются неявные, ничего не делающие конструкторы), либо, как и в представленном случае — чтобы был явно определен конструктор по умолчанию. Каждый элемент — mystuff[0], mystuff[1] и т.д. — является объектом класса Stock, а потому может применяться с методами Stock:

Для инициализации элементов массива можно использовать конструктор. В этом случае необходимо вызывать конструктор для каждого индивидуального элемента:

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

В коде элементы stocks[0] и stocks[2] инициализируются с помощью конструктора Stock(const string & со, long n, double pr), a stock[1] — посредством конструктора Stock(). Поскольку такое объявление инициализирует массив только частично, оставшиеся семь членов инициализируются конструктором по умолчанию.

В примере 1.3 эти принципы реализованы в программе, которая инициализирует четыре элемента массива, отображает их содержимое и проверяет элементы в поисках того, который имеет наибольшее значение total_val. Поскольку метод total() сравнивает только два объекта за раз, для просмотра всего массива в программе используется цикл for. Для отслеживания элемента с наибольшим значением total_val применяется указатель на Stock.

Пример 1.3 (usestock2. срр).

Вывод программы:

Область видимости класса

Вспомним, что существует глобальная (на уровне файла) и локальная (на уровне блока) область видимости. Переменную с глобальной областью видимости можно использовать повсюду в файле, в котором она определена, в то время как переменная с локальной областью видимости является локальной по отношению к блоку, содержащему ее определение. Имена функций также могут иметь глобальную область видимости, но никогда — локальную. Классы C++ вводят новую разновидность области видимости — область видимости класса.

Область видимости класса применима к именам, определенным в классе, таким как имена данных-членов и функций-членов класса. Сущности, имеющие область видимости класса, известны внутри класса, но не известны за его пределами. Таким образом, одни и те же имена членов класса можно без конфликтов использовать в разных классах. Например, член shares класса Stock отличается от члена shares класса JobRide. Кроме того, область видимости класса означает, что вы не можете непосредственно обращаться к членам класса из внешнего мира. Это правило действует даже для открытых функций-членов. То есть для вызова открытой функции-члена должен использоваться объект:

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

Другими словами, в пределах объявления класса или определения функции-члена можно использовать неуточненные (короткие) имена членов, как в ситуации, когда sell() вызывает функцию-член set_tot(). Имя конструктора распознается при вызове потому, что оно совпадает с именем класса. В противном случае должна применяться прямая операция членства – точка (.), косвенная операция членства – стрелка (->) или операция разрешения контекста (::). В следующем фрагменте кода иллюстрируется получение доступа к идентификаторам с областью видимости класса:

Константы с областью видимости класса

Иногда хорошо бы иметь символические константы с областью видимости класса. Например, объявление класса может использовать литерал 12 для указания размера массива. Поскольку одна и та же константа применяется для всех объектов, было бы неплохо создать единственную константу, разделяемую всеми объектами. На первый взгляд, может показаться, что решается это следующим образом:

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

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

Обратите внимание, что такое объявление перечисления не создает переменную-член класса. То есть каждый индивидуальный объект не содержит его в себе. Вместо этого Months становится просто символическим именем, которое компилятор заменяет числом 12, когда встречает его в коде внутри области видимости класса.

Поскольку класс Bakery использует перечисление просто для создания символической константы, без намерения создавать переменные типа перечисления, то нет необходимости предоставлять дескриптор перечисления. Между прочим, во многих реализациях класс ios_base делает нечто подобное в своем разделе public; так объявлены идентификаторы вроде ios_base::fixed. Здесь fixed — обычно перечисление, определенное в классе ios_base.

В C++ имеется и второй способ определения константы в классе — с использованием ключевого слова static:

Это создает одиночную константу па имени Months, хранящуюся вместе с остальными статическими переменными, а не в каждом объекте. Это значит, что существует только одна константа Months, которая разделяется между всеми объектами Stock.