Борис Пахомов Санкт Петербург бхв петербург 2013 удк 004. 4 Ббк 32. 973. 26018. 2 П
Скачать 17.38 Mb.
|
функций В процессе программной реализации алгоритмов часто возникает необходимость выполнения повторяющихся действий на разных группах данных. Например, требуется вычислять синусы заданных величин или начислять заработную плату работникам. Ясно, что неразумно всякий раз, когда надо вычислить синус какого-то аргумента или начислить зарплату какому-то работнику, создавать заново соответствующую программу именно под конкретные данные. Напрашивается вывод, что этот процесс надо как-то параметризовать, те. создать параметрическую программу, которая могла бы, например, вычислять синус от любого аргумента, получая извне его конкретное значение, или создать программу, которая бы начисляла зарплату любому работнику, получая данные конкретного работника. Такие программы, созданные с использованием формальных значений своих параметров, при передаче им конкретных значений параметров возвращают пользователю результаты расчетов. Их называют функциями по аналогии с математическими функциями. Если в математике определена некая функция y = f (x 1 , x 2 ,..., тона конкретном наборе данных {x 11 , x 21 ,..., x N1 } эта функция возвратит вычисленное ею значение y1 = f (x 11 , x 21 ,..., x N1 ). В данном случае можем сказать, что аргументы x 1 , x 2 ,..., x N — это формальные параметры функции f (),a x 11 , x 21 ,..., x N1 — их конкретные значения. Функция в С объявляется почти аналогичным образом задается тип возвращаемого ею значения (из рассмотренного материала мы знаем, что переменные могут иметь типы int , float , long и т. д. Тип значения, возвращаемого функцией, может быть таким, как и тип переменных после задания типа возвращаемого значения задается имя функции (как и для математической функции. Затем в круглых скобках указываются ее аргументы — формальные параметры, каждый из которых должен быть описан так, как если бы он описывался в программе самостоятельно вне функции. Это только первая часть объявления функции в языке СВ языке С типом функции может быть специальный тип void() , когда функция ничего не возвращает. Такая функция вырождается в подпрограмму — конструкцию, которая просто производит некоторые вычисления 58 Часть I. Изучение языка С/С++ Далее формируется тело функции — программный код, реализующий тот алгоритм, который и положено выполнять определяемой функции. Например, объявим условную функцию расчета зарплаты одного работника float salary(int TabNom, int Mes) { здесь должен быть программный код расчета зарплаты значение вычисленной зарплаты Тип возвращаемого значения — float , т. к. сумма зарплаты, в общем случае, число нецелое. Имя функции — salary . У функции два формальных параметра и оба целого типа табельный номер работника и номер месяца, за который должен производиться расчет. Особым признаком функции является наличие оператора return() , который возвращает результат расчетов. После того как такая функция разработана, пользоваться ею можно также, как мы пользуемся математической функцией. Для данного примера мы смогли бы записать float y; или float или int tn=1001; int ms=12; float Во всех случаях при обращении к функции, мы в ее заголовочную часть подставляли вместо формальных параметров их конкретные значения. Каков внутренний механизм параметризации программы и превращения ее в функцию Мы описываем в заголовке формальные параметры и затем в теле функции используем их сих объявленными именами при создании программного кода так, как будто известны их значения. Это возможно благодаря тому, что компилятор, когда начнет компилировать функцию, соотнесет с каждым ее параметром определенный адрес некоторого места в так называемой стековой памяти, созданной специально для обеспечения вызова подпрограмм из других программ. А функция — это ведь тоже своего рода подпрограмма, да еще и возвращающая некоторое значение, а не только получающая какой-то результат расчетов. Размер такой адресованной области для каждого параметра определяется типом описанного формального параметра. Выделяется место и для будущего возвращаемого результата. В теле функции будет построен программный код, работающий, когда речь идет о формальных параметрах, с адресами необычной, а стековой памяти. Когда мы, обращаясь к функции, передаем ей фактические значения параметров, то эти значения пересылаются по тем адресам стека, которые были определены для формальных параметров (те. кладутся на "полочки" в стеке, отведенные для формальных параметров. Но программный код тела функции как рази работает с этими "полочками, содержащими параметры. Поскольку тело строится так, что оно работает с "полочками, то остается только класть на них разные данные и получать соот- Глава 4. Создание и использование функций ветствующие результаты. Вот это и осуществляется, когда мы каждый раз передаем функции конкретные значения ее параметров. Отсюда можно сделать выводы поскольку передаваемые функции значения пересылаются в стековую память (те. там формируется их копия, то сама функция, работая со стеком и ни с чем другим, не может изменять значения переменных, которые подставляются в ее заголовочную часть вместо формальных параметров. В примере мы писали float y=salary(tn,ms) , подставляя вместо формальных параметров и Mes значения переменных tn и ms . И мы утверждаем, что значения tn и ms не изменятся. Если же передавать функции не значения переменных, а их адреса, то переменные, адреса которых переданы в качестве фактических параметров, смогут изменяться в теле функции. Ведь по адресу можно записать все, что угодно, где бы он ни находился (в стеке или в обычной памяти. Функции в основной программе должны описываться в том месте программы, чтобы компилятор мог ее обработать до момента ее использования в программе. В частности, если функцию описать до начала самой программы (до main() ), либо текст функции подключается к main() оператором #include (который тоже стоит раньше main() в тексте, если функция расположена где-то в другом файле, то проблем с компиляцией программы не будет. Некоторые понятия, касающиеся функций, мы рассмотрим в этой же главе дальше. Создание некоторых функций Перейдем теперь от столь пространного введения к созданию некоторых функций и проверке их работы в основной программе. Ввод строки с клавиатуры Создадим функцию, вводящую строку символов с клавиатуры и возвращающую длину введенной строки. Такая функция getline() представлена в листинге 4.1. Листинг Возвращает длину введенной строки с учетом '\0' cимвола; lim — максимальное количество символов, которое можно ввести в строку s[] */ int getline(char s[],int lim) { int c,i; for(i=0; i 60 Часть I. Изучение языка С/С++ Входным параметром функции является lim — ограничитель на количество вводимых в строку символов. Дело в том, что в языке С строка символов представляется в виде массива символов (об этом мы говорили в предыдущей главе, а любой массив имеет свою конкретную размерность (количество элементов. Поэтому если символы будут вводиться в массив s[] , размерность которого указана в вызывающей программе, то мы должны задавать параметр lim , значение которого не должно превосходить размерности массива (те. определенной длины строки. При определении функции можно писать s[] , не указывая конкретной размерности, что удобно, т. к. такую функцию можно использовать в различных случаях, задавая разные размерности. Далее идет знакомый нам обычный цикл ввода по символу, организованный с помощью оператора Введенный функцией getchar() символ присваивается очередному элементу массива s[] , чем и формируется строка. Ввод обеспечивается необходимостью вычисления условия продолжения/завершения цикла (цикл идет попеременной В момент вычисления этого выражения требуется ввести один символ с клавиатуры, иначе выражение не может быть вычислено. Цикл ввода может завершиться при нарушении хотя бы одного из выражений, связанных операцией "и ": номер введенного символа ( i ) превзойдет ограничитель в условии стоит потому что номер последнего элемента массива, размерность которого lim , будет lim-1 , а надо еще оставить место и для признака конца строки \0 ); будет введен признак конца ввода (он должен быть задан в вызывающей программе так, чтобы был известен в этой функции ввод строки завершится, когда будет введен признак конца строки символ Первым в сложном условии продолжения/окончания цикла стоит выражение i — формировать порядковый номер элемента массива, в который будет записываться очередной введенный символ. Глава 4. Создание и использование функций Оператор return(i) возвращает количество введенных символов. Здесь следует различать возвращаемые функцией значения. Результат ввода возвращается не только через возврат количества введенных символов, но и через массив который играет роль выходного параметра. Это возможно, потому что s (точнее это адрес первого элемента массива, как определено в языке С. Мы уже говорили, что в теле функции можно изменять значения тех переменных, которые передают в функцию не свои значения, а свои адреса. Массив передан в функцию своим начальным адресом. Вот мы и изменяли значение переменной Когда определяют функцию как подпрограмму, возвращающую обязательно какое- то значение, то имеют ввиду, что речь идет о значениях, которые возвращает оператора не о выходных параметрах. Если бы наша функция не возвращала количество введенных символов, то она "ничего бы не возвращала, и тогда бы мы определили тип возвращаемого значения как void . Функция, которая имеет тип void , не возвращает ничего. Функция может что-то возвращать, ноне иметь совсем параметров. Тогда при ее создании пишут, например, float aaa(void) , а обращение к ней пойдет как float Итак, мы заметили, что параметры функции могут быть входными и выходными, и что не следует путать сними возвращаемые значения, которые идут через оператор Детально посмотреть, как работает функция или любая другая программа, можно, воспользовавшись программой-отладчиком (Debugger), о которой мы говорили в предыдущей главе. Напомним, что включить его можно в любой точке программы, щелкнув мышью в поле подшивки редактора текстов (Text Editor). При этом в поле подшивки редактора появится красный кружочек. Убрать точку останова (Breakpoint) можно, повторно щелкнув на красном кружочке. Запуск программы на выполнение должен осуществляться либо с помощью нажатия клавиши 62 Часть I. Изучение языка С/С++ Рис. 4.1. Подключение отладчика к программе Функция выделения подстроки из строки Пример программы с функцией substr() представлен в листинге 4.2. Листинг 4.2 void substr(char v[],char s[],int n,int m) { й элемент находится в массиве нам месте int i,j; for(i=0,j=n-1; j<(n-1+m); i++,j++) v[i]=s[j]; v[i]='\0'; } Эта функция с именем substr() ничего не возвращает (тип возвращаемого значения, а на свой вход получает строку символов char вспомним, что строка символов в языке С задается массивом символов, порядковый номер символа, с которого требуется выделить подстроку ( int n ), количество символов, которое требуется выделить ( int m ). А выходным параметром будет выделенная подстрока. Алгоритм очень прост в цикле участвуют две переменные i и j . Цикл организован с помощью оператора for . Здесь мы имеем пример того, что в первом (инициализирующем) выражении оператора for может быть более одной переменной (или одного выражения. Такие переменные должны отделяться друг от друга запятой, ко Глава 4. Создание и использование функций торую в этом случае называют "операция Запятая. Соответственно ив третьем выражении оператора for , где наращиваются переменные цикла, имеется более одной переменной, которые также разделены запятой. Цикл начинается заданием значений переменных цикла индекс i начинается сну- ля, т. к. массив формируется с нулевого элемента. В нашем случае индекс i — это порядковый номер символов, заносимых в массив Переменная j начинается ст. к. с ее помощью станут извлекаться элементы из массива s[] (те. из выделяемой строки. Первый элемент (в данном случае — символ) надо извлечь с места n-1 , поскольку пользователь этой функции задает нумерацию в естественном порядке, те. считает, что строка начинается с первого, а нес нулевого символа. Тело for состоит всего из одного оператора v[i]=s[j] . В нем происходит пересылка символа из входной строки с места j в выходную строку на место i . Итак будет продолжаться до тех пор, пока условие продолжения/окончания цикла не нарушится, пока значение переменной j , меняясь в третьем выражении от цикла к циклу, не станет равным (n-1+m) , те. не изменится m раз. Это будет означать, что все необходимые символы из s[] пересланы в v[] . Осталось только выполнить требование языка Св отношении признака конца строки символов добавить вконец символ Функция копирования строки в строку Функция copy() показана в листинге 4.3. Листинг 4.3 void copy(char save[],char line[]) { int i=0; while((save[i]=line[i]) != '\0') i++; } Эта функция похожа на предыдущую ( substr() ), но пересылка символов начинается с нулевого элемента входного массива line[] в нулевой элемент выходного массива. Цикл организован с помощью оператора while . Поскольку на входе имеется строка символов, то она обязательно заканчивается символом '\0' . В условии окончания цикла имеется выражение save[i]=line[i]) != '\0' Чтобы вычислить это выражение, потребуется, во-первых, переслать сначала й символ из входного массива line[] в й элемент выходного массива save[] и после этого его значение проверить на совпадение с Если совпадения не будет, выполнится тело while : индекс элемента массива возрастет на единицу, после чего станет готовым к тому, чтобы по нему переслать сле- 64 Часть I. Изучение языка С/С++ дующий символ изв. Поскольку эта функция ничего не возвращает, то отсутствует оператор return() . Как только будет передан символ \0 , цикл прекратится и программа "провалится" на закрывающую тело while фигурную скобку. Это означает, что функция завершилась. Головная программа для проверки функций getline(), substr(), Составим теперь головную программу для проверки работы функций getline() , substr() , copy() . Эта программа приведена в листинге 4.4. Листинг 4.4 // 4.1_2011.cpp #include "stdafx.h" #include #include #include #define eof -1 признак конца ввода (Ctrl+z) #define maxline 1000 размерность массивов (максимальная длина строк) #define from 2 константа для выделения подстроки (с этого символа будет начинаться выделение) */ #define howmany 3 константа для выделения подстроки (столько символов будет выделено) */ //-----substr(s,n,m)---------------------------------------- void substr(char v[],char s[],int n,int m) { й элемент находится в массиве нам месте int i,j; for(i=0,j=n-1; j<(n-1+m); i++,j++) v[i]=s[j]; v[i]='\0'; } //------------------------------------------------------------- int getline(char s[],int lim) { int c,i; for(i=0; i Глава 4. Создание и использование функций return(i); } Копирование строки в строку- void copy(char save[],char line[]) { int i=0; while((save[i]=line[i]) != '\0') i++; } //-------------------------------------------------------- int main() { char s[maxline],v[maxline],w[maxline]; printf("Enter your string\n"); int i=getline(s,maxline); copy(v,s); substr(w,v,from,howmany); if((i-1) < from) { printf("Length of the entered string is not enough for extraction from it"); getch(); exit(0); } printf("Entered string = %s\n",s); printf("Copied string .= %s\n",v); printf("substring..... = %s\n",w); _getch(); } Смысл приведенной основной программы ясен из комментария к ней. Заметим только, что здесь встретилась новая библиотечная функция exit() , которая прерывает выполнение программы. Чтобы ее использовать, надо подключить файл stdlib.h. Так как строка символов может быть разной длины, то приходится проверять, достаточно ливней символов, чтобы выделить подстроку с указанием количества выделяемых символов и номера символа, с которого начнется выделение if((i-1) < Напомним, что длину строки возвращает функция getline() , а количество выделяемых символов мы задали с помощью оператора #define В выражении if((i-1) < from) мы записали i-1 , чтобы длина проверялась без учета символа. Если длина введенной строки меньше номера символа, с которого надо выделять подстроку, то, естественно, надо об этом сообщить пользователю что мы и делаем) и завершить программу. Это делает функция exit() (можно было 66 Часть I. Изучение языка С/С++ бы добавить к программе блок возврата на повторный ввод новой строки, ноу нас была другая задача познакомиться с работой функций и новой функцией exit() ). Результат работы основной программы приведен на рис. 4.2. Рис 4.2. Результат работы программы листинга 4. 4 Внешние и внутренние переменные Функции можно создавать и без параметров, если воспользоваться внешними переменными. Внешняя переменная — это переменная, значение которой известно во всех функциях, объявленных после нее, в том числе ив самой функции Этот прием "беспараметризации" удобно применять тогда, когда в действительности у функции надо вводить столько параметров, что это затруднит ее понимание, или когда две функции обмениваются общими данными, не вызывая друг друга. Удобно это применять и тогда, когда в теле функции существуют массивы, требующие инициализации. Если массив, объявленный в теле функции, требует инициализации, то каждый раз при входе в функцию эта инициализация будет происходить, что продлит время выполнения программы. Лучше объявить такой массив вне функции, но так, чтобы он был известен в этой функции и один раз был про- инициализирован (те. массив надо объявить как внешнюю переменную по отношению к данной функции. Следует сказать, что существуют и внутренние (или локальные) переменные (их еще называют "автоматическими. Это такие переменные, которые объявлены в теле какого-либо оператора ( if , while , for , do...while ) или в теле функции. Такие переменные, как говорят, локализуются в блоке объявления, те. известны только в самом этом блоке и неизвестны за его пределами. Например, можно писать for(int i=0; i<10; i++) {какие-то операторы} i=0; В этом случае первая переменная i известна только в цикле for , а в выражении это будет уже другая переменная, т. к. ей компилятор присвоит совсем другой адрес. Если внешняя переменная объявлена в некотором другом файле, подключаемом с помощью оператора #include , то она должна быть объявлена ив той программе, которую мы составляем, нос атрибутом extern . Такая переменная может Глава 4. Создание и использование функций инициализироваться при объявлении только один рази вместе ее основного объявления. Например, в файле F1.h имеем int объявление с инициализацией. В нашей программе мы должны выполнить #include "Чтобы подключить библиотечный файл, его имя нужно указать в угловых скобках, а имя файла, созданного пользователем, заключают в двойные кавычки. В этом случаев программе мы должны написать extern int a; ноне, поскольку никакое другое значение присвоить при этом "дополнительном" объявлении уже нельзя. Приведем виды программ getline() , и main() , которые вместо параметров используют значения внешних переменных (листинг 4.5). Основная программа будет вычислять строку максимальной длины из введенного множества строк. Листинг 4.5 // 4.2_2011.cpp #include "stdafx.h" #include #include #include #define eof -1 признак конца ввода (Ctrl+z) #define maxline 1000 длина максимально возможной строки внешние переменные, но объявлены в этом же программном файле char line[maxline]; char save[maxline]; int max; Объявление функций Формирование строки ввода с клавиатуры в line[]. getline() возвращает длину введенной строки lim — максимальное количество символов, которое можно ввести в строку line[] */ int getline() { int c,i; extern char line[]; Использование глобальной переменной в функции т. к. описание находится в этом же файле, тов функции такую переменную можно было бы не описывать. Номы это сделали для общего случая. */ for(i=0; i 68 Часть I. Изучение языка С/С++ i++; line[i]='\0'; return(i); } Копирование строки в строку- void copy() { extern char line[]; Писать общий extern для нескольких объявляемых переменных нельзя extern char save[]; int i=0; while((save[i]=line[i]) != '\0') i++; } Основная программа выбирает строку наибольшей длины из всех, вводимых с клавиатуры int main() { int len; Длина текущей строки extern int max; Здесь будет храниться длина наибольшей из х сравниваемых по длине строк extern char save[]; max=0; printf("Enter some strings >\n"); while((len=getline()) >1) { Когда введем Ctrl+z или Enter, то длина строки станет = 1 за счет учета признака конца '\0' */ if(len > max) { max=len; copy(); } } Когда мы нажмем комбинацию клавиш (конец ввода, то getline() выдаст единичную длину (с учетом символа '\0') и мы попадем сюда if(max > 0) Была введена хоть одна строка printf("Max's string = %s\n",save); _getch(); } Результат работы программы приведен на рис. 4.3. Глава 4. Создание и использование функций Рис 4.3. Результат работы программы листинга 4.5 Область действия |