§13 Функции не возвращающие значение

Что такое функция и для чего она нужна?

Любая программа может содержать вполне самостоятельные подзадачи, которые могут повторяться. Функции предназначены для того, чтобы вынести часть реализации (некий алгоритм) за пределы главной функции программы (main) и обращаться к ней, по мере необходимости, по имени, из любой части программы. Это позволяет, в случае многократно повторяющегося кода, значительно сократить текст программы, а код сделать более понятным и удобным для восприятия. В объектно-ориентированном программировании функции, объявления которых являются неотъемлемой частью определения класса, называются методами. Функция может принимать аргументы (не обязательно) и возвращать некоторое значение, возможно пустое. Если функция не возвращает никакого значения (т.е. возвращает пустое значение), то такая функция называется процедурой. Однако в C++ нет понятия процедуры, а синтаксически различие между функциями возвращающими значение и не возвращающими — небольшое. Рассмотрим вначале функции не возвращающие значения (далее — просто функция).
Примечание: автор учебника, тем не менее, использует понятие процедуры.
Для того, чтобы использовать функцию её нужно объявить, определить и вызвать. Ниже приводится общая схема размещения в программе функции не возвращающей значение.

Функция в программе

Объявление функции. Прототип

void Имя_функции(список типов_параметров);

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

int main() {
    // Операция вызова
    Имя_функции(список аргументов);
    // ...
}

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

void Имя_функции(список параметров) {
    // Инструкции
}

Прототипирование

Объявление функции должно следовать непосредственно за директивами #include. Такое объявление функции называется прототипом. Если функция определяется до входа в программу main(), то прототип опускается. Но в сложных программах, в которых используется множество функций, такой подход может усложнить восприятие программного кода. Поэтому общепринятой практикой является прототипирование в начале файла исходного кода, а описание — в конце. Если текст программы велик и находится в нескольких файлах, то прототипы обычно собираются в заголовочном файле (имеющем расширение .h). В таком случае заголовок, в котором объявлена функция, должен добавляться директивой #include в том файле, в котором данная функция определена.
Если функция не возвращает значения, то перед именем функции должен находиться спецификатор типа void. После имени функции следуют () в которых указываются (через запятую) типы параметров. В случае, если функции не передаются аргументы список параметров оставляется пустым или содержит ключевое слово void. Заканчивается строка объявления — ;.
Постановка задачи
Рассмотрим использование функции на примере программы, в которой используется функция (xorSwap) обмена значениями между двумя переменными (целого типа). Функция не возвращает значение, но имеет два целочисленных параметра.
Объявление функции

void xorSwap(int&, int&);

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

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

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

int main() {
    int a, b;
    cout << "a = "; cin >> a;
    cout << "b = "; cin >> b;
    xorSwap(a, b); // Вызов функции
    cout << "Значения переменных после обмена:" << endl;
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    return 0;
}

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

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

void xorSwap(int& x, int& y) {
	if (x == y)
		return;
	x ^= y;
	y ^= x;
	x ^= y;
}

Полостью программа будет выглядеть следующим образом:
Программа 13.1

#include <iostream>
using namespace std;

void xorSwap(int&, int&);

int main() {
	int a, b;
	cout << "a = "; cin >> a;
	cout << "b = "; cin >> b;
	xorSwap(a, b);
	cout << "Значения переменных после обмена:\n"
		 << "a = " << a << endl
		 << "b = " << b << endl;
	return 0;
}

void xorSwap(int& x, int& y) {
	if (x == y)
		return;
	x ^= y;
	y ^= x;
	x ^= y;
}
a = 10
b = 5
Значения переменных после обмена:
a = 5
b = 10

Примечание. В данной программе (стр. 23-25) используется операция побитовый xor (исключающее ИЛИ) и сокращенная форма присваивания (^=). Т. е. в полной форме операция будет выглядеть так: x = x ^ y. Побитовые операции (&, ^ и |) производят логические операции над соответствующими разрядами двоичного представления целых типов. Следовательно, для вещественных типов нужен иной подход в программировании функции обмена (например, использование буферной переменной). Подробнее об операции XOR: учебник 10 кл., т. 1, п.19.

Передача аргументов по значению

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

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

В функции xorSwap (программа 13.1) значения передаются в функцию по ссылке. Ссылочные параметры связаны с объектами, а не их копиями, поэтому вызов функции в программе изменит значения переменных a и b. Подобным образом можно использовать указатели, но передача по ссылке выглядит более компактно и воспринимается визуально лучше. В программе ниже, для сравнения, используются указатели вместо ссылок.
Программа 13.1b

#include <iostream>
using namespace std;

void xorSwap(int*, int*);

int main() {
	int a = 2, b = 3;
	int *m = &a, *n = &b;
	xorSwap(m, n);
	cout << "Значения переменных после обмена:\n"
		 << "a = " << a << endl
		 << "b = " << b << endl;
	return 0;
}
void xorSwap(int *x, int *y) {
	if (*x == *y)
		return;
	*x ^= *y;
	*y ^= *x;
	*x ^= *y;
}

В отличие от примера передачи по ссылке (программа 13.1), здесь использование операции косвенной адресации (*) в определении обязательно, в противном случае мы будем иметь дело с локальной копией адреса.
Заметим, что в функцию можно передавать параметры одновременно и по ссылке, и по указателю, и по значению. Константы передаются в функцию по всем тем правилам, которые существуют для инициализации обычных переменных. Аналогично будет происходить неявное приведение типов как описано в §4.

Функции и массивы

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

#include <iostream>
using namespace std;

// Прототип
void arrGen(int*, int, int);

int main() {
	const int n = 20;
	int k, t;
	int mass[] {1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
	           11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
	cout << "k = "; cin >> k;
	cout << "t = "; cin >> t;
	// Исходный массив:
	for (int i = 0; i < n; i++) {
		cout << mass[i] << " ";
	}
	// Вызов
	arrGen(mass, k, t);
	cout << endl;
	// Модифицированный массив:
	for (int i = 0; i < n; i++) {
		cout << mass[i] << " ";
	}
	return 0;
}

// Определение
void arrGen(int *p, int x, int y) {
	for (int i = 0; i < x; i++) {
		*p = *p * y;
		p++;
	}
}
k = 10
t = 5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
5 10 15 20 25 30 35 40 45 50 11 12 13 14 15 16 17 18 19 20 

Массив, в отличие от переменных, нельзя скопировать, следовательно по значению его передавать тоже нельзя! Существует две возможности передачи массива в функцию. Первая — это передача массива как указателя на первый элемент. Данная возможность реализована в программе 13.2. Обратите внимание, что в прототипе заявлено, что первый параметр будет — указатель (int*). В основной программе функция arrGen первым аргументом имеет имя массива (mass), он же является константным указателем на первый элемент. В описании функции первый аргумент — это указатель (*p). Вторая возможность — это использовать ссылку на массив. Ниже показано как изменить исходный код программы для передачи массива по ссылке.

// Прототип
void arrGen(int[], int, int);

// Вызов
arrGen(mass, k, t);

// Определение
void arrGen(int arr[], int x, int y) {
	for (int i = 0; i < x; i++) {
		arr[i] *= y;
	}
}

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

Вопросы
  1. Для чего используются функции?
  2. Что такое прототип?
  3. Приведите примеры функций, которые не имеют параметров
  4. Что значит передача параметров по значению?
  5. Что значит передача параметра по указателю?
  6. Чем отличается передача по ссылке и указателю?
  7. Как передать в функцию массив?
Домашняя работа

Составьте только определение функции

  1. Зад. уч. №3 стр. 25. Напишите функцию, которая выводит на экран все делители переданного ей числа
  2. Зад. уч. №7 стр. 25. Напишите функцию, которая принимает параметр n и выводит на экран линию из n символов '#'
Учебник

§59

Литература
  1. Прата, Стивен. Язык программирования C++. Лекции и упражнения, 6-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильяме», 2012
  2. Липпман Б. Стенли, Жози Лажойе, Барбара Э. Му. Язык программирования С++. Базовый курс. Изд. 5-е. М: ООО "И. Д. Вильямс", 2014
  3. Эллайн А. C++. От ламера до программера. СПб.: Питер, 2015
  4. Джосаттис Н. М. Стандартная библиотека C++: справочное руководство, 2-е изд.-М.: Вильямс, 2014


Comments are closed