Сдам Сам

ПОЛЕЗНОЕ


КАТЕГОРИИ







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





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

void varParFun(param_list, ...);

void varParFun(...);

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

Примером вынужденного использования многоточия служит функция printf() стандартной библиотеки С. Ее первый параметр является C-строкой:

int printf (const char* ...);

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

printf (“hello, world\n”);

имеет один строковый параметр. Но

printf (“hello, %s\n”, userName);

имеет два параметра. Символ “%” говорит о наличии второго параметра, а буква s, следующая за ним, определяет его тип - в данном случае символьную строку.

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

Следующие объявления неэквивалентны:

void f();

void f(...);

В первом случае f() объявлена как функция без параметров, во втором – как имеющая нуль или более параметров. Вызовы:

f (someValue);

f (cnt, a, b, c);

корректны только для второго объявления. Вызов

f();

применим к любой из двух функций.

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

Для обеспечения удобного способа доступа к аргументам функции с переменным числом параметров имеются три макроопределения (макросы) va_start, va_arg, va_end, находящиеся в заголовочном файле stdarg.h. Эти макросы указывают на то, что функция, разработанная пользователем, имеет некоторое число обязательных аргументов, за которыми следует переменное число необязательных аргументов. Обязательные аргументы доступны через свои ID как при вызове обычной функции. Для извлечения необязательных аргументов используются макросы 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 <stdarg.h>

int main(void) {

int n;

int sred_znach(int,...);

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

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

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

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

return (0);

}

 

int sred_znach(int x,...)

{

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

va_list uk_arg;

va_start(uk_arg,x);

// Установка указателя uk_arg на первый необязятельный параметр

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

else return (0);

j++;

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

// Выборка очередного параметра и проверка на конец списка

{

sum+=i;

j++;

}

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

return (sum / j);

}

 

16. Классы памяти и области действия объектов

 

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

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

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

 

16.1. Классы памяти объектов в языке C:

- динамическая память, которая выделяется при вызове функции и осво­бо­ждается при выходе из нее (атрибуты: auto - автоматический; register - регистровый);

- статическая память, которая распределяется на этапе трансляции и заполняется по умолчанию нулями (атрибуты: внешний – extern, статический – static).

 

Автоматические переменные

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

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

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

Принадлежность к этому классу можно также подчеркнуть явно с помощью ключевого слова auto. Например:

void main(void) {

auto int max, lin;

... }

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

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

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

 

Внешние переменные

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

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

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

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

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

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

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

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

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

Важно различать описание внешней переменной и ее определение. Описание указывает свойство переменной, ее размер, тип и т. д.; определение же вызывает еще и отведение памяти. Например, если вне какой-либо функции появляются строчки:

int sp;

double val[20];

то они определяют внешние переменные sp и val, вызывают отведение памяти для них и служат в качестве описания для остальной части этого исходного файла. В то же время строчки:

extern int sp;

extern double val [];

описывают в остальной части этого исходного файла переменную sp как int, а vol как массив типа double, но не создают переменных и не отводят им места в памяти.

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

Любая инициализация внешней переменной проводится только в декларации. В декларации должны указываться размеры массивов, а в описании extern этого можно не делать.

Например, в файле 1:

int sp = 0;

double val [20];

...

в файле 2:

extern int sp;

extern double val [];

...

В Си есть возможность с помощью #include иметь во всей программе только одну копию описаний extern и вставлять ее в каждый файл во время его компиляции.

Если переменная с таким же идентификатором как внешняя декларирована в функции без указания extern, то тем самым она становится внутренней в данной функции.

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

 

Файл 1: Файл 2:
int x, y; char str[ ] = “Rezult = ”;   void main(void) { . . . }   void fun1(void) { y = 15; cout << str << y; }   extern int x, y; extern char str[ ]; int r, q; void fun2(void) { x = y / 5; cout << str << x; }   void fun3(void) { int z= x + y; cout << str << z; }  

 

16.4. Область действия переменных

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

Напомним общую структуру исходного текста программ на языке С:

<директивы препроцессора>

<описание глобальных объектов>

<заголовок функции>

{

<описание локальных объектов>

<список инструкций>

}

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

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

. . .

int n; // глобальное n

void main (void) {

int i;

...

f1(i);

...

f2(n); // локальное n

}

f1(int i) {

...

i=n; // глобальное n

...

}

f2(int n) {

int i;

...

i=n; // локальное n

...

}

 

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

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

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

void main(void) {

int i = 3;

cout << “\n Block 1 - “ << i ;

{

float i = 2.5;

cout << “\n Block 2 - “ << i ;

{

char i = ‘a’;

cout << “\n Block 3 - “ << i ;

}

}

cout << “\n New Block 1 - “ << ++i ;

getch();

}

В результате выполнения этой программы, на экране получим:

Block 1 - 3

Block 2 - 2.5

Block 3 - a

New Block 1 - 4

 









Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:


©2015- 2018 zdamsam.ru Размещенные материалы защищены законодательством РФ.