алгоритмизация и программирование 13 глава
11. Особенности работы с функциями 11.1. Способы передачи параметров в функцию Все переменные, объявленные в теле функции без указания класса памяти, имеют класс памяти auto, т.е. они являются локальными. При вызове функции локальным переменным отводится память в стеке и производится их инициализация. Управление передается первому оператору тела функции и начинается выполнение функции, которое продолжается до тех пор, пока не встретится оператор return или последний оператор тела функции. Управление при этом возвращается в точку, следующую за точкой вызова, а локальные переменные становятся недоступными. При новом вызове функции для локальных переменных память распределяется вновь, и поэтому старые значения локальных переменных теряются. Параметры функции передаются по значению и могут рассматриваться как локальные переменные, для которых выделяется память при вызове функции и производится инициализация значениями фактических параметров. При выходе из функции значения этих переменных теряются. Поскольку передача параметров происходит по значению, в теле функции нельзя изменить значения переменных, являющихся фактическими параметрами в вызывающей функции. Однако, если в качестве параметра передать указатель на некоторую переменную, то используя операцию разадресации можно изменить значение этой переменной. Пример. /* использование параметров по значению */ void change (int x, int y); void main() { int a=5,b=15; cout<<”\na=”<<a<<”, b=”<<b; change(a,b); cout<<”\na=”<<a<<”, b=”<<b; return; } void change (int x, int y) { int k=x; x=y; y=k; } В данной функции значения переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными, то есть значения переменных a и b как были 5 и 15 соответственно, так и остались. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную в следующем примере.
Пример. /* использование параметров по адресу */ void change (int *x, int *y) { int k=*x; *x=*y; *y=k;} При вызове такой функции в качестве фактических параметров должны быть использованы не значения переменных, а их адреса change (&a,&b);.Тогда функция будет оперировать не с локальными значениями, а со значениями по адресам, переданным в функцию в качестве параметров, что приведет к обмену значениями переменных a и b. Существует еще один способ передачи параметров в функцию – по ссылке. Механизм использования ссылок очень удобен и прост в использовании. Рассмотрим тот же самый пример, но с использованием механизма ссылок. Пример. /* использование параметров по ссылке */ void change (int &x, int &y) { int k=x; x=y; y=k;} При вызове такой функции в качестве фактических параметров должны быть использованы имена переменных: change (a,b);.При этом, в функции мы будем работать с самими переменными a и b, но через альтернативные их имена: x и y. Любая функция по результатам своей работы может возвращать какое-либо значение. Функция не может возвращать массив или функцию, но может возвращать указатель на любой тип, в том числе и на массив и на функцию. Тип возвращаемого значения, задаваемый в определении функции, должен соответствовать типу в объявлении этой функции. Функция возвращает значение, если ее выполнение заканчивается оператором return, содержащим некоторое выражение. Указанное выражение вычисляется, преобразуется, если необходимо, к типу возвращаемого значения и возвращается в точку вызова функции в качестве результата. Если оператор return не содержит выражения или выполнение функции завершается после выполнения последнего ее оператора (без выполнения оператора return), то возвращаемое значение не определено. Для функций, не использующих возвращаемое значение, должен быть использован тип void, указывающий на отсутствие возвращаемого значения. Если функция определена как функция, возвращающая некоторое значение, а в операторе return при выходе из нее отсутствует выражение, то поведение вызывающей функции после передачи ей управления может быть непредсказуемым.
11.2. Передача имен функций в качестве параметров Функцию можно вызвать через указатель на нее. Для этого объявляется указатель соответствующего типа и ему с помощью операции взятия адреса присваивается адрес функции: void f(int a){ /*…*/ } // определение функции void (*pf)(int); // указатель на функцию … pf=&f; //указателю присваивается адрес функции //(можно написать pf=f;) pf(10); //функция f вызывается через указатель pf //(можно написать (*pf)(10)) Для того чтобы сделать программу легко читаемой, при описании указателей на функции используют переименование типов (typedef). Можно объявлять массивы указателей на функции (это может быть полезно, например, при реализации меню): // Описание типа PF как указателя // на функцию с одним параметром типа int: typedef void (*PF)(int); // Описание и инициализация массива указателей: PF menu[]= {&new, &open, save}; // Вызов функции open menu[1](10); Здесь new, open и save - имена функций, которые должны быть объявлены ранее. Указатели на функции передаются в программу таким же образом, как и параметры других типов: # include <iostream.h> typedef void (*PF)(int); void f1(PF pf) { //функция f1 получает в качестве параметра указатель типа PF pf(5) //вызов функции, переданной через указатель } void f(int i) {cout <<i} int main() { f1(f); return 0; } Тип указателя и тип функции, которая вызывается посредством этого указателя, должны совпадать в точности. 11.3. Перегрузка функций Часто бывает удобно, чтобы функции, реализующие один и тот же алгоритм для различных типов данных, имели одно и то же имя. Если это имя мнемонично, то есть несет нужную информацию, это делает программу более понятной, поскольку для каждого действия требуется помнить только одно имя. Использование нескольких функций с одним и тем же именем, но с различными типами параметров, называется перегрузкой функций.
Компилятор определяет, какую именно функцию требуется вызвать, по типу фактических параметров. Этот процесс называется разрешением перегрузки (перевод английского слова resolution в смысле «уточнение»). Тип возвращаемого функцией значения в разрешении не участвует. Механизм разрешения основан на достаточно сложном наборе правил, смысл которых сводится к тому, чтобы использовать функцию с наиболее подходящими аргументами и выдать сообщение, если такой не найдется. Допустим, имеется четыре варианта функции, определяющей наибольшее значение:
// Возвращает наибольшее из двух целых: int max(int, int); // Возвращает подстроку наибольшей длины: char * max(char *, char *); // Возвращает наибольшее из первого параметра и длины второго: int max (int, char *); // Возвращает наибольшее из второго параметра и длины первого: int max (char *, int); void f(int a, int b, char * c, char * d) { cout << max (a,b) << max (c,d) << max (a,c) << max (c,b); } При вызове функции max компилятор выбирает соответствующий типу фактических параметров вариант функции (в приведенном примере будут последовательно вызваны все четыре варианта функции). Если точного соответствия не найдено, выполняются продвижения порядковых типов в соответствии с общими правилами (см. [2] с. 38 и приложение 3), например, bool и char в int, float и double и т.п. Далее выполняются стандартные преобразования типов, например, int в double или указателей в void*. Следующим шагом является выполнение преобразований типа, заданных пользователем (об этих преобразованиях рассказывается во второй части [2], с. 195), а также поиск соответствий за счет переменного числа аргументов функций. Если соответствие на одном и том же этапе может быть получено более чем одним способом, вызов считается неоднозначным и выдается сообщение об ошибке. Неоднозначность может появиться при: - преобразовании типа; - использовании параметров-ссылок; - использовании аргументов по умолчанию. Ниже приведены правила описания перегруженных функций.
- Перегруженные функции должны находиться в одной области видимости, иначе произойдет сокрытие аналогично одинаковым именам переменных во вложенных блоках. - Перегруженные функции могут иметь параметры по умолчанию, при этом значения одного и того же параметра в разных функциях должны совпадать. В различных вариантах перегруженных функций может быть различное количество параметров по умолчанию. Функции не могут быть перегружены, если описание их параметров отличается только модификатором const или использованием ссылки (например, int и const int или int&). Лабораторная работа № 11 ЦЕЛЬ РАБОТЫ: совершенствование навыков в программировании с использованием указателей, исследование способов передачи параметров в функцию и порядка возвращения параметров из функции Выполнение работы: освоить теоретический материал, выполнить общее для всех задание I и в соответствии с вариантом составить программу (задание II). Задание I Исследовать механизм перегрузки и способы передачи параметров при реализации пользовательских функций · Реализовать в среде программирования листинги 1-3. · С использованием режима отладки исследовать процесс исполнения программ в микропроцессоре. Обратить внимание на способ и место хранения значений локальных переменных a, b и c. · Сделать выводы по рассмотренным методам передачи параметров в функцию 1-3. 1. Исследование способа передачи параметров в функцию по значению void Swap(int a, int b); int main(){ int x,y; cout<<"\nEnter two numbers: "; cin>>x>>y; cout<<"\nx="<<x<<", y="<<y<<endl; Swap(x,y); cout<<"\nx="<<x<<", y="<<y<<endl; return 0; } void Swap(int a, int b){ int c; c=a; a=b; b=c; return; } 2. Исследование способа передачи параметров в функцию по адресу void Swap(int *a, int *b); int main(){ i n t x,y; cout<<"\nEnter two numbers: "; cin>>x>>y; cout<<"\nx="<<x<<", y="<<y<<endl; Swap(&x,&y); cout<<"\nx="<<x<<", y="<<y<<endl; return 0; } void Swap(int *a, int *b){ int c; c=*a; *a=*b; *b=c; return; } 3. Исследование способа передачи параметров в функцию по ссылке void Swap(int &a, int &b); int main(){ int x,y; cout<<"\nEnter two numbers: "; cin>>x>>y; cout<<"\nx="<<x<<", y="<<y; Swap(x,y); cout<<"\nx="<<x<<", y="<<y<<endl; return 0; } void Swap(int &a, int &b){ int c; c=a; a=b; b=c; return; } 4. Исследование порядка возвращения параметров из функции · реализовать в среде программирования следующий листинг: in t* GetMem(); int main(){ int *a=GetMem(); cout<<"\n a is "<<a; cout<<"\n*a is "<<*a; cout<<endl; return 0; } int * GetMem(){ int *c = new int (5); return c; } · с использованием режима отладки исследовать процесс исполнения программы в микропроцессоре; сделать выводы.
5. Использование механизма перегрузки функций · реализовать в среде программирования следующий листинг: void PrintData(int *s, int N); void PrintData(char *w); int main() { int *a,n; char str[50]; cout<<”\nEnter the array size: “; cin>>n; a = new int [n]; for (int i=0;i<n;i++) a[i] = rand()%101-50; PrintData(a,n); cout<<”\nEnter a string: “; cin>>str; PrintData(str); return 0; } void PrintData(int *s, int N) { cout<<endl; for (int i=0;i<N;i++) cout<<s[i]<<” “; return; } void PrintData(char *w) { cout<<endl<<w; return; } · сделать выводы об использовании механизма перегрузки функций в данной программе. Задание II В соответствии с вариантом составить и реализовать программу; пояснить какой способ передачи параметров в функцию был использован и почему: 1. Даны 4 натуральных числа. Вычислить их наибольший общий делитель, используя подпрограмму для вычисления наибольшего общего делителя двух чисел. 2. Даны координаты вершин двух треугольников. Определить, какой из них имеет большую площадь. 3. Найти наименьшее общее кратное четырех заданных натуральных чисел, используя подпрограмму для вычисления наименьшего общего кратного двух чисел. 4. Два натуральных числа называются "дружественными", если каждое из них равно сумме всех делителей другого, за исключением его самого (например, 284 и 220). Найти все пары "дружественных" чисел, не превосходящих заданного натурального числа. 5. Даны длины сторон треугольника. Вычислить длины медиан треугольника, сторонами которого являются медианы исходного треугольника. 6. Вычислить площадь произвольного выпуклого шестиугольника, заданного координатами вершин. 7. Даны длины сторон треугольника. Вычислить среднее арифметическое длин биссектрис этого треугольника. 8. Вычислить площадь полной поверхности правильной пятиугольной пирамиды, заданной длиной ребра. 9. Составить программу, которая будет вычислять сумму нужного числа n членов данной арифметической прогрессии по любым двум ее членам, номера которых известны. Примечание: , a 1 – первый член прогрессии; d – разность прогрессии; n – номер взятого члена, sn – сумма первых n членов арифметической прогрессии. 10. Составить программу, которая будет вычислять сумму нужного числа n членов данной арифметической прогрессии по любому члену прогрессии, номер которого известен, и разности прогрессии. См. примечание к задаче 9. 11. Три точки заданы своими координатами на плоскости. Найти площадь и длины диагоналей параллелограмма, вершинами которого являются эти точки. 12. Вычислить периметры трех треугольников и их среднее арифметическое. Если каждый из треугольников задается координатами своих вершин. 13. Найти площадь полной поверхности правильной n - угольной пирамиды по стороне основания при n = 4. 14. Сгенерировать члены ряда Фибоначчи, не превосходящие заранее заданного числа n. Проверить, что если число Фибоначчи f (k > 4) - простое, то и k - простое. 15. Вычислить площадь произвольного выпуклого пятиугольника, заданного координатами вершин. 16. Составить программу, которая будет вычислять сумму нужного числа n членов данной геометрической прогрессии по любому члену прогрессии, номер которого известен, и знаменателю прогрессии. Примечание: , , b 1 – первый член прогрессии; q – знаменатель прогрессии; n – номер взятого члена, sn – сумма первых n членов геометрической прогрессии. 17. Составить программу, которая будет вычислять сумму нужного числа n членов данной геометрической прогрессии по любым двум ее членам, номера которых известны (см. примечание к 16 варианту) 18. Вычислить члены ряда Фибоначчи, не превосходящие заранее заданного числа n. Проверить для нескольких четверок fi, fi + 1, fi + 2, fi + 3 последовательные члены ряда Фибоначчи справедливость соотношения . 19. Запрограммировать игру "Угадай число". Программа спомощью генератора случайных чисел выбирает целое число в диапазоне от 0 до 9. Угадать это число за три попытки. После каждой попытки сообщается больше или меньше названное число задуманного. 20. Найти все простые числа в промежутке между натуральными числами а и b (а > 2000, b - а ≥ 20) 21. Дано целое число а. Вычислить , где . 22. Сколькими способами можно отобрать команду в составе m человек из n кандидатов? Провести вычисления при m =5, n = 8; при m =6, n = 11. 23. n интервалов заданы координатами своих концов на координатной прямой и пронумерованы. Задана также точка х на этой прямой. Выяснить, скольким интервалам принадлежит точка х, указать их номера. 24. Даны три пары комплексных чисел, представленных в алгебраической форме. Организовать в виде подпрограммы выполнение четырех арифметических действий над парой комплексных чисел. Составить программу выполнения этих действий для каждой пары заданных чисел. 25. Даны координаты вершин треугольника и координаты двух точек на плоскости. Определить, лежат ли они внутри треугольника. Контрольные вопросы 1. Перечислите способы передачи параметров в функцию. 2. Особенности передачи параметров в функцию по значению. 3. Особенности передачи параметров в функцию по адресу. 4. Каковы особенности передачи параметров в функцию по ссылке? 5. Может ли функция возвращать массив или функцию в качестве результата своей работы? 6. Перечислите способы вызова функций. 7. Какое служебное слово используют для переименования типов? 8. Что понимают под перегрузкой функций? 9. Какой процесс называют разрешением «уточнением» перегрузки? 10. На чем основан механизм разрешения перегрузки? Дайте пояснения. 11. Перечислите правила описания перегруженных функций. 12. Строки как массив элементов типа char 12.1. Способы представления строк в СИ; реализация некоторых типовых операций над строками В Cи отсутствует специальный тип строк. Строки рассматриваются как массивы символов, оканчивающиеся нулевым символом ('\0'). Строка доступна через указатель на первый символ в строке. Значением строки является адрес ее первого символа. Таким образом, можно сказать, что в Cи строка является указателем – указателем на первый символ строки. В этом смысле строки подобны массивам, потому что массив тоже является указателем на свой первый элемент (рисунок 12.1).
Рисунок 12.1 – Представление массива элементов в памяти ЭВМ В приведенном примере объявлен символьный массив A на 5 элементов: char A[]=”Вася”; Обратите внимание на то, что под хранение каждого символа выделяется один байт памяти, а под указатель будет выделено 4 байта. Необходимо отметить, что в приведенном примере размер массива не указан, он определен автоматически по длине строки-инициализатора, причем, автоматически добавлен символ окончания строки. Строка может быть объявлена либо как массив символов, либо как переменная типа char *. Каждое из двух приведенных ниже эквивалентных объявлений: char S[] = "строка"; char *Sp = "строка"; присваивает строковой переменной начальное значение "строка". Первое объявление создает массив из 7 элементов S содержащий символы 'с', 'т', 'р', 'о', 'к', 'а' и '\0'. Второе объявление создает переменную-указатель Sp, который указывает на строку с текстом "строка", лежащую где-то в памяти ЭВМ. Но в любом случае число хранимых символов на 1 больше числа значащих символов за счет конечного нулевого символа. Доступ к отдельным символам строки осуществляется по индексам, начинающимся с нуля. Например, S[0] и Sp[0] - первые символы объявленных выше строк, S[1] и Sp[1] - вторые и т.д. В приведенных объявлениях длина строк определялась автоматически компилятором. Можно объявлять строковые переменные заданной длины. Например, оператор char buff[100]; объявляет переменную buff, которая может содержать строку до 99 значащих символов плюс заключительный нулевой символ. Теперь приступим к знакомству с наиболее полезным применением символьных указателей: запоминанию массивов строк. На самом деле нельзя создать массив строк, но можно запомнить массив символьных указателей, каждый из которых содержит адрес строки в памяти. Определением массива символьных указателей создается динамический массив. Этот массив похож на двумерную таблицу, с одним исключением: все строки в этом массиве могут иметь разную длину (разное количество символов в строке). Например, объявим символьную таблицу с 5 строками и 10 колонками, каждая строка которой содержит одинаковое число символов. Ее можно определить следующим образом: char names [ 5 ] [ 10 ] = { { "Сергей" }, { "Михаил" }, { "Николай" }, { "Иван" }, { "Алексей" }}; Она показана на рисунке 12.2. Обратите внимание, в таблице много пробелов. Каждая строка занимает 10 символов даже в том случае, когда символов в строке намного меньше. Незаполненные элементы содержат символ ограничитель строки, потому что Cи записывает этот символ во все неинициализированные элементы массива. Этот тип таблиц занимает слишком много места в памяти.
Рисунок 12.2 – Пример двумерного символьного массива строк Для устранения проблемы нерационального использования памяти объявим одномерный массив символьных указателей. Каждый указатель содержит адрес сроки в памяти, и эти строки могут иметь разную длину. Вот примеры определения (инициализации) таких массивов:
Это одномерные массивы. Звездочка перед names говорит о том, что это массив указателей. Указатели имеют символьный тип. Строки не присваиваются элементам массива, но в массиве содержатся адреса этих строк. На рисунке 12.3 показан этот массив указателей. Строки могут находиться в любом месте памяти. Их местоположение не существенно, так как каждый указатель содержит адрес первого элемента соответствующей строки. Строки не занимают лишнюю память; каждая строка использует ровно столько пространства, сколько нужно для размещения ее и символа ограничителя строки. Такая структура содержит данные в форме с рваными краями.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|