Продолжаем определение класса ComplexType. Теперь объявим и определим функцию-член PrintVal, которая будет выводить значение чисел-объектов.
Прототип функции разместим в классе:
void PrintVal();
При определении функции используется квалифицированное имя:
void ComplexType::PrintVal() { cout << "(" << real << ", " << imag << "i)" << endl; cout << (int)CTcharVal << ", " << x << "…" << endl; }
Значения данных-членов объекта выводятся при выполнении выражения вызова функции PrintVal:
CDw1.PrintVal();
Объекты класса имеют свои собственные экземпляры данных-членов. Данные-члены имеют свои собственные специфические значения. Вместе с тем, все объекты используют единый набор функций-членов, с помощью которого можно получить доступ к значениям данных-членов во всех объектах класса.
Среди операторов функции-члена PrintVal() нет ни одного оператора, который позволял бы определить, какому объекту принадлежат данные-члены. И, тем не менее, вызов этой функции для каждого из определённых и различным образом проинициализированных объектов, в том числе и для безымянного объекта, который создаётся в результате непосредственного вызова конструктора:
ComplexType(0.0,0.0, 1).PrintVal(); ,
а также вызов функции для объекта, адресуемого указателем:
pCD->PrintVal();
сопровождается сообщением о значениях собственных данных-членов. Заметим, что "собственные" данные-члены объектов, как и те функции-члены класса, с которыми мы уже успели познакомиться, считаются нестатическими данными и функциями-членами класса. Существуют также и статические члены класса, к изучению свойств которых мы обратимся в недалёком будущем.
Автоматическое определение принадлежности данных-членов конкретному объекту характерно для любой нестатической функции-члена класса. Объекты являются "хозяевами" нестатических данных и потому каждая нестатическая функция-член класса должна уметь распознавать "хозяйские" данные.
Вряд ли алгоритм распознавания хозяина данных очень сложен. Здесь проблема заключается совсем в другом: этот алгоритм должен быть реализован практически для каждой нестатической функции-члена класса. Он используется везде, где производится обращение к данным-членам объектов, а это означает, что на программиста может быть возложена дополнительная обязанность по кодированию. Несколько обязательных строк для каждой функции-члена? Да никогда…
К счастью, C++ освобождает программистов от утомительной и однообразной работы кодирования стандартного алгоритма распознавания. В C++ вообще многое делается без их участия. Функции-члены определяются как обычные функции. Транслятор переопределяет эти функции, обеспечивая при этом стандартными средствами связь между объектами и их данными. Эта связь реализуется благодаря специальному преобразованию исходного кода программы. Мы опишем это преобразование, условно разделив его на два этапа.
На первом этапе каждая нестатическая функция-член преобразуется в функцию с уникальным именем и дополнительным параметром - константным указателем на объект класса. Затем преобразуются обращения к нестатическим данным-членам в операторах функции-члена. Они переопределяются с учётом нового параметра. В C++ при подобном преобразовании для обозначения дополнительного параметра-указателя (константного указателя) и постфиксного выражения с операциями обращения для обращения к нестатическим данным-членам используется одно и то же имя this. Вот как могла бы выглядеть функция-член PrintVal после её переопределения:
void ComplexType::ComplexType_PrintVal(ComplexType const *this) { cout << "(" << this->real << "," << this->imag << "i)" << endl; cout << int(this->CTcharVal) << "," << x << "…" << endl; }
На втором этапе преобразуются вызовы функций-членов. К списку значений параметров выражения вызова добавляется выражение, значением которого является адрес данного объекта. Это вполне корректное преобразование. Дело в том, что нестатические функции-члены всегда вызываются для конкретного объекта. И потому не составляет особого труда определить адрес объекта. Например, вызов функции-члена PrintVal() для объекта CDw1, который имеет вид
CDw1.PrintVal();
после преобразования принимает вид:
ComplexType_PrintVal(&CDw1);
А вызов функции-члена безымянного объекта, адресуемого указателем pCD
pCD->PrintVal();
преобразуется к виду
ComplexType_PrintVal(&(*pCD));
что эквивалентно следующему оператору:
ComplexType_PrintVal(pCD);
Первый (и в нашем случае единственный) параметр в вызове новой функции является адресом конкретного объекта.
В результате такого преобразования функция-член приобретает новое имя и дополнительный параметр типа указатель на объект со стандартным именем this и типом, а каждый вызов функции-члена приобретает форму вызова обычной функции.
Причина изменения имени для функций-членов класса очевидна. В разных классах могут быть объявлены одноименные функции-члены. В этих условиях обращение к функции-члену класса непосредственно по имени может вызвать конфликт имён: в одной области действия имени одним и тем же именем будут обозначаться различные объекты - одноименные функции-члены разных классов. Стандартное преобразование имён позволяет решить эту проблему.
Указатель this можно использовать в теле функции-члена без его дополнительного объявления. В частности, операторы функции ComplexType::PrintVal() могут быть переписаны с использованием указателя this:
void ComplexType::PrintVal() { cout << "(" << this->real << "," << this->imag << "i)" << endl; cout << int(this->CTcharVal) << "," << x << "…" << endl; }
Явное употребление this указателя не вызывает у транслятора никаких возражений, что свидетельствует об эквивалентности старого и нового вариантов функции. В этом случае указатель this считается не именем (имя вводится объявлением), а первичным выражением. Напомним, что имя, как и первичное выражение this являются частными случаями выражения.
В ряде случаев при написании программы оправдано явное использование указателя this. При этом выражение
this
представляет адрес объекта, а выражение
*this
представляет сам объект:
this->ВЫРАЖЕНИЕ (*this).ВЫРАЖЕНИЕ
(здесь нетерминальный символ ВЫРАЖЕНИЕ обозначает член класса). Эти выражения обеспечивают доступ к членам уникального объекта, представленного указателем this с целью изменения значения данного, входящего в этот объект или вызова функции-члена.
Следует помнить о том, что this указатель является константным указателем. Это означает, что непосредственное изменение его значение (перенастройка указателя, например, this++) недопустимо. Указатель this с самого начала настраивается на определённый объект.
При описании this указателя мы не случайно подчёркивали, что этот указатель используется только для нестатических функций-членов. Использование этого указателя в статических функциях-членах класса (о них речь впереди) не имеет смысла. Дело в том, что эти функции в принципе не имеют доступа к нестатическим данным-членам класса.
В объявлении нестатической функции-члена this указателю можно задавать дополнительные свойства. В частности, возможно объявление константного this указателя на константу. Синтаксис языка C++ позволяет сделать это. Среди БНФ, посвящённых синтаксису описателей, есть и такая форма:
Описатель ::= Описатель (СписокОбъявленийПараметров) [СписокCVОписателей] ::= ***** CVОписатель ::= const ::= *****
Так что небольшая модификация функции-члена PrintVal, связанная с добавлением cvОписателя const:
void PrintVal() const;
в прототипе и
void ComplexType::PrintVal() const { ::::: }
в определении функции обеспечивает относительную защиту данных от возможной модификации.
CVОписатель const в заголовке функции заставляет транслятор воспринимать операторы, которые содержат в качестве леводопустимых выражений имена данных-членов, возможно, в сочетании с this указателем, как ошибочные. Например, следующие операторы в этом случае оказываются недопустимы.
this->CTcharVal = 125; real = imag*25; imag++;
cvОписатель const в заголовке функции не допускает непосредственной модификации значений принадлежащих объекту данных.
Заметим также, что this указатель включается также в виде дополнительного параметра в список параметров конструктора. И в этом нет ничего удивительного, поскольку его значением является всего лишь область памяти, занимаемая объектом.
Назад | Содержание | Вперед