Функции

Функция – это самостоятельная единица программы, которая спроектирована для реализации конкретной подзадачи. Функция может быть многократно вызвана из другого участка программы, это позволяет исключить повтор одних и тех же действий. Функции также помогают логически выстроить программу.
Функция определяется таким образом:
1. Тип возвращаемого значения;
2. Имя функции;
3. Информация о формальных аргументах;
4. Тело функции.

Формальный аргумент — это переменная в вызываемой функции.

Общая форма записи функции:

<Тип возвращаемого значения> <Имя функции> (тип1 agr1, тип2 agr2, ...) {
        Тело функции;
}

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

Давайте посмотри пример функции:

int SUM(int a, int b) {
	int sum = a + b;
	return sum;
}

Функция SUM принимает два значения типа int и возвращает их сумму. a, b - формальные аргументы, а sum - локальная переменная.

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

int s = SUM(x, y);

В переменную s будет записано значение, которое вернет функция SUM.

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

Полный код:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include<stdio.h>
#include<stdlib.h>
 
int SUM (int a, int b) {
	int sum = a + b;
	return sum;
}
 
int main() {
	int x, y;
	scanf_s("%i%i", &x, &y);
	int s = SUM(x, y);
	printf("%i", s);
	return 0;
}

При передаче аргументов происходит их копирование. Это значит, что любые изменения, которые функция производит над переменными, имеют место быть только внутри функции. Чтобы изменить фактические значения, нужно передать указатель на этот элемент.
Например, функция sqr возвращает квадрат числа и изменяет фактическую переменную x на 5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include<stdio.h>
#include<stdlib.h>
 
int sqr(int *a) {
	int b = *a;
	*a = *a + 5;  // !
	return b * b;
}
 
int main() {
	int x;
	int *p = &x;
	scanf_s("%i", &x);
	int ans = sqr(p);
	printf("ans =  %i\nnew x = %i", ans, x);
	return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include<stdio.h>
#include<stdlib.h>
 
void sqr(int x) {
	printf("%i\n", x * x);
}
 
int main() {
	int x;
	scanf_s("%i", &x);
	sqr(x);
	x += 10;
	sqr(x);
	return 0;
}

Передача массива в функцию

Так как имя массива - это указатель, поэтому передача массива в функцию равна передачи указателя.
Пример передачи массива в функцию:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include<stdio.h>
#include<stdlib.h>
 
void F(int *mas, int n) {  // или <int mas[]>
	for (int i = 0; i < n; i++)	
		mas[i] = 100;
}
 
int main() {
	int n, a[10];
	scanf_s("%i", &n);
	for (int i = 0; i < n; i++)	
		scanf_s("%i", &a[i]);
	F(a, n);
	return 0;
}

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

Чтобы передать функции двумерный массив, необходимо четко указать размеры массива:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include<stdio.h>
#include<stdlib.h>
 
void print(int mas[][10], int n, int m) {
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			printf("%i ", mas[i][j]);
		}
		printf("\n");
	}
}
 
int main() {
	int n, m, a[10][10];
	scanf_s("%i%i", &n, &m);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++) {
			scanf_s("%i", &a[i][j]);
		}
	}
	print(a, n, m);
	return 0;
}

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

void F(int **mas, int n, int m) {
	...
}

Передача структуры в функцию

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include<stdio.h>
#include<stdlib.h>
 
struct point {
	int x;
	int y;
};
 
void F(struct point* a) {
	a->x = 100; 
	a->y = 22;
}
 
int main() {
	struct point P;
	P.x = 10; P.y = 4;
	F(&P);
	printf("%i %i", P.x, P.y);
	return 0;
}

Если мы хотим передать структуру по значению, то изменяем строку 17 на F(P), а функцию так:

void F(struct point a) {
	a.x = 100; 
	a.y = 22;
}

Передача массива структур полностью совпадает с передачей обычного массива:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#include<stdio.h>
#include<stdlib.h>
 
struct point {
	int x;
	int y;
};
 
void print(struct point* a, int n) {
	for (int i = 0; i < n; i++) {
		printf("%i %i\n", a[i].x, a[i].y);
	}
}
 
int main() {
	struct point P[10];
	int n;
	scanf_s("%i", &n);
	for (int i = 0; i < n; i++) {
		scanf_s("%i%i", &P[i].x, &P[i].y);
	}
	print(P, n);
	return 0;
}

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

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

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

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

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

Задача: дано натуральное число n > 1. Выведите все простые множители этого числа в порядке неубывания с учетом кратности.
Код на Си:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include<stdio.h>
#include<stdlib.h>
 
void F(int n, int i) {
	if (n == 1) {
		return;
	}
	if (n % i == 0) {
		printf("%i ", i);
		F(n / i, i);
	}
	else {
		F(n, i + 1);
	}
	return;
}
 
int main() {
	int n;
	scanf_s("%i", &n);
	F(n, 2);
	return 0;
}

Чтобы подробнее разобраться с действием рекурсии, можно посмотреть как используется рекурсия в DFS - обход графа в глубину.


Code.C © Copyright Павел Калашников 2021
обратная связь code.c04@mail.ru