Функции в языке C. Передача аргументов по значению и по ссылке

Объявление и определение функций

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

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

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

#include <stdio.h>
 
// объявление функции
float median (int a, int b); 
 
int main () {
    int num1 = 18, num2 = 35;
    float result;
 
    printf("%10.1f\n", median(num1, num2));
    result = median(121, 346);
    printf("%10.1f\n", result);
    printf("%10.1f\n", median(1032, 1896));
}
 
// определение функции
float median (int n1, int n2) {
    float m;
 
    m = (float) (n1 + n2) / 2;
    return m;
}

В данном случае в начале программы объявляется функция median. Объявляются тип возвращаемого ею значения ‒ float, количество и типы параметров (int a, int b). Обратите внимание, когда объявляются переменные, то их можно группировать: int a, b;. Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (int a, int b).

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

Функция median() возвращает число типа float. Оператор return возвращает результат выполнения переданного ему выражения; после return функция завершает свое выполнение, даже если далее тело функции имеет продолжение. Функция median() вычисляет среднее значение от двух целых чисел. В выражении (float) (n1 + n2) / 2 сначала вычисляется сумма двух целых чисел, результат преобразуется в вещественное число и только после этого делится на 2. Иначе мы бы делили целое на целое и получили целое (в таком случае дробная часть просто усекается).

В теле main функция median() вызывается три раза. Результат выполнения функции не обязательно должен быть присвоен переменной.

Эту же программу можно написать и так:

#include <stdio.h>
 
float median (int n1, int n2) {
    float m;
 
    m = (float) (n1 + n2) / 2;
    return m;
}
 
int main () {
    int num1 = 18, num2 = 35;
    float result;
 
    printf("%10.1f\n", median(num1, num2));
    result = median(121, 346);
    printf("%10.1f\n", result);
    printf("%10.1f\n", median(1032, 1896));
}

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

Напишите функцию, возвращающую куб числа, переданного ей в качестве аргумента. Вызовите эту функцию с разными аргументами.

Статические переменные

В языке программирования C существуют так называемые статические переменные. Они могут быть как глобальными, так и локальными. Перед именем статической переменной пишется ключевое слово static.

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

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

#include <stdio.h>
 
int hello();
 
int main() {
    printf(" - %d-й вызов\n", hello());
    printf(" - %d-й вызов\n", hello());
    printf(" - %d-й вызов\n", hello());
}
 
int hello () {
    static count = 1;
    printf("Hello world!");
    return count++;
}

Результат:

Hello world! - 1-й вызов 
Hello world! - 2-й вызов 
Hello world! - 3-й вызов 

В этом примере в функции hello() производится подсчет ее вызовов.

Передача аргументов по ссылке

В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.

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

#include <stdio.h>
 
void epow(int *, int);
 
int main() {
    int x = 34, y = 6;
 
    epow(&x, 3);
    epow(&y, 1);
    printf("%d %d\n", x, y);  // 34000 60
}
 
void epow(int *base, int pow) {
    while (pow > 0) {
        *base = *base * 10;
        pow--;
    }
}

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

Когда epow() вызывается в main, то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов epow(x, 3) привел бы к ошибке, а вызов epow(&x, 3) ‒ правильный, т. к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main указатель и передавать именно его (в данном случае сама переменная p содержит адрес):

int x = 34, y = 6;
int *p;
 
p = &x;
epow(p, 3);
p = &y;
epow(p, 1);
printf("%d %d\n", x, y);

Кроме того, следует знать, что функция может возвращать адрес.

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

Напишите программу, в которой помимо функции main были бы еще две функции: в одной вычислялся факториал переданного числа, в другой ‒ находился n-ый элемент ряда Фибоначчи (n ‒ параметр функции). Вызовите эти функции с разными аргументами.

Курс с решением задач:
pdf-версия


Основы языка C. Курс




Все разделы сайта