Функция инвертирования строки-аргумента с параметром-массивом. — КиберПедия 

Состав сооружений: решетки и песколовки: Решетки – это первое устройство в схеме очистных сооружений. Они представляют...

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

Функция инвертирования строки-аргумента с параметром-массивом.

2017-07-09 396
Функция инвертирования строки-аргумента с параметром-массивом. 0.00 из 5.00 0 оценок
Заказать работу

void invert(char e[ ])

{

char s;

int i, j, m;

/* номер позиции символа ‘\0’ в строке e */

for (m=0; e[m]!= ‘\0’; m++);

for (i=0, j=m-1; i <j; i++, j--)

{

s=e[i];

e[i] = e[j];

e[j] = s;

}

}

Пример использования:

#include <sdtio.h>

void main ()

{

char ct[ ] = “0123456789”

/* прототип функции */

void invert(char [ ]);

/* вызов функции */

invert(ct);

printf(“\n%s”,ct”);

}

Результат выполнения:

В конце строки символ ‘\0’ остается.

 

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

 

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

Самый употребительный указатель на функцию – это ее имя (идентификатор). Именно так указатель на функцию вводится в ее определении:

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

тело_функции

Прототип:

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

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

Имя_функции в ее определении и в ее прототипе – указатель–константа. Он навсегда связан с определяемой функцией и не может быть «настроен» на что-либо иное, чем ее адрес. Для идентификатора имя_функции термин «указатель» обычно не используют, а говорят об имени функции.

Указатель на функцию как переменная вводится отдельно от определения и прототипа какой-либо функции. Для этих целей используется конструкция?

тип (*имя_указателя) (спецификация_параметров);

где тип – определяет тип возвращаемого функцией значения;

имя_указателя – идентификатор, произвольно выбранный программистом;

спецификация_параметров – определяет состав и типы параметров функций.

Например, запись

int (*point) (void);

определяет указатель–переменную с именем point на функцию без параметров, возвращающие значения типа int.

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

int * funct (void);

то это будет не определением указателя, а прототипом функции без параметров с именем funct, возвращающей значения типа *int.

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

Пример:

#include <stdio.h>

void f1 (viod)

{

printf (“\n выполняется f1()”);

}

void f2 (void)

{

printf(“\n выполняется f2() “);

}

void main ()

{

void (*point) (void);

/* point указатель-переменная на функцию */

f2(); /* явный вызов функции */

point =f2; /* настройка указателя на f2() */

(* point) (); /* вызов f2() по ее адресу с разыменованием указателя */

point = f1; /* настройка указателя на f1() */

(*point) (); /* вызов f1() по ее адресу с разыменованием указателя */

point (); /* вызов f1() без разыменования указателя */

}

Результат:

выполнение f2()

выполнение f2()

выполнение f1()

выполнение f1()

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

тип (* имя_массива[размер]) (спецификация_параметров);

где тип определяет тип возвращаемых функциями значений;

имя_массива – произвольный идентификатор;

размер – количество элементов в массиве;

спецификация_параметров – определяет состав и типы параметров функций.

Пример:

int (*parray [4]) (char);

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

Массив функций создать нельзя, однако, можно определить массив указателей на функции. Тем самым появляется возможность создавать «таблицы переходов». Для этого все ветви обработки (например, N+1) оформляются в виде однотипных функций (с одинаковым типом возвращаемого значения и одинаковой спецификацией параметров). Определяется массив указателей из N+1 элементов, каждому элементу которого присваивается адрес конкретной функции обработки. Затем формируются условия, на основе которых должна выбираться та или иная функция (ветвь) обработки. Вводится индекс, значение которого должно находиться в пределах от 0 до N включительно, где (N+1) – количество ветвей обработки. Каждому условно ставится в соответствие конкретное значение индекса. По конкретному значению индекса выполняются обращение к элементу массива указателей на функции и вызов соответствующей функции обработки:

имя_массива [индекс] (список_фактических_параметров);

или (*имя_массива [индекс]) (список_фактических_параметров);

 

Пример:

#include <stdio.h>

void act0(char *name)

{

...

}

void act1(char *name)

{

...

}

void act2(char * name)

{

...

}

void main ()

{

/* массив указателей на функции */

void (*pact[ ]) (char *) = {act0, act1, act2};

char string[12];

int number;

...

while (1)

{

scanf(“%d”, &number);

/* ветвление */

pact[number] (string);

}

}

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

double f (double (* pf) (double), double a, double b)

{

int N = 20;

int i;

double h, s = 0.0;

h = (b – a) / N;

for (i = 0; i < N; i++)

s += pf(a+h / 2 + i *h);

return h *s;

}

 

