Значения формальных параметров по умолчанию

Заголовочные файлы

В Си имеется общая технология, которая касается как организации модульных программ, так и библиотек. Любой модуль, который претендует на дальнейшее использование через обращение к собственным внешним переменным и вызов собственных внешних функций, должен иметь некоторое описание своего интерфейса. Оно заключается в составлении заголовочного файла (файла с расширением - ".h"), который используется другими модулями. Заголовочный файл должен включать в себя:

  • определение используемых типов данных в формальных параметрах и результатах функций с использованием оператора typedef;
  • объявления внешних переменных и функций модуля, к которым возможно обращение.

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

#include <alloc.h> - заголовочный файл из системного каталога;
#include "myhead.h" - заголовочный файл из текущего (явно указанного) каталога.

Процесс подготовки библиотеки включает в себя следующие шаги:

  • создание заголовочного файла, содержащего определения используемых типов данных и объявления внешних функций и переменных библиотеки;
  • создание модуля, включающего определение функций и переменных библиотеки и трансляция его в объектный модуль;
  • включение объектного модуля в библиотеку.

Прототипы функций

При обращении к функции, формальные параметры заменяются фактическими, причем соблюдается строгое соответствие параметров по типам. В отличие от своего предшественника - языка Си, Си++ не предусматривает автоматического преобразования в тех случаях, когда фактические параметры не совпадают по типам с соответствующими им формальными параметрами. Говорят, что язык Си++ обеспечивает “строгий контроль типов”. В связи с этой особенностью языка Си++ проверка соответствия типов формальных и фактических параметров выполняется на этапе компиляции.

Строгое согласование по типам между формальными и фактическими параметрами требует, чтобы в модуле до первого обращения к функции было помещено либо ее определение, либо ее описание (прототип), содержащее сведения о ее типе (о типе результата, т.е. возвращаемого значения) и о типах всех параметров. Именно наличие такого прототипа либо полного определения позволяет компилятору выполнять контроль соответствия типов параметров. Прототип (описание) функции может внешне почти полностью совпадать с заголовком ее определения:

тип_функции имя_функции (спецификация_формальных_параметров);

Основное различие - точка с запятой в конце описания (прототипа). Второе отличие - необязательность имен формальных параметров в прототипе даже тогда, когда они есть в заголовке определения функции.

Перегрузка функций

Цель перегрузки функций состоит в том, чтобы функция с одним именем по-разному выполнялась и возвращала разные значения при обращении к ней с разными по типам и количеству фактическими параметрами. Например, может потребоваться функция, возвращающая максимальное из значений элементов одномерного массива, передаваемого ей в качестве параметра. Массивы, использованные как фактические параметры, могут содержать элементы разных типов, но пользователь функции не должен беспокоиться о типе результата. Функция всегда должна возвращать значение того же типа, что и тип массива - фактического параметра.

Для обеспечения перегрузки функций необходимо для каждого имени определить, сколько разных функций связано с ним, т.е. сколько вариантов сигнатур допустимы при обращении к ним. Предположим, что функция выбора максимального значения элемента из массива должна работать для массивов типа int, long, float, double. В этом случае придется написать четыре разных варианта функции с одним и тем же именем.

Распознавание перегруженных функций при вызове выполняется по их сигнатурам. Перегруженные функции поэтому должны иметь одинаковые имена, но спецификации их параметров должны различаться по количеству и (или) по типам, и (или) по расположению.

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

Значения формальных параметров по умолчанию

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

тип имя_параметра
тип имя_параметра = умалчиваемое_значение

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

Ссылки

В языке Си++ ссылка определена как другое имя уже существующего объекта. Основные достоинства ссылок проявляются при работе с функциями, однако ссылки могут использоваться и безотносительно к функциям. Для определения ссылки используется символ &, если он употребляется в таком контексте:

type & имя_ссылки инициализатор;

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

type & имя_ссылки = выражение;

или

type & имя_ссылки (выражение);

Раз ссылка есть “другое имя уже существующего объекта”, то в качестве инициализирующего выражения должно выступать имеющее значение леводопустимое выражение (lvalue), т.е. имя некоторого объекта, имеющего место в памяти. Значением ссылки после определения с инициализацией становиться адрес этого объекта. Примеры определений ссылок:

int L = 777; // Определена и инициализирована переменная Lint & RL = L; // Значением ссылки RL является адрес переменной Lint & 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, rpd == a, ra == a[0].

