Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология Образование Политология Производство Психология Стандартизация Технологии |
При выполнении этой версии программы получен следующий результат.
Найденная подстрока: три четыре В данном случае, когда подстрока " три" была найдена в строке " один два три четыре" , функция 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++ содержат прототипы стандартных библиотечных функций, а также различные значения и определения, используемые этими функциями. Подобно функциям, создаваемым программистами, стандартные библиотечные функции также должны " заявить о себе" в форме прототипов до их использования. Поэтому любая программа, в которой используется библиотечная функция, должна включать заголовок, содержащий прототип этой функции. Чтобы узнать, какой заголовок необходим для той или иной библиотечной функции, следует обратиться к справочному руководству, прилагаемому к вашему компилятору. Помимо описания каждой функции, там должно быть указано имя заголовка, который необходимо включить в программу для использования выбранной функции. Сравнение старого и нового стилей объявления параметров функций Если вам приходилось когда-либо разбираться в старом С-коде, то вы, возможно, обратили внимание на необычное (с точки зрения современного программиста) объявление параметров функции. Этот старый стиль объявления параметров, который иногда называют классическим форматом, устарел, но его до сих пор можно встретить в программах " эпохи раннего С". В C++ (и обновленном С-коде) используется новая форма объявлений параметров функций. Но если вам придется работать со старыми С-программами и, особенно, если понадобится переводить их в С++-код, то вам будет полезно понимать и форму объявления параметров, " выдержанную" в старом стиле. Объявление параметра функции согласно старому стилю состоит из двух частей: списка параметров, заключенного в круглые скобки, который приводится после имени функции, и собственно объявления параметров, которое должно находиться между закрывающей круглой скобкой и открывающей фигурной скобкой функции. Например, это " новое" объявление (т.е. по новому стилю) Float f(int a, int b, char ch) {... Будет выглядеть с использованием старого стиля несколько по-другому. Float f(a, b, ch) int а, b; char ch; {... Обратите внимание на то, что в классической форме после указания имени типа в списке может находиться несколько параметров. В новой форме объявления это не допускается. В общем случае, чтобы преобразовать объявление параметров из старого стиля в новый (С++-стиль), достаточно внести объявление типов параметров в круглые скобки, следующие за именем функции. При этом помните, что каждый параметр должен быть объявлен отдельно, с собственным спецификатором типа. Рекурсия Рекурсивная функция — это функция, которая вызывает сама себя. Рекурсия — это последняя тема, которую мы рассмотрим в этой главе. Рекурсия, которую иногда называют циклическим определением, представляет собой процесс определения чего-либо на собственной основе. В области программирования под рекурсией понимается процесс вызова функцией самой себя. Функцию, которая вызывает саму себя, называют рекурсивной. Классическим примером рекурсии является вычисление факториала числа с помощью функции 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 < < ''; // Использование итеративной версии. cout < < " Факториал числа 4 равен " < < fact(4); cout < < ''; 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 и заканчивая числом, заданным в качестве параметра: на каждой итерации цикла текущее значение управляющей переменной цикла умножается на текущее значение произведения, полученное в результате выполнения предыдущей итерации цикла. Рекурсивная функция factr() несколько сложнее. Если она вызывается с аргументом, равным 1, то сразу возвращает значение 1. В противном случае она возвращает произведение factr(n-1) * n. Для вычисления этого выражения вызывается метод factr() с аргументом n-1. Этот процесс повторяется до тех пор, пока аргумент не станет равным 1, после чего вызванные ранее методы начнут возвращать значения. Например, при вычислении факториала числа 2 первое обращение к методу factr() приведет ко второму обращению к тому же методу, но с аргументом, равным 1. Второй вызов метода factr() возвратит значение 1, которое будет умножено на 2 (исходное значение параметра n). Возможно, вам будет интересно вставить в функцию factr() инструкции cout, чтобы показать уровень каждого вызова и промежуточные результаты. Когда функция вызывает сама себя, в системном стеке выделяется память для новых локальных переменных и параметров, и код функции с самого начала выполняется с этими новыми переменными. Рекурсивный вызов не создает новой копии функции. Новыми являются только аргументы. При возвращении каждого рекурсивного вызова из стека извлекаются старые локальные переменные и параметры, и выполнение функции возобновляется с " внутренней" точки ее вызова. О рекурсивных функциях можно сказать, что они " выдвигаются" и " задвигаются". Следует иметь в виду, что в большинстве случаев использование рекурсивных функций не дает значительного сокращения объема кода. Кроме того, рекурсивные версии многих процедур выполняются медленнее, чем их итеративные эквиваленты, из-за дополнительных затрат системных ресурсов, связанных с многократными вызовами функций. Слишком большое количество рекурсивных обращений к функции может вызвать переполнение стека. Поскольку локальные переменные и параметры сохраняются в системном стеке и каждый новый вызов создает новую копию этих переменных, может настать момент, когда память стека будет исчерпана. В этом случае могут быть разрушены другие (" ни в чем не повинные" ) данные. Но если рекурсия построена корректно, об этом вряд ли стоит волноваться. Основное достоинство рекурсии состоит в том, что некоторые типы алгоритмов рекурсивно реализуются проще, чем их итеративные эквиваленты. Например, алгоритм сортировки Quicksort довольно трудно реализовать итеративным способом. Кроме того, некоторые задачи (особенно те, которые связаны с искусственным интеллектом) просто созданы для рекурсивных решений. Наконец, у некоторых программистов процесс мышления организован так, что им проще думать рекурсивно, чем итеративно. При написании рекурсивной функции необходимо включить в нее инструкцию проверки условия (например, if-инструкцию), которая бы обеспечивала выход из функции без выполнения рекурсивного вызова. Если этого не сделать, то, вызвав однажды такую функцию, из нее уже нельзя будет вернуться. При работе с рекурсией это самый распространенный тип ошибки. Поэтому при разработке программ с рекурсивными функциями не стоит скупиться на инструкции cout, чтобы быть в курсе того, что происходит в конкретной функции, и иметь возможность прервать ее работу в случае обнаружения ошибки. Рассмотрим еще один пример рекурсивной функции. Функция reverse() использует рекурсию для отображения своего строкового аргумента в обратном порядке. // Отображение строки в обратном порядке с помощью рекурсии. #include < iostream> using namespace std; void reverse(char *s); Int main() { char str[] = " Это тест"; reverse(str); return 0; } // Вывод строки в обратном порядке. void reverse(char *s) { if(*s)reverse(s+1); else return; cout < < *s; } Функция reverse() проверяет, не передан ли ей в качестве параметра указатель на нуль, которым завершается строка. Если нет, то функция reverse() вызывает саму себя с указателем на следующий символ в строке. Этот " закручивающийся" процесс повторяется до тех пор, пока той же функции не будет передан указатель на нуль. Когда, наконец, обнаружится символ конца строки, пойдет процесс " раскручивания", т.е. вызванные ранее функции начнут возвращать значения, и каждый возврат будет сопровождаться " довыполнением" метода, т.е. отображением символа s. В результате исходная строка посимвольно отобразится в обратном порядке. Популярное:
|
Последнее изменение этой страницы: 2016-03-17; Просмотров: 1177; Нарушение авторского права страницы