...

double funct1(double x)

{

return x*x / 2.;

}

double funct2(double v);

{

return v/3.0;

}

void main ()

{

double a, b c1, c2;

a = -1.0;

b = 2.0;

c1 = f(funct1, a, b);

c2 = f(funct2, a, b);

}

 

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

 

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

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

где тип – тип возвращаемого функцией значения;

имя – имя функции;

спецификация_явных_параметров – список спецификаций параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры можно назвать обязательными.

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

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

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

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

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

Пример:

#include <stdio.h>

/* функция суммирует значения своих параметров типа int */

long summa (int k,...)

/* k – число суммируемых параметров */

{

int pick = &k; / * настроили указатель на параметр k */

long total = 0;

for (; k; k--)

total += *(++pick);

return total;

}

void main ()

{

printf(“\n summa(2, 6, 4) = %d”, summa(2, 6, 4));

printf(“\n summa(6, 1, 2, 3, 4, 5, 6) = %d”, summa(6, 1, 2, 3, 4, 5, 6));

}

Результат

summa(2, 6, 4) =10

summa(6, 1, 2, 3, 4, 5, 6) = 21

 

Для доступа к списку параметров используется указатель pick типа int *. Вначале ему присваивается адрес явно заданного параметра k, т.е. он устанавливается на начало списка параметров в памяти. Затем в цикле указатель pick перемещается по адресам следующих фактических параметров, соответствующих неявным формальным параметрам. С помощью разыменования *(++ pick) выполняется выборка их значений. Параметром цикла суммирования служит аргумент k, значение которого уменьшается на 1 после каждой итерации и, наконец, становится нулевым. Особенность функции – возможность работы только с целочисленными фактическими параметрами, так как указатель pick после обработки значения очередного параметра «перемещается вперед» на величину sizeof(int) и должен быть всегда установлен на начало следующего параметра.

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

Пример: вычисление произведения параметров.

#include <stdio.h>

/*функция вычисляет произведение параметров */

double prod(double arg,...)

{

double aa = 1.0; /* формируемое произведение */

double prt = &arg; / * настроили указатель на параметр arg */

if (*prt == 0.0)

return 0.0;

for (; *prt; prt++)

aa *= *prt;

return aa;

}

void main ()

{

double prod(dpuble,...); /* прототип функции */

printf(“\n prod(2e0, 4e0, 3e0, 0e0) = %e”, prod(2e0, 4e0, 3e0, 0e0));

printf(“\n prod(1.5, 2.0, 3.0, 0.0) = %f”, prod(1.5, 2.0, 3.0, 0.0));

printf(“\n prod(1.4, 3.0, 0.0, 16.0, 84.3, 0.0) = %f”, prod(1.4, 3.0, 0.0, 16.0, 84.3, 0.0));

printf(“\n prod(0e0) = %e”, prod(0e0));

}

Результат:

prod(2e0, 4e0, 3e0, 0e0) = 2.400000e+01

prod(1.5, 2.0, 3.0, 0.0) = 9.000000

prod(1.4, 3.0, 0.0, 16.0, 84.3, 0.0) = 4.200000

prod(0e0) = 0.000000e+00

В функции prod() перемещение указателя prt по списку фактических параметров выполняется всегда за счет изменения prt на величину sizeof(double). Поэтому все фактические параметры при обращении к функции должны иметь тип double. В вызовах функции проиллюстрированы некоторые варианты задания параметров. Недостаток функции – неопределенность действий при отсутствии в списке фактических параметров параметра с нулевым значением.

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

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

void va_start (va_list param, последний явный параметр);

type va_arg(va_list param, type);

void va_end(va_list param);

Кроме перечисленных макросов, в файле stdarg.h определен специальный тип данных va_list, соответствующий потребностям обработки переменных списков параметров. Именно такого типа должны быть первые фактические параметры, используемые при обращении к макрокомандам va_start(), va_arg(), va_end(). Кроме того, для обращения к макросу va_arg() необходимо в качестве второго параметра использовать обозначение типа (type) очередного параметра, к которому выполняется доступ. В теле функции обязательно определяется объект типа va_list. Например, так:

va_list factor;

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

va_start(factor, последний_явный_параметр);

 

 

int int float int char int[ ] long double
N K sum Z cc ro smell

va_list factor; /* определение указателя */

va_start (factor, sum); /* настройка указателя */

N K sum Z cc ro smell

factor

int ix;

ix = va_arg(factor, int); /* считали в ix значение Z типа int */

