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

Язык Си / Функции в языке Си

 

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

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

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

 

Определение функции

Каждая функция в языке Си должна быть определена, то есть должны быть указаны:

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

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

 
 
 
 
 
 
ТипВозвращаемогоЗначения ИмяФункции(СписокФормальныхАргументов)
{
   ТелоФункции;
  ...
  return(ВозвращаемоеЗначение);
}


Пример: Функция сложения двух вещественных чисел
1
2
3
4
5
6
float function(float x, float z)
{
  float y;
  y=x+z;
  return(y);
}


В указанном примере возвращаемое значение имеет тип float. В качестве возвращаемого значения в вызывающую функцию передается значение переменной y. Формальными аргументами являются значения переменных x и z.

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

Различают системные (в составе систем программирования) и собственные функции.

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

Собственные функции — это функции, написанные пользователем для решения конкретной подзадачи.

Разбиение программ на функции дает следующие преимущества:

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

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

 

Вызов функции

Общий вид вызова функции

 
Переменная = ИмяФункции(СписокФактическихАргументов);


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

 

Возврат в вызывающую функцию

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

 
return(ВозвращаемоеЗначение);

 
return ВозвращаемоеЗначение;


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

Оператор return также завершает выполнение функции и передает управление следующему оператору в вызывающей функции. Оператор return не обязательно должен находиться в конце тела функции.

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

 
return;


Пример: Посчитать сумму двух чисел.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define _CRT_SECURE_NO_WARNINGS // для возможности использования scanf
#include <stdio.h>
// Функция вычисления суммы двух чисел
int sum(int x, int y) // в функцию передаются два целых числа
{
  int k = x + y;  // вычисляем сумму чисел и сохраняем в k
  return k;       // возвращаем значение k
}
int main()
{
  int a, r;      // описание двух целых переменных
  printf("a= ");
  scanf("%d", &a); // вводим a
  r = sum(a, 5);    // вызов функции: x=a, y=5
  printf("%d + 5 = %d", a, r); // вывод: a + 5 = r
  getchar(); getchar(); // мы использовали scanf(),
  return 0;  // поэтому getchar() вызываем дважжы
}

Результат выполнения
Сумма двух чисел

В языке Си нельзя определять одну функцию внутри другой.

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define _CRT_SECURE_NO_WARNINGS // для возможности использования scanf
#include <stdio.h>
int sum(intint);   // сигнатура
int main()
{
  int a, r;
  printf("a= ");
  scanf("%d", &a);
  r = sum(a, 5);    // вызов функции: x=a, y=5
  printf("%d + 5 = %d", a, r);
  getchar(); getchar();
  return 0;
}
int sum(int x, int y) // семантика
{
  int k;
  k = x + y;
  return(k);
}


 

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

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

Рекурсия — вызов функции из самой функции.

Пример рекурсивной функции — функция вычисления факториала.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define _CRT_SECURE_NO_WARNINGS // для возможности использования scanf
#include <stdio.h>
int fact(int num)  // вычисление факториала числа num
{
  if (num <= 1)   return 1;  // если число не больше 1, возвращаем 1
  else return num*fact(num - 1);  // рекурсивный вызов для числа на 1 меньше
}
// Главная функция
int main() 
{
  int a, r;
  printf("a= ");
  scanf("%d", &a);
  r = fact(a);    // вызов функции: num=a
  printf("%d! = %d", a, r);
  getchar(); getchar();
  return 0;
}

Результат выполнения
Результат рекурсивного вычисления факториала

Более подробно рекурсивные функции рассмотрены в этой статье.

Математические функции

Математические функции хранятся в стандартной библиотеке math.h. Аргументы большинства математических функций имеют тип double. Возвращаемое значение также имеет тип double.
Углы в тригонометрических функциях задаются в радианах.

Основные математические функции стандартной библиотеки.

Функция Описание
int abs(int x) Модуль целого числа x
double acos(double x) Арккосинус x
double asin(double x) Арксинус x
double atan(double x) Арктангенс x
double cos(double x) Косинус x
double cosh(double x) Косинус гиперболический x
double exp(double x) Экспонента x
double fabs(double x) Модуль вещественного числа
double fmod(double x, double y) Остаток от деления x/y
double log(double x) Натуральный логарифм x
double log10(double x) Десятичный логарифм x
double pow(double x, double y) x в степени y
double sin(double x) Синус x
double sinh(double x) Синус гиперболический x
double sqrt(double x) Квадратный корень x
double tan(double x) Тангенс x
double tanh(double x) Тангенс гиперболический x

Особенности использования функций в языке C++ рассмотрены в этой статье.


Назад: Язык Си

