+---------------------------+
|.-------------------------.|
|| kee_reel@blog:~/ru $ cd ||
|| ссылки контакты         ||
|| c c++ linux opengl sql  ||
|| python сети             ||
||                         ||
|.-------------------------.|
+-::---------------------::-+
.---------------------------.
 // /oooooooooooooooooooooo\\ \\ 
 // /oooooooooooooooooooooooo\\ \\ 
//-------------------------------\\
\\-------------------------------//


C. Функции

Время чтения: 6 минут

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

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

#include <stdio.h>

int add(int a, int b)
{
	int result = a + b;
	return result;
}

int square(int value)
{
	int result = value * value;
	return result;
}

void show_result(int result)
{
	printf("Result: %d\n", result);
}

int main()
{
	int result = 0;

	result = add(2, 2);
	show_result(result); // Result: 4

	result = square(4);
	show_result(result); // Result: 16

	result = add(result, square(2));
	show_result(result); // Result: 20
}

Это называется определением функции (в следующем разделе разберём подробно):

int add(int a, int b)
{
	int result = a + b;
	return result;
}

Это называется вызовом функции:

add(2, 2)
square(2)

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

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

В прошлом разделе я использовал такую функцию:

int add(int a, int b)
{
	int result = a + b;
	return result;
}

На первой строчке описана сигнатура функции. Сигнатура, это полное описание всех свойств функции, которое состоит из следующих частей.

Название функции

  • В примере это “add”.
  • Имя, которое мы будем писать чтобы вызвать эту функцию.
  • Требования к имени функции такие же, как к имени переменной.

Параметры (аргументы) функции

  • В примере это два int параметра “a” и “b”.
  • Параметры, которые мы будем передавать в функцию для того, чтобы она что-то с нимим сделала.
  • Параметры являются локальными переменными, которым присваисваиваются значения, переданные в функицю.
  • Требования к имени параметра такие же, как к имени переменной.
  • Тип параметра может быть любым.

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

  • В этом примере это “int” в начале строчки.
  • Значение, которое функция возвращает в то место, откуда её вызвали.
  • Тип возвращаемого значения может быть любым.
  • В примере функция возвращает int значение в место вызова.
  • Очень часто переменные просто исполняются, и не возвращают никаких значний – в этом случае надо использовать тип возвращаемого значения void.
  • Чтобы вернуть значение, надо написать “return ЗНАЧЕНИЕ;” (в случае void просто “return;”) – эта операция завершает выполнение функции и программа продолжает выполняться в месте вызова функции.

Определение (прототип) и реализация функции

Так, с функцией разобрались – давай теперь разберёмся как её описать в программе.

Сначала два важных понятия:

  • Определение (definition) функции – сигнатура функции (определение также называют прототипом)
  • Реализация (implementation) функции – сигнатура функции + текст функции

Описать функцию можно двумя способами:

Сначала определение, потом реализация

#include <stdio.h>

// Определение (definition)
void test();

int main()
{
	// Вызов функции test
	test();
}

// Реализация (implementation)
void test()
{
	printf("I'm function implementation!\n");
}

Смотри, есть одно важное правило:

Вызов функции должен располагаться в коде после её определения.

То есть такой код не скомпилируется:

// НЕ СКОМПИЛИРУЕТСЯ
#include <stdio.h>
int main()
{
	test();
}
void test()
{
	printf("I'm function implementation!\n");
}
// НЕ СКОМПИЛИРУЕТСЯ

Ага, понятно – поэтому там определение сначала написано. А что со вторым вариантом?

Только реализация

#include <stdio.h>

void test()
{
	printf("I'm function implementation!\n");
}

int main()
{
	test();
}

Здесь реализация функции выступает сразу и определением – и мы без проблем можем вызвать её в main().

Задание на закрепление

Создай функцию, которая возвращает int число 42 и выведи её значение в функции main().

Опиши функцию двумя способами: “сначала определение, потом реализация” и “только реализация”.

Возможные трудности

У начинающих мастеров владения функциями возникают проблемы такого характера:

  • Как мне веруть больше одного значения?
  • Почему я передаю указатель, а туда не записывается адрес на выделенную память?
  • Как объединить две похожих функции?

Давай разберём эти проблемы.

Как мне веруть больше одного значения?

