ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 31.03.2021
Просмотров: 333
Скачиваний: 1
Инструкция return
До сих пор (начиная с главы 2) мы использовали инструкцию return без подробных разъяснений. Напомним, что инструкция return выполняет две важные операции. Во-первых, она обеспечивает немедленное возвращение управления к инициатору вызова функции. Во-вторых, ее можно использовать для передачи значения, возвращаемого функцией. Именно этим двум операциям и посвящен данный раздел.
Завершение функции
Как вы уже знаете, управление от функции передается инициатору ее вызова в двух ситуациях: либо при обнаружении закрывающейся фигурной скобки, либо при выполнении инструкции return. Инструкцию return можно использовать с некоторым заданным значением либо без него. Но если в объявлении функции указан тип возвращаемого значения (т.е. не тип void), то функция должна возвращать значение этого типа. Только void-функции могут использовать инструкцию return без какого бы то ни было значения.
Для void-функций инструкция return главным образом используется как элемент программного управления. Например, в приведенной ниже функции выводится результат возведения числа в положительную целочисленную степень. Если же показатель степени окажется отрицательным, инструкция return обеспечит выход из функции, прежде чем будет сделана попытка вычислить такое выражение. В этом случае инструкция return действует как управляющий элемент, предотвращающий нежелательное выполнение определенной части функции.
void power(int base, int exp)
{
int i;
if(exp<0) return; /* Чтобы не допустить возведения числа в отрицательную степень, здесь выполняется возврат в вызывающую функцию и игнорируется остальная часть функции. */
i = 1;
for( ; exp; exp--) i = base * i;
cout << "Результат равен: " << i;
}
Функция может содержать несколько инструкций return. Функция будет завершена при выполнении хотя бы одного из них. Например, следующий фрагмент кода совершенно правомерен.
void f()
{
// ...
switch(с) {
case 'a': return;
case 'b': // ...
case 'c': return;
}
if(count<100) return;
// ...
}
Однако следует иметь в виду, что слишком большое количество инструкций return может ухудшить ясность алгоритма и ввести в заблуждение тех, кто будет в нем разбираться. Несколько инструкций return стоит использовать только в том случае, если они способствуют ясности функции.
Возврат значений
Каждая функция, кроме типа void, возвращает какое-нибудь значение. Это значение явно задается с помощью инструкции return. Другими словами, любую не void-функцию можно использовать в качестве операнда в выражении. Следовательно, каждое из следующих выражений допустимо в C++.
х = power(у);
if(max(х, у)) > 100) cout << "больше";
switch(abs (х)) {
Несмотря на то что все не void-функции возвращают значения, они необязательно должны быть использованы в программе. Самый распространенный вопрос относительно значений, возвращаемых функциями, звучит так: "Поскольку функция возвращает некоторое значение, то разве я не должен (должна) присвоить это значение какой-нибудь переменной?". Ответ: нет, это необязательно. Если значение, возвращаемое функцией, не участвует в присваивании, оно попросту отбрасывается (теряется).
Рассмотрим следующую программу, в которой используется стандартная библиотечная функция abs().
#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
int i;
i = abs(-10); // строка 1
cout << abs(-23); // строка 2
abs(100); // строка
return 0;
}
Функция abs() возвращает абсолютное значение своего целочисленного аргумента. Она использует заголовок <cstdlib>. В строке 1 значение, возвращаемое функцией abs(), присваивается переменной i. В строке 2 значение, возвращаемое функцией abs(), ничему не присваивается, но используется инструкцией cout. Наконец, в строке 3 значение, возвращаемое функцией abs(), теряется, поскольку не присваивается никакой другой переменной и не используется как часть выражения.
Если функция, тип которой отличен от типа void, завершается в результате обнаружения закрывающейся фигурной скобки, то значение, которое она возвращает, не определено (т.е. неизвестно). Из-за особенностей формального синтаксиса C++ не void-функция не обязана выполнять инструкцию return. Это может произойти в том случае, если конец функции будет достигнут до обнаружения инструкции return. Но, поскольку функция объявлена как возвращающая значение, значение будет таки возвращено, даже если это просто "мусор". В общем случае любая создаваемая вами не void-функция должна возвращать значение посредством явно выполняемой инструкции return.
Выше упоминалось, что void-функция может иметь несколько инструкций return. То же самое относится и к функциям, которые возвращают значения. Например, представленная в следующей программе функция find_substr() использует две инструкции return, которые позволяют упростить алгоритм ее работы. Эта функция выполняет поиск заданной подстроки в заданной строке. Она возвращает индекс первого обнаруженного вхождения заданной подстроки или значение -1, если заданная подстрока не была найдена. Например, если в строке "Я люблю C++ " необходимо отыскать подстроку "люблю", то функция find_substr() возвратит число 2 (которое представляет собой индекс символа "л" в строке "Я люблю C++ ").
#include <iostream>
using namespace std
int find_substr(char *sub, char *str);
int main()
{
int index;
index = find_substr("три", "один два три четыре");
cout << "Индекс равен " << index; // Индекс равен 9.
return 0;
}
// Функция возвращает индекс искомой подстроки или -1, если она не была найдена.
int find_substr(char *sub, char *str)
{
int t;
char *p, *p2;
for(t=0; str[t]; t++) {
p = &str[t]; // установка указателей
p2 = sub;
while(*p2 && *p2==*p) { // проверка совпадения
p++; p2++;
}
/* Если достигнут конец р2-строки (т.е. подстроки), то подстрока была найдена. */
if(!*p2) return t; // Возвращаем индекс подстроки.
}
return -1; // Подстрока не была обнаружена.
}
Результаты выполнения этой программы таковы.
Индекс равен 9
Поскольку искомая подстрока существует в заданной строке, выполняется первая инструкция return. В качестве упражнения измените программу так, чтобы ею выполнялся поиск подстроки, которая не является частью заданной строки. В этом случае функция find_substr() должна возвратить значение -1 (благодаря второй инструкции return).
Функцию можно объявить так, чтобы она возвращала значение любого типа данных, действительного для C++ (за исключением массива: функция не может возвратить массив). Способ объявления типа значения, возвращаемого функцией, аналогичен тому, который используется для объявления переменных: имени функции должен предшествовать спецификатор типа. Спецификатор типа сообщает компилятору, значение какого типа данных должна возвратить функция. Указываемый в объявлении функции тип должен быть совместимым с типом данных, используемым в инструкции return. В противном случае компилятор отреагирует сообщением об ошибке.
Функции, которые не возвращают значений (void-функции)
Как вы заметили, функции, которые не возвращают значений, объявляются с указанием типа void. Это ключевое слово не допускает их использования в выражениях и защищает от неверного применения. В следующем примере функция print_vertical() выводит аргумент командной строки в вертикальном направлении (вниз) по левому краю экрана. Поскольку эта функция не возвращает никакого значения, в ее объявлении использовано ключевое слово void.
#include <iostream>
using namespace std;
void print_vertical(char *str);
int main(int argc, char *argv[])
{
if(argc==2) print_vertical(argv[1]);
return 0;
}
void print_vertical(char *str)
{
while(*str)
cout << *str++ << '\n';
}
Поскольку print_vertical() объявлена как void-функция, ее нельзя использовать в выражении. Например, следующая инструкция неверна и поэтому не скомпилируется.
х = print_vertical("Привет!"); // ошибка
Важно! В первых версиях языка С не был предусмотрен тип void. Таким образом, в старых С-программах функции, не возвращающие значений, по умолчанию имели тип int. Если вам придется встретиться с такими функциями при переводе старых С-программ "на рельсы" C++, просто объявите их с использованием ключевого слова void, сделав их void-функциями.
Функции, которые возвращают указатели
Функции могут возвращать указатели. Указатели возвращаются подобно значениям любых других типов данных и не создают при этом особых проблем. Но, поскольку указатель представляет собой одно из самых сложных (или небезопасных) средств языка C++, имеет смысл посвятить ему отдельный раздел.
Чтобы вернуть указатель, функция должна объявить его тип в качестве типа возвращаемого значения. Вот как, например, объявляется тип возвращаемого значения для функции f(), которая должна возвращать указатель на целое число.
int *f();
Если функция возвращает указатель, то значение, используемое в ее инструкции return, также должно быть указателем. (Как и для всех функций, return-значение должно быть совместимым с типом возвращаемого значения.)
В следующей программе демонстрируется использование указателя в качестве типа возвращаемого значения. Это — новая версия приведенной выше функции find_substr(), только теперь она возвращает не индекс найденной подстроки, а указатель на нее. Если заданная подстрока не найдена, возвращается нулевой указатель.
// Новая версия функции find_substr().
// которая возвращает указатель на подстроку.
#include <iostream>
using namespace std;
char *find_substr(char *sub, char *str);
int main()
{
char *substr;
substr = find_substr("три", "один два три четыре");
cout << "Найденная подстрока: " << substr;
return 0;
}
// Функция возвращает указатель на искомую подстроку или нуль, если таковая не будет найдена.
char *find_substr(char *sub, char *str)
{
int t;
char *p, *p2, *start;
for(t=0; str[t]; t++) {
p = &str[t]; // установка указателей
start = p;
р2 = sub;
while(*р2 && *p2==*p) { // проверка совпадения
р++; р2++;
}
/* Если достигнут конец р2-подстроки, то эта подстрока была найдена. */
if(!*р2) return start; // Возвращаем указатель на начало найденной подстроки.
}
return 0; // подстрока не найдена
}
При выполнении этой версии программы получен следующий результат.
Найденная подстрока: три четыре
В данном случае, когда подстрока "три" была найдена в строке "один два три четыре", функция find_substr() возвратила указатель на начало искомой подстроки "три", который в функции main() был присвоен переменной substr. Таким образом, при выводе значения substr на экране отобразился остаток строки, т.е. "три четыре".
Многие поддерживаемые C++ библиотечные функции, предназначенные для обработки строк, возвращают указатели на символы. Например, функция strcpy() возвращает указатель на первый аргумент.
Прототипы функций
Прототип объявляет функцию до ее первого использования.
До сих пор в приводимых здесь примерах программ прототипы функций использовались без каких-либо разъяснений. Теперь настало время поговорить о них подробно. В C++ все функции должны быть объявлены до их использования. Обычно это реализуется с помощью прототипа функции. Прототипы содержат три вида информации о функции:
■ тип возвращаемого ею значения;
■ тип ее параметров;
■ количество параметров.
Прототипы позволяют компилятору выполнить следующие три важные операции.
■ Они сообщают компилятору, код какого типа необходимо генерировать при вызове функции. Различия в типах параметров и значении, возвращаемом функцией, обеспечивают различную обработку компилятором.
■ Они позволяют C++ обнаружить недопустимые преобразования типов аргументов, используемых при вызове функции, в тип, указанный в объявлении ее параметров, и сообщить о них.
■ Они позволяют компилятору выявить различия между количеством аргументов, используемых при вызове функции, и количеством параметров, заданных в определении функции.
Общая форма прототипа функции аналогична ее определению за исключением того, что в прототипе не представлено тело функции.
type func_name(type parm_name1, type parm_name2,
...,
type parm_nameN);
Использование имен параметров в прототипе необязательно, но позволяет компилятору идентифицировать любое несовпадение типов при возникновении ошибки, поэтому лучше имена параметров все же включать в прототип функции.
Чтобы лучше понять полезность прототипов функций, рассмотрим следующую программу. Если вы попытаетесь ее скомпилировать, то получите от компилятора сообщение об ошибке, поскольку в этой программе делается попытка вызвать функцию sqr_it() с целочисленным аргументом, а не с указателем на целочисленное значение (согласно прототипу функции). Ошибка состоит в недопустимости преобразования целочисленного значения в указатель.
/* В этой программе используется прототип функции, который позволяет осуществить строгий контроль типов.
*/
void sqr_it(int *i); // прототип функции
int main()
{
int х;
х = 10;
sqr_it(x); // *** Ошибка *** — несоответствие типов!
return 0;
}
void sqr_it(int *i)
{
*i=*i * *i;
}
Важно! Несмотря на то что язык С допускает прототипы, их использование не является обязательным. Дело в том, что в первых версиях С они не применялись. Поэтому при переводе старого С-кода в С++-код перед компиляцией программы необходимо обеспечить наличие прототипов абсолютно для всех функций.
Подробнее о заголовках
В начале этой книги вы узнали о существовании стандартных заголовков C++, которые содержат информацию, необходимую для ваших программ. Несмотря на то что все вышесказанное — истинная правда, это еще не вся правда. Заголовки C++ содержат прототипы стандартных библиотечных функций, а также различные значения и определения, используемые этими функциями. Подобно функциям, создаваемым программистами, стандартные библиотечные функции также должны "заявить о себе" в форме прототипов до их использования. Поэтому любая программа, в которой используется библиотечная функция, должна включать заголовок, содержащий прототип этой функции.
Чтобы узнать, какой заголовок необходим для той или иной библиотечной функции, следует обратиться к справочному руководству, прилагаемому к вашему компилятору. Помимо описания каждой функции, там должно быть указано имя заголовка, который необходимо включить в программу для использования выбранной функции.
Рекурсия
Рекурсивная функция — это функция, которая вызывает сама себя.
Рекурсия, которую иногда называют циклическим определением, представляет собой процесс определения чего-либо на собственной основе. В области программирования под рекурсией понимается процесс вызова функцией самой себя. Функцию, которая вызывает саму себя, называют рекурсивной.
Классическим примером рекурсии является вычисление факториала числа с помощью функции factr(). Факториал числа N представляет собой произведение всех целых чисел от 1 до N. Например, факториал числа 3 равен 1x2x3, или 6. Рекурсивный способ вычисления факториала числа демонстрируется в следующей программе. Для сравнения сюда же включен и его нерекурсивный (итеративный) эквивалент.
#include <iostream>
using namespace std;
int factr(int n);
int fact(int n);
int main()
{
// Использование рекурсивной версии.
cout << "Факториал числа 4 равен " << factr(4);
cout << '\n';
// Использование итеративной версии.
cout << "Факториал числа 4 равен " << fact(4);
cout << '\n';
return 0;
}
// Рекурсивная версия.
int factr(int n)
{
int answer;
if(n==1) return(1);
answer = factr(n-1)*n;
return(answer);
}
// Итеративная версия.
int fact(int n)
{
int t, answer;
answer =1;
for(t=1; t<=n; t++) answer = answer* (t);
return (answer);
}
Нерекурсивная версия функции fact() довольно проста и не требует расширенных пояснений. В ней используется цикл, в котором организовано перемножение последовательных чисел, начиная с 1 и заканчивая числом, заданным в качестве параметра: на каждой итерации цикла текущее значение управляющей переменной цикла умножается на текущее значение произведения, полученное в результате выполнения предыдущей итерации цикла.