Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Информатика_1 / C / lecture6 / lecture6

.html
Скачиваний:
11
Добавлен:
09.06.2015
Размер:
32.54 Кб
Скачать

    Лекция 6. Функции в Си. Table of contents. 1. Функции в Си. 2. Передача имен функций и указателей через список аргументов. 3. Передача указателей на массивы через список аргументов. 4. Создание и использование библиотек (статических и динамических). 5. Задачи. Функции в Си. Функция - это набор объявлений и операторов предназначенных для решения какой-либо конкретной задачи. Любая программа на языке Си содержит по меньшей мере одну фнкцию - main() , но может включать в себя и любое количество других функций. В этой лекции рассказывается как объявлять, описывать и вызывать функции. Объявление функции задает ее имя, тип возвращаемого значения, а также число и типы формальных аргументов. Например: double F(double x); - объявление функции F double F(double); - альтернативное объявление функции F double F(double s) { return log(s) + 2 - s; } - описание функции F . В данном примере показан формат объявления и описания функции. Приведено два варианта объявления функции - в первом варианте при объявлении типа аргумента ему присвоено имя x, а во втором объявлении использован абстрактный декларатор - указан лишь тип принимаемого функцией аргумента . Вы можете использовать любую форму объявления аргументов - используя абстрактные деклараторы (как в альтернативном объявлении) или присваивая имена формальным параметрам. Однако следует иметь в виду , что имена формальных аргументов видны в программе только до конца объявления заголовка функции, т.е. до точки с запятой. Поэтому при описании формальных параметров функции обычно используют абстрактные деклараторы. Перед именем функции при обявлениии заголовка или при описании функции всегда указывается тип возвращаемого значения. В нашем примере - функция F возвращает значение типа double. Функция возвращает значение с помощью оператора return. С помощью этого оператора можно вернуть только одно значение. Вернуть несколько значений после работы функции можно либо объединив эти значения в структуру , либо передавая указатели через список аргументов функции. Если функция не использует оператора return , то тип возвращаемого значения должен быть объявлен как void ( пустой в переводе с английского) . Если функция не получает никаких аргументов при вызове , то в списке аргументов следует указать void , как того требует стандарт ANSI. Объявление заголовка функции всегда заканчивается точкой с запятой. Описание функции всегда заключено в фигурные скобки. Формальные аргументы при описании функции показывают , что будет делаться с фактическими аргументами , когда функция будет вызвана. Для вызова функции достаточно написать ее имя со списком передаваемых фактических аргументов. Например для вызова функции F из приведенного выще примера достаточно написать s=F(t); . В результате выполнения этого оператора в переменную s будет занесено значение log(t)+2-t. Следует помнить о соответствии типов данных формальных и фактических аргументов. Поэтому переменные s и t в вашей программе должны быть объявлены , как double. Компилятор использует объявление заголовка функции для сравнения типов фактических аргументов при вызове функции с формальными параметрами даже при отсутствии явного описания функции. Вообще говоря, в языке Си объявление заголовка функции не считается обязательным. Если таковое отсутствует то компилятор сам создаст это объявление, при этом считается , что функция возвращает значение типа int , а тип формальных аргументов берется из первого обращения к функции. Oднако, я настоятельно рекомендую использовать объявления заголовков. Иногда именно они дают единственно приемлемую основу на которой компилятор может осуществить правильную передачу аргументов. Именно заголовки позволяют компилятору либо диагностировать, либо правильно обрабатывать несовпадения в типах и количестве передаваемых аргументов, которые в противном случае могут проявиться в момент выполнения программы. Передача имен фуекций и указателей через список аргументов. Зададимся задачей - написать функцию, которая решает произвольное нелинейное уравнения x=f(x) методом простых итераций с заданной точностью. Этот метод рассматривался в лекции 4. Напомню , что основная вычислителная формула , вычисляющая последующее приближение к корню через предыдущее имеет вид: Чтобы начать итерационный процесс необходимо задать первоначальное приближение к корню и допустимую погрешность вычисления корня. Понятно , что функция должна получать по крайней мере три входных параметра - начальное приближение к корню, допустимую погрешность вычисления корня и имя функции f, которая определяет вид уравнения. Функция должна возвращать найденное с заданной точностью значение корня. Вот как может выглядеть заголовок этой функции: double iter1(double x, double eps, double (*f)(double) );. Я дал этой функции имя iter1. Первые два формальных аргумента функции x и eps предназначены для передачи в функцию начального приближения к корню и допустимой погрешности при его вычислении . Третий аргумент предназначен для передачи в функцию имени другой функции, которая определяет вид нелинейного уравнения. В языке Си имя любой функции всегда является адресом (указателем). Для того , чтобы можно было передать имя какой-либо функции через список аргументов другой функции, соответствующий формальный аргумент этой функции должен быть описан , как указатель на функцию. Теперь посмотрим на третий формальный аргумент в заголовке функции iter1. Сначала интерпретируется содержимое первых круглых скобок (*f) - f является адресом, далее интерпретируем вторые круглые скобки (*f)(double) - f является адресом функции, получающей один вещественный аргумент. И теперь весь аргумент целиком double (*f)(double) - f является адресом функции, получающей один вещественный аргумент и возвращающей вещественное значение типа double. Именно такая функция и определяет вид нашего нелинейного уравнения. Итак , минимальное необходимое количество аргументов мы включили в список аргументов функции iter1. Кроме того мы объявили функцию iter1 , как возвращающую вещественное значение с помощью оператора return. Естественно это возвращаемое значение будет корнем нашего уравнения. Приведем теперь описание функции iter1 double iter1(double y, double e, double (*g)(double)){ while(fabs(y-g(y))>e) {y=g(y); QUIT; } return y; } В описании этой функции первый формальный аргумент y используется в качестве первоначального приближения к корню. В дальнейшем в операторе цикла while в эту - же переменную записывается и следующее приближение к корню. Второй формальный параметр e в списке аргументов используется в качестве допустимой погрешности вычисления корня. И третий входной параметр g используется , как имя функции , определяющей нелинейное уравнение. Макрос QUIT , который вписан для возможности прерывания работы цикла while по нажатию клавиши q обсуждался ранее. Заметим , что цикл while в функции iter1 может закончить работу в двух случаях - либо | y-g(y) | < e ( найден корень уравнения y=g(y) с допустимой погрешностью ) , либо была нажата клавиша q на клавиатуре. В любом случае функция iter1 возвращает с помощью оператора return значение , хранящееся в переменной y - последнее найденное приближение к корню. К сожелению , после работы функции iter1 мы не сможем определить как завершилась работа функции - либо потому, что было нарушено условие работы цикла while и следовательно вычислен корень с погрешностью e, либо была нажата клавиша q на клавиатуре и работа цикла while была прервана оператором break. И все это в силу того , что функция iter1 может вернуть лишь одно значение с помощью оператора return. Возникает законный вопрос , как модифицировать код функции iter1, так чтобы после окончания ее работы мы могли-бы не только получить последнее приближение к корню , но и , скажем, код ошибки. Для того, чтобы после работы функции можно было получить произвольное количество результатов есть два пути. Первый - это передача указателей через список аргументов функции. В этом случае мы сообщаем функции при вызове адреса переменных, по которым после окончания работы функция оставит результаты. Второй вариант - это использование переменных типа sruct. Можно объединить несколько переменных в одну структуру и вернуть эту структуру оператором return. Рассмотрим сначала первый вариант , как используя указатели получить несколько результатов после работы функции. Рассмотрим заголовок функции void iter2(double *z, double e, int *err, double (*f)(double)); . Лексема void перед именем функции означает , что функция ничего не возвращает оператором return. Первым аргументом функция получает указатель (адрес) вещественного числа типа double. По этому адресу функции передается адрес ячейки памяти , где хранится первоначальное приближение к корню и где после окончания работы функции можно будет получить найденное значение корня. Второй аргумент - это обычное вещественное значение типа double, назначение этого аргумента - допустимая погорешность при вычислении корня. Третий аргумент - адрес целой переменной. По этому адресу функция после окончания работы должна оставить код завершения, 0 - если корень найден, 1- если функция завершила работу по нажатию клавиши q на клавиатуре. И четвертый аргумент - функция определяющая нелинейное уравнение. Вот описание функции iter2: void iter2(double *z, double e, int *err, double (*g)(double) ) { while( fabs(*z-g(*z) )>e) { *z = g(*z); QUIT; } if( fabs( *z-g(*z) )<e ) *err=0; else *err=1; } Обратите внимание, что первый формальный аргумент z этой функции является указателем (адресом), поэтому во всех выражениях, чтобы получить значение , хранящееся по этому адресу, используется операция разадресации *. То же самое замечание относится и к переменной err. Цикл while в теле функции вычисляет последовательные приближения к корню и может быть завершен в двух случаях. Либо нарушено условие цикла, что означает , что найден корень с требуемой точностью, либо вы нажали клавишу q на клавиатуре и цикл завершен по оператору break. После окончания цикла while в операторе if проверяется как завершился цикл и по адресу err заносится 0 (корень найден) или 1 ( корень не найден). Таким образом после завершения работы функции iter2 по двум переданным ей при вызове адресам будет оставлено два результата - последнее приближение к корню и код завершения. Пример вызова будет дан немного ниже после обсуждения третьего варианта функции. Рассмотрим теперь , как можно вернуть несколько значений после работы функции , используя структуры. Определим структуру с именем ans: struct ans { double root; int err ; }; Используем эту структуру в заголовке следующей функции: struct ans iter3(double y, double e, double (*f)(double)); Этот заголовок отличается от заголовка функции iter1 только типом возвращаемого значения . Назначение формальных аргументов этой функции такое-же , как и у iter1. Однако, возвращаемое значение относится к составному типу struct ans. Через такую структуру можно вернуть , как корень уравнения - через поле root, так и код ошибки - через поле err. Вот описание функции iter3: struct ans iter3(double y, double e, double (*g)(double)){ struct ans ret; while(fabs( y-g(y) )>e){y=g(y); QUIT;} if( fabs(y-g(y) )<e ) { ret.err=0; ret.root=y; } else ret.err=1; return ret; } Цикл while этой функции такой-же , как и в предыдущих функциях. Однако перед выполнением оператора return функция заполняет поля возвращаемой структуры ret. В случае если корень найден в поле root возвращаемой структуры ret заносится значение корня (ret.root=y ) , а в поле err заносится 0 ( ret.err=0; ), в случае если корень не найден в поле err структуры ret заносится 1, а в поле root не заносится никакого значения. Ниже приводится программа из файла l6_1.c , демострирующая работу всех трех описанных выше функций. /*1*/ #include<stdio.h> /*2*/ #include<math.h> /*3*/ #include<conio.h> /*4*/ #define QUIT if(kbhit()) if( getch()=='q') break /*5*/ struct ans {double root; int err; }; /*6*/ double f(double); /*7*/ double f(double z) { return log(z)+2; } /*8*/ double iter1(double, double, double (*f)(double)); /*9*/ void iter2(double *, double, int *, double (*)(double)); /*10*/ struct ans iter3(double , double, double (*)(double)); /*11*/ double iter1(double y, double e, double (*g)(double)){ /*12*/ while(fabs(y-g(y))>e){y=g(y); QUIT;} /*13*/ return y; /*14*/ } /*15*/ void iter2(double *z, double e, int *err, double (*g)(double)){ /*16*/ while(fabs(*z-g(*z))>e){*z=g(*z); QUIT;} /*17*/ if(fabs(*z-g(*z))<e)*err=0; else *err=1; /*18*/ } /*19*/ /*20*/ struct ans iter3(double y, double e, double (*g)(double)){ /*21*/ struct ans ret; /*22*/ while(fabs(y-g(y))>e){y=g(y); QUIT;} /*23*/ if(fabs(y-g(y))<e){ret.err=0; ret.root=y; } else ret.err=1; /*24*/ return ret; /*25*/ } /*26*/ /*27*/ void main(void){ /*28*/ double x,eps,root,x1; /*29*/ int error; /*30*/ struct ans report; /*31*/ printf("input x,eps -->"); /*32*/ scanf("%le%le",&x,&eps); x1=x; /*33*/ root=iter1(x,eps,f); /*34*/ printf("root from iter1=%e\n",root); /*35*/ iter2(&x1,eps,&error,f); /*36*/ if(error==0)printf("root from iter2=%e\n",x1); /*37*/ else printf("error in iter2\n"); /*38*/ report=iter3(x,eps,f); /*39*/ if(report.err==0)printf("root from iter3=%e\n",report.root); /*40*/ else printf("error in iter3\n"); /*41*/ /*42*/ } В строке 33 показан пример вызова функции iter1 - функция получает при вызове в качестве фактических аргументов переменные x, eps , и f. В этом примере функция , определяющая нелинейное уравнение , объявлена и описана с именем f. После окончания работы функция iter1 возвращает результат в переменную root, которая в следующей строке выводится на экран. Может показаться , что после работы функции iter1 в переменной x будет содержаться последнее приближение к корню, так как функция iter1 использует эту переменную для хранения текущего приближения к корню. Однако это не так. Дело в том, что в языке программирования Си все аргументы в функцию при ее вызове передаются по значениям. Это означает , что при вызове функции делаются копии со всех передаваемых фактических аргументов. И дальнейшая работа осуществляется именно с копиями переданных аргументов. После окончания работы функции все копии уничтожаются. Таким образом после работы функции iter1 в строке 33 примера значение переменной x останется таким-же, каким оно было до вызова функции iter1, т.к. вся работа велась с копией этой переменной. Именно по этой причине для того, чтобы получить результат работы функции через передаваемые ей аргументы , в список аргументов включают указатели (адреса) на переменные в которые необходимо записать результаты работы функции. Примером такой функции и является функция iter2. В строке 35 приведен пример вызова функции iter2. Первым фактическим аргументом этой функции передается адрес переменной x1 (типа double). Напомню , что в языке Си для получения адреса используется знак операции взятия адреса - & (амперсанд). После работы iter2 по этому адресу будет оставлен корень уравнения. Для получения кода возврата третьим аргументом при вызове iter2 передается адрес целой переменной error. В 36 строке проверяется код возврата функции iter2 и на экран выводится либо значение корня из перемннной x1, либо сообщение об ошибке. Назначение фактических аргументов eps и f такое-же, как и в случае функции iter1. В строке 38 показан пример вызова функции iter3. Назначение фактических аргументов x , eps и f - такое- же , как и в случае функции iter1. Результат работы возвращается в переменную report типа struct ans. В строке 39 анализируется поле err этой переменной и в зависимости от ее значения на экран выводится либо значение корня, либо сообщение об ошибке. Передача указателей на массивы через список аргументов. Предположим нам необходимо написать функцию для суммирования заданного количества элементов произвольного массива из элементов типа double. В языке Си передавать массив через список аргументов функции нельзя. Однако мы знаем , что имя массива в языке Си всегда указатель ( адрес ) , указывающий на первый элемент массива. Указатели передавать через список аргументов мы уже умеем. Поэтому для того , чтобы предать функции имя массива мы должны передавать указатель. Вот пример программы, хранящейся в файле l6_2.c , в которой описана функция sum, предназначенная для суммирования заданного количества элементов массива и возвращения результата через указатель (третий аргумент функции) : /*1*/ #include <stdio.h> /*2*/ void sum(double *,int ,double *); /*3*/ void main(void) { /*4*/ double a[5]={0.2,0.45,1.34,3.6,-57.93},s; /*5*/ int num=3; /*6*/ sum(a,num,&s); /*7*/ printf("s=%e\n",s); /*8*/ } /*9*/ void sum(double *d,int k,double *ans){ /*10*/ int i; /*11*/ for(i=0,*ans=0;i<k;i++)*ans+=d[i]; /*12*/ } В строке 2 объявлен заголовок функции sum. Функция получает три аргумента , первый типа double * для передачи имени массива, второй типа int определяет количество суммируемых элементов, третий аргумент типа double * для возвращаемого результата. В строке 6 показан пример вызова функции sum. В качестве первого фактического аргумента передается имя вещественного массива а, второй аргумент num типа int определяет , что функция будет суммировать первые три элемента массива a. Результат суммирования будет записан по адресу переменоой s. В строке 7 результат суммирования выводится на экран. В строках 9-11 приведено описание функции sum. Обратите внимание на использование операции разадресации для получения значения, хранящегося по адресу ans. Создание и использование библиотек (статических и динамических). . Обычно , готовые к использованию функции включают в состав библиотек (статических или динамических) объектных модулей , чтобы в дальнейшем больше не включать коды функций, написанные на одном из языков программирования , в свои программы , а подключать готовые библиотеки при сборке программ. Существует два вида библиотек - статические и динамические. Коды функций из статической библиотеки включаются статически в exe - файл в момент его создания. Таким образом код одной и той-же функции (например iter2 или iter3) может содержаться одновременно в нескольких exe - файлах. Это приводит к нерациональному использованию дискового пространства - одни и те - же коды функций хранятся многократно на диске. Коды функций из динамических библиотек не включаются в exe - файлы и хранятся лишь в одном экземпляре в составе динамически подключаемой библиотеки. Подключение необходимой функции из динамической библиотеки происходит в момент загрузки выполняемого exe файла в память. В этой лекции рассматривается способ создания и использования статических и динамических библиотек. Начнем наше рассмотрение с создания и использования статических библиотек. Для создания статической библиотеки необходимо: 1. Иметь файл с текстами функций , которые необходимо включить в библиотеку. Например, требуется включить в библиотеку функции iter1, iter2 и iter3. Кроме описания самих функций , необходимо включить в этот-же файл заголовки всех используемых функций и макросы. Вот пример такого файла (staticlib.c) #include<math.h> #include<conio.h> #define QUIT if(kbhit())if(getch()=='q')break struct ans {double root; int err; }; double iter1(double y, double e, double (*g)(double)){ while(fabs(y-g(y))>e){y=g(y); QUIT;} return y; } void iter2(double *z,double e,int *err,double (*g)(double)){ while(fabs(*z-g(*z))>e){*z=g(*z); QUIT;} if(fabs(*z-g(*z))<e)*err=0; else *err=1; } struct ans iter3(double y, double e, double (*g)(double)){ struct ans ret; while(fabs(y-g(y))>e){y=g(y); QUIT;} if(fabs(y-g(y))<e){ret.err=0; ret.root=y; } else ret.err=1; return ret; } 2. Когда вы имеете файл с исходными кодами функций , включаемыми в библиотеку, можно приступать к созданию самой библиотеки. Для этого необходимо в среде разработки Visual C++ открыть новый проект Win32 Static Library , как это показано на следующем рисунке Как видно из этого рисунка я открыл новый проект Win32 Static Library c именем staticlib в директории e:\bvv\lectures\C\lecture6. Теперь к этому проекту добавим файл с кодами функций ( в моем примере это файл staticlib.c) через меню project - add to project - files, как это показано на следующем рисунке Теперь осталось приготовить библиотеку через меню оболочки Build Build staticlib.lib или нажатием клавиши F7. Если в исходных кодах не было ошибок , тогда в директории E:\bvv\lectures\C\lecture6\staticlib\debug будет создан файл staticlib.lib. Это и есть готовая к использованию статическая библиотека. Теперь рассмотрим вопрос о том , как подключать статические библиотеки. Пусть у нас имеется программа , которая использует функции iter1, iter2 и iter3. В моем примере эта программа содержится в файле test_static_lib.c. #include<stdio.h> #include<math.h> #include "staticlib.h" double f(double); double f(double z){return log(z)+2; } void main(void){ double x,eps,root,x1; int error; struct ans report; printf("input x,eps -->"); scanf("%le%le",&x,&eps); x1=x; root=iter1(x,eps,f); printf("root from iter1=%e\n",root); iter2(&x1,eps,&error,f); if(error==0)printf("root from iter2=%e\n",x1); else printf("error in iter2\n"); report=iter3(x,eps,f); if(report.err==0)printf("root from iter3=%e\n",report.root); else printf("error in iter3\n"); } Функция main этой программы полностью аналогична функции main из программы l6_1.c. Однако описания функций iter1, iter2 и iter3 отсутствуют, коды этих функций будут браться из библитеки staticlib.lib. В начале программы добавлена директива #include "staticlib.h". В заголовочном файле staticlib.h содержаться заголовки функций iter1, iter2 и iter3 и описание структуры struct ans. Файл staticlib.h должен находиться в той - же директории , что и файл test_static_lib.c. Вот содержимое этого файла struct ans {double root; int err; }; double iter1(double, double, double (*f)(double)); void iter2(double *,double,int *,double (*)(double)); struct ans iter3(double , double, double (*)(double)); . Итак, для использоания функций из статической библиотеки нам необходимо иметь саму библиотеку (в нашем примере staticlib.lib) и заголовочный файл (в нашем примере staticlib.h) c заголовками используемых функций. Далее открываем проект Win32 Console Application c какой - либо программой , использующей функциии из приготовленной статической библиотеки ( в нашем примере это программа test_static_lib.c) , подключаем к проекту требующуюся библиотеку ( в нашем примере это staticlib.lib) через меню Project - settings - Link , как это показано на следующем рисунке Обратите внимание на три окна - в окне Category выбрано значение Input, в окне Additional Library Path указан дополнительный путь поиска библиотек и в окне Options в списке библиотек добавлена библиотека staticlib.lib. После подключения библиотеки проект можно компилировать и выполнять полученный exe файл. Теперь рассмотрим способ создания и использования динамических библиотек. Также , как и в предыдущем случае , прежде всего готовим файл , содержащий коды функций . Как и в примере со статическими библиотеками я включил в него три функции - iter1, iter2 и iter3. Пример содержится в файле dlllib.c. Содержимое файла мало отличается от содержимого файла staticlib.c. Единственное отличие в том, что перед типом возвращаемого функцией значения добавляются модификатор __declspec (dllexport) , означающий , что функция будет экспортироваться из dll библиотеки. В моем примере для добавления этого модификатора используется макрос EXPORT. Вот текст файла dlllib.c , подготовленного для создания динамически подключаемой библиотеки. #define EXPORT __declspec (dllexport) #include<math.h> #include<conio.h> #define QUIT if(kbhit())if(getch()=='q')break struct ans {double root; int err; }; EXPORT double iter1(double y, double e, double (*g)(double)){ while(fabs(y-g(y))>e){y=g(y); QUIT;} return y; } EXPORT void iter2(double *z,double e,int *err,double (*g)(double)){ while(fabs(*z-g(*z))>e){*z=g(*z); QUIT;} if(fabs(*z-g(*z))<e)*err=0; else *err=1; } EXPORT struct ans iter3(double y, double e, double (*g)(double)){ struct ans ret; while(fabs(y-g(y))>e){y=g(y); QUIT;} if(fabs(y-g(y))<e){ret.err=0; ret.root=y; } else ret.err=1; return ret; } После подготовки файла вы открываете в среде Visual C++ новый проект типа Win32 Dynamic-Link Library и включаете в его состав приготовленный файл также, как это делалось в случае проекта со статической библиотекой. Предположим , что мы назвали проект dlllib. После компиляции проекта (клавиша F7 или меню Build - Build dlllib.dll) в директории debug или release проекта будут созданы два файла - dlllib.dll и dlllib.lib. Коды скомпилированных функций содержаться в динамической библиотеке dlllib.dll . Именно из этого файла подключаются коды функций при запуске exe файла. Однако, подключается эта библиотека к проекту не прямо , а через файл dlllib.lib. Именнно файл *.lib ( в нашем примере dlllib.lib) подключается к проекту для использования функций из библиотеки динамической компоновки ( dlllib.dll в моем примере). Подключение библиотеки dlllib.lib к проекту производится точно также, как и в случае статической библиотеки. Есть одна особенность использования dll библиотек. Библиотека dll ищется при запуске exe файла либо в директориях, указанных в переменной окружения PATH ( значение этой переменной можно просмотреть , набрав команду path c консоли) или в текущей директории из которой запускается exe файл. Для использования динамически подключаемой библиотеки кроме двух файлов *.dll и *.lib необходимо также иметь заголовочный файл с заголовками функций импортируемых из динамически подключаемой библиотеки. Вот пример такого файла - dlllib.h #define IMPORT __declspec (dllimport) struct ans {double root; int err; }; IMPORT double iter1(double, double, double (*f)(double)); IMPORT void iter2(double *,double,int *,double (*)(double)); IMPORT struct ans iter3(double , double, double (*)(double)); . Этот файл отличается от аналогичного файла для статической библиотеки только модификатором __declspec (dllimport) перед заголовками функций . Этот модификатор указывает , что функции импортируются из динамически подключаемой библиотеки. А вот пример программы , использующей функции из динамически подключаемой библиотеки ( файл test_dll_lib.c ) #include<stdio.h> #include<math.h> #include "dlllib.h" double f(double); double f(double z){return log(z)+2; } void main(void){ double x,eps,root,x1; int error; struct ans report; printf("input x,eps -->"); scanf("%le%le",&x,&eps); x1=x; root=iter1(x,eps,f); printf("root from iter1=%e\n",root); iter2(&x1,eps,&error,f); if(error==0)printf("root from iter2=%e\n",x1); else printf("error in iter2\n"); report=iter3(x,eps,f); if(report.err==0)printf("root from iter3=%e\n",report.root); else printf("error in iter3\n"); } Текст этой программы отличается от аналогичной тестирующей программы для статической библиотеки только строкой #include "dlllib.h". Файл dlllib.h должен находиться в той- же директории, что и файл test_dll_lib.c. Для приготовления exe файла из этой программы необходимо открыть проект Win32 Console Application , добавить к проекту файл test_dll_lib.c , подключить библиотеку dlllib.lib к проекту и скомпилировать проект (клавиша F7 или меню Build - Build test_dll_lib.exe ) . Напомню еще раз , что для запуска exe файла необходимо , чтобы файл dlllib.dll находился в одной из директорий, указанных в переменной окружения PATH или в той-же директории, что и запускаемый exe файл. Задачи к лекции . Задача 1. Написать функцию для решения нелинейного уравнения методом Ньютона. Функция должна возвращать корень уравнения и код завершения через указатели. Задача 2. Написать функцию для решения нелинейного уравнения методом Ньютона. Функция должна возвращать корень уравнения и код завершения через структуру. Задача 3. Написать функцию для решения нелинейного уравнения методом хорд. Функция должна возвращать корень уравнения и код завершения через указатели. Задача 4. Написать функцию для решения нелинейного уравнения методом хорд. Функция должна возвращать корень уравнения и код завершения через структуру. Задача 5. Написать функцию для решения нелинейного уравнения методом бисекций. Функция должна возвращать корень уравнения и код завершения через указатели. Задача 6. Написать функцию для решения нелинейного уравнения методом бисекций. Функция должна возвращать корень уравнения и код завершения через структуру. Задача 7. Из функций , созданных при решении задач 1 - 6 создать статическую библиотеку и написать программу , использующую функции из этой библиотеки. Задача 8. Из функций , созданных при решении задач 1 - 6 создать библиотеку динамической компоновки и написать программу , использующую функции из этой библиотеки. previous next home

Соседние файлы в папке lecture6