Комментариев к записи: 22

  • 3D Mountain Bike
    В языке СИ нет требования, чтобы определение функции обязательно предшествовало ее вызову. Определения используемых функций могут следовать за определением функции main, перед ним, или находится в другом файле.

    • Елена Вставская
      Согласна отчасти. В языке Си нет требования, чтобы определение функции было до вызова. Но это требование среды разработки Visual Studio. Для того, чтобы в Visual Studio программа корректно откомпилировалась, до вызова функции должна быть описана хотя бы сигнатура функции. В противном случае при компиляции возникнет ошибка. Если функция находится в другом файле, и этот файл добавляется в проект, то до вызова функции также должна быть описана сигнатура функции. Способом не включать сигнатуру функции в файл является подключение заголовочного файла, внутри которого содержится описание функции, с помощью директивы #include. В этом случае всё содержимое заголовочного файла вставляется в начало файла кода на этапе компиляции.

  • Алексей
    Добрый день! Елена, подскажите плз, возможен ли вариант double main(), float main(), char main(), string main() и т.д.? И в чём будет практическое отличие, например, int main() и int main(void)? Как это отобразиться на финальном результате программы?

    • Елена Вставская
      Тип возвращаемого значения функции main() - это значение, возвращаемое ей операционной системе. Операционная система анализирует целочисленное значение, поэтому используется int main() {...} Возможен также вариант функции main() не возвращающей значение. При этом ее можно записать как void main() {...} В этом случае последняя команда return 0; в функции main() отсутствует. Насчет аргументов, которые могут передаваться в функцию main() можно посмотреть здесь.

  • Алексей
    void main() и int main(void) дадут одинаковый результат? В обоих случаях не будет возвращаться результат?

    • Елена Вставская
      То, что написано слева от имени функции - это тип возвращаемого значения. В первом случае функция не возвращает значения - void, во втором случае возвращает целочисленное значение - int. То, что записано внутри круглых скобок - это перечень аргументов. В приведенном Вами примере оба случая - пустые круглые скобки или (void) не содержат аргументов.

  • Добрый день. При изучении языка Си у меня возник вопрос, каким образом написаны функции стандартных библиотек. К примеру, функции ввода-вывода. При помощи только операторов языка Си, или еще, что-то используется, например другие функции. Тогда как эти функции созданы? Или есть какие-то первичные функции, которые написаны при помощи основных примитивов языка Си (операторы, определение переменных и т.д. без применения каких либо функций)?   Можно ли например увидеть код функции printf(), или getch()?

  • Валерий
    Елена, добрый день. Мне попался примерно такой программный текст на Си: Основная программа:
    1
    2
    func(0,1);      // Вызов функции func()

    Функция:
    1
    2
    3
    func(bool x=TRUE, bool y=TRUE)   { // удивительно то, что формальным параметрам задают значения

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

    • Елена Вставская
      Здравствуйте, Валерий! Действительно, формальным аргументам функции могут быть присвоены значения по умолчанию. Эти значения будут использоваться в том случае если они не переданы в функцию при вызове. Например, в Вашем случае можно вызвать функцию func(0); // равносильно вызову func(0,1); или func(); // равносильно вызову func(1,1); То есть если в функции есть значения по умолчанию, то значения фактических параметров для них могут не передаваться. Но значения по умолчанию могут иметь только формальные параметры, находящиеся справа в перечне параметров. Иными словами, если для аргумента задано значение по умолчанию, то правее его аргумент без заданного значения по умолчанию стоять не может. Также некорректным будет вызов функции func(,0); // компилироваться не будет Это свойство стало доступно в языке C++, но не было доступно в Си. Подробнее смотрите здесь.

  • Елена,у меня возник вопрос ,есть ли ограничение на количество формальных/фактических параметров функции? Просто написал программу , вычисляющую интеграл методом Симпсона , где содержится 3 функции(функция ввода данных,функция реализующая заданный алгоритм и функция вывода данных), отмечу , что в функции алгоритма использовал указатель на функцию , мол, препод отдельно требовал вычислить значения y[i] ,чтобы функция вызывалась для этого отдельно.  Это всё я то сделал , но вскоре захотелось чуть намудрить и сделать ,чтобы в функции вывода было не только значение интеграла , но и выводилась таблица , содержащая расчёты x[i],y[i], проблема в том , что добавляю дополнительный формальный параметр и все катится к чертям

    • Елена Вставская
      Я не знаю ограничения на количество передаваемых аргументов. Скорее всего, это будет определяться свободной областью памяти в стеке. Единственное ограничение - если Вы используете аргументы со значениями по умолчанию, то они обязательно должны находиться в конце в списке аргументов. А так - зависит от кода.

  • Написал код согласно первому примеру темы в компиляторе DevC++:
    1
    2
    3
    4
    5
    6
    7
    8
    void main()
    {}
    float function(float x, float z)
    {
    float y;
    y=x+z;
    return (y);
    }
    Почему-то командная строка упрямо возвращает мне единицу

    • Елена Вставская
      Функцию я вижу, а вот вызова ее - нет.
      1
      2
      3
      4
      void main()
      {
      printf("%f", function(3,2));
      }
      Тогда должно появиться 5.000000


        • Елена Вставская
          Потому что цель этой статьи - показать, как использовать функции, на простом примере.

  • Зелимхан
    Елена, а может ли функция вернуть два значения? Причем разного типа... допустим Int и Char... К примеру:
    1
    2
    3
    intchar funcName(void){
    return(25, "Saratov");
    }
    Понятно что такая семантика не будет работать, но как сделать, что то похожее? если это возможно...

    • Елена Вставская
      Функция, к сожалению, может возвращать только одно значение. Но действительно, возникают ситуации, когда требуется передать несколько значений. В этом случае значения (кроме одного), которые должны быть вычислены функцией, передаются в общем списке аргументов как местодержатели. Причем передача таких аргументов производится по ссылке: prog-cpp.ru/cpp-amp/. Например, функция должна посчитать минимум и максимум:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      #include <iostream>
      using namespace std;
      void minimax(int a, int b, int c, int &min, int &max)
      {
        min = a;
        max = a;
        if (b < min) min = b;
        else max = b;
        if (c < min) min = c;
        if(c > max) max = c;
        return;
      }
      int main()
      {
        int min, max;
        minimax(3, 8, 4, min, max);
        cout << "min = " << min << endl;
        cout << "max = " << max << endl;
        cin.get();
        return 0;
      }

  • Арсений
    Как написать функцию bool Even(int K) логического типа, возвращающую True, если целый параметр K является четным, и False в противном случае.

  • Здраствуйте, у меня есть функция которая принимает два значения, могу ли я передать ей одно значение?

    • Елена Вставская
      Зависит от конкретной реализации. Например, если второй аргументы задан по умолчанию, то можете.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *