4310

Функции на языке Си

Контрольная

Информатика, кибернетика и программирование

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

Русский

2012-11-16

84 KB

4 чел.

Функции

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

В общем случае функции в языке Си необходимо объявлять. Объявление функции (т.е. описание заголовка) должно предшествовать ее использованию, а определение функции (т.е. полное описание) может быть помещено как после тела программы (т.е. функции 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. Функция возвращает несколько результатов

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

#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 - указатель на функцию. Для любой функции ее имя (без скобок и аргументов) является указателем на эту функцию.

Аргументы функции main( )

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

Любая такая строка представляется в виде:

    переменная = значение\0

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

Назовем аргументы функции main( ) соответственно: argc, argv и env (возможны и любые другие имена). Тогда допустимы следующие описания:

    main( )

    main(int argc)

    main(int argc, char *argv[ ] )

    main(int argc, char *argv[ ], char *env[ ] )

Предположим, что на диске A: есть некоторая программа prog.exe. Обратимся к ней следующим образом:

    A:\>prog.exe file1 file2 file3 <Enter>

Тогда argv[0] - это указатель на строку A:\prog.exe, argv[1] - на строку file1 и т.д. На первый фактический аргумент указывает argv[1], а на последний - argv[3]. Если argc=1, то после имени программы в командной строке параметров нет. В нашем примере argc=4.

Библиотечные функции

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

    #include <включаемый_файл_типа_h>

Например:

    #include <condio.h>

Существуют также средства для расширения и создания новых библиотек с программами пользователя.

Рекурсия

Рекурсией называется такой способ вызова, при котором функция обращается к самой себе.

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

Пример 8. Программа демонстрирует использование рекурсивной функции для вычисления факториала. Отметим, что определение функции factorial( ) может находиться и после функции main( ), но в этом случае функция factorial( ) должна быть объявлена перед функцией main( ), т.е. до main( ) необходимо поместить строку: long factorial(int);

#include <stdio.h>

#include <values.h>

#include <process.h>

long factorial(int value)         /* Рекурсивная функция */

{

   long result = 1;

   if (value != 0)

   {

       result = factorial(value - 1);

     /* Проверка возможности вычисления факториала */

       if (result > MAXLONG / (value + 1))

       {

           fprintf(stderr, "Очень большое число\n");

           getch( );             /* Ожидание нажатия клавиши */

           exit (1);

       }

       result *= value;

   }

   return(result);

}

/* Рекурсивное вычисление факториала числа value */

void main(void)

{

   int value;             /* Факториал этого значения вычисляется */

   long result;           /* Переменная для результата */

   puts("Факториал какого числа?");

   scanf("%d", &value);

   result = factorial(value);

   printf("Результат: %ld\n", result);

   getch( );              /* Ожидание нажатия клавиши */

}

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

Факториал какого числа? 10<Enter>

Результат: 362880


 

А также другие работы, которые могут Вас заинтересовать

30695. Типы носителей информации и их особенности 109.15 KB
  В современном обществе, где информация проблема носителей информации встала очень остро, так как объемы информации, генерируемые пользователями, возрастают в геометрической прогрессии.
30696. Мотив дороги в произведениях отечественной классики 19 века 25.31 KB
  Есенина Мотив дороги звучит в двух значительнейших произведениях 19 века. Образ дороги в этом произведении не выходит на первый план. Образ дороги здесь – традиционный символ жизненного пути.
30697. Стихотворное новаторство В.В. Маяковского. Чтение наизусть и анализ стихотворения «А вы могли бы?» 12.76 KB
  Чтение наизусть и анализ стихотворения А вы могли бы. Тема этого стихотворения – желание и способность лирического героя изменить в корне обыденную ни чем не примечательную жизнь причем сделать это так как никто другой и не подумал бы. Идея же заключается в названии стихотворения и в последних строках:А вы ноктюрн сыграть могли быНа флейте водосточных трубКаждая строка этого стихотворения – вызов каждое слово – экспрессивно и ярко; при своей лаконичности стихотворение оставляет более глубокое впечатление чем многие более длинные...
30698. Психологизм изображения внутреннего мира личности в лирике А.А.Ахматовой (на примере 3–4 стихотворений по выбору экзаменуемого). Земное и вечное в стихотворении «Приморский сонет» 15.95 KB
  Облик героини поэзии Ахматовой предстает в житейской простоте но в нем заключается пафос сильной личности. Ее лирическая героиня не отражает персональной судьбы Ахматовой а отражает проявление женской доли женского голоса. Предметный мир воспринимается уже в ином виде: три ступеньки кажутся вечностью любимый прием Ахматовой – оксюморон темный дом свечи горевшие равнодушножелтым огнем. То в образе лирической героини проступают черты самой Ахматовой которая не верит что все происходит именно с ней – насмешницей любимицей всех...
30699. Стихотворение А. Блока «Незнакомка» 12.25 KB
  Блока Незнакомка Тема страшного мира звучит в третьем томе стихотворений А. Это лишь внешняя видимая сторона страшного мира. При этом все зримые образы материального мира у Блока обретают символический подтекст. Повествование о ресторанной встрече превращается в рассказ о человеке угнетенном пошлостью окружающего мира его стремлении освободиться от этого.
30700. АНАЛИЗ 1 ГЛАВЫ 1 ЧАСТИ «МАСТЕРА И МАРГАРИТЫ» 20.62 KB
  Патриавшие пруды – это центр Москвы давно пользующийся дурной славой 3 Время года месяц: Весна май 4 Время суток: Небывало жаркий закат 5 странности возникающие в это время на Патриарших: одновременная икота литераторов; отсутствие в жаркое время отдыхающих под липами; появление прозрачного человека в клетчатом пиджаке; чувство необоснованного страха появившегося у Берлиоза. 6 Главные герои: Воланд Берлиоз Иван Бездомный. 8 Главная тема беседы: и никого из сынов Божьих не было в том числе и Иисуса Берлиоз Имейте...
30701. Анализ романа Замятина Мы 19.86 KB
  Солженицын1 История создания и смысл названия романа: Роман создавался вскоре после возвращения автора из Англии в революционную Россию в 1920 году по некоторым сведениям работа над текстом продолжалась и в 1921 году. Первая публикация романа состоялась за границей в 1924 году. В случае с названием романа Мы и с героем романа это утверждение особенно справедливо.
30702. Приём антитезы в произведениях русской литературы 2-й половины XIX века. Ф.М. Достоевский «Преступление и наказание» 132.77 KB
  I антитеза ос6новное идейно композиционный принцип романа Преступление и наказание II функции антитезы. Приём антитезы при создании образа главного героя: А замечательная внешность Раскольникова и одежда нищего; Б описание каморки и страшная теория Раскольникова; В бесчеловечность теории и её неприятие сердцем сны Раскольникова. Приём антитезы в основе системы персонажей: А двойники Раскольникова Лужин и Свидригайлов; Б правда Сони Мармеладовой и правда Раскольникова.
30703. И. А. Бунин. Тема любви 15.98 KB
  Тема любви. В теме любви Бунин раскрывается как человек удивительного таланта тонкий психолог умеющий передать состояние души раненной любовью. На протяжении столетий многие художники слова посвящали свои произведения великому чувству любви и каждый из них находил чтото неповторимое индивидуальное этой теме. Эта тайна бытия становится темой бунинского рассказа Грамматика любви1915.