одновременно сместили указатель на длину переменной типа int */

N K sum Z cc ro smell

factor

char cx;

cx = va_arg(factor, char); /* считали в cx значение cc типа char и сместили указатель на длину объекта типа char */

N K sum Z cc ro smell

 

factor

 

int * pr;

pr = va_arg(factor, int); /* считали в pr адрес ro типа int * массива типа int [ ] и сместили указатель factor на размер указателя int * */

N K sum Z cc ro smell

factor

long double pp;

pp = va_arg (factor, long double); /* считали значение smell */

 

С помощью разыменования указателя factor можем получить значение первого фактического параметра из переменного списка. Однако нам не известен тип этого фактического параметра. Как и без использования макросов, тип параметра нужно каким-то образом передать в функцию. Если это сделано, т.е. определен тип type очередного параметра, то обращение к макросу

va_arg(factor, type)

позволяет, во-первых, получить значение очередного (сначала первого) фактического параметра типа type. Вторая задача макрокоманды va_arg() – заменить значение указателя factor на адрес следующего фактического параметра в списке. Теперь, узнав каким-то образом тип, например, type1, этого следующего параметра, можно вновь обратиться к макросу:

va_arg(factor, type1)

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

Макрокоманда va_end() предназначена для организации корректного возврата из функции с переменным списком параметров. Ее единственным параметром должен быть указатель типа va_list, который использовался в функции для перебора параметров. Таким образом, для наших иллюстраций вызов макрокоманды должен иметь вид:

va_end(factor);

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

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

#include <stdarg.h> /* для макросредств */

#include <stdlib.h> /* для функции calloc() */

double * set_array (int k,...)

{

int i;

va_list par; /* вспомогательный указатель */

double * rez; /* указатель на результат */

rez = calloc(k, sizeof(double));

if (rez == NULL)

return NULL;

va_start (par, k); /* настройка указателя */

/* цикл «чтения» параметров */

for (i = 0; i < k; i++)

rez[ i ] = va_arg(par, double);

va_end(par);

return rez;

}

#include <stdio.h>

void main ()

{

double * array;

int j;

printf(“\n”);

arrray = set_array (n, 1.0, 2.0, 3.0, 4.0, 5.0);

if (array == NULL) return;

for (j = 0; j < n; j++)

printf(“\t%f”, array [ j ]);

free (array);

}

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

В функциях с переменным количеством параметров есть один уязвимый пункт – елси при «чтении» параметров с помощью макроса va_arg() указатель выйдет за пределы явно использованного списка фактических параметров, то результат, возвращаемый макросом va_arg(), не определен. Таким образом, в нашей функции set_array() количество явно заданных параметров переменного списка ни в коем случае не должно быть меньше значения первого фактического параметра (заменяющего int k).

В основной программе main() определен указатель double * array, которому присваивается результат (адрес динамического массива), возвращаемый функцией set_array(). Затем с помощью указателя array в цикле выполняется доступ к элементам массива, и их значения выводятся на экран дисплея.

Поучительным примером функции с переменным числом параметром являются библиотечные функции форматного ввода-вывода:

int printf(const char * format,...);

int scanf(const char * format,...);

В обеих функциях форматная строка, связанная с указателем format, содержит спецификации преобразования.

Пример: упрощенный вариант функции печати.

/* упрощенный аналог printf() */

#include <stdio.h>

#include <stdarg.h>

void miniprint(char *format,...)

{

va_list ap; /* указатель на необязательный параметр */

char p; / для просмотра строки format */

int ii; /* целые параметры */

double dd; /* параметры типа double */

va_start (ap, format); /* настроились на первый параметр */

for (p = format; *p; p++)

{

if (*p!= ‘%’)

{

printf(“%c”, *p);

continue;

}

switch (*++p)

{

case ‘d’:

ii = va_arg(ap, int);

printf(“%d”, ii);

break;

case ‘f’:

dd = va_arg(ap, double)l

printf(“%f”, dd);

break;

default:

printf(“%c”, *p);

} /* конец переключателя */

} /* конец цикла просмотра строки-формата */

va_end(ap);

}

void main ()

{

void miniprint(char *,...); /* прототип */

int k = 154;

double e = 2.718292;

miniprint(“\n целое k = %d, \t число е= %f”, k, e);

}

Результат:

целое k = 154, число е= 2.718292

