Имя_функции( выражение_1, выражение_2,...)

ФУНКЦИИ

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

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

Функция объявляется следующим образом:

тип имя_функции(тип1 имя_параметра_1, тип2 имя_параметра_2, ...);

Тип функции определяет тип значения, которое возвращает функция. Если тип не указан, то предполагается, что функция возвращает целое значение (int). Функция, не возвращающая значение, имеет тип void.

При объявлении функции для каждого ее параметра можно указать только его тип (например: тип функция (int, float, ...), а можно дать и его имя (например: тип функция (int а, float b, ...) ).

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

Определение функции имеет следующий вид:

тип имя_функции(тип1 имя_параметра_1, тип2 имя_параметра_2,...) { тело функции }

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

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

имя_функции( выражение_1, выражение_2,...)

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

Передача значения из вызванной функции в вызвавшую происходит с помощью оператора возврата return, который записывается в следующем формальном виде:

return выражение;

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

Например:

int f(int a, int b) { if (a > b) { printf("max = %d\n", a); return a; } printf("max = %d\n", b); return b; }

Вызвать эту функцию можно, например, следующим образом:

c = f(15, 5); c = f(d, g);

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

f(d, g);

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

Пример 1.Передача аргумента в функцию по значению.

Вычислить куб последовательности целых чисел 0, 2, 4, …18.

#include <stdio.h>

int icube(int ival); //получение куба числа

int main(void)

{

int k, irez;

for(k=0; k<20; k+=2)

{

irez=icube(k);

printf("Куб числа %d \tравен %d\n", k, irez);

}

return 0;

}

//вычисление куба

int icube(int ival)

{

return(ival*ival*ival);

}

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

Однако это легко сделать, если передавать в функцию не переменные, а их адреса.

Например:

void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }

Вызов swap(&b, &c) (здесь в функцию передаются адреса переменных b и с) приведет к тому, что значения переменных b и c поменяются местами.

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

Пример 2. Функция возвращает несколько результатов

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

Имя_функции( выражение_1, выражение_2,...) - student2.ru

Имя_функции( выражение_1, выражение_2,...) - student2.ru

#include <stdio.h>

#include <math.h>

void pol(int, int, float *, float *); //полярные координаты

int main()

{

float x, y, ro=0, fi=0;

puts("Введите x");

scanf("%f", &x);

puts("Введите y");

scanf("%f", &y);

pol(x, y, &ro, &fi);

printf("Пол. координата: ro=%f\tfi=%f", ro, fi);

return 0;

}

void pol(int a1, int a2, float *k1, float *k2)

{

*k1=sqrt(a1*a1+a2*a2);

*k2=atan(a2/a1);

}

Функции и массивы

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

Рассмотрим, как функции можно передать массив в виде параметра. Здесь возможны следующие варианты:

  1. Параметр описывается как массив.

При этом числовое значение первой размерности можно не указывать.

Например: int a[100], int b[], float c[50][30], double s[][100]

  1. Параметр описывается как указатель.

Например: int *m, int **m1 .

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

Пример 3. Указатели на одномерные массивы в качестве параметров

Функция max_vect формирует массив z, каждый элемент которого равен максимальному из соответствующих значений двух других массивов параметров (x и y). Одномерные массивы передаются в функцию через указатели.

#include <stdio.h>

void max_vect(int, int*, int*, int*);

main()

{

int a[]={1, 2, 3, 4, 5, 6, 7};

int b[]={7, 6, 5, 4, 3, 2, 1};

int c[7];

max_vect(7, a, b, c);

for (int i=0; i<7; i++)

printf("%d\t",c[i]);

}

void max_vect(int n, int *x, int *y, int *z)

{

for (int i=0; i<n; i++)

// z[i]=x[i]>y[i] ? x[i] : y[i];

*(z+i)=*(x+i)>*(y+i) ? *(x+i) : *(y+i);

}

Пример 4.Функция возвращает указатель на массив

Функция fussion формирует массив h из двух целочисленных упорядоченных по не убыванию массивов с и d. Массив h должен включать все элементы двух исходных массивов таким образом, чтобы они оказались упорядоченными по неубыванию.

#include <stdio.h>

#include <stdlib.h>

int *fusion(int, int*, int, int*);

void main()

{

int c[]={1, 3, 5, 7, 9};

int d[]={0, 2, 4, 5};

int *h; // указатель для массива с результатом

int kc=sizeof(c)/sizeof(c[0]); // количество элементов в c[0]

int kd=sizeof(d)/sizeof(d[0]); // количество элементов в d[0]

h=fusion(kc, c, kd, d);

puts("\nРезультат объединения массивов: \n");

for (int i=0; i<kc+kd; i++)

printf("%3d", h[i]);

// delete[] h; // освобождение памяти

free((void*)h);

}

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

int *fusion(int n, int* a, int m, int* b)

{

//int *x=new int[n+m]; // захватываем память

int *x=(int*)malloc((n+m)*sizeof(int));

int ia=0, ib=0, ix=0;

while (ia<n && ib<m) // цикл до конца одного из массивов

if (a[ia]>b[ib]) x[ix++]=b[ib++];

else x[ix++]=a[ia++];

if(ia>=n) //массив a[] исчерпан

while (ib<m) x[ix++]=b[ib++];

else //массив b[] исчерпан

while (ia<n) x[ix++]=a[ia++];

return x;

}

