МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ ВОЛЖСКИЙ ПОЛИТЕХНИЧЕСКИЙ ИНСТИТУТ (филиал) ГОУ ВПО ВОЛГОГРАДСКОГО ГОСУДАРСТВЕННОГО ТЕХНИЧЕСКОГО УНИВЕРСИТЕТА КАФЕДРА «ИНФОРМАТИКА И ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ» Функции, определяемые пользователем, в языках Си и Си++ Методические указания к лабораторным работам по курсу «Алгоритмические языки. Программирование на языках высокого уровня» Волгоград 2011 УДК 004.056 Рецензент: к.т.н., доцент каф. ВАЭ и ВТ ВПИ (филиал) ВолгГТУ Капля В.И. МЕТОДИЧЕСКИЕ УКАЗАНИЯ К ЛАБОРАТОРНЫМ РАБОТАМ: Функции, определяемые пользователем, в языках Си и Си++/ Сост. Лясин Д.Н., Фадеева М.В..; Волгоград. гос. техн. ун-т. - Волгоград, 2011, – 24 с. Содержатся сведения, необходимые для изучения основ разработки программ с использованием пользовательских функций: рассмотрены основные преимущества структурного подхода в программировании; приведены синтаксические правила определения, описания и вызова функций на языках Си и Си++; описаны способы передачи в функции параметров различных типов. Приведены варианты заданий к лабораторным работам. Предназначены для студентов, обучающихся по направлению 230100 "Информатика и вычислительная техника" и специальности 230102 "Автоматизированные системы обработки информации и управления" всех форм обучения в рамках курса «Методы и средства защиты компьютерной информации» Ил. 3. Библиогр.: - 8 назв. Издается по решению редакционно-издательского совета Волгоградского государственного технического университета © Волгоградский государственный технический университет, 2011 © Волжский политехнический институт, 2011 Лабораторная работа №9 Функции, определяемые пользователем, в языках Си и Си++ Цель: изучить основные принципы структурного подхода к решению задач на ЭВМ, ознакомиться с синтаксисом определения и вызова функций с различными параметрами на языке Си++, получить навыки программирования на языке Си++ с использованием функций. 1. Основные сведения В соответствии со структрурной методологией программирования части программы, требующие многократного исполнения, могут быть оформлены в виде подпрограмм. Это позволяет не прописывать этот фрагмент в каждом месте программы, где требуется выполнение соответствующего действия, а оформить обращение к подпрограмме с требуемым набором аргументов. В языке Си существует единственный вид подпрограмм – функции. Функция – обособленный программный блок, реализующий собственный алгоритм работы, обрабатывая поступающие на вход параметры и формируя некоторое выходное значение. Структура программы на языке Си, строящейся как совокупность взаимодействующих функций, подчеркивает важность этих объектов программы. Функция на языке Си определяется следующим образом: [ класс ] [ тип ] имя_функции ([список формальных параметров]) { //тело функции return [выражение]; } Класс определяет класс памяти для функции. Допустимы значения extern (по умолчанию) – внешний класс памяти и static – статический класс. Функции внешнего класса доступны во всех модулях программы (с предварительным описанием), статического класса – только в текущем модуле. Тип функции определяет тип вычисляемого и возвращаемого функцией в результате своей работы значения. Если функция не формирует значений (работает аналогично процедуре языка Паскаль), то для нее указывается тип void. Если тип не указан, по умолчанию принимается тип int. Имя функции задает идентификатор, под которым функция будет доступна в программы для обращения к ней. Список формальных параметров определяет перечень аргументов, которые функция принимает для обработки от вызывающих функций и использует для формирования результата своей работы. Тело функции – совокупность операторов, реализующих алгоритм работы функции. 3 Оператор return завершает работу функции и передает управление в точку вызова. Параметр оператора return (если указан) определяет возвращаемое функцией значение. Рассмотрим подробнее формат определения параметров функции. Список формальных параметров может быть пустым, а может включать определение от одного до произвольного количества параметров функции. В общем виде список формальных параметров функции выглядит следующим образом: параметр1 , параметр2 , … ,параметр n Каждый параметр определяется наподобие переменной или константы: [const] тип [ имя [ = выражение ] ] Здесь тип задает тип формального параметра, имя – его идентификатор, под которым к этому параметру можно обратиться внутри функции, а выражение определяет значение, которое получит параметр в том случае, если при обращении к функции не будет указан соответствующий фактический параметр. Использование ключевого слова const позволяет определять формальный параметр константным, не допускающим его изменения внутри тела функции. В качестве примера рассмотрим определение функции, вычисляющей факториал числа, переданного ей в качестве параметра. //Листинг 1. Определение функции factorial. typedef unsigned int UI; UI factorial (UI n) { UI f=1, i; for (i=2; i<=n; i++) f*=i; return f; } Функция factorial стереотипна по своим действиям – она принимает параметр (беззнаковое целое n) и на его основе вычисляет по собственному алгоритму значение факториала этого числа и возвращает его в качестве результата своей работы. Данная функция не рассматривает вариант возможного переполнения переменной f при больших значениях n, но для методических целей знакомства с особенностями определения, описания и вызова функций этого и не требуется. Для обращения к функции используется выражение с операцией «круглые скобки»: имя_функции (список фактических параметров); Здесь список фактических параметров – перечень через запятую выражений, значения которых передаются в функцию на место соответствующих формальных параметров. При формировании списка фактических параметров вызова функции необходимо следить за его соответствием списку формальных параметров функции по количеству, типам и порядку следования параметров. Вызов функции является выражением. Значение этого выражения формируется в процессе работы функции, и оно равно тому значению, которое возвращается из функции оператором return. 4 К функции factorial можно обратиться из функции main: //Листинг 2. Обращение к функции factorial //Определение функции factorial из листинга 1 int main() { cout<<factorial(5); //вывод 120 UI n, f; cin>>n; f=factorial(n); cout<<factorial( factorial(3)); //вывод 720 return 0;} Если при обращении к функции компилятор обнаруживает несоответствие типов формального и фактического параметров, то он пытается привести тип фактического к типу формального. Так, если мы обратимся к нашей функции с вещественным параметром, то компилятор автоматически приведет его к целому, т.е. вызов factorial(3.0) будет трансформирован в factorial((unsigned int) 3.0). Подобное приведение допустимо не для всех типов данных, поэтому, например, попытка вызова factorial(”5”) завершится ошибкой на этапе компиляции. Рассмотрев синтаксис определения функций в программе, перечислим основные преимущества их использования в программе. Во-первых, использование функций позволяет сократить размер программы. Если в программе многократно используется некоторый фрагмент кода, его можно не множить в разных местах программы, а оформить в виде функции, организовав ее вызов в тех местах, где требуется выполнение данного кода. Во-вторых, использование функций позволяет быстро вносить изменения в текст программы. Если некоторый многократно используемый в программе фрагмент кода потребует изменения (в связи с найденной в нем ошибкой или для оптимизации выполняемых действий), то проще однократно изменить содержимое функции, чем вносить изменения во всех разбросанных по тексту программы фрагментах. В-третьих, использование функций позволяет распределять работу над программой группе программистов, тогда каждый из них будет реализовывать свой фрагмент в виде одной или нескольких функций в отдельном модуле, а вся программа будет строиться на взаимодействии этих функций. В-четвертых, использование функций облегчает повторное использование кода, когда функции, разработанные в рамках одного проекта, оформляются в виде библиотеки функций и могут быть использованы для разработки других программ. Например, стандартные библиотеки языка Си (математическая, ввода-вывода, графическая и т.д.) предоставляют программисту удобный инструмент для работы, позволяя не реализовывать самостоятельно стандартные алгоритмы обработки данных. В-пятых, как было отмечено ранее, разбиение программы на отдельные функции является базовым принципом структурного программирования. Такое разбиение позволяет проектировать программу «сверху вниз», от сложного к простому, разбивая стоящую задачу на все более простые подзадачи и реализуя каждую из них в виде простых и понятных функций. Возвращаясь к синтаксическим особенностям работы с функциями в языке Си, рассмотрим понятие описание функции. Встретив обращение к 5 функции в программе, компилятор должен иметь о ней некоторую информацию (имя функции, список ее параметров). Рассмотрим пример: //Листинг 3. Проблемы определения функций int func(int x) { func2(); //ошибка } main() { func (4); //корректно func2(); //ошибка } void func2() { func(0); //корректно } Обнаружив вызов функции func2, компилятор выдаст ошибку «Call to undefined function ‘func2’». Ошибка связана с тем, что определение функции расположено в программе ниже точки вызова. Выходом здесь могло бы стать перемещение определения функции в тексте программы выше определения функций main и func, но тогда ошибка возникнет уже при вызове функции func в теле самой func2, поскольку уже определение func окажется ниже точки ее вызова. Выходом здесь является предварительное описание функции. В качестве описания в языке Си++ используется прототип функции – строка заголовка в ее определении: тип имя_функции ( список параметров ) ; Для функций, определенных в листинге 3 описания могут быть следующими: void func2(); int func(int x); int func(int x); //при описании функции имена параметров мож//но опускать //Листинг 4. Пример описания функции void func2(); //описываем функцию int func(int x) { func2(); //ошибки не возникает, компилятор имеет информа} //цию о функции void main() { func (4); func2();} void func2() { func(0); } Когда в текст программы добавляется директива препроцессора вида #include <math.h>, фактически происходит описание всех функций конкретной библиотеки. В заголовочном файле, подключаемом к тексту программы этой директивой, содержатся прототипы всех функций данной библиотеки, что делает их известными компилятору. Возврат из функции происходит при вызове оператора return. При этом та часть тела функции, которая расположена ниже оператора return не будет выполнена. Рассмотрим пример определения функции, поиска максимума из двух переданных целых чисел: int func(int x, int y) 6 { if (x>y) return x; return y; } Здесь при определения максимума из двух чисел используется оператор if в краткой форме, поскольку при выполнении условия происходит возврат из функции. Возврат второго числа (в примере – y) не обусловлен какимлибо сравнением, поскольку второй оператор return выполнится только в том случае, если условие x>y ложно. Если же оно истинно, то первый оператор return завершит работу функции. Из этого примера очевидно, что функция может иметь несколько «выходов» - вызовов return в зависимости от выполнения или невыполнения тех или иных условий. Если функция в результате своей работы не формирует никакого значения (то есть работает аналогично подпрограммам-процедурам языка Паскаль), то ей в качестве типа возвращаемого значения указывается тип void. В этом случае возврат из функции осуществляется оператором return без параметров. void func(int x, int y) { cout << x+y; return; } Вызов оператора return в последнем примере необязателен, при его отсутствии компилятор автоматически добавит в конец тела функции код возврата в вызывающую функцию. Из обобщенного определения функции, приведенного выше, видно, что для формальных параметров можно задавать некоторые значения по умолчанию. При этом формальный параметр примет своё умалчиваемое значение в том случае, если в списке фактических параметров вызова опущен соответствующий фактический параметр. Рассмотрим функцию, использующую подобные параметры. //Листинг 4. Пример определения функции с умалчиваемыми пара//метрами void print(int x=1, int y=1, char str[20]=“Default string”) { clrscr(); gotoxy(x, y); printf(“%s”, str); } Приведенная функция print выводит переданную ей в качестве параметра строку (параметр str) в заданную позицию на экране (параметры x, y), при этом для каждого параметра задано умалчиваемое значение. Теперь при вызове функции допустимо указывать не все фактические параметры: int main() { print(10,5,”Hello”); print(3,3); print(4); print(); return 0;} //x=10, y=5, str=”Hello” //x=3, y=3, str=”Defualt string” //x=4, y=1, str=”Defualt string” //x=1, y=1, str=”Defualt string” В комментариях к каждой строке указано, какие значения примут формальные параметры функции для каждого их вызовов. Необходимо также 7 отметить, что приведены все возможные варианты вызова функции print с точки зрения указания или пропуска фактических параметров. Дело в том, что правила языка Си++ регламентируют, что пропускать параметры можно только в конце списка фактических параметров, а, следовательно, ошибочными будут вызовы print(, 10, “Hello”) или print ( , , “Hello”). Еще одно ограничение касается определения функции: если какой-либо формальный параметр функции имеет умалчиваемое значение, то все параметры, стоящие правее него в списке, должны также его иметь. То есть недопустимо такое определение функции: void print(int x=1, int y=1, char str[20]) {…} Синтаксис языка Си предлагает один способ передачи параметров в функцию – по значению. Такой способ предполагает при вызове функции создании в стеке формальных параметров как локальных объектов, недоступных вне тела функции. Значения фактических параметров копируются в соответствующие формальные параметры, и на этом их связь разрывается: изменение формального параметра в теле функции не приводит к изменению сопоставленного ему при вызове фактического, при возврате из функции значения формальных параметров в фактические не копируются. Отсутствие подобной связи лучше всего поясняет пример реализации функции, которая должна обменивать значения объектов-параметров. //Листинг 5. Функция обмена значениями объектов, вариант 1, //неправильный void swap (int a, int b) { int t=a; a=b; b=t;} int main() { int x=4, y=5; swap(x, y); cout<<”x=”<<x<<”, y=”<<y; //вывод x=4, y=5 return 0;} При вызове функции swap в стеке программы создаются локальные переменные с именами a и b. В них копируются значения переменных x и y (a=4, b=5). Функция swap обменивает значения этих двух переменных, то есть при выходе и функции a=5, b=4. Однако переменные x и y не изменяют своих значений, они являются самостоятельными по отношению к a и b переменными и не изменяются синхронно с ними. По выходе из функции переменные a и b уничтожаются, а на экран выводятся значения x и y, не изменившиеся после вызова функции. Выходом из ситуации может стать использование параметровуказателей. Если в качестве параметров функции swap использовать адреса переменных x и y, то передача параметров осуществится по прежнему по значению, но этими значениями будут уже адреса переменных - фактических параметров, что позволит изменять их значения внутри тела функции. //Листинг 6. Функция обмена значениями объектов, вариант 2, //работающий, но не оптимальный void swap (int *pta, int *ptb) { int t=*pta; 8 *pta=*ptb; *ptb=t;} void main() { int x=4, y=5; swap(&x, &y); cout<<”x=”<<x<<”, y=”<<y; //вывод x=5, y=4 } Внутри функции swap происходит обмен значениями объектов, адреса которых переданы в качестве параметров функции. Так как при вызове указаны адреса переменных x и y, то значения именно этих переменных поменяет функция. Несмотря на то, что вторая реализация функции swap позволила решить поставленную задачу, использование в ней операций разыменования делают ее несколько громоздкой и избыточной по выполняемым операциям. Язык Си++ предлагает еще один тип данных, использование которого делает связь формальных и фактических параметров функции эффективнее. Этот тип данных – ссылки. Если определить формальный параметр функции как ссылку, то он станет некоторым псевдонимом фактического параметра вызова на все время ее работы: всякое обращение к формальному параметру будет преобразовано компилятором в обращение к фактическому параметру. //Листинг 7. Функция обмена значениями объектов, вариант 3, //использование ссылок void swap (int& a, int& b) { int t=a; a=b; b=t;} main() { int x=4, y=5; swap(x, y); cout<<”x=”<<x<<”, y=”<<y; //вывод x=5, y=4 } В листинге 7 формальные параметры a и b объявлены как ссылки на соответствующие фактические параметры. Изменение a приводит к изменению того объекта, на который эта переменная ссылается, в данном случае x. Поэтому обмен значениями состоится. Вообще, использование формальных параметров в виде ссылок позволяет использовать их не только в качестве входных, но и выходных аргументов: через такие параметры результаты работы функции могут стать известными вызывающей функции. //Листинг 8. Выходной параметр функции int SumAndMult (int a, int b, int& mul) { mul=a*b; return a+b;} void main() { int x, y, m; cout<<”Введите 2 целых числа”; cin>>x>>y; cout<<”Cумма введенных чисел”<<SumAndMult(x, y, m); cout<<”\nПроизведение введенных чисел”<<m; } 9 В примере из листинга 8 функция SumAndMult формирует два значения – сумму и произведение переданных ей в качестве двух первых параметров целых чисел. Но механизм возврата из функции предполагает передачу в вызывающую функцию лишь одного значения. Для возврата второго значения используется третий параметр функции mul. Его значение при вызове функции может быть произвольным, оно никак не используется в алгоритме ее работы, однако, одно из формируемых функцией значений помещается в этот параметр. Поскольку он является ссылкой, изменяется соответствующий фактический параметр и произведение чисел становится известным в функции main как значение переменной m. Зачастую в качестве параметра функции необходимо использовать массив. Рассмотрим функцию заполнения одномерного массива случайными значениями. //Листинг 9. Передача массива в функцию const int N=10; void initMas( int m[N] ) { for( int i=0; i<N ; i++) m[i]=random(100); } void main() { int mas[N], arr[N]; initMas(mas); initMas(arr); … } Необходимо сразу сказать, что функция initMas выполняет предписанные действия: массивы mas и arr действительно будут заполнены случайными числами в диапазоне от 0 до 100. Это достигается за счет того, что при вызове функции не создается копии массива - фактического параметра. Формальный параметр m интерпретируется компилятором как указатель, в который будет занесен адрес обрабатываемого массива. Поэтому определение формального параметра m функции initMas можно изменить на int m [ ] или int * m, что будет более точно отражать суть этого параметра. Это означает, что в функцию можно передавать массивы различной длины. Однако обрабатывать он будет только N=10 первых элементов массива. Чтобы сделать функцию более универсальной, можно параметризовать ее и количеством элементов в массиве. //Листинг 10. Более универсальная функция инициализации мас//сива случайными числами void initMas( int m[], int n ) { for( int i=0; i<n ; i++) m[i]=random(100); } void main() { int mas[20], arr[10]; initMas(mas, 20); initMas(arr, 10); 10 int *ptm=new int [30]; initMas(ptm); //можно инициализировать и динамические // массивы … } В новой редакции функция initMas может инициализировать одномерные целочисленные массивы разной длины, но при вызове необходимо указывать размер массива. При обработке двумерных массивов возможности универсализации работы функции (если под ней понимать способность принимать массивы с произвольным количеством строк и столбцов) ограничены. При работе со статическими массивами можно варьировать лишь количество строк пересылаемого в функцию массива, количество столбцов должно быть фиксировано. //Листинг 11. Передача двумерного массива в функцию void initMas2d( int m[][5], int nRow ) { for( int i=0; i<nRow ; i++) for(int j=0;j<5; j++) m[i][j]=random(100); } void main() { int mas[20][5], arr[10][5]; initMas2d(mas, 20); initMas2d(arr, 10); int (*ptm)[5]=new int [30][5]; initMas2d(ptm, 5);//можно инициализировать и динамические // массивы, но только на 5 столбцов int mas2[6][6]; initMas2d(mas2, 6); //Ошибка, у массива mas2 6 столбцов } Cделать функцию initMas2d более универсальной, параметризуя и количество строк и количество столбцов в виде void initMas2d( int m[][ ], int nRow, int nCol ) { … } нельзя, потому что для статического массива обязательно должно быть указано количество столбцов. Если подобная универсальность необходима, можно определить структуру данных из нескольких динамических одномерных массивов, имитирующую поведение двумерного массива. //Листинг 12. Передача двумерного массива произвольного //размера в функцию void initDynMas( int **ptm, int nRow, int nCol ) { for( int i=0; i<nRow ; i++) for(int j=0;j<nCol; j++) ptm[i][j]=random(100); } int main() { int a,b, **m; cout<<”Введите количество строк и столбцов”; cin>>a>>b; m=new int* [a]; 11 for(int i=0; i<a; i++) m[i]=new int[b]; initDynMas(m,a,b); return 0;} Функция initDynMas в листинге 12 принимает в качестве параметра указатель ptm, хранящий адрес динамической структуры из нескольких массивов, имитирующей поведение двумерного массива. При этом количество строк (nRow) и столбцов (nCol) в таком массиве может быть произвольно, что демонстрируется в вызове функции, когда они задаются пользователем с клавиатуры. Для функций, как и для других объектов программы, допустимо определять указатели. С учетом специфики использования функций, указатели на них предназначены не для чтения или изменения значения по адресу, а только с целью вызова адресуемой функции. Определение указателя на функцию выглядит следующим образом: тип ( * имя_указателя) ( список формальных параметров); Пример определения указателей на функции в программе: void (*ptf)(int a); int (*funcPtr)(); void (*pt)(int *, int); //как и в описании функций имена па//раметров можно опускать Для инициализации указателя в него необходимо занести адрес некоторой функции. При этом данная функция должна иметь такие же тип возвращаемого значения и сигнатуру (список параметров), что и указатель. Адрес функции можно получить, используя ее имя без параметров и круглых скобок. //Листинг 13. Инициализация указателя на функцию void func( int x) { … } void func2( ) { … } int func3( int x) { … } int main() { int (*ptf)(int); ptf=func; //заносим в указатель адрес функции func (*ptf)(0); //Вызов функции func ptf=func2; //Ошибка! Не совпадают по сигнатуре ptf=func3; //Ошибка! Не совпадают по типу возвращаемого //значения return 0;} Основная цель указателя на функцию – вызов адресуемой функции. В листинге 13 приведен пример вызова функции func с помощью указателя ptf. В общем виде вызов функции, адрес которой хранится в некотором указателе, выглядит следующим образом: (* имя_указателя) (список фактических параметров); Очевидно, что использование указателя ptf в листинге 13 несколько искусственно, проще оформить привычный вызов func(0). В каких случаях использование указателей на функцию оправданно? 12 Указатель на функцию можно использовать в качестве параметра другой функции. Это позволяет при обращении к функции варьировать не только данные, обрабатываемые функцией, но и действия, ею выполняемые. //Листинг 14. Указатель на функцию в качестве параметра функции int sqr(int a) //вычисляет квадрат аргумента {return a*a;} int factorial(int n) //вычисляет факториал аргумента { int f=1, i=2; for (;i<=n;f*=i++); return f; } void table(int (*ptf)(int), int n) //Строит таблицу функции { for (int i=1;i<=n;i++) cout<<i<<‘\t’<< (*ptf)(i)<<‘\n’; } int main() { table (sqr,10);//вывести таблицу квадратов чисел от 1 до 10 table (factorial, 15);// вывести таблицу факториалов чисел от 1 до 15 return 0;} В примере из листинга 14 функция table выводит на экран таблицу значений некоторой функции, адрес которой передаётся ей в качестве аргумента, для диапазона аргументов от 1 до n. Для указания того, таблицу какой именно функции необходимо выводить, используется параметр-указатель ptf. Учитывая тип этого указателя, в функцию table можно передавать адреса не любых функций, а только тех, которые принимают один целочисленный параметр и возвращают целое число. Именно такой прототип имеют функции sqr и factorial, передаваемые при вызове table из функции main. Такой же подход используется при вызове стандартной функции сортировки qsort из библиотеки stdlib. //Листинг 15. Сортировка массива стандартной функцией qsort #include <stdlib.h> int comp_function( void *a, void *b) { int first=*(int *)a, second=*(int *)b; return first-second; } int m[20]; int main(void) { … qsort(m, 20, sizeof(m[0]), comp_function); … } Функция qsort вызывается с четырьмя параметрами – адресом сортируемого массива, количеством элементов массива, размером одного элемента массива и адресом функции сравнения двух элементов массива. Такая функция должна быть написана отдельно и ее задача – сравнивать два элемента массива в соответствии с выбранным критерием сортировки. Подробнее о работе данной функции можно посмотреть, например, в [7]. 13 Указатели на функции можно объединять в массивы. Тогда вызов той или иной функции можно осуществлять не по имени, а по номеру-индексу в массиве указателей, что позволяет, например, эффективнее сопоставить вызов функции некоторому числу (номеру выбранного пункта меню, идентификатору пришедшего сообщения и т.п.). Пример подобного применения указателей на функции можно посмотреть в [5]. Объем настоящих указаний не позволяет рассмотреть всех возможностей определения и использования функций в языках Си и Си++. За рамками остались такие вопросы, как определение функций с переменным количеством параметров, перегрузка функций, шаблоны функций, передача параметров в функцию main. Подробную информацию по этим темам можно найти в [1, 4, 5, 7]. Для выполнения заданий настоящей лабораторной работы знакомство с данными темами необязательно. 2. Порядок выполнения работы 1. Ознакомьтесь с теоретическими основами работы с функциями на языке Си в настоящих указаниях и конспектах лекций. 2. Получите вариант задания у преподавателя. 3. Составьте алгоритм решения задачи согласно варианту задания, оформите его в графической форме. 4. Используя разработанный алгоритм, напишите программу. 5. Отладьте разработанную программу и покажите результаты работы программы преподавателю. 6. Составьте отчет по лабораторной работе. 7. Отчитайте работу преподавателю. 3. Содержание отчета Отчет по лабораторной работе должен содержать следующие сведения: - название и цель работы; - вариант задания; - графическую схему алгоритма решения задачи; - листинг разработанной программы с комментариями; - результаты работы программы. 4. Пример оформления отчета: Задание Дана целочисленная квадратная матрица. Определить: количество упорядоченных по неубыванию строк в этой матрице; номер столбца, количество нулевых элементов в котором минимально. Каждый пункт задания оформить в виде отдельной функции. 4.1. Схема алгоритма решения задачи. 14 начало countZero вход: целый mas[][5], nRow, nCol выход: целый nZero nZero=0 i=0; i<nRow да mas[i][numCol]=0 nZero++ нет i++ конец Рис.1. Блок-схема алгоритма функции подсчета количества нулей в столбце матрицы начало checkOrder вход: целый mas[], n выход: целый order order=1 i=0; i<n да mas[i]>mas[i+1] нет order=0 i++ конец Рис.2. Блок-схема алгоритма функции проверки массива на упорядоченность по неубыванию 15 начало i=0; i<5 j=0; j<5 Ввод m[i][j] j++ i++ i=0; i<5 j=0; j<5 Вывод m[i][j] j++ i++ countOrd=0 i=0; i<5 countOrd+=checkOrder(m[i],5) i++ Вывод countOrd nMinZeroColumn=0 nMinZero=countZero(m,5,0) i=0; i<5 j=countZero(m, 5, i) да j<nMinZero nMinZero=j nMinZeroColumn=i нет i++ Вывод nMinZero, nMinZeroColumn конец Рис.3. Блок-схема алгоритма решения задачи 16 4.2. Листинг программы. #include <iostream> using namespace std; #include #include #include #include <conio.h> <windows.h> <stdlib.h> <stdio.h> /* Функция checkOrder определяет, упорядочен ли по неубыванию переданный ей одномерный массив mas из n элементов*/ int checkOrder(int mas[], int n) { for (int i=0; i < n-1; i++) if (mas[i]>mas[i+1]) return 0; return 1; } /* Функция countZero подсчитывает количество нулевых элементов в столбце numCol матрицы целых чисел mas из nRow строк и 5 столбцов */ int countZero(int mas[][5], int nRow, int numCol) { int nZero=0; for(int i=0; i<nRow; i++) if (mas[i][numCol]==0) nZero++; return nZero; } //-------------------------------------------------------------int main(int argc, char* argv[]) { int m[5][5], i, j, countOrd=0; int nMinZero; //минимальное колчество нулей в столбце int nMinZeroColumn; // номер столбца с минимальным кол-вом //нулей SetConsoleCP(1251); SetConsoleOutputCP (1251); //вводим элементы массива с клавиатуры с эхом данных на экране for(i=0;i<5; i++) for(j=0;j<5; j++) cin>>m[i][j]; cout<< "Введена матрица:\n"; for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) cout<<m[i][j]<<'\t'; cout<<'\n'; } 17 /* В переменной countOrd подсчитываем количество упорядоченных по неубыванию строк матрицы m, передавая каждую строку в функцию checkOrder */ for (i=0; i < 5; i++) countOrd+=checkOrder(m[i], 5); cout<<"Количество упорядоченных по возрастанию строк: " <<countOrd; //Предполагаем, что минимальное количество нулей в нулевом //столбце nMinZeroColumn=0; nMinZero=countZero(m, 5, 0); for (i=1; i < 5; i++) { j=countZero(m, 5, i); //получаем количество нулей в i-м //столбце if (j<nMinZero) //сравниваем с текущим минимумом { nMinZero=j; nMinZeroColumn=i; } } cout<<"\nМинимальное количество нулей ( " << nMinZero << " ) в столбце "<<nMinZeroColumn; getch(); return 0; } 4.3. Результаты работы программы. 18 5. Варианты заданий Вариант №1 Вычислить приближенное значение интеграла по формуле прямоугольни- 1 x2 ков для заданного целого n > 39. dx Формула прямоугольников: 3 x x 0.1 b n ba f xi , где h , xi x0 ih, x0 a, xn b a f x dx h n i 1 0.5 Вычисление подынтегральной функции и суммы оформить в виде функций. Вариант №2 Для заданных вещественных чисел a, b и h - шага интегрирования, вычис- x3 лить приближенное значение интеграла dx по формуле трапеции 4 a 1 x b f x0 f xn ba n1 f x dx h f x , где h , xi x0 ih, x0 a, xn b . i a i1 2 n b Вычисление подынтегральной функции и суммы оформить в виде функций. Вариант №3 Определить в программе функцию, которая удаляет из переданной ей строки все множественные пробелы между словами. Используя эту функцию, удалить все множественные пробелы между словами в текстовом файле, имя которого задается пользователем. Вариант№4 Вычислить приближенное значение интеграла по формуле прямоугольников для заданного целого n > 100. 0 ln2 sin x dx Формула прямо угольников: b a n f x dx h f xi , где h i 1 ba , xi x0 ih, x0 a, xn b n Вычисление подынтегральной функции и суммы оформить в виде функций. Вариант №5 Дано четное число n > 2. Проверить для этого числа гипотезу Гольдбаха. Эта гипотеза (по сегодняшний день не опровергнутая и полностью не доказанная) заключается в том, что каждое четное n, большее двух, представляется в виде суммы двух простых чисел. Для решения задачи определить и воспользоваться функцией распознавания простых чисел. 19 Вариант №6 Определить функцию, которая будет В заданной строке на русском языке отыскивать все стегонаграфические вставки символов из английского алфавита (английское p вместо русского р, английское x вместо русского х, с вместо с, а вместо a и т.д.) и исправлять такие вставки. С использованием этой функции очистить от стеговставок содержимое текстового файла, имя которого задается пользователем. Вариант №7 Даны n и p - натуральные числа. Вычислить приближенное значение опре1 1 dx по формуле левых прямоугольников 2 p x 0 ba f xi , где h , xi x0 ih, x0 a, xn b n деленного интеграла b a n 1 f x dx h i 0 2 Вычисление подынтегральной функции и суммы оформить в виде функций. Вариант №8 Для заданных значений a, b - вещественных и целого n 1 вычислить b приближенное значение определенного интеграла x 2 x 2 0.1 dx a по формуле трапеции. Формула трапеции: b f x0 f x n n1 f x dx h f xi , 2 i 1 a ba где h , xi x0 ih, x0 a, xn b Вычисление значения подынтеn гральной функции и суммы оформить в виде функций. Вариант №9 Дана целочисленная квадратная матрица. Определить: количество строк, не содержащих ни одного нулевого элемента; отсортировать .столбцы матрицы по убыванию Каждый пункт задания оформить в виде отдельной функции. . Вариант №10 Дан массив структур, описывающих окружности на плоскости (координаты центра окружности, радиус). Определить функцию, которая определяет 20 – пересекаются ли две переданные ей окружности. Используя данную функцию определить окружность, имеющую наибольшее количество пересечений с остальными. Вариант№11 Дан массив структур, описывающих прямоугольники на плоскости (координаты левого верхнего угла, ширина, высота). Определить функцию, которая возвращает структуру, описывающую прямоугольник-пересечение двух прямоугольников-параметров. Используя данную функцию определить какие два прямоугольника дают наибольшую площадь пересечения Вариант №12 Дана целочисленная квадратная матрица. Определить: сумму элементов в тех столбцах, которые содержат хотя бы один отрицательный элемент; номера тех строк, в которых сумма положительных элементов максимальна Каждый пункт задания оформить в виде отдельной функции. . 6. Контрольные вопросы 1. Как выглядит обобщенное определение функции на языке Си? 2. Что такое формальный параметр функции, фактический параметр функции, прототип функции, сигнатура функции, описание функции? 3. Для чего дается описание функции в программе? Как оно выглядит? 4. Какова роль умалчиваемых значений параметров функции? Какие ограничения на использование умалчиваемых значений налагает синтаксис языка? 5. Что такое передача параметров в функцию по значению? По адресу? По ссылке? 6. Как передавать в функцию одномерные массивы? Двумерные массивы? 7. Перечислите основные преимущества использования функций в программе. 8. Как определяются и для чего используются указатели на функции? 7. Литература 1. Березин Б. И., Березин С. Б. Начальный курс С и С++. М.: ДиалогМИФИ, 2007 г., - 288с. 21 2. Демидович Е.И. Основы алгоритмизации и программирования. Язык Си. Учебник для вузов. СПб.:BHV, 2008 г. – 439с. 3. Костюкова Н. И., Калинина Н. А. Язык Си и особенности работы с ним. М: Бином. Лаборатория знаний, 2006 г. – 208с. 4. Павловская Т.А. C/C++. Программирование на языке высокого уровня. СПб.: Питер, 2003., 461 с. 5. Подбельский В.В. Язык Си++. М.:Финансы и статистика, 2003 г., 560 с. 6. Страуструп Б. Язык программирования С++. Специальное издание. СПб.: Бином, 2008 г., 1104с. 7. Фомин С.С. Подбельский В.В. Программирование на языке Си: Учебное пособие. М.:Финансы и статистика, 2007 г. – 600с. 8. Шилдт Г. Полный справочник по C++. М.: Вильямс, 2007 г., - 800с. 22 Дмитрий Николаевич Лясин Марина Викторовна Фадеева Функции, определяемые пользователем, в языках Си и Си++. Методические указания к лабораторным работам по курсу «Алгоритмические языки. Программирование на языках высокого уровня». План выпуска электронных изданий 2011г., поз. N л49В Подписано на выпуск в свет 28.05.11 . Уч. -изд.л. 1.44 На магнитноносителе. Волгоградский государственный технический университет. 400131 Волгоград , пр. Ленина , 28. . 23