Предварительная инициализация параметров функции. Определение и вызов функций

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

При такой структуре исходной программы функции, находящиеся в разных исходных файлах могут использовать глобальные внешние переменные. Все функции в языке Си по определению внешние и всегда доступны из любых файлов. Например, если программа состоит из двух исходных файлов, как показано на рис.2., то функция main может вызывать любую из трех функций fun1, fun2, fun3, а каждая из этих функций может вызывать любую другую.

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

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

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

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

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

Если объект объявлен внутри блока, то он видим в этом блоке, и во всех внутренних блоках. Если объект объявлен на внешнем уровне, то он видим от точки его объявления до конца данного исходного файла.

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

Спецификатор класса памяти в объявлении переменной может быть auto, register, static или extern. Если класс памяти не указан, то он определяется по умолчанию из контекста объявления.

Объекты классов auto и register имеют локальное время жизни. Спецификаторы static и extern определяют объекты с глобальным временем жизни.

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

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

Переменная с классом памяти auto автоматически не инициализируется. Она должна быть проинициализирована явно при объявлении путем присвоения ей начального значения. Значение неинициализированной переменной с классом памяти auto считается неопределенным.

Спецификатор класса памяти register предписывает компилятору распределить память для переменной в регистре, если это представляется возможным. Использование регистровой памяти обычно приводит к сокращению времени доступа к переменной. Переменная, объявленная с классом памяти register, имеет ту же область видимости, что и переменная auto. Число регистров, которые можно использовать для значений переменных, ограничено возможностями компьютера, и в том случае, если компилятор не имеет в распоряжении свободных регистров, то переменной выделяется память как для класса auto. Класс памяти register может быть указан только для переменных с типом int или указателей с размером, равным размеру int.

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

Пример: /* объявления переменной i на внутреннем уровне с классом памяти static. */ /* исходный файл file1.c */ main() { ... } fun1() { static int i=0; ... } /* исходный файл file2.c */ fun2() { static int i=0; ... } fun3() { static int i=0; ... }

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

Переменные класса памяти static могут быть инициализированы константным выражением. Если явной инициализации нет, то такой переменной присваивается нулевое значение. При инициализации константным адресным выражением можно использовать адреса любых внешних объектов, кроме адресов объектов с классом памяти auto, так как адрес последних не является константой и изменяется при каждом входе в блок. Инициализация выполняется один раз при первом входе в блок.

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

Пример: /* объявления переменной i, являющейся именем внешнего массива длинных целых чисел, на локальном уровне */ /* исходный файл file1.c */ main() { ... } fun1() { extern long i; ... } /* исходный файл file2.c */ long i={0}; fun2() { ... } fun3() { ... }

Объявление переменной i как extern в приведенном примере делает ее видимой внутри функции fun1. Определение этой переменной находится в файле file2.c на глобальном уровне и должно быть только одно, в то время как объявлений с классом памяти extern может быть несколько.

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

Пример: main() { extern int st; ... } static int st={0}; fun1() { ... }

Объявление переменной со спецификатором extern информирует компилятор о том, что память для переменной выделять не требуется, так как это выполнено где-то в другом месте программы.

При объявлении переменных на глобальном уровне может быть использован спецификатор класса памяти static или extern, а так же можно объявлять переменные без указания класса памяти. Классы памяти auto и register для глобального объявления недопустимы.

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

1. Переменная объявлена с классом памяти static. Такая переменная может быть инициализирована явно константным выражением, или по умолчанию нулевым значением. То есть обявления static int i=0 и static int i эквивалентны, и в обоих случаях переменной i будет присвоено значение 0.

2. Переменная объявлена без указания класса памяти, но с явной инициализацией. Такой переменной по умолчанию присваивается класс памяти static. То есть объявления int i=1 и static int i=1 будут эквивалентны.

Переменная объявленная глобально видима в пределах остатка исходного файла, в котором она определена. Выше своего описания и в других исходных файлах эта переменная невидима (если только она не объявлена с классом extern).