Как ты успел заметить, в функции можно указать только одно возвращаемое значение. Однако, это не проблема для мастера владения функциями:

#include <stdio.h>
// В параметрах - указатели на переменные
void get_multiple_results(int value, int *a, int *b, int *c)
{
	// Через операцию разыменования достаю и записываю значения в a, b, c
	*a = *a * 2 + value;
	*b = *b / 2 + value;
	*c = *c % 2 + value;
}

int main()
{
	int a = 1, b = 2, c = 4;
	// Передаю адреса переменных a, b, c
	get_multiple_results(1, &a, &b, &c);
	printf("%d %d %d\n", a, b, c); // 3 2 1
}

Вот и пригодились указатели.

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

Вот пример кода с ошибкой:

#include <stdio.h>

void fill_arr(int *arr, int n)
{
	arr = (int*)calloc(n, sizeof(int));
	if(arr == NULL)
		return;
	for(int i = 0; i < n; i++)
		scanf("%d", &arr[i]);
}

int main()
{
	int *A = NULL;
	fill_arr(A, 5);
	A[0]; // Ошибка! A == NULL
}

Здесь проблема в том, что параметр arr это локальная переменная функции fill_arr, которая просто скопировала себе адрес переменной A.

Из-за того что arr это вообще другая переменная, в переменную A адрес на выделенную память так и не доходит. Что можно сделать?

Есть два пути:

  • Вспомнить что надо передавать адрес переменной, чтобы туда что-то записать:
#include <stdio.h>

void fill_arr(int **arr, int n)
{
	*arr = (int*)calloc(n, sizeof(int));
	if(*arr == NULL)
		return;
	for(int i = 0; i < n; i++)
		scanf("%d", &(*arr)[i]);
}

int main()
{
	int *A = NULL;
	fill_arr(&A, 5);
	// Не забываем предохраняться от NULL
	if(A == NULL)
		return 0;
	A[0]; // Всё ок!
}
  • Вспомнить что существует возвращаемое значение
#include <stdio.h>

int* fill_arr(int n)
{
	int *arr = (int*)calloc(n, sizeof(int));
	if(arr == NULL)
		return arr;
	for(int i = 0; i < n; i++)
		scanf("%d", &arr[i]);
	return arr;
}

int main()
{
	int *A = fill_arr(5);
	// Не забываем предохраняться от NULL
	if(A == NULL)
		return 0;
	A[0]; // Всё ок!
}

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

Как объединить две похожих функции?

Например, у тебя есть две функции – одна ищет в массиве максимальное положительное число, а другая минимальное положительное число:

#include <stdio.h>

int find_max(int *arr)
{
	int max_i = -1;
	for(int i = 0; i < n; i++)
		if(arr[i] > 0 && (max_i == -1 || arr[i] > arr[max_i]))
			max_i = i;
	return max_i;
}

int find_min(int *arr)
{
	int min_i = -1;
	for(int i = 0; i < n; i++)
		if(arr[i] > 0 && (min_i == -1 || arr[i] < arr[min_i]))
			min_i = i;
	return min_i;
}

int main()
{
	int A[] = {-1, 0, 2, 5, 11};
	int max_i = find_max(A), min_i = find_min(A);
	printf("%d %d\n", max_i, min_i); // 4 2
}

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

#include <stdio.h>

int find(int *arr, char is_max)
{
	int res_i = -1;
	for(int i = 0; i < n; i++)
		// Вынес, чтобы сократить запись большой проверки
		if(arr[i] >= 0)
			continue;
		if(res_i == -1 || (is_max ? arr[i] > arr[res_i] : arr[i] < arr[res_i]))
			res_i = i;
	return res_i;
}

int main()
{
	int A[] = {-1, 0, 2, 5, 11};
	int max_i = find(A, 1), min_i = find(A, 0);
	printf("%d %d\n", max_i, min_i); // 4 2
}

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

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

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

Заключение

Итого, ты узнал что такое:

  • Функция
  • Сигнатура функции
    • Имя
    • Параметры (аргументы)
    • Возвращаемое значение
  • Определение (прототип) функции
  • Реализация функции
  • Возвращение нескольких параметров
  • Передача массивов
  • Объединение функций
  • Рефакторинг

▲ В начало ▲