Возврат значения из функции

Чистые функции в языке Си

Чистая функция, это функция, которая:

  1. является детерминированной;
  2. не обладает побочными эффектами.

 

int f(int x) {
    int y = 0;
    int z = 0;
    y = 2 * x;
    z = y + 1;
    return z / 3;

Возврат нескольких разнотипных значений

Что делать, если функции нужно вернуть сразу два или три значения, причем, возможно, разных типов?

Увы, просто так этого сделать нельзя.

Такие ситуации можно разделить на два характерных случая:

  1. Возвращаемые значения фактически представляют собой единый объект.
    Пример 1: функции getPixelColor(x, y) передаются координаты точки на текстуре, возвращается ее цвет.
  2. Возвращаемые данные не вполне связаны между собой (являются различными объектами или просто набором слабо связанных данных).
    Пример 2: функция getStudentMark() получает с клавиатуры строчку с фамилией студента и его оценку по информатике.

Пример 1

Итак, функции getPixelColor(x, y) передаются координаты точки на текстуре, надо вернуть ее цвет.

Цвет в модели RGB TrueColor описывается тремя целыми беззнаковыми числами в диапазоне от 0 до 255.

Здесь цвет точки однозначно представляет собой объект, который должен быть выделен в структуру данных.

 

typedef struct {
    unsigned char r, g, b; //компоненты цвета red, green, blue
} tColor; 

Как только создана такая Си-структура, вопрос передачи цвета из функции решен:

tColor getPixelColor(int x, int y)
{
    tColor color; //локальная переменная, куда будем доставать цвет.
    //... достать цвет с текстуры (без подробностей)
    return color;
}
 
 

Пример 2

Функция getStudentMark() должна получить с клавиатуры строчку с фамилией студента и его оценку по информатике. При этом для студентов уже существует структура данных:

 

typedef struct {
    int studentID; //идентификатор студента
    char lastName[30]; //фамилия
    char name[30]; //имя
    unsigned int groupNumber; //номер группы
} tStudent; 

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

Фамилия -- лишь часть этой структуры данных, остальное нам в этой функции заполнить неоткуда. Заполнить пустыми значениями? А вдруг мы в месте вызова забудем, что в возвращенном экземпляре структуры правдива лишь фамилия?

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

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

 

typedef struct {
    char lastName[30]; //фамилия
    unsigned int mark; //оценка
} tStudentMark; 

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

Функция будет выглядеть так:

 

tStudentMark getStudentMark()
{
    tStudentMark studentMark; //локальная переменная, куда будем доставать фамилию и оценку.
    //... достать фамилию и оценку (без подробностей)
    return studentMark;

В месте вызова:

 

char lastName[20];
int mark;
tStudentMark studentMark; //переменная, которая нужна только для разбора значений из функции
studentMark = getStudentMark();
lastName = studentMark.lastName;
mark = studentMark.mark; 

Текста получилось много, зато мы сохранили "чистоту функции".

 

Передача через нарушение чистоты функции

Есть еще два способа вернуть из функции значение:

  1. Передавать параметры не по значению, а по адресу, а изнутри функции просто менять их значения.
    Пример: функция getStudentMark() получает в качестве параметров адреса строки и оценки, считывает с клавиатуры строчку с фамилией студента и его оценку по информатике, и сохраняет в параметры.
  2. Сохранять возвращаемые значения в глобальных переменных.
    Стандартной функции open(filename, O_RDONLY) передается имя файла и флаги доступа, функция возвращает открывает файл, и возвращает его дескриптор (число-идентификатор, необходимое для последующих обращений к открытому файлу), однако может случиться ошибка доступа, причем код ошибки важен.

Пример

Итак, функция getStudentMark() получает в качестве параметров адрес строки и адрес оценки, считывает с клавиатуры строчку с фамилией студента и его оценку по информатике, и сохраняет в параметры.

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

 

void getStudentMark(char lastName[], int *mark)
{
    scanf("%s%d", lastName, mark);

В месте вызова:

 

char lastName[20];
int mark;
getStudentMark(lastName, &mark); 

Плюсы: отказались от "одноразовой" структуры данных, уменьшили длину кода.

Минусы: корректность памяти определяется местом вызова. Внутри функции хорошо бы проверять корректность данных адресов памяти.