Глобальная переменная может быть определена только один раз в пределах своей области видимости. В другом исходном файле может быть объявлена другая глобальная переменная с таким же именем и с классом памяти static, конфликта при этом не возникает, так как каждая из этих переменных будет видимой только в своем исходном файле.

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

В объявлениях с классом памяти extern не допускается инициализация, так как эти объявления ссылаются на уже существующие и определенные ранее переменные.

1.6.2. Объявления функций

Функции всегда определяются глобально. Они могут быть объявлены с классом памяти static или extern. Объявления функций на локальном и глобальном уровнях имеют одинаковый смысл.

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

1. Функция, объявленная как static, видима в пределах того файла, в котором она определена. Каждая функция может вызвать другую функцию с классом памяти static из своего исходного файла, но не может вызвать функцию определенную с классом static в другом исходном файле. Разные функции с классом памяти static имеющие одинаковые имена могут быть определены в разных исходных файлах, и это не ведет к конфликту.

2. Функция, объявленная с классом памяти extern, видима в пределах всех исходных файлов программы. Любая функция может вызывать функции с классом памяти extern.

3. Если в объявлении функции отсутствует спецификатор класса памяти, то по умолчанию принимается класс extern.

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

1.6.3. Время жизни и область видимости программных объектов

Время жизни переменной (глобальной или локальной) определяется по следующим правилам.

1. Переменная, объявленная глобально (т.е. вне всех блоков), существует на протяжении всего времени выполнения программы.

2. Локальные переменные (т.е. объявленные внутри блока) с классом памяти register или auto, имеют время жизни только на период выполнения того блока, в котором они объявлены. Если локальная переменная объявлена с классом памяти static или extern, то она имеет время жизни на период выполнения всей программы.

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

1. Переменная, объявленная или определенная глобально, видима от точки объявления или определения до конца исходного файла. Можно сделать переменную видимой и в других исходных файлах, для чего в этих файлах следует ее объявить с классом памяти extern.

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

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

4. Функции с классом памяти static видимы только в исходном файле, в котором они определены. Всякие другие функции видимы во всей программе.

Метки в функциях видимы на протяжении всей функции.

Имена формальных параметров, объявленные в списке параметров прототипа функции, видимы только от точки объявления параметра до конца объявления функции.

1.6.4. Инициализация глобальных и локальных переменных

При инициализации необходимо придерживаться следующих правил:

1. Объявления содержащие спецификатор класса памяти extern не могут содержать инициаторов.

2. Глобальные переменные всегда инициализируются, и если это не сделано явно, то они инициализируются нулевым значением.

3. Переменная с классом памяти static может быть инициализирована константным выражением. Инициализация для них выполняется один раз перед началом программы. Если явная инициализация отсутствует, то переменная инициализируется нулевым значением.

4. Инициализация переменных с классом памяти auto или register выполняется всякий раз при входе в блок, в котором они объявлены. Если инициализация переменных в объявлении отсутствует, то их начальное значение не определено.

5. Начальными значениями для глобальных переменных и для переменных с классом памяти static должны быть константные выражения. Адреса таких переменных являются константами и эти константы можно использовать для инициализации объявленных глобально указателей. Адреса переменных с классом памяти auto или register не являются константами и их нельзя использовать в инициаторах.

Пример: int global_var; int func(void) { int local_var; /* по умолчанию auto */ static int *local_ptr=&local_var; /* так неправильно */ static int *global_ptr=&global_var; /* а так правильно */ register int *reg_ptr=&local_var; /* и так правильно */ }

В приведенном примере глобальная переменная global_var имеет глобальное время жизни и постоянный адрес в памяти, и этот адрес можно использовать для инициализации статического указателя global_ptr. Локальная переменная local_var, имеющая класс памяти auto размещается в памяти только на время работы функции func, адрес этой переменной не является константой и не может быть использован для инициализации статической переменной local_ptr. Для инициализации локальной регистровой переменной reg_ptr можно использовать неконстантные выражения, и, в частности, адрес переменной local_ptr.