Пример 5.Функции работают с двумерными массивами. Память под массив получаем динамически.

//Найти минимальный элемент каждой строки квадратной матрицы,

//лежащий ниже главной диагонали (включая главную).

#include<stdio.h>

#include<stdlib.h>

void in_mas(int*p, int n, int m); //ввод массива

void out_mas(int*p, int n, int m); //вывод массива

int min_mas(int*p, int n, int m, int k); //нахождение минимума

void main(void)

{ int*p, i, min, N, M;

puts("Введите размер двухмерного массива");

scanf("%d%d", &N, &M);

p=(int*)malloc(N*M*sizeof(int));

in_mas(p,N,M);

out_mas(p,N,M);

printf("\n\n");

//находим минимум для каждой строки

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

{

min=min_mas(p,N,M,i);

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

}

free((void*)p);

}

void in_mas(int*p,int n,int m)

{

int i,j;

randomize();

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

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

*(p+i*m+j)=random(11)-5;

}

void out_mas(int*p,int n,int m)

{

int i,j;

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

{

printf("\n");

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

printf("%3d",*(p+i*m+j));

}

}

int min_mas(int*p, int n, int m, int k)

{

int min,j;

min=*(p+k*m); //переход на 1 элемент нужной строки

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

{

if(j<=k) //условие ниже главной диагонали

if(*(p+k*m+j)<min)

min=*(p+k*m+j);

}

return min;

}

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

Найти сумму элементов, лежащих на главной диагонали.

#include<stdio.h>

#include<stdlib.h>

#include<conio.h>

#define M 5

#define N 5

int* vvod(int, int); //заполнение матрицы случайными числами

void out (int*,int ,int ); //вывод матрицы

int kol_z(int*,int ,int ); //сумма элементов главной диагонали

void main(void)

{

int* p;

int s=0;

p=vvod(N, M);

puts("\nИсходный массив:");

out(p, N, M);

s=kol_z(p,M,N);

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

free((void*)p);

}

//заполнение матрицы случайными числами

int* vvod(int aN, int aM)

{

int*pp;

int i, j;

randomize();

pp=(int*)malloc(aN*aM*sizeof(int)); //захватываем память

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

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

*(pp+i*aM+j)=random(10);

return pp;

}

//вывод матрицы

void out(int *pp,int aN, int aM)

{

int i,j;

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

{

printf("\n");

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

printf("%3d",*(pp+i*aM+j));

}

}

//сумма элементов главной диагонали

int kol_z(int*pp,int aN ,int aM)

{

int i, j, ss=0;

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

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

if(i==j) ss+=*(pp+i*aM+j);

return ss;

}

Классы памяти

В языке Си различают четыре основных класса памяти: внешнюю (глобальную), автоматическую (локальную), статическую и регистровую память.

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

Например:

extern int a; /* Объявление a; память под переменную не резервируется */

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

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

Регистровые переменные относятся к последнему классу. Ключевое слово register говорит о том, что переменная, о которой идет речь, будет интенсивно использоваться. Если возможно, значения таких переменных помещаются во внутренние регистры микропроцессора, что может привести к более быстрой и короткой программе (разработчики компиляторов фирмы Borland утверждают, что оптимизация компиляторов данной фирмы по использованию регистровых переменных сделана так хорошо, что указание использовать переменную как регистровую может только ухудшить эффективность создаваемого машинного кода). Для регистровых переменных нельзя взять адрес; они могут быть только автоматическими с допустимыми типами int или char.

Таким образом, можно выделить четыре модификатора класса памяти: extern, auto, static, register. Они используются в следующей общей форме:

модификатор_класса_памяти тип список_переменных;

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

Пример7.Программа подсчитывает число символов и слов во вводимых строках (пробелы входят в число введенных символов). #include <stdio.h> #include <conio.h> #define ESC 27 /* 27 - ASCII-код клавиши ESC */ void CountOfLines(void) {/* Статические переменные будут сохранять старые значения при каждом новом вызове функции CountOfLines */ static int words = 0, symbols = 0; /* words-число слов, symbols-число символов */ char temp, t = 0; /* Временные переменные */ ++symbols; /* Число символов и слов выдается после нажатия клавиши <Enter> */ while ((temp = getche( )) != '\r' ) { ++symbols; /* Подсчитывается каждый символ *//* После одного или нескольких пробелов подсчитывается слово */ if ((temp == ' ') && (t == 1)) continue; if (temp == ' ') { t = 1; ++words; } else t = 0; } if (t == 1) --words; else ++words; printf ("\n Слов: %d; символов: %d\n", words, symbols); }void main(void) { puts("Для завершения программы нажмите <ESC> в начале строки"); puts("Строка не должна начинаться с пробела и с нажатия клавиши" "<Enter>"); puts("Строка не должна завершаться пробелом"); while (getche( ) != ESC) CountOfLines(); putch('\b'); putch(' '); putch('\b');}

Результаты работы этой программы:

Для завершения программы нажмите <ESC> в начале строки Строка не должна начинаться с пробела и с нажатия клавиши <Enter>Строка не должна завершаться пробелом Mouse Keyboard <Enter> Слов: 2 символов: 14<ESC>

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

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

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

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

int (*f)( );

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

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