И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 17
Текст из файла (страница 17)
Однако если хотябы одно из перегруженных имен не является именем подпрограммы, то имеет место конфликт имен, который рассматривается какошибка.Разрешение иметь одно имя для разных вариантов подпрограммысвязано с тем, что подпрограмма — это абстракция операции (функция) или действия с побочным эффектом (процедура), а операции(действия) могут иметь разные варианты реализации, оставаясь содержательно одной и той же сущностью. Более того, большинствостандартных операций и процедур в языках программирования ужеперегружено.
Например, операция сложения в любом языке с несколькими числовыми типами имеет несколько вариантов (по одномудля каждого типа). То же самое относится к стандартным процедурамввода-вывода (например, в языке Паскаль): для каждого простоготипа есть свой вариант процедуры write или read. Естественнораспространить перегрузку и на имена пользовательских процедурных абстракций.Таким образом, в случае перегруженных имен подпрограмм мыимеем дело не с разными одноименными сущностями, а с одной80сущностью (операцией или действием), имеющей несколько вариантов реализации.Такая ситуация (одна сущность — несколько форм сущности)называется полиморфизмом.
Языки программирования имеют несколько видов полиморфизма. Полиморфизм при перегрузке частоназывают статическим, поскольку выбор формы (варианта реализации) происходит статически, во время трансляции. Подробнееварианты полиморфизма обсуждаются в гл. 8 и 10.Перегрузка имен подпрограмм сводится к тому, что транслятордолжен связать вызов подпрограммы с вариантом ее реализации(объявлением). Какая информация требуется для того, чтобы этотвыбор был однозначным?В языках C++, C# и Java перегрузка имен функций (напомним,что термина «подпрограмма» в этих языках нет) происходит наоснове профиля параметров функции.
Профиль функции (называемый иногда сигнатурой) — это упорядоченный список типовформальных параметров из объявления функции. Имена параметрови тип возвращаемого значения не входят в профиль. Так как типыфактических параметров вызова известны компилятору, то вызовуфункции соответствует список типов фактических параметров (профиль вызова). Следовательно, для нахождения требуемого вариантареализации (объявления функции) нужно найти объявление с профилем, соответствующим профилю вызова. Самый простой вариантсоответствия — совпадение профилей функции и вызова.
Такимобразом, понятно, что разные объявления одноименной функциив одной области видимости должны иметь разные профили. Например:void f ();void f (int);void f(const char *);f () ; // вызов первой функцииf(0); // вызов второй функцииf ("overloading sample"); // вызов третьей функцииКонкретный механизм сопоставления профилей может бытьдостаточно сложным, так как точное совпадение — это частныйслучай. При поиске соответствия профилей необходимо учитыватьстандартные неявные преобразования, пользовательские преобразования (если они разрешены) и другие аспекты. Подробностиправил перегрузки рассматриваются в конкретных руководствах[2, 17, 28].Некоторые языки программирования (например, C# и C++) позволяют перегружать не только имена подпрограмм, но и некоторыестандартные операции, трактуя их как функции. Особенно богатыевозможности перегрузки стандартных операций предоставляютсяязыком C++.
Подробнее это рассматривается в гл. 7.816.3. Подпрограммные типы данныхКак уже говорилось в подразд. 5.1, имена функций в С и C++ рассматриваются как указатели. В результате можно рассматривать имена функций как константы некоторых указательных типов данных.В С и C++ такие типы можно назвать функциональными (а в другихязыках подпрограммными).Функциональные типы необходимы, например для передачифункций как параметров других функций.Приведем пример переменной функционального типа:double (*pFunc)pFunc = cos;(double);Такое описание означает, что pFunc — это указатель на функциюот одного параметра типа double, возвращающую значение тожетипа double. Далее переменной pFunc присваивается значениеадреса функции cos (косинус).
Указатели функционального типанеобязательно разыменовывать. Вызывает функцию, присвоеннуюpFunc, следующий оператор:double х = pFunc(1.57);Опишем заголовок функции, вычисляющей определенный интеграл от функции F на интервале [А, В] с точностью ерs:double Integrate (doubledouble B, double eps);(*F)(double), doubleA,Вызов этой функции может иметь видdouble t = Integrate(sin, 0.0, 1.57, IE-6);Заметим, что несмотря на наличие функционального типа, функции нельзя считать полноценными объектами данных в языке C++(по сравнению с функциональными языками). Главная причинаэтого заключается в том, что множество значений функциональныхтипов состоит только из функциональных констант (имен функций).Нельзя динамически (в процессе выполнения программы) строитьи исполнять новые функции.В целом понятие указателя провоцирует появление ошибокв программах и является слишком низкоуровневым.
Поэтому языкJava не содержит понятия функционального типа, а следовательно,в нем нельзя передавать функции, как параметры. Однако объектноориентированный стиль программирования позволяет обойтись безтакой концепции.Язык C# тем не менее включает в себя понятие «делегаты», аналогичное функциональному типу, но не основанное непосредственнона концепции указателя и являющееся более надежным.82Делегаты обладают достоинствами функционального типа данных,и в то же время не имеют его недостатков. Внешне объявление делегата очень похоже на объявление функционального типа.
Объявлениеделегата начинается с ключевого слова delegate, за которым идетспецификация функции:delegate void Handler (Object о, EventArgs e);// Handler - имя делегатаДелегат Handler можно рассматривать как функциональный тип,значениями которого являются списки функций, каждая из которыхимеет указанный в объявлении делегата прототип. Экземпляр делегата объявляется как переменная такого типа:Handler hndl;К экземпляру делегата можно применять следующие операции:присваивание (=);добавление функции (+=);удаление функции(-=);вызов.Представим, что у нас есть несколько функций, удовлетворяющихобъявлению делегата Handler (значение ключевого слова staticобъясняется в гл. 7, сейчас его можно проигнорировать):••••static void MyHandlerl(Object о, EventArgs args){ ••• }static void MyHandler2(Object o, EventArgs args){ ... }Тогда следующее присваивание сотрет старое содержимоеэкземпляра делегата и создает в нем список из одной функцииMyHandlerl:hndl = MyHandlerl;Можно добавить к списку новую функцию MyHandler2:hndl = new Handler(MyHandler2);Теперь операция вызова делегата будет вызывать по очереди всефункции из списка, передавая каждой функции аргументы из вызова:EventArgs а = new EventArgs();hndl(this,a); // вызов MyHandlerl и MyHandler2hndl -= MyHandlerl;// удаление MyHandlerl из спискаhndl(this,a); // вызов MyHandler2Делегаты могут использоваться и для передачи функций какпараметров другим функциям (если описать параметр функции какэкземпляр делегата).Глава 7ОПРЕДЕЛЕНИЕ НОВЫХ ТИПОВ ДАННЫХ7.1.
Класс как тип данныхВ современных языках программирования типы данных рассматриваются как совокупность множества значений и множестваопераций над типом. Множество значений определяется структуройданных, а множество операций — это набор подпрограмм, имеющихформальные параметры соответствующего типа. Механизм классовотражает эту концепцию, позволяя объединить структуру данныхтипа и набор его операций в единое понятие, которое и называетсяклассом.Автор языка C++ Б.Страуструп вводит понятие класса как типаданных, определяемого пользователем [28].Класс расширяет понятие записи (структуры) в том смысле, чтоструктуры обладают только данными (полями), а класс может обладать и данными, и функциями.Класс представляет собой набор определений членов класса,которому присвоено имя.
Членами класса могут быть переменные(аналогично полям структуры), называемые членами-данными,функции, называемые методами доступа (или просто методами),и другие классы, называемые вложенными. Объявление класса имеетследующий вид:class имя_класса {список_объявлений_членов_класса}//в C++послеобъявлениякласса// поставить точку с запятойнужноОбъявление члена-данного идентично объявлению переменной,объявление метода (члена-функции) — это объявление функции.В C++ в объявлении класса можно указать как полное определениеметода, так и только прототип метода (т.е. заголовок без тела), а вC# и Java требуется полностью определить функцию. Объявлениевложенного класса — это обычное объявление класса.
Перед объявлением члена класса могут стоять модификаторы, смысл которыхбудем определять по мере введения модификаторов.Имя класса — это имя типа. Можно объявлять объекты (илиссылки на объекты) так же, как это делается с базисными типамиданных:84имя_класса имя_объектаКак уже отмечалось, объекты классов в C++ размещаются в любомвиде памяти (все зависит от контекста объявления), а в C# и Java —только в динамической памяти при выполнении операции new.Члены класса подразделяются на статические и нестатические.Начнем с рассмотрения статических членов.Приведем пример законченной программы на Java:public class Sample { // это пример на Javapublic static void main(String[] args){Hello.Greet ();}}class Hello {static final String msg = "Hello, world!";static void Greet (){System.out.println(msg);}}Если запустить эту программу, то она выдаст строку Hello,w o r l d ! .
Обратим внимание на класс Hello. Он содержит двачлена, перед обоими стоит модификатор static, превращающийих в статические (по умолчанию эти члены нестатические). Передчленом-данным msg стоит также модификатор final, который сообщает транслятору о том, что присвоенное переменной значениене может измениться (финальное). Другими словами, msg — этоконстанта.Статический член-данное — это переменная, которая существуетв единственном экземпляре для всех объектов класса. Статическийчлен размещается в памяти при загрузке программы (точнее призагрузке класса) независимо от размещения (или неразмещения)объектов класса.Статический метод — это функция, которую можно вызывать, неиспользуя объекты класса.Таким образом, статические члены — это члены, никак не зависящие от конкретных объектов класса.