[

Инициализация

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

= <инициализатор >

Можно инициализировать переменные любого типа. Функции не инициализируются. Объявления, которые используют спецификацию класса памяти extern , не могут содержать инициализатор.

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

Переменная класса памяти static , объявленная как на внешнем, так и на внутреннем уровне, может быть инициализирована константным выражением не более одного раза в исходном файле. Если ее явная инициализация отсутствует, то компилятор языка Си инициализирует ее нулевым значением.

Инициализация переменных класса памяти auto и register выполняется каждый раз при входе в блок (за исключением входа в блок по оператору goto ), в котором они объявлены. Если инициализатор опущен в объявлении переменной класса памяти auto или register , то ее начальное значение не определено. Инициализация переменных составных типов (массив, структура, объединение), имеющих класс памяти auto , запрещена в СП MSC, но допускается в СП ТС даже для переменных, объявленных с модификатором const . Переменные составного типа, имеющие класс памяти static , могут быть инициализированы на внутреннем уровне.

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

Из книги C++ автора Хилл Мюррей

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

Из книги Справочное руководство по C++ автора Страустрап Бьярн

6.6 Присваивание и Инициализация Рассмотрим очень простой класс строк string:struct string (* char* p; int size; // размер вектора, на который указывает pstring(int sz) (* p = new char; *) ~string() (* delete p; *) *);Строка – это структура данных, состоящая из вектора сиволов и длины этого вектора. Вектор

Из книги Идиомы и стили С++ автора Makhmutov Albert

Из книги Системное программирование в среде Windows автора Харт Джонсон М

R.12.6 Инициализация Объект класса без конструкторов, без частных или защищенных членов, без виртуальных функций и без базовых классов можно инициализировать с помощью списка инициализаторов (§R.8.4.1). Объект класса с конструктором должен инициализироваться или иметь

Из книги Инфраструктуры открытых ключей автора Полянская Ольга Юрьевна

Из книги Язык программирования Си для персонального компьютера автора Бочков C. О.

Шаг 12 - Двухэтапная инициализация. Когда мы создаем нестековый экземпляр, то пишем такой код:CClass* cc = new CClass();Попробуем поразбираться. new - это глобальный оператор с определением:void* operator new (size_t bytes);Он получает от компилятора количество байт, необходимое для хранения объекта,

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Инициализация Winsock Winsock API поддерживается библиотекой DLL (WS2_32.DLL), для получения доступа к которой следует подключить к программе библиотеку WS_232.LIB. Эту DLL следует инициализировать с помощью нестандартной, специфической для Winsock функции WSAStartup, которая должна быть первой из

Из книги Язык Си - руководство для начинающих автора Прата Стивен

Инициализация На этапе инициализации в зависимости от входных параметров устанавливаются переменные состояния, необходимые для валидации пути сертификации . В переменных состояния сохраняются различные ограничения, учитываемые при валидации пути. Переменные

Из книги Программирование для Linux. Профессиональный подход автора Митчелл Марк

Инициализация Переменной в объявлении может быть присвоено начальное значение посредством инициализатора. Записи инициализатора в объявлении предшествует знак равенства=<инициализатор>Можно инициализировать переменные любого типа. Функции не инициализируются.

Из книги C++ для начинающих автора Липпман Стенли

Инициализация параметров int pthread_mutexattr_init(const pthread_mutexattr_t* attr);Функция инициализирует структуру атрибутов мьютекса, на которую указывает параметр attr. Тип данных pthread_mutexattr_t определен в файле (производный от типа sync_attr_t, который в свою очередь определен в файле

Из книги автора

Инициализация мьютекса int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr);Структура данных pthread_mutex_t определена в файле (производный тип от типа sync_t, который в свою очередь определен в файле ) и имеет следующий вид:struct _sync_t { /* Счетчик для рекурсивного

Из книги автора

Инициализация структуры Мы видели, как инициализируются переменные и массивы: int count = 0;static int fibo={0, 1, 1, 2, 3, 5, 8};Можно ли инициализировать и структурную переменную? Да, если структурная переменная будет внешней или статической. Здесь следует иметь в виду, что

Из книги автора

5.2.2. Инициализация семафоров Выделение и инициализация семафора - две разные операции. Чтобы проинициализировать семафор, вызовите функцию semctl(), задав второй аргумент равным нулю, а третий аргумент - равным константе SETALL. Четвертый аргумент должен иметь тип union semun, поле

Из книги автора

7.9.2. Инициализация и присваивание Вспомним, что имя массива без указания индекса элемента интерпретируется как адрес первого элемента. Аналогично имя функции без следующих за ним скобок интерпретируется как указатель на функцию. Например, при вычислении

Из книги автора

14.1. Инициализация класса Рассмотрим следующее определение класса:class Data {public:int ival;char *ptr;};Чтобы безопасно пользоваться объектом класса, необходимо правильно инициализировать его члены. Однако смысл этого действия для разных классов различен. Например, может ли ival

Из книги автора

14.6. Почленная инициализация A Инициализация одного объекта класса другим объектом того же класса, как, например:Account oldAcct(" Anna Livia Plurabelle");Account newAcct(oldAcct);называется почленной инициализацией по умолчанию. По умолчанию - потому, что она производится автоматически, независимо

Классификация указателей на функцию

Определение понятия указатель на функцию

Указатели на функцию

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

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

В языке Си существуют две разновидности указателей на функцию:

· указатель – выражение,

· указатель переменная.

38.9.3. Указатель – выражение

Как было отмечено выше, указателем выражение является выражение, значением которого является адрес функции. Приведем пример.

Пусть имеется прототип функции для вычисления квадрата числа типа double. Тогда выражение &sqr - это выражение – указатель. Здесь & - унарный оператор взятия адреса, который и предписывает компилятору вычислить адрес йункции.

38.9.4. Указатель – переменная

Указатель - переменная это переменная, предназначенная для хранения адреса функции. Рассмотрим формат определения такой переменной.

(* <α>) (<γ>)

· * - оператор разыменования,

α – идентификатор, объявленного указателя,

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

· γ – список типов параметров функции, на которую может быть установлен указатель.

Приведем пример объявления указателя – переменной.

double (*pf)(double);

Это объявление переменной. Имя переменной pf. значением этой переменной может служить адрес любой функции, которая принимает один параметр типа double и которая возращает значения типа double.

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

double sqr(double x)
{

double cube(double x)
{

return x * x * x;

Вернемся к объявлению указателя – переменной pf. Рассмотрим вопрос о назначении используемых в нем скобок. Здесь их две пары.



double (*pf)(double);

Вторая пара скобок – это оператор функция. Возникает вопрос: «Какую роль играет первая пара скобок?». С целью разобраться в этом вопросе на время удалим эту пару скобок. Тогда рассматриваемое выражение примет следующий вид.

double *pf(double);

Полученное выражение является объявлением функции, которая принимает один параметр типа double и возращает указатель типа double*. Для того чтобы убедиться в этом вспомним, что в синтаксис объявлений в языке Си заложен синтаксис выражений. Для определения назначения имени pf в рассматриваемом выражении следует рассмотреть подвыражение *pf(double). Здесь к имени pf применяются два оператора: * и () . Трактовка имени pf зависит от приоритетов этих операторов. Если обратиться к таблице приоритетов операторов, то можно убедиться в том, что оператор функция () имеет более высокий приоритет по сравнению с оператором разыменования *. Отсюда следует, что оператор () сильнее связывает имя pf по сравнению с оператором *. Итак, в новой редакции pf является именем функции, а не именем указателя.

Вернемся теперь к исходному объявленияю

double (*pf)(double);

Первые скобки в этом выражении слияют на порядок группирововки операторов и операндов. Теперь имя pf становится именем указателя.

Указатель – переменная может быть инициализирована во время ее объявления либо нулевым указателем, либо адресом функции того типа, на который может быть установлен указатель.

Пусть имеются две функции, имеющие следующие прототипы.

double sqr(double x);
double cube(double x);

Объявим три указателя на функцию:

double (*pf1)(double) = &sqr;
double (*pf2)(double)= cube;

double (*pf3)(double)= 0;

Синтаксис, с которым объявлены первые две переменные (pf1 и pf2), несколько отличаются. Синтаксис, с которым объявлена первая переменная (pf1) не вызывает сомнений в корректности. Справа от знака присваивания находится выражение, значением которого является адрес функции. Могут возникнуть сомнения в корректности второго объявления, где объявлена переменная pf2. В этом объявлении в качестве инициализатора используется имя функции, а не ее адрес. Однако этот код нормально компилируется. Дело в том, что языке Си всегда в том коде, где ожидается указатель на функцию, а встречается имя функции, выполняется автоматическое преобразование имени функции в указатель.

Замечание . Операция инициализации выполняется и при использовании казателя на функцию в качестве формального параметра функции.

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

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

Следующие строки демонстрируют пример объявления функции с инициализацией параметров. Для инициализации параметра ww используется функция XX.

int ZZ(int tt, int ww = XX(BigVal));

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

int ZZ(int tt, int = XX(BigVal));

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

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

Кроме того, в C++ действует еще одно ограничение, связанное с порядком инициализации параметров в пределах области видимости. Инициализация проводится непременно с самого последнего (самого правого) параметра в списке объявлений параметров. Инициализация параметров не допускает пропусков: инициализированные параметры не могут чередоваться с параметрами неинициализированными.

int MyF1 (int par1, int par2, int par3, int par4 = 10);

………………………………….

int MyF1 (int par1, int par2 = 20, int par3 = 20, int par4);

………………………………….

int MyF1 (int par1 = 100, int, int, int);

#include

int f(int, int=4);

int main(int argc, char* argv)

printf("%d\n", f(2)); //8

printf("%d\n", f(2,3)); //6

int f(int a, int b)

Функции с переменным числом параметров

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

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

Примерами функций с переменным числом параметров являются функции из библиотеки функций языка СИ, осуществляющие операции ввода-вывода информации (printf, scanf и т.п.). Функция printf() в библиотеке объявлена следующим образом:

int printf(const char* ...);

Это гарантирует, что при любом вызове printf() ей будет передан первый аргумент типа const char*. Содержание такой строки, называемой форматной, определяет, необходимы ли дополнительные аргументы при вызове. При наличии в строке формата метасимволов, начинающихся с символа %, функция ждет присутствия этих аргументов.

Программист может разрабатывать свои функции с переменным числом параметров. Для обеспечения удобного способа доступа к аргументам функции с переменным числом параметров имеются три макроопределения (макросы) va_start, va_arg, va_end, находящиеся в заголовочном файле stdarg.h. Эти макросы указывают на то, что функция, разработанная пользователем, имеет некоторое число обязательных аргументов, за которыми следует переменное число необязательных аргументов. Обязательные аргументы доступны через свои имена как при вызове обычной функции. Для извлечения необязательных аргументов используются макросы va_start, va_arg, va_end в следующем порядке.

Макрос va_start предназначен для установки аргумента arg_ptr на начало списка необязательных параметров и имеет вид функции с двумя параметрами:

void va_start(arg_ptr, prav_param);

Параметр prav_param должен быть последним обязательным параметром вызываемой функции, а указатель arg_prt должен быть объявлен с предопределением в списке переменных типа va_list в виде:

va_list arg_ptr;

Макрос va_start должен быть использован до первого использования макроса va_arg.

Макрокоманда va_arg обеспечивает доступ к текущему параметру вызываемой функции и тоже имеет вид функции с двумя параметрами

type_arg va_arg(arg_ptr,type);

Эта макрокоманда извлекает значение типа type по адресу, заданному указателем arg_ptr, увеличивает значение указателя arg_ptr на длину использованного параметра (длина type) и таким образом параметр arg_ptr будет указывать на следующий параметр вызываемой функции. Макрокоманда va_arg используется столько раз, сколько необходимо для извлечения всех параметров вызываемой функции.

Макрос va_end используется по окончании обработки всех параметров функции и устанавливает указатель списка необязательных параметров на ноль (NULL).

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

#include

#include

int sred_znach(int,...);

n=sred_znach(2,3,4,-1); /* вызов с четырьмя параметрами */

printf("n=%d\n",n);

n=sred_znach(5,6,7,8,9,-1); /* вызов с шестью параметрами */

printf("n=%d\n",n);

int sred_znach(int x,...)

int i=0, j=0, sum=0;

va_start(uk_arg,x); /* установка указателя uk_arg на */

/* первый необязятельный параметр */

if (x!=-1) sum=x; /* проверка на пустоту списка */

else return (0);

while ((i=va_arg(uk_arg,int))!=-1)

/* выборка очередного */

{ /* параметра и проверка */

sum+=i; /* на конец списка */

va_end(uk_arg); /* закрытие списка параметров */

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

#include

#include

int sred_znach2(int,...);

n=sred_znach2(3,2,3,4);

printf("n=%d\n",n);

int sred_znach2(int n,...)

int *pPointer = &n;

for (int i=n ; i; i--) Sum += *(++pPointer);

При создании программных продуктов необходимо уметь чётко и качественно оперировать всей необходимой терминологий. Вот, например, взять такое слово, как инициализация. Это что такое, вы знаете? Сомневаюсь, иначе бы вы не читали эту статью. Поэтому давайте рассмотрим, что подразумевают под этим термином, какие бывают виды, а также разберём несколько проблем, которые могут возникнуть при использовании электроники.

Инициализация - это что такое?

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

Примеры

Давайте рассмотрим, что представляет собой инициализация. Это что такое? Понять будет легче всего, используя несколько реальных примеров:

  1. Под инициализацией магнитного диска винчестера понимают его форматирование. Также может быть сюда отнесена и запись управляющей информацией (меток томов, описателей дорожек и подобные манипуляции).
  2. Под инициализацией программы понимают задание необходимых переменных или установку в нулевое значение программных переменных (счётчиков, адресов, переключателей, указателей) перед тем, как само приложение будет выполнено. Всё устанавливается согласно тому, что делается для этого типа ПО или содержится в виде указаний в самом файле.
  3. Инициализация для вывода на принтер данных подсистемы печати. Это определение, которое подразумевает следующее: на каком устройстве необходимо перенести данные на бумагу и провести учёт всех необходимостей. Так, следует определить формат печати, расширение, использовать ли цвет (если он есть) и другие параметры. Сначала на устройство подаётся управляющий сигнал и проводится сканирование его возможностей, чтобы произвести первоначальную настройку принтера и предложить пользователю вариант, который может быть осуществлён имеющейся техникой.

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

Из-за чего возникает в Sims-3

Самый распространённый вариант проблемы - это 0×0175dcbb. Этот номер используется для обозначения именно неполадок с инициализацией приложения. Она может возникнуть из-за того, что базовая игра конфликтует с дополнениями, модами и драйверами видеокарты, которые были установлены на компьютере. Причём вы не застрахованы даже при покупке лицензионной игры - если проблема есть, лицензия не приведёт к её автоматическому решению.

Что же делать, если ошибка 0×0175dcbb уже есть?

Итак, ошибка возникла. Сейчас разберёмся, как мы можем её убрать. Хотя рассматривать будем игру, но многие знания, написанные далее, могут пригодиться и при работе с серьёзными приложениями. Помните, что самая старая проблема - это архаические программные компоненты. В первую очередь нас интересуют драйвера видеокарт. Их можно скачать на сайтах производителей, что мы собственно и сделали. Также нелишним будет установить или обновить до последней версии NET Framework - желательно делать это с ресурса разработчика, коим является Microsoft.

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

Заключение

Что устанавливать на свой компьютер, решать только вам. Но впредь, прежде чем добавлять в стабильно работающий продукт контент, разработанный сторонними людьми, необходимо хорошо подумать и проявить значительную внимательность к качеству скачанных дополнений. Потому что может быть ошибка инициализации обновления, а возможность отката обычно не предусматривается. И тогда придётся удалять всю программу и устанавливать её заново. Если уж решено было так сделать, то необходимо позаботится о сохранности наработанных данных. Их копию нужно разместить где-то в безопасном и укромном месте, например, на рабочем столе или флеш-накопителе. Вот и всё, нами была детально рассмотрена инициализация. Это что такое, вы наверняка поняли. Ведь мы разобрали не только текстовую составляющую, но и рассмотрели несколько конкретных примеров.