Применив к ссылке операцию получения адреса &, определим не адрес ссылки, а адрес того объекта, которым инициализирована ссылка. Можно рассмотреть и другие операции, но вывод один - каждая операция над ссылкой является операцией над тем объектом, с которым она связана.

Так как ссылки не есть настоящие объекты, то существуют ограничения при определении и использовании ссылок. Во-первых ссылка не может иметь тип void, т.е. определение void имя_ссылки запрещено. Ссылку нельзя создать с помощью операции new, т.е. для ссылки нельзя выделить новый участок памяти. Не определены ссылки на другие ссылки. Нет указателей на ссылки и невозможно создать массив ссылок.

Параметры-ссылки

В качестве основных причин включения ссылок в язык СИ++ указывают необходимость повысить эффективность обмена с функциями через аппарат параметров и целесообразность возможности использовать вызов функции в качестве леводопустимого значения. При использовании ссылки в качестве формального параметра обеспечивается доступ из тела функции к соответствующему фактическому параметру, т.е. к участку памяти, выделенному для фактического параметра. При этом параметр-ссылка обеспечивает те же самые возможности, что и параметр-указатель. Отличия состоят в том, что в теле функции для параметра-ссылки не нужно применять операцию разыменования *, а фактическим параметром должен быть не адрес (как для параметра-указателя), а обычная переменная.

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

В спецификации ссылки как формального параметра инициализация необязательна, однако она не запрещена. Сложность состоит в том, что объект, имя которого используется для инициализации параметра-ссылки, должен быть известен при определении функции. Иначе параметр должен быть ссылкой на константу, и с его помощью можно будет передавать значения только внутрь функции, а не из нее.

Подобно указателю на функцию определяется и ссылка на функцию:

тип_функции (&имя_ссылки) (спецификация_параметров) инициализирующее_выражение;

Здесь тип_функции - это тип возвращаемого функцией значения, спецификация_параметров определяет сигнатуру функций, допустимых для ссылки, инициализирующее_выражение - включает имя уже известной функции, имеющей тот же тип и ту же сигнатуру, что и определяемая ссылка. Например,

int infunc(float,int); // Прототип функцииint (& iref) (float, int) = infunc; // Определение ссылки

iref - ссылка на функцию, возвращающую значение типа int и имеющую два параметра с типами float и int. Напомним, что использование имени функции без скобок (и без параметров) воспринимается как адрес функции.

Ссылка на функцию обладает всеми правами основного имени функции, т.е. является его синонимом (псевдонимом). Изменить значение ссылки на функцию невозможно, поэтому указатели на функции имеют гораздо большую сферу применения, чем ссылки.

Объявления переменных

Переменные (объекты) следует определять по месту их использования. Например:

for(int i=0; i<10; ++i){/*...*/}

Встраиваемые функции

Использование функций, наряду с сокращением размера памяти, занимаемой кодом, увеличивает время выполнения программы. Так как для выполнения функций должны быть сгенерированны:

  • команды переходов;
  • команды, сохраняющие значения регистров процессора;
  • команды, помещающие и извлекающие из стека аргументы;
  • команды, восстанавливающие значения регистров после выполнения;
  • команды перехода из функции обратно в программу.

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

inline тип имя_функции(список_параметров);

Однако, если в функции требуется вызов деструктора, подстановки не происходит.

Операции new и delete

Именованный объект является либо статическим, либо автоматическим. Статический объект размещается в памяти в момент запуска программы и существует там до ее завершения. Автоматический объект размещается в памяти всякий раз, когда управление попадает в блок, содержащий определение объекта, и существует только до тех пор, пока управление остается в этом блоке. Тем не менее, часто бывает удобно создать новый объект, который существует до тех пор, пока он не станет ненужным. В частности, бывает удобно создать объект, который можно использовать после возврата из функции, где он был создан. Подобные объекты создает операция new (и возвращает указатель), а операция delete используется для их уничтожения в дальнейшем. Про объекты, созданные операцией new, говорят, что они размещаются в свободной памяти.

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

Операция new может также создавать массивы объектов, например:

char* save_string(const char* p){ char* s = new char[strlen(p)+1]; strcpy(s,p); return s;}

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

int main(int argc, char* argv[]){ if (argc < 2) exit(1); char* p = save_string(arg[1]); delete[] p;}

Чтобы добиться этого, приходится под объект, размещаемый стандартной операцией new, отводить немного больше памяти, чем под статический (обычно, больше на одно слово). Простой оператор delete уничтожает отдельные объекты, а операция delete[] используется для уничтожения массивов.

Наши рекомендации