Интересной особенностью предложенной функции miniprint() и ее серьезных прародителей – библиотечных функций языка Си printf(), scanf() – является использование одного явного параметра и для задания типов последующих параметров, и для определения их количества. Для этого в строке, определяющей формат вывода, записывается последовательность спецификаций, каждая из которых начинается символом ‘%’. Предполагается, что количество спецификаций равно количеству параметров в следующем за форматом списке. Конец обмена и перебор параметров определяется по достижению конца форматной строки, когда *p = ‘0\’.

 

9.6. Рекурсивные функции

 

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

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

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

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

Классический пример – функция для вычисления факториала неотрицательного целого числа.

long fact (int k)

{

if (k < 0) return 0;

if (k == 0) return 1;

return k * fact(k – 1);

}

Для отрицательного аргумента результат (по определению факториала) не существует. В этом случае функция возвратит нулевое значение. Для нулевого параметра функция возвращает значение 1, так как по определению, 0! равен 1. В противном случае вызывается та же функция с уменьшенным на 1 значением параметра и результат умножается на текущее значение параметра. Тем самым для положительного значения параметра k организуется вычисление произведения

k * (k – 1) * (k – 2) *... * 3 * 2 * 1 * 1

Функция прерывается при вызове fact(0). Именно этот вызов приводит к последнему значению 1 в произведении, так как последнее выражение, из которого вызывается функция, имеет вид 1 * fact(1 – 1).

Пример: функция возведения в степень.

double expo (double a, int n)

{

if (n == 0) return 1;

if (a == 0.0) return 0;

if (n > 0) return a * expo(a, n – 1);

if (n < 0) return expo(a, n + 1) / a;

}

При обращении вида expo(2.0, 3) рекурсивно выполняются вызовы функции expo() с изменяющимся вторым аргументом expo(2.0, 3), expo(2.0, 2), expo(2.0, 1), expo(2.0, 0). При этих вызовах последовательно вычисляется произведение

2.0 * 2.0 * 2.0 * 1

и формируется нужный результат.

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

expo(5.0, -2)

эквивалентен вычислению выражения

expo(5.0, 0) / 5.0 / 5.0

 

9.7. Классы памяти и организация программ

 

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

Пример:

#include <stdio.h>

/* применение автоматической памяти */

void autofunc(void)

{

int K = 1;

printf(“\tK =%d”, K);

K++;

return;

}

void main ()

{

int i;

for (i = 0; i < 5; i++)

autofunc();

}

Результат:

K = 1 K = 1 K = 1 K = 1 K = 1

Пример:

 

#include <stdio.h>

/* применение переменных статической памяти */

void stat(void)

{

static int K = 1;

printf(“\tK =%d”, K);

K++;

return;

}

void main ()

{

int i;

for (i = 0; i < 5; i++)

stat();

}

Результат:

K = 1 K = 2 K = 3 K = 4 K = 5

 

Отличие функций autofunc() и stat() состоит в наличии спецификатора static при определении переменной int K, локализованной в теле функции stat(). Переменная К в функции autofunc() – это переменная автоматической памяти, она определяется и инициализируется при каждом входе в функцию Переменная static int K получает память и инициализируется только один раз. При выходе из функции stat() последнее значение внутренней статической переменной К сохраняется до последующего вызова этой же функции.

Глобальные объекты.

Пример:

#include <stdio.h>

int N = 5; /* глобальная переменная */

void func(void)

{

printf(“\t N = %d”, N);

N--;

return;

}

void main ()

{

int i;

for (i = 0; i < 5; i ++)

{

funct();

N += 2;

}

}

Результат:

N = 5 N = 6 N = 7 N = 8 N = 9

 

Переменная int N определена вне функция main() и func() и является глобальным объектом по отношению к каждой из них. При каждом вызове func() значение N уменьшается на 1, в основной программе – увеличивается на 2, что и определяет результат.

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

Пример:

#include <stdio.h>

int N = 5; /* глобальная переменная */

void func(void)

{

printf(“\t N = %d”, N);

N--;

return;

}

void main ()

{

int N;

for (N = 0; N < 5; N ++)

funct();

}

Результат:

N = 5 N = 4 N = 3 N = 2 N = 1

Переменная int N автоматической памяти из функции main() никак не влияет на значение глобальной переменной int N. Это разные объекты, которым выделены разные участки памяти. Внешняя переменная N «не видна» из функции main(), и это результат определения int N внутри нее.

Динамическая память – это память, выделяемая в процессе выполнения программы.

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

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

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

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

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

- указатель является глобальным объектом по отношению к блоку. Динамическая память доступна во всех блоках, где «виден» указатель. Память нужно освободить только по окончании ее использования.

 

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

Рис. 26. Схема программы, размещенной в двух файлах.

 

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

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

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

В соответствии с рисунком:

- объект Х доступен в f11(), f12() как глобальный; доступен как внешний в файле 2 только в тех функциях, где будет помещено описание extern X;

- объект Y доступен как глобальный в f21(), f22(); доступен как внешний в тех функциях файла 1, где будет помещено описание extern Y;

- объект Z доступен как глобальный в f22() и во всех функциях файла 1 и файла 2, где помещено описание extern Z.

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

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

 

Примеры определений:

double summa [5];

char D_Phil [ ] = “Doctor of Philosophy”;

long M = 1000;

В описаниях инициализация невозможна, нельзя указать и количество элементов массива:

extern double summa[ ];

extern char D_ Phil [ ];

extern long M;

 

Контрольные вопросы

1. Определение функций в языке Си.

2. Формальные и фактические параметры функций.

3. Как осуществляется выход из функций в языке Си?

4. Как задается тип возвращаемого функций значения?

5. Применение указателей в качестве параметров функции.

6. Указатели на функции как параметры.

7. Как описываются функции с переменным количеством параметров?

8. Какие функции называют рекурсивными?

9. Какие объекты называют глобальными и как их описывать в программе?


 

Глава 10. Структуры и объединения

 

10.1. Структурные типы и структуры

 

Данные базовых типов (int, float,...) считаются скалярными данными. Массивы и структуры являются агрегирующими типами данных в отличие от объединений и скалярных данных, которые относятся к неагрегирующим типам. Обычно агрегирующий тип включает несколько компонентов. Например, массив long В[ 12 ] состоит из 12 элементов, причем каждый элемент имеет тип long. Язык Си позволяет определять и одноэлементные массивы. Так, массив float A[ 1 ] состоит из одного элемента A[ 0 ]. Итак, в общем случае массив есть агрегирующий тип данных, состоящий из набора однотипных элементов, размещенных в памяти подряд в соответствии с ростом индекса. Для агрегирующего типа данных (например, для массива) в основной памяти ЭВМ выделяется такое количество памяти, чтобы сохранять (разместить) одновременно значения всех элементов.

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

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

Формат определения структурного типа:

struct имя_структурного_типа

{

определение_элементов

}

где struct – спецификатор структурного типа;

имя_структурного_тип – идентификатор, произвольно выбираемый программистом;

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

Конструкция struct имя_структурного_типа играет ту же роль, что и спецификаторы типов, например, double или int.

Например, может быть введена структура, описывающая товары на складе, с компонентами:

- название товара (char *);

- оптовая (закупочная) цена (long);

- торговая наценка в процентах (float);

- объем партии товара (int);

- дата поступления партии товара (char [9]).

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

struct goods

{

char * name; /* наименование */

long price; /* цена оптовая */

float percent; /* наценка в % */

int vol; /* объем партии */

char date [ 9 ]; /* дата поставки партии */

}

С помощью struct goods можно либо определить конкретную структуру, либо указатель на структуры такого типа. Например:

/* определение структуры с именем food */

struct goods food;

/* определение указателя point_to на структуры */

struct goods *point_to;

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

Еще одну возможность ввести структурный тип дает служебное слова typedef, позволяющее ввести собственное обозначение для любого определения типа. В случае структурного типа он может быть введен и поименован следующим образом:

typedef struct { определение элементов }

обозначение_структурного_типа;

 

Пример:

typedef struct /* определение элементов */

{

double real;

double imag;

}

complex; /* обозначение_структурного_типа */

Приведенное определение вводит структурный тип struct и присваивает ему обозначение (название, имя) complex. С помощью этого обозначения можно вводить структуры (объекты) так же, как с обычным именем структурного типа:

/* определены две структуры */

comlex sigma, alfa;

Структурный тип, которому программист назначает имя с помощью typedef, может в то же время иметь второе имя, вводимое стандартным образом после служебного слова struct. В качестве примера введем определение структурного типа "рациональная дробь" – будем считать, что рациональная дробь – это пара целых числе m, n, где число n отлично от нуля.

Определение такой дроби:

typedef struct rational_fraction

{

int numerator; /* числитель */

int denominator; /* знаменатель */

}

fraction;

Здесь fraction – обозначение структурного типа, вводимое с помощью typedef. Имя rational_fraction введено для того же структурного типа стандартным способом. После такого определения структуры типа «рациональная дробь» могут вводиться как с помощью названия fraction, как с помощью обозначения того же типа struct rational_fraction.

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

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


Поделиться с друзьями:

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

История создания датчика движения: Первый прибор для обнаружения движения был изобретен немецким физиком Генрихом Герцем...

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

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.38 с.