С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 57
Текст из файла (страница 57)
Трассировка вызова rgcd (15,123)vlv2return151231531231530rgcd(123,15)rgcd(15,3)rgcd(3,0)3Последний вызов,rgcd(3,0);удовлетворяет условию окончания. Функция возвращает наибольший общий делитель, онже возвращается и каждым предшествующим вызовом. Говорят, что значение всплывает(percolates) вверх, пока управление не вернется в функцию, вызвавшую rgcd() в первыйраз.Рекурсивные функции обычно выполняются медленнее, чем их нерекурсивные(итеративные) аналоги.
Это связано с затратами времени на вызов функции. Однако, какправило, они компактнее и понятнее.Приведем пример. Факториалом числа n является произведение натуральных чисел от 1до n. Так, факториал 5 равен 120: 1 × 2 × 3 × 4 × 5 = 120.Вычислять факториал удобно с помощью рекурсивной функции:С++ для начинающихunsigned longfactorial( int val ) {if ( val > 1 )return val * factorial( val-1 );return 1;}Рекурсия обрывается по достижении val значения 1.Упражнение 7.12Перепишите factorial() как итеративную функцию.Упражнение 7.13Что произойдет, если условием окончания factorial() будет следующее:if ( val != 0 )7.6. Встроенные функцииint min( int vl, int v2 ){return( vl < v2 ? vl : v2 );Рассмотрим следующую функцию min():}Преимущества определения функции для такой небольшой операции таковы:•как правило, проще прочесть и интерпретировать вызов min(), чем читатьусловный оператор и вникать в смысл его действий, особенно если v1 и v2являются сложными выражениями;•модифицировать одну локализованную реализацию в приложении легче, чем300.
Например, если будет решено изменить проверку на:( vl == v2 || vl < v2 )поиск каждого ее вхождения будет утомительным и с большой долей вероятностиприведет к ошибкам;•семантика единообразна. Все проверки выполняются одинаково;•функция может быть повторно использована в другом приложении.Однако этот подход имеет один недостаток: вызов функции происходит медленнее, чемнепосредственное вычисление условного оператора. Необходимо скопировать двааргумента, запомнить содержимое машинных регистров и передать управление в другоеместо программы. Решение дают встроенные функции. Встроенная функция“подставляется по месту” в каждой точке своего вызова. Например:int minVa12 = min( i, j );346С++ для начинающихзаменяется при компиляции наint minVal2 = i < j ? i : j;Таким образом, не требуется тратить время на реализацию min() в виде функции.Функция min() объявляется как встроенная с помощью ключевого слова inline передтипом возвращаемого значения в объявлении или определении:inline int min( int vl, int v2 ) { /* ...
*/ }Заметим, однако, что спецификация inline – это только подсказка компилятору.Компилятор может проигнорировать ее, если функция плохо подходит для встраиванияпо месту. Например, рекурсивная функция (такая, как rgcd()) не может быть полностьювстроена в месте вызова (хотя для самого первого вызова это возможно). Функция из1200 строк также скорее всего не подойдет. В общем случае такой механизмпредназначен для оптимизации небольших, простых, часто используемых функций. Онкрайне важен для поддержки концепции сокрытия информации при разработкеабстрактных типов данных.
Например, встроенной объявлена функция-член size() вклассе IntArray из раздела 2.3.Встроенная функция должна быть видна компилятору в месте вызова. В отличие отобычной, такая функция определяется в каждом исходном файле, где есть обращения кней. Конечно же, определения одной и той же встроенной функции в разных файлахдолжны совпадать. Если программа содержит два исходных файла compute.C и draw.C,не нужно писать для них разные реализации функции min(). Если определения функцииразличаются, программа становится нестабильной: неизвестно, какое из них будетвыбрано для каждого вызова, если компилятор не стал встраивать эту функцию.Рекомендуется помещать определение встроенной функции в заголовочный файл ивключать его во все файлы, где есть обращения к ней. Такой подход гарантирует, что длявстроенной функции существует только одно определение и код не дублируется;дублирование может привести к непреднамеренному расхождению текстов в течениежизненного цикла программы.Поскольку min() является общеупотребительной операцией, реализация ее входит встандартную библиотеку С++; это один из обобщенных алгоритмов, описанных в главе12 и в Приложении.
Функция min() реализована как шаблон, что позволяет ей работать соперандами арифметического типа, отличного от int. (Шаблоны функцийрассматриваются в главе 10.)7.7. Директива связывания extern "C" AЕсли программист хочет использовать функцию, написанную на другом языке, вчастности на С, то компилятору нужно указать, что при вызове требуются несколькоиные условия.
Скажем, имя функции или порядок передачи аргументов различаются взависимости от языка программирования.Показать, что функция написана на другом языке, можно с помощью директивысвязывания в форме простой либо составной инструкции:347С++ для начинающих// директива связывания в форме простой инструкцииextern "C" void exit(int);// директива связывания в форме составной инструкцииextern "C" {int printf( const char* ... );int scanf( const char* ... );}// директива связывания в форме составной инструкцииextern "C" {#include <cmath>}Первая форма такой директивы состоит из ключевого слова extern, за которым следуетстроковый литерал, а за ним – “обычное” объявление функции.
Хотя функция написанана другом языке, проверка типов вызова выполняется полностью. Несколько объявленийфункций могут быть помещены в фигурные скобки составной инструкции директивысвязывания – второй формы этой директивы. Скобки отмечают те объявления, к которымона относится, не ограничивая их видимости, как в случае обычной составнойинструкции.
Составная инструкция extern "C" в предыдущем примере говорит только отом, что функции printf() и scanf() написаны на языке С. Во всех остальныхотношениях эти объявления работают точно так же, как если бы они были расположенывне инструкции.Если в фигурные скобки составной директивы связывания помещается директивапрепроцессора #include, все объявленные во включаемом заголовочном файле функциирассматриваются как написанные на языке, указанном в этой директиве.
В предыдущемпримере все функции из заголовочного файла cmath написаны на языке С.Директива связывания не может появиться внутри тела функции. Следующий фрагментint main() {// ошибка: директива связывания не может появиться// внутри тела функцииextern "C" double sqrt( double );double getValue(); //правильноdouble result = sqrt ( getValue() );//...return 0;кода вызывает ошибку компиляции:}Если мы переместим директиву так, чтобы она оказалась вне тела main(), программаextern "C" double sqrt( double );int main() {double getValue(); //правильноdouble result = sqrt ( getValue() );//...return 0;откомпилируется правильно:348С++ для начинающих}Однако более подходящее место для директивы связывания – заголовочный файл, гденаходится объявление функции, описывающее ее интерфейс.Как сделать С++ функцию доступной для программы на С? Директива extern "C"// функция calc() может быть вызвана из программы на Cпоможет и в этом:extern "C" double calc( double dparm ) { /* ...
*/ }Если в одном файле имеется несколько объявлений функции, то директива связыванияможет быть указана при каждом из них или только при первом – в этом случае она// ---- myMath.h ---extern "C" double calc( double );// ---- myMath.C ---// объявление calc() в myMath.h#include "myMath.h"// определение функции extern "C" calc()// функция calc() может быть вызвана из программы на Cраспространяется и на все последующие объявления. Например:double calc( double dparm ) { // ...
}В данном разделе мы видели примеры директивы связывания extern "C" только дляязыка С. Это единственный внешний язык, поддержку которого гарантирует стандартС++. Конкретная реализация может поддерживать связь и с другими языками. Например,extern "Ada" для функций, написанных на языке Ada; extern "FORTRAN" для языкаFORTRAN и т.д. Мы описали один из случаев использования ключевого слова extern вС++. В разделе 8.2 мы покажем, что это слово имеет и другое назначение в объявленияхфункций и объектов.Упражнение 7.14exit(), printf(), malloc(), strcpy() и strlen() являются функциями из библиотекиС. Модифицируйте приведенную ниже С-программу так, чтобы она компилировалась исвязывалась в С++.349С++ для начинающихconst char *str = "hello";void *malloc( int );char *strcpy( char *, const char * );int printf( const char *, ...
);int exit( int );int strlen( const char * );int main(){/* программа на языке С */char* sstrcpy(printf(exit( 0= malloc( strlen(str)+l );s, str );"%s, world\n", s ););}7.8. Функция main(): разбор параметров команднойстрокиПри запуске программы мы, как правило, передаем ей информацию в командной строке.Например, можно написатьprog -d -o of lie dataOФактические параметры являются аргументами функции main() и могут быть полученыиз массива C-строк с именем argv; мы покажем, как их использовать.Во всех предыдущих примерах определение main() содержало пустой список:int main() { ...
}Развернутая сигнатура main() позволяет получить доступ к параметрам, которые былизаданы пользователем в командной строке:int main( int argc, char *argv[] ){...}argc содержит их количество, а argv – C-строки, представляющие собой отдельныезначения (в командной строке они разделяются пробелами). Скажем, при запускекомандыprog -d -o ofile data0argc получает значение 5, а argv включает следующие строки:argv[argv[argv[argv[argv[01234]]]]]====="prog";"-d";"-o";"ofile";"dataO";350С++ для начинающихВ argv[0] всегда входит имя команды (программы).
Элементы с индексами от 1 доargc-1 служат параметрами.Посмотрим, как можно извлечь и использовать значения, помещенные в argv. Пустьprog [-d] [-h] [-v][-o output_file] [-l limit_value]file_nameпрограмма из нашего примера вызывается таким образом:[ file_name [file_name [ ... ]]]Параметры в квадратных скобках являются необязательными. Вот, например, запускпрограммы с их минимальным количеством – одним лишь именем файла:prog chap1.docprog -l 1024 -o chap1-2.out chapl.doc chap2.docprog d chap3.docНо можно запускать и так:prog -l 512 -d chap4.docПри разборе параметров командной строки выполняются следующие основные шаги:1. По очереди извлечь каждый параметр из argv.