Лекции_ООП_ИС. Курс лекций по объектноориентированному программированию
Скачать 0.58 Mb.
|
2. Расширение языка С. ( 3 час.)Прототипы функций. Перегрузка функций. Значения формальных параметров по умолчанию. Ссылки и параметры-ссылки. Объявления переменных. Встраиваемые функции. Операции new и delete Прототипы функций. При обращении к функции, формальные параметры заменяются фактическими, причем соблюдается строгое соответствие параметров по типам. В отличие от своего предшественника - языка Си Си++ не предусматривает автоматического преобразования в тех случаях, когда фактические параметры не совпадают по типам с соответствующими им формальными параметрами. Говорят, что язык Си++ обеспечивает «строгий контроль типов». В связи с этой особенностью языка Си++ проверка соответствия типов формальных и фактических параметров выполняется на этапе компиляции. Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы в модуле до первого обращения к функции было помещено либо ее определение, либо ее описание (прототип), содержащее сведения о ее типе (о типе результата, т.е. возвращаемого значения) и о типах всех параметров. Именно наличие такого прототипа либо полного определения позволяет компилятору выполнять контроль соответствия типов параметров. Прототип (описание) функции может внешне почти полностью совпадать с заголовком ее определения: тип_функции имя_функции (спецификация_формальных_параметров); Основное различие - точка с запятой в конце описания (прототипа). Второе отличие - необязательность имен формальных параметров в прототипе даже тогда, когда они есть в заголовке определения функции. Перегрузка функций. Цель перегрузки функций состоит в том, чтобы функция с одним именем по-разному выполнялась и возвращала разные значения при обращении к ней с разными по типам и количеству фактическими параметрами. Например, может потребоваться функция, возвращающая максимальное значение элементов одномерного массива, передаваемого ей в качестве параметра. Массивы, использованные как фактические параметры, могут содержать элементы разных типов, но пользователь функции не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива - фактического параметра. Для обеспечения перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов сигнатур допустимы при обращении к ним. Предположим, что функция выбора максимального значения элемента из массива должна работать для массивов типа int, long, float, double. В этом случае придется написать четыре разных варианта функции с одним и тем же именем. Распознавание перегруженных функций при вызове выполняется по их сигнатурам. Поэтому перегруженные функции должны иметь одинаковые имена, но спецификации их параметров должны различаться по количеству и (или) по типам, и (или) по расположению. При использовании перегруженных функций нужно с осторожностью задавать начальные значения их параметров. Значения формальных параметров по умолчанию. Спецификация формальных параметров - это либо пусто, либо void, либо список спецификаций отдельных параметров, в конце которого может быть поставлено многоточие. Спецификация каждого параметра в определении функции имеет вид: тип имя_параметра тип имя_параметра = умалчиваемое_значение Как следует из формата, для параметра может быть задано (а может отсутствовать) умалчиваемое значение. Это значение используется в том случае, если при обращении к функции соответствующий параметр опущен. При задании начальных (умалчиваемых) значений должно соблюдаться следующее соглашение. Если параметр имеет умалчиваемое значение, то все параметры, специфицированные справа от него, также должны иметь начальные значения. Ссылки и параметры-ссылки. В языке Си++ ссылка определена как другое имя уже соответствующего объекта. Основные достоинства ссылок проявляются при работе с функциями, однако ссылки могут использоваться и безотносительно к функциям. Для определения ссылки используется символ *, если он употребляется в таком контексте: type &имя_ссылки = инициализатор; В соответствии с синтаксисом инициализатора, наличие которого обязательно, определение ссылки может быть таким: type &имя_ссылки = выражение; или type &имя_ссылки (выражение); Раз ссылка есть «другое имя уже существующего объекта», то в качестве инициализирующего выражения должно выступать имеющее значение леводопустимое выражение, т.е. имя некоторого объекта, имеющего место в памяти. Значением ссылки после определения с инициализацией становиться адрес этого объекта. Примеры определений ссылок: int L = 777; // Определена и инициализирована переменная L int &RL = L; // Значением ссылки RL является адрес переменной L int &RI(0); // Опасная инициализация - значением ссылки RI // становится адрес объекта, в котором // временно размещено нулевое целое значение В определении ссылки символ «&» не является частью типа, т.е. RL или RI имеют тип int и именно так должны восприниматься в программе. Итак, имя_ссылки определяет местоположение в памяти инициализирующего выражения, то есть значением ссылки является адрес объекта, связанного с инициализирующим выражением. Функционально ссылка ведет себя подобно обычной переменной, того же, что и ссылка, типа. Для доступа к содержимому участка памяти, на который «смотрит» ссылка, нет необходимости явно выполнять разыменование, как это нужно для указателя. Если рассматривать переменную как пару «имя_переменной - значение_переменной», то инициализированная этой переменной ссылка может быть представлена парой «имя_ссылки - значение_переменной». Из этого становится понятной необходимость инициализации ссылок при их определении. Тут ссылки схожи с константами языка Си++. Раз ссылка есть имя, связанное со значением (объектом), уже размещенным в памяти, то, определяя ссылку, необходимо с помощью начального значения определить тот объект (тот участок памяти), на который указывает ссылка. После определения с инициализацией имя_ссылки становится еще одним именем (синонимом, псевдонимом, алиасом) уже существующего объекта. Таким образом для нашего примера оператор RL -= 77; уменьшает на 77 значение переменной L. Связав ссылку (RL) с переменной (L), мы получаем две возможности изменять значение переменной: RL = 88; или L = 88; Здесь есть аналогия с указателями, однако отсутствует необходимость в явном разыменовании, что обязательно при обращении к значению переменной через указатель. Ссылки не есть полноправные объекты, подобные переменным, либо указателям. После инициализации значение ссылки изменить нельзя, она всегда (смотрит) на тот участок памяти (на тот объект), с которым она связана инициализацией. Ни одна из операций не действует на ссылку, а относится к тому объекту, с которым она связана. Можно считать, что это основное свойство ссылки. Таким образом, ссылка полностью аналогична исходному имени объекта. Конкретизируем и поясним сказанное. Пусть определены: double a[] = { 10.0, 20.0, 30.0, 40.0 } ; // a - массив double *pa = a; // pa - указатель на массив double &ra = a[0] ; // ra - ссылка на первый элемент массива double *&rpd = a ; // Ссылка на указатель (на имя массива) Для ссылок и указателей из нашего примера соблюдаются равенства: pa == &ra, *pa == ra == a[0], rpd == a. Применив к ссылке операцию получения адреса &, определим не адрес ссылки, а адрес того объекта, которым инициализирована ссылка. Можно рассмотреть и другие операции, но вывод один - каждая операция над ссылкой является операцией над тем объектом, с которым она связана. Так как ссылки не есть настоящие объекты, то существуют ограничения при определении и использовании ссылок. Во-первых, ссылка не может иметь тип void, т.е. определение void имя_ссылки запрещено. Ссылку нельзя создать с помощью операции new, т.е. для ссылки нельзя выделить новый участок памяти. Не определены ссылки на другие ссылки. Нет указателей на ссылки и невозможно создать массив ссылок. Параметры ссылки. В качестве основных причин включения ссылок в язык СИ++ указывают необходимость повысить эффективность обмена с функциями через аппарат параметров и целесообразность возможности использовать вызов функции в качестве леводопустимого значения. При использовании ссылки в качестве формального параметра обеспечивается доступ из тела функции к соответствующему фактическому параметру, т.е. к участку памяти, выделенному для фактического параметра. При этом параметр - ссылка обеспечивает те же самые возможности, что и параметр - указатель. Отличия состоят в том, что в теле функции для параметра - ссылки не нужно применять операцию разыменования *, а фактическим параметром должен быть не адрес (как для параметра - указателя), а обычная переменная. Ссылки обеспечивают доступ из тела функции к фактическим параметрам, в качестве которых используются обычные переменные, определенные в вызывающей программе. В спецификации ссылки как формального параметра инициализация необязательна, однако она не запрещена. Сложность состоит в том, что объект, имя которого используется для инициализации параметра - ссылки, должен быть известен при определении функции. Иначе параметр должен быть ссылкой на константу, и с его помощью можно будет передавать значения только внутрь функции, а не из нее. Подобно указателю на функцию определяется и ссылка на функцию: тип_функции (&имя_ссылки)(спецификация_параметров) = инициализирующее_выражение; Здесь тип_функции - это тип возвращаемого функцией значения, спецификация_параметров определяет сигнатуру функций, допустимых для ссылки, инициализирующее_выражение - включает имя уже известной функции, имеющей тот же тип и ту же сигнатуру, что и определяемая ссылка. Например, int infunc(float, int); // Прототип функции int (&iref)(float, int) = infunc; // Определение ссылки iref - ссылка на функцию, возвращающую значение типа int и имеющую два параметра с типами float и int. Напомним, что использование имени функции без скобок (и без параметров) воспринимается как адрес функции. Ссылка на функцию обладает всеми правами основного имени функции, т.е. является его синонимом (псевдонимом). Изменить значение ссылки на функцию невозможно, поэтому указатели на функции имеют гораздо большую сферу применения, чем ссылки. |