close

Вход

Забыли?

вход по аккаунту

?

Функции (2)

код для вставкиСкачать
Общие сведения о функциях.
В соответствие с синтаксисом языка Си функция - это производный тип. С одной стороны функция - это производный тип. С другой - это минимальный исполняемый модуль программы на языке Си. В языке Си и Си++ используется только одно понятие - функция. Для этого языка это основное понятие. Каждая программа может состоять из многих функций, но обязательно должна содержать единственную функцию с именем main(). Именно эта функция обеспечивает создание точки входа в откомпилированную программу. Неглавные функции могут инициироваться прямо в функции main(), или опосредственно вызовами из функции main(). Всем именам функций по умолчанию присваивается класс памяти extern, каждая функция глобальна и доступна всем модулям программ. Единственное условие доступности - функция должна быть описана или определена (описан прототип) до первого вызова.
Существует единый формат определения функции:
возв-ый_тип имя_функции(спец-ия форматов формальных параметров) тело_функции.
Первая строка - это заголовок функции. Он отличается от прототипа отсутствием точки с запятой.
Прототип: возвр_тип имя_функции (спец-ия форматов формальных параметров); Возвращаеый_тип - либо void (если функция не возвращает значение в точку вызова), либо базовый (char, int, double и т.д.), либо производный (указатель, ссылка).
Имя_функции - либо main, либо произвольное имя, не совпадающее со служебными словами.
Спецификация параметров - либо пусто, либо список формальных параметров, каждый элемент которого имеет вид: обозначение_типа имя_параметра. Список параметров может оканчиваться запятой с последующим многоточием "...". Многоточие обозначает возможность обращаться к функции с большим количеством параметров такого же типа, чем указано в спецификации форматов. Но такая возможность должна быть "подкреплена" специальными средствами в теле функции.
Пример - уже известная функции printf(char * format, ....);
Тело_функции - часть определения функции, заключенная в {}.
В функции можно определять свои локальные переменные, но нельзя определить другую функцию. Т.е. определение функций не может быть вложенным.
Обязательным является в функции оператор возврата в точку вызова: return (для void); или return выражение. В случае возвращаемого значения void оператор return можно не писать.
Выражение в return должно иметь тип, указанный перед именем функции.
Допустима форма определения функции:
тип имя_функции (список параметров);
спецификация параметров;
тело функции.
Пример: void my(int n, chart*string)
{/*Тело функции */} void my (n, string)
int n;
char*string;
{/*Тело функции */}
Описание функции и ее тип.
Для вызова функции на исполнение сведения о ней должны быть известны компьютеру, т.е. до ее вызова функцию нужно определить или описать в том же файле. .cpp заранее. Двум формам определения соответствуют две формы описания прототипа.
1. тип имя_функции (спецификация_параметров);
В прототипе можно не указывать имена формальных параметров, а только спецификацию их типов:
char *f(int n, float x); или char * f (int, float);
2. тип имя_функции()
char *f();
Лучше использовать первый способ определения и описания функций.
Вызов функции.
Для вызова функции на исполнение используется операция "круглые скобки" в виде:
имя_функции (список_фактических_параметров);
Список фактических параметров называется аргументами. Фактические параметры могут быть выражениями. Соответствие между формальными и фактическими параметрами устанавливается по их взаимному расположению в списках. Порядок вычисления значений фактических параметров (слева направо или справа налево) не определяется. Между формальными и фактическими параметра должно быть соответствие по типам. Если типы не совпадают, то компилятор автоматически добавляет команды преобразования типов. Это возможно, если приведение типов допустимо.
Пример:
прототип: int g (int,long); вызов: g(3.o+m, 6.4e+2);
В этом вызове оба фактических параметра имеют тип double. Компилятор ориентируется на прототип и компилятор сделает преобразование:
g((int)(3.o+m),(long)(6.4e+2));
Эта функция должна вернуть в точку вызова строго тип int. Если возвращаемое оператором значение не соответствует типу в объявлении функции, то по возможности компилятор производит автоматическое приведение типа. В противном случае будет ошибка.
Формальные параметры функции локализованы в ней, т.е. недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значений фактических параметров.
Передача параметров по значению предусматривает этапы:
1) При компиляции для формальных параметров выделяются участки памяти. Для параметров типа float резервируется место для объекта типа double, для параметров типа char, int, short int - тип int, для массива формируется указатель на начало массива и он служит представлением массива-параметра в теле функции.
2) Вычисляются значения выражений, использованных в качестве фактических параметров при вызове функции.
3) Значения выражений - фактических параметров записываются в участки памяти, зарезервированные для формальных параметров, при этом идет преобразование типов в соответствии с пунктом 1.
4) Функция выполняет обработку значений полученных параметров в соответствии со своими операторами , а результат возвращает в точку вызова функции. При этом никакого влияния на значения фактических параметров функция не оказывает.
5) После обработки функции память, выделенная для ее формальных параметров, освобождается.
Если функция возвращает значение, отличное от void, то вызов этой функции можно разместить в каком либо выражении:
Например: int f-f (int, float);
.........................
main()
{
int a=5; float b=3.3;
a=a*b+f-f(a,b);
}....................
Если функция не возвращает значение, то она в программе может использоваться, как отдельный оператор, например:
void dat (int gg, int mm, int dd)
{
print f ("\n год_%", gg),
print f ("\t месяц-%d", mm);
print f ("\t день_% d", dd);
}
Обращение к ней в программе:
dat (1912, 1, 13); приведет к выводу результата
год - 1912, месяц - 1, день - 13.
Пример функции, которая не возвращает значение и не получает параметров:
void t_time ()
{ print f ("\n %s"," _ _ TIME _ - текущее время в формате час:мин:сек");
}
В результате обращения к ней в виде:
..............
t_time();
..............
на экран будет выведено сообщение 9:12:5 - текущее время в формате час:мин:сек.
Указатели в параметрах функций.
Схема передачи параметров по значениям не оставляет никакой надежды на возможность непосредственно изменить фактический параметр за счет выполнения операторов тела функции. И это действительно так. Объект, использованный в качестве фактического параметра, не может быть изменен из тела функции. Но косвенная возможность изменять значение объектов вызывающей программы действиями в ней. Эту возможность обеспечивает аппарат указателей. С помощью указателя в функцию передается адрес объекта, который нельзя изменить в теле вызываемой функции. Но в функции мы получаем доступ к адресуемому указателем объекту, т.е., не изменяя самого параметра (указатель), можно изменять содержимое по указанному адресу.
Пример 1:
# include <stdio.h>
void positive (int *m) //Определение функции, но не описание!!!
{
*m=*m>0? *m: - (*m);
}
void main()
{
int k=-3
positive (&k); //Передача адреса переменной с именем k
printf("\n%d",k);
}
Результат: 3.
Объяснение: Параметр функции positive() - указатель типа - integer! При обращении к ней из основной программы в качестве фактического параметра используется адрес (&k) переменной типа int. Внутри функции, значение аргумента, т.е. адрес, записывается в участок оперативной памяти, выделенный для указателя int*. Разыменование m обеспечивает доступ к тому участку памяти, на который в этот момент смотрит " ("указывает") указатель. В выражении *m=m*>0?*m:-*m; все действия выполняются над значениями той переменной основной программы (int k;), адрес которой (&k) использован в качестве фактического параметра.
Графическая иллюстрация:
Функция positive()
iint *m
m=...&k
Основная программа
int k = -3
m-адрес;
*m - содержимое
*(&k) - переменная k
При обращении к функции positive () она присваивает абсолютное значение той переменной, адрес которой использован в качестве ее параметра.
Пример2. Функция, меняющая местами значения переменных, на которые указывают фактические параметры.
# include <stdio.h>
void main()
{
float x,y;
voud aa(float *, float *); // прототип функции
printf("\n введите x=");
scanf("%f", &x);
printf ("\n введите y="); scanf ("%f", &y);
aa(&x, &y); // вызов функции
printf("\n Результат x=%f, y=%f ", x,y);
}
void aa(float *b, float *c) //в и с - указатели на вещественное число
{ float e; // вспомогательная переменная (локальная)
e = *b;
*b = *c; *c=e;}
(Объяснить работу программы)
Возможен результат:
x=33,3 x=66.600000
y=66.0 y=33.300000
Пример 3.
Использование функции для вычисления периметра и площади треугольника.
# include <stdio.h>
# include <math.h> //для функции sqrt0
void main()
{ float x,y,z, pp, ss;
int tring (float, float, float, float *, float *); // Прототип
printf ("\n Введите x,y,z - "); scanf ("%f%f%f", &z,&y,&z);
if (tring(x,y,z,&pp,&ss)!=-1)
{printf ("Периметр = %f,",pp);
printf(" а площадь = %f, ss);
else printf ("Ошибок в данных \n");
// Определение функции
int tring (float a, float b, float c, float * perimeter, float * area)
{ float e;
if (a+b<=c && a+c<=b && b+c<=a) return (-1);
* perimeter = a++b+c;
e = *perimeter/2;
* area = sqrt(e*(e-a)*(e-b)*(e-c));
return 1;
}
Результат: x=3 y=4 z=5
Периметр = 12.000000, а площадь = 6.000000
Массивы и строки - параметры функции.
Если в качестве параметра функции используется обозначение массива, то на самом деле внутрь функции передается только адрес первого элемента массива. Например конструкции, приведенные ниже, совершенно равноправны:
float Scalar_Pz (int n, float a[], float b []);
float Scalar_Pz (int n, float *a, float *b);
float Scalar_Pz (int, float *,float *);
Массив всегда передается в функцию как указатель. Внутри фукции можно изменить значения элементов массива. К элементам массива внутри функции можно обращаться, используя индексирование - a[i] и разыменовывая указатель на элемент - *(a+i).
Пример.
void quart (int n, float *x)
{
int i;
for (i=0; i<n; i++)
*(x+i) *=* (x+i);
}
void main()
{ float z[]={1.0, 2.0, 3.0, 4.0};
int j;
quart (4,z); for(j=0; j<4; j++)
printf("\n z[%d]=%f", j, z[j]);
}
Результат:
z[0] = 1.000000
z(1) = 4.000000
z(2) = 9.000000
z(3) = 16.000000
Интересная возможность состоит в том, что можно изменять внутри тела функции значение указателя на массив, т.е. использовать, например, такой оператор.
*x = (*x)*(*(x+1));
Отметим, что имя массива внутри тела функции не воспринимается как константный указатель.
Строки, как параметры функции.
Строки в качестве формальных параметров могут быть либо как одномерные массивы типа char[] или как указатель типа char *. В обоих случаях с помощью фактического параметра в функцию передается адрес первого элемента символьного массива, содержащего строку. Для параметров строк нет необходимости явно указывать их длину. В качестве примеров рассмотрим несколько функций, решающих типовые задачи обработки строк. Их прототипы и другие средства связи с этими функциями находятся в заголовочных файлах string.h и stdlib.h
1. Функция инвертирования строки.
void invert (char e [])
{char s;
int i, j, m; // 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;
}
}
В определении функции invert можно заменить параметр - массив параметром - указателем: void invert (char * e);
Применим в функции main(0):
# include <stdio.h>
void main ()
{
char ct[] = "0123456789";
void invert (char []);
invert (ct);
printf ("%s \n", ct);
}
Результат: 9876543210. Функция invert() оставляет признак конца строки на месте.
2. Функция поиска в строке ближайшего вхождения другой строки.
// Поиск строки CT2 в строке CT1
int index (char *CT2, char * CT1)
{ int i,j,m1,m2;
// Вычисляем m1 и m2 - длины строк
for (m1=0; CT1[m1] !='\0'; m1++);
for (m2=0; CT2[m2] !='\0'; m2++);
if (m2>m1) return -1; // если вторая строка длиннее первой, то возвращаемся в главную программу с - 1.
int m3 = m2;
for (i=0; i<=m1-m2; i++)
{
for (j=0; j<=m1-m2; i++) // Цикл сравнивания.
if (CT2[j]! = CT1[i+j] break;
if (j = = m3) return i; // возврат номера позиции, начиная с которой CT2 полностью совпадает с частью строки CT1.
} // конец цикла по i.
return -1; // возврат - 1, если CT2 не входит в CT1.
} Обращение к ней:
# include <stdio.h>
void main() {char C1[] = " Сумма масс";
int index (char *, char *); char C2[]="ма", C3[]="aм";
printf ("\n Для %s индекс = %d", c2, index (c2,c1));
printf ("\n Для %s индекс %d, c3, index(c3,c1));
Результат: Для ма индекс = 3
Для ам индекс = -1.
3. Копирование содержимого строки С2 в С1
void copy (char *c1, char * c2)
{int i;
for (i=0; c2[i] !='\0'; i++) c1[i]=c2[i];
c1[i]='\0';
}
другой вариант этой же функции:
void copy (char c1[ ], char c2 [ ])
{ int i = 0;
do { c1[i] = c2 [i]; } while (c1[i++] ! = '\0');
// Здесь в гл. ор. main () длина строки определена была
Операция присваивания может быть перенесена в условие продолжения цикла.
void copy (char * c1, char * c2) {inti=0;
while ((c1[i]=c2[i] !='\0') i++;}
Т.к. не нулевое значение в while считается истинным, то явное сравнение с '\0' необязательно. Можно записать:
void copy (char *c1, char *c2) {int i=0; while (c1[i] = c2[i]) i++;}
Наиболее короткий вариант с использованием операций *: void copy (char *c1, char *c2) {while (*c1++=*c2++);}
Указатели на функцию.
Мы рассмотрели функцию, как минимальный исполняемый модуль, обмен данными с которым происходит через набор параметров функции и с помощью значений, возвращаемых в точку вызова. Перейдем к вопросу о том, почему в Си функция введена как один из производных типов. Необходимость такая связана с такими, например задачами, в которых функция (или ее адрес) должна выступать в качестве параметра другой функции или в качестве значения, возвращаемого другой функцией. Обратимся к уже рассмотренным ранее выражениям.
Обозначение_функции (список фактических параметров); - вызов функции.
Тип имя_функции (спецификация параметров); - прототип функци.
Тип имя_функции (спецификация параметров) {} - описание функции
Имя (идентификатор) функции - это самый употребляемы (частный случай) указатель на функцию. В общем случае указатель на функцию - это выражение или переменная, используемые для представления адреса функцию. Указатель на функцию (в част. случае идентификатор) содержит адрес первого байта или первого слова выполняемого кода функции. Указатель навсегда связан с определяемой функцией и не может быть настроен на что-нибудь другое, чем на данную функцию. Арифметические операции над указателями на функцию запрещены.
Указатель на функцию как переменная вводится отдельно от определения и прототипа какой-либо функции. Для этого используется конструкция: тип (*имя_указателя)(спецификация_параметров);
где тип - определяет тип возвращаемого функцией значения;
имя_указателя - идентификатор, произвольно выбранный программистом,
спецификация_параметров - определяет состав и типы параметров функции.
Например в записи int (*paint)(void); определяется указатель - переменная с именем point на функции без параметров, возвращающие значения типа int. Круглые скобки в определении указателя на функции является важнейшим элементом. Если записать int func(void); то это будет прототип функции func без параметров, возвращающая значение типа int.
В отличие от имени функции (например func) указатель на функцию (например (*point)) является переменной, т.е. ему можно присваивать значения других указателей, определяющих адреса функций программы. Единственное требование - тип указателя - переменной должен полностью соответствовать прототипу функций, адрес которой ему присваивается. Т.е., если записать point = func;, то это общий ошибка несоответствия типов, т.к. типы возвращаемых значений для point и func различны. Здесь указатель - переменная point возвращает int, а функция func возвращает int*.
Указатель на функцию, настроенный на адрес конкретной функции, может быть использован для вызова этой функции.
Т.о. к функции можно обратиться (т.е. вызвать на исполнение) тремя способами:
1) перед круглыми скобками со списком фактических параметров поместить имя_функции; 2) перед круглыми скобками со списком фак. параметров по местной указатель_переменную того же типа, значение которого равно адресу функции.
3) Перед круглыми скобками со списком фактических параметров поместить выражение разыменования такого же указателя с таким же значением.
Примеры этих трех способов.
# void f1 (void)
{printf("\n Вызывалась функция f1()");}
void f2(void)
{printf("\n Вызывалась функция f2()");}
main()
{void (*paint)(void); //*point - указатель_переменная на функцию. // типа void (void)
point = f1; // Настройка указателя_переменной на функцию f1()
f1(); // Явный вызов функции f1()
point(); // Вызов функции f1() без разыменования указателя
(*point)(); // Вызов функции f1() по ее адресу с разыменованием указателя.
Point = f2; // Настройка указателя на функцию f2()
f2(); //
point(); //
(*point)(); // return 0; }
Результат: Вызывалась функция f1()
- // -
- // -
Вызывалась функция f2()
*point(); - будет ошибочным вызовом, т.е. () имеют более высокий приоритет, потому сначала выполнится вызов функции point (), а затем к результату будет применена операция разыменования (косвенности).
При определении указателя на функцию от может быть инициализирован сразу, например:
Int func (char);
Int (*funct)(char) = func; // теперь funct указывает на функцию func()
Массив функций создать нельзя, зато можно определить массив указателей на функции. Тем самым появляется возможность создавать "таблицы передачи управления" (таблица переходов - jump lables). С помощью такой таблицы удобно организовывать ветвления с возвратом по результатам анализа некоторых условий.
Например: массив указателей удобно применять для организации меню, точнее программ, которыми управляет пользователь с помощью меню:
# include <stdlib.h>
# include <stdio.h>
# define №2
void act 0 (char *name)
{printf("%S: Работа завершена! \n", name);
exit 0; }
void act1 (char *name)
{printf("%S:работа 1\n", name); }
void act2(char * name)
{printf("%S: работа 2 \n", name);}
void main()
{void (*pact [])(char *) = {act0, act1, act2}; // массив указателей на функции act0, act 1, act 2.
Char string[12]; int number;
Printf("\n Введите свое имя: "); scanf ("%S', string);
Printf ("\n Введите номер работы от 0 до %d:\n",N);
While (1) // Пока истина
{ scanf ("%d", & numbers);
pact [number](string);
}
}
Результат работы программы:
Введите имя: Иван
Введите номер работы 0 до 2;
1
Иван: работа 1
2
Иван: работа 2
1
Иван: работа 1
0
Иван: работа завершена
Эта программы упрощена, в ней нет защиты от неверно введенного индекса, например 4. В такой ситуации результат непредсказуем, т.е. будет зависеть от того, что находится в оперативной памяти за пределами массива указателей. В реальной программе обязательно должна быть проверка на корректность введенного number и соответствующая реация.
Можно: 1) создавать функции, реализующие тот или иной метод обработки другой функции, заранее не определенной.
2) Определить функцию которая возвращает указатель на другую функцию.
Указатели на функцию, как параметры.
Такие параметры позволяют создавать функции, реализующие тот или иной метод обработки другой функции, которая заранее не определена. Например, можно определить функцию для вычисления определенного интеграла. Подинтегральная функция может быть передана в функцию вычисления интеграла с помощью параметра-указателя.
Пример функции для вычисления определенного интеграла с помощью формулы прямоугольников:
Double rectangle (double(*pf)(double), double a, double b)
1) указатель pf на функция с параметром типа double, возвращающая тип double.
2) a и b - пределы интегрирования
{int N=20; // количество интервалов, а которое разб.отр. [a,b]
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;
}
Сохраним текст этой функции в папке пользователя под именем rect.cpp.
Предложим, что эта функция должна быть использована для вычисления приближенных значений интегралов.
1) 2) После математических преобразований этих интегралов выводим формулы для вычисления площади, ограниченной осью OX и кривыми и аHFРабо(v) на каждом отрезке Программа для решения этой задачи может иметь вид:
...
# include "rect.cpp"
double ratio (double x) // Подинтегральная функция 1)
{ double z;
z = x*x+1;
return x/(z*z);
}
double cos 4_2 (double v)
{double W;
W = cos (v)
Return 4*W+W;
}
void main () {double a,b,c;
a = - 1.0; b = 2.0;
c = rectangle (ratio, a,b);
printf ("\n первый интеграл: %f", c);
printf ("\n Второй интеграл: % f" , rectangle (cos 4_2,0,0.5));
}
Результат: Первый интеграл: 0.149847
Второй интеграл: 1.841559
В приведенном примере функции ratio() и cos 4_2 () через указатель pf получают фактический аргумент (a+h/2+i*h) в цикле, где i є [ 0, n ).
Функции с переменным количеством параметров.
В языке Си ++ допустимы функции, количество параметров у которых при компиляции функции не фиксировано. Кроме того, могут быть неизвестными и типы параметров. Количество и типы параметров становятся известными только в момент вызова функции на выполнение.
Формат описания таких функций:
Тип имя_функции (спецификация_явных_параметров, ...);
Параметры, которые входят в список "спецификация_явных_параметров", являются обязательными. Каждая функция с переменным количеством параметров должна иметь хотя бы один обязательный параметр. Каждая такая функция должна иметь механизм определения количества параметров и их типов. 2 подхода: 1) В конец списка реально использованных фактических параметров добавляется параметр - индикатор с уникальным значением, который сигналит о конце списка.
2) При помощи одного из обязательных параметров передается количество реально фактического параметра к другому выполняется с помощью указателей, т.е. используется адресная арифметика.
Пример 1: Функция суммирует значения своих параметров типа int. Обязательный параметр определяет количество их.
... long summa (int k, ...) {
int * ptr = &k;
long total = 0;
for (; k; k - -) total + = * (++ptr);
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,0);
}
Результат: Summa (2,6,4) = 10
Summa (6,1,2,3,4,5,6) = 21
Пример 2. Функция для вычисления суммы параметров. Признаком окончания списка служит параметр со значением 0.
...
Double prod (double arg ...)
{double aa = 0,0;
double *prt = & arg;
if (*prt = = 0) return 0,0;
for (; *prt; prt ++)
aa + = * prt;
return aa;
}
void main ()
{double prod (double,...);
printf ("\n prod (2e0, 4e0, 3e1, 0e0) = %e",
prod (2e0, 4e0, 3e1, 0e0));
printf ("\n prod (1.5, 2.0, 3.0, 0.0) = % f",
prod (1.5, 2.0, 3.0, 0.0);
}
Результат: prod (2e0, 4e0, 3e1, 0e0) = 36.000000
prod (1.5, 2.0, 3.0, 0.0) = 6.500000
Пример 3. Функция определения минимального значения среди переданных фактических. Параметры могут быть 2-х типов_long или int. Признак типа параметра -значение первого обязательного параметра, кол-во необязательных параметров - значение второго обязательного параметра.
...
void main () {
long min (char, int, ...);
printf ("\n \t min ('e', 3,10L, 20 L, 30L) = % ld",
min ('e', 3, 10L, 20L, 30L));
printf ("\n \ tmin ('i', 4, 11, 2, 3, 4)=% ld", min ('k', 2,0,64));
}
long min (char z, int k, ...)
{
long min (char z, int k, ...)
{ if (z = = 'i')
{ int * pi = & k +1; // настроились на первый необязательный параметр
int min = *pi; // значение 1-го необязательного параметра.
for (; k; k - -, pi ++)
Mini = mini > *pi ? *pi : mini;
return (long) mini;
}
if (z= = 'l')
{long *pl = (long *) (&k+1);
long minl = *pl;
for (; k; k - - ,pl + +)
min l = min > *pl? *pl: min l;
return (long) minl;
}
printf ("Ошибка! Неверно задан 1-й параметр ");
return 2222L;
}
Результат:
\t min ('l', 3, 10L, 20L, 30L) = 10 -->
\t min ('i', 4, 11, 2, 3, 4) = 2 -->
Ошибка! Неверно задан 1-й параметр:
Min ('k', 2, 0, 64) = 2222
Документ
Категория
Рефераты
Просмотров
24
Размер файла
51 Кб
Теги
функции
1/--страниц
Пожаловаться на содержимое документа