Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






В языке С определения функций не могут быть вложенными, т.е. внутри одной функции нельзя объявить и расписать тело другой функции.






Возвращаемый тип возвр-тип функции определяет тип данного, возвращаемого функцией значения и может задавать любой тип. Например, это могут быть int, float, double и т.д. В случае, когда функция ничего не возвращает, ей присваивается тип void. Если спецификатор-типа не задан, то предполагается, что функция возвращает значение типа int.

Функция не может возвращать массив или функцию, но может возвращать указатель на любой тип, в том числе и на массив и на функцию.

Тип возвращаемого значения, задаваемый в определении функции, должен соответствовать типу в объявлении этой функции.

Функция возвращает значение если ее выполнение заканчивается оператором return, содержащим некоторое выражение. Указанное выражение вычисляется, преобразуется, если необходимо, к типу возвращаемого значения и возвращается в точку вызова функции в качестве результата. Если оператор return не содержит выражения или выполнение функции завершается после выполнения последнего ее оператора (без выполнения оператора return), то возвращаемое значение не определено.

Если функция определена как функция, возвращающая некоторое значение, а в операторе return при выходе из нее отсутствует выражение, то поведение вызывающей функции после передачи ей управления может быть непредсказуемым.

Список параметров – это список, элементы которого отделяются друг от друга запятыми. При вызове функции параметры принимают значения аргументов. Если функция без параметров, то такой пустой список можно указать в явном виде, поместив для этого внутри скобок ключевое слово void. Все параметры функции (входящие в список параметров) должны объявляться отдельно, причем для каждого из них надо указывать и тип, и имя. В общем виде список объявлений параметров должен выглядеть следующим образом:

fun(тип имя_перем1, тип имя_перем2,..., тип имя_перем N)

Например:

fun(int i, int j, float k, char str1, char str2)

Рассмотрим пример программы с выводом сообщения не в главной функции main(), а в другой:

#include < stdio.h> #include < conio.h> void printMessage (void){printf(" \n\t hello, world\n"); return; printf(" \n\t 123\n"); } int main(void){printMessage(); printf(" \n Press any key: "); _getch(); return 0; }

Результат выполнения программы показан на рис. 12.1.

Рис. 12.1. Вывод сообщения с помощью двух функций

Программа состоит из двух функций: printMessage() и main(). Выполнение программы всегда начинается с функции main(), которую называют еще главной. Внутри функции main() происходит вызов функции printMessage() без параметров. Когда происходит вызов функции, выполнение программы передается непосредственно вызванной функции. Внутри функции printMessage() выполняется только утверждение

printf(" \n\t hello, world\n");

Несмотря на то, что в функции printMessage() есть еще одно утверждение printf(" \n\t 123\n"), которое не выполняется, поскольку используется утверждение возврата (return) из функции.

В языке С функция введена как один из производных типов.

Формальные параметры в определениях функций могут объявляться в форме прототипа. Прототипы дают компилятору возможность тщательнее выполнять проверку типов аргументов. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.

Список-формальных-параметров - это последовательность объявлений формальных параметров, разделенная запятыми. Формальные параметры - это переменные, используемые внутри тела функции и получающие значение при вызове функции путем копирования в них значений соответствующих фактических параметров.

Список-формальных-параметров может заканчиваться запятой (,) или запятой с многоточием (,...), это означает, что число аргументов функции переменно. Однако предполагается, что функция имеет, по крайней мере, столько обязательных аргументов, сколько формальных параметров задано перед последней запятой в списке параметров. Такой функции может быть передано большее число аргументов, но над дополнительными аргументами не проводится контроль типов.

Если функция не использует параметров, то наличие круглых скобок обязательно, а вместо списка параметров рекомендуется указать слово void.

Порядок и типы формальных параметров должны быть одинаковыми в определении функции и во всех ее объявлениях. Типы фактических параметров при вызове функции должны быть совместимы с типами соответствующих формальных параметров. Тип формального параметра может быть любым основным типом, структурой, объединением, перечислением, указателем или массивом. Если тип формального параметра не указан, то этому параметру присваивается тип int.

Для формального параметра можно задавать класс памяти register, при этом для величин типа int спецификатор типа можно опустить.

Идентификаторы формальных параметров используются в теле функции в качестве ссылок на переданные значения. Эти идентификаторы не могут быть переопределены в блоке, образующем тело функции, но могут быть переопределены во внутреннем блоке внутри тела функции.

При передаче параметров в функцию, если необходимо, выполняются обычные арифметические преобразования для каждого формального параметра и каждого фактического параметра независимо. После преобразования формальный параметр не может быть короче чем int, т.е. объявление формального параметра с типом char равносильно его объявлению с типом int. А параметры, представляющие собой действительные числа, имеют тип double.

Преобразованный тип каждого формального параметра определяет, как интерпретируются аргументы, помещаемые при вызове функции в стек. Несоответствие типов фактических аргументов и формальных параметров может быть причиной неверной интерпретации.

Тело функции - это составной оператор, содержащий операторы, определяющие действие функции.

Все переменные, объявленные в теле функции без указания класса памяти, имеют класс памяти auto, т.е. они являются локальными. При вызове функции локальным переменным отводится память в стеке и производится их инициализация. Управление передается первому оператору тела функции и начинается выполнение функции, которое продолжается до тех пор, пока не встретится оператор return или последний оператор тела функции. Управление при этом возвращается в точку, следующую за точкой вызова, а локальные переменные становятся недоступными. При новом вызове функции для локальных переменных память распределяется вновь, и поэтому старые значения локальных переменных теряются.

Параметры функции передаются по значению и могут рассматриваться как локальные переменные, для которых выделяется память при вызове функции и производится инициализация значениями фактических параметров. При выходе из функции значения этих переменных теряются. Поскольку передача параметров происходит по значению, в теле функции нельзя изменить значения переменных в вызывающей функции, являющихся фактическими параметрами. Однако, если в качестве параметра передать указатель на некоторую переменную, то используя операцию разадресации можно изменить значение этой переменной.

void change (int x, int y)

{

int k=x;

x=y;

y=k;

}

В данной функции значения переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную вследующем примере.

void change (int *x, int *y)

{

int k=*x;

*x=*y;

*y=k;

}

При вызове такой функции в качестве фактических параметров должны быть использованы не значения переменных, а их адреса

change(& a, & b);

Если требуется вызвать функцию до ее определения в рассматриваемом файле, или определение функции находится в другом исходном файле, то вызов функции следует предварять объявлением этой функции. Объявление (прототип) функции имеет следующий формат:

[спецификатор-класса-памяти] [спецификатор-типа] имя-функции ([список-формальных-параметров]) [, список-имен-функций];

В отличие от определения функции, в прототипе за заголовком сразу же следует точка с запятой, а тело функции отсутствует. Если несколько разных функций возвращают значения одинакового типа и имеют одинаковые списки формальных параметров, то эти функции можно объявить в одном прототипе, указав имя одной из функций в качестве имени-функции, а все другие поместить в список-имен-функций, причем каждая функция должна сопровождаться списком формальных параметров. Правила использования остальных элементов формата такие же, как при определении функции. Имена формальных параметров при объявлении функции можно не указывать, а если они указаны, то их область действия распространяется только до конца объявления.

Прототип - это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции.

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров используемых при данном вызове.

Таким образом, прототип функции необходимо задавать в следующих случаях:

1. Функция возвращает значение типа, отличного от int.

2. Требуется проинициализировать некоторый указатель на функцию до того, как эта функция будет определена.

Наличие в прототипе полного списка типов аргументов параметров позволяет выполнить проверку соответствия типов фактических параметров при вызове функции типам формальных параметров, и, если необходимо, выполнить соответствующие преобразования.

В прототипе можно указать, что число параметров функции переменно, или что функция не имеет параметров.

В приведенной выше программе прототип функции printMessage() не использовался, так как сама функция была объявлена до главной функции main(). Для переносимости С -кода в С ++ использование прототипа функции обязательно. Поэтому к хорошему стилю программирования относится использование прототипов функций, поскольку большие программы обычно состоят из нескольких функций, часто расположенных в различных файлах.

Вышеприведенная программа с использованием прототипа функции printMessage() будет выглядеть следующим образом:

#include < stdio.h> #include < conio.h> //void printMessage (void); //Прототип функции int main(void) { void printMessage (void); //Прототип функции printMessage(); // Вызов функции printf(" \n Press any key: "); _getch(); return 0; } // Определение функцииvoid printMessage (void){ printf(" \n\t hello, world\n"); return; printf(" \n\t 123\n"); }

В листинге программы показаны две возможности использования прототипа функции printMessage(). При этом, сама функция printMessage() объявлена после функции main().

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

адресное-выражение ([список-выражений])

Поскольку синтаксически имя функции является адресом начала тела функции, в качестве обращения к функции может быть использовано адресное-выражение (в том числе и имя функции или разадресация указателя на функцию), имеющее значение адреса функции.

Список-выражений представляет собой список фактических параметров, передаваемых в функцию. Этот список может быть и пустым, но наличие круглых скобок обязательно.

Формальные параметры функции определены в прототипе функции. При обращении к функции используются фактические параметры, называемые аргументами функции.

Список фактических параметров – это список выражений, количество которых равно количеству формальных параметров функции (исключение составляют функции с переменным числом параметров). Соответствие между формальными и фактическими параметрами устанавливается по их взаимному расположению в списках. Между формальными и фактическими параметрами должно быть соответствие по типам.

Фактический параметр может быть величиной любого основного типа, структурой, объединением, перечислением или указателем на объект любого типа. Массив и функция не могут быть использованы в качестве фактических параметров, но можно использовать указатели на эти объекты.

Выполнение вызова функции происходит следующим образом:

1. Вычисляются выражения в списке выражений и подвергаются обычным арифметическим преобразованиям. Затем, если известен прототип функции, тип полученного фактического аргумента сравнивается с типом соответствующего формальногопараметра. Если они не совпадают, то либо производится преобразование типов, либо формируется сообщение об ошибке. Число выражений в списке выражений должно совпадать с числом формальных параметров, если только функция не имеет переменного числа параметров. В последнем случае проверке подлежат только обязательные параметры.Если в прототипе функции указано, что ей не требуются параметры, а при вызове они указаны, формируется сообщение об ошибке.

2. Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции, а возвращаемое значение не определено.

Синтаксис языка С предусматривает только один способ передачи параметров – передачу по значениям. Это означает, что формальные параметры функции локализованы в ней, т.е. недоступны вне определения функции и никакие операции над формальными параметрами в теле функции не изменяют значений фактических параметров.

Передача параметров по значению предусматривает следующие шаги:

  1. При компиляции функции выделяются участки памяти для формальных параметров, т.е. формальные параметры оказываются внутренними объектами функции. При этом для параметров типа float формируются объекты типа double, а для параметров типов char и short int создаются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива, и он служит представлением массива-параметра в теле функции.
  2. Вычисляются значения выражений, использованных в качестве фактических параметров при вызове функции.
  3. Значения выражений – фактических параметров заносятся в участки памяти, выделенные для формальных параметров функции.
  4. В теле функции выполняется обработка с использованием значений внутренних объектов-параметров, и результат передается в точку вызова функции как возвращаемое ею значение.
  5. Никакого влияния на фактические параметры (на их значения) функция не оказывает.
  6. После выхода из функции освобождается память, выделенная для ее формальных параметров.

Важным является момент, что объект вызывающей программы, использованный в качестве фактического параметра, не может быть изменен из тела функции. Для подобного изменения существует косвенная возможность изменять значения объектов вызывающей программы действиями в вызванной функции. Это становится возможным с помощью указателя (указателей), когда в вызываемую функцию передается адрес любого объекта из вызывающей программы. С помощью выполняемого в тексте функции разыменования указателя осуществляется доступ к адресуемому указателем объекту из вызывающей программы. Тем самым, не изменяя самого параметра (указатель-параметр постоянно содержит только адрес одного и того объекта), можно изменять объект вызывающей программы.

 

Массивы и строки также могут быть параметрами функции. В этом случае внутрь функции передается только адрес начала массива. Тогда можно в качесстве параметра использовать указатель. Приведем два равноправных прототипа функций:

float fun(int n, float A[ ], float B[ ]); float fun(int n, float *a, float *b);

Поскольку массив передается в функцию как указатель, внутри функции можно изменять значения элементов массива–фактического параметра, определенного в вызывающей программе. Это возможно и при использовании индексирования, и при разыменовании указателей на элементы массива.

В языке С существует возможность создавать функции, число аргументов которых не определено – функции с переменным числом аргументов. При этом следует указать только количество аргументов. Пример прототипа функции с переменным числом аргументов:

int fun(int n, …);

Многоточие (…) в прототипе функции означает, что функция получает переменное число аргументов любого типа. Многоточие должно всегда находиться в конце списка параметров. Для того, чтобы получить доступ ко всем параметрам, принимаемых функцией, нужно знать имя и тип хотя бы одного параметра.

# include < stdio.h> #include< stdlib.h> void MyFunc(int a, …){}void main(void){ system(“CLS”); MyFunc(100, 200, 300, 400); // в функцию передается 4 параметра system(“PAUSE”); }

Вот такой код не вызывает возражений, а многоточие после первого параметра внутри параметров функции обозначает, что в функцию можно передать для обработки более одного параметра.

Должен возникнуть вопрос как же получить доступ к каждому из этих параметров. Для того, чтоб хорошо понять то что будет написано, требуется знание указателей. Чтобы иметь доступ к каждому из параметров, нужно знать адрес первого параметра, а чтобы взять этот адрес, как раз и требуется указатель

# include < stdio.h> #include< stdlib.h> void MyFunc(int a, …){ int *P=& a; //Взятие адреса у первого параметра while (*P) //Пока встречаются параметры и параметр не равен 0 { printf(“%d ”, *p); //Вытаскиваем значение с адреса по которому живет параметр P++; //Адресная арифметика. Смена текущего адреса на следующий }}void main(void){ system(“CLS”); MyFunc(100, 200, 300, 400); // В функцию передается 4 параметра system(“PAUSE”); }

Что можно увидеть? Первым делом внутри функции происходят взятие адреса первого параметра, при этом тип указателя должен совпадать с типом этого параметра. Дальше идет простой перебор всех параметров с помощью цикла.

Таким же образом можно привести классический пример суммирования элементов

# include < stdio.h> #include< stdlib.h> void MyFunc(int a, …){ double *P=& a; //Взятие адреса у первого параметра double sum=0; //Инициализация значения суммы в ноль while (*P) //Пока встречаются параметры и параметр не равен 0 { sum+=*P; //Прибавляем к сумме то что взяли по адресу P P++; //Адресная арифметика. Смена текущего адреса на следующий } printf(“sum=%lf ”, sum); //Вывод результата на экран}void main(void){ system(“CLS”); MyFunc(100, 200, 300, 400); // В функцию передается 4 параметра system(“PAUSE”); }

Вообще весь этот механизм удобно использовать тогда, когда требуется обработка однотипных элементов и чем-то всё напоминает обычный массив элементов. Учитывая, что указатель должен быть того же типа, что и элемент, расположенный по адресу на который он ссылается, можно сказать, что переменное число параметров может быть корректно использовано как раз тогда, когда все параметры передаваемые в такую функцию принадлежат одному и тому же типу (имеются ввиду те параметры, которые идут туда где расположено многоточие).

Даже если типы передаваемых параметров будут различны, то компилятор не выдаст ошибки, но ошибки не выведется только потому что если компилятор видит эти три точки на месте параметров, то он отключает проверку типов. На самом деле легко увидеть эффект небезопасности работы с функцией, в которой указано, что будет более одного параметра, если первый параметр объявить например как int, (соответственно указатель на первый параметр как int), а во время передачи в функцию написать число с точкой (например 1.0 запустить и потом 11.0 и запустить). Т.е. если требуется передать параметры других типов, то их все нужно дописать перед списком предполагаемых неизвестных.

Например void MyFunc(float x, char S[], int n, …), после чего с первыми работать в обычной манере, а с теми что после n с помощью указателя на int и, соответственно, передавать туда параметры с тем же типом, что и у этого n (Исходя из логики того, что тип указателя должен совпадать с типом объекта на адрес которого указатель ссылается).

Но вообще в функцию можно передавать произвольное число параметров с переменным типом. При определении функций с переменным количеством параметров, рекомендуется использовать специальный набор макроопределений, которые становятся доступными при включении в программу заголовочного файла stdarg.h

Макросы и определения заголовочного файла переменных аргументов stdarg.h (табл. 12.1) предоставляют программисту средства, необходимые для построения функций со списком аргументов переменной длины.

Таблица 12.1. Макросы заголовочного файла stdarg.h
Идентификатор Объяснение
va_list Тип, предназначающийся для хранения информации, необходимой макросам v_start, va_arg и va_end. Чтобы получить доступ к аргументам в списке переменной длины, необходимо объявить объект типа va_list
va_start Макрос, который вызывается перед обращением к аргументам списка переменной длины. Он инициализирует объект, объявленный с помощью va_list, для использования макросами va_arg и va_end
va_arg Макрос, расширяющийся до выражения со значением и типом следующего аргументов списке переменной длины. Каждый вызов его изменяет объект, объявленный с помощью va_list так, что объект указывает на следующий аргумент списка
va_end Макрос обеспечивает нормальный возврат из функции, на список аргументов которой ссылается макрос va_start
# include < stdlib.h> # include < stdio.h> # include < stdarg.h> void Ok (char *format, …)//Функция Оk с произвольным числом параметров{ va_list ap; //Указатель на список параметров va_start(ap, format); //Настроились на список параметров for (char *p=format; *p; p++) { if (%p==’%’) //Если встретится символ % { switch(*++p)//Анализуется следующий за этим символом символ { case ‘d’: int ival=va_arg(ap, int); //Если это символ d, то значит //параметр int printf(“%d”, ival); break; //Выводим параметр int на экран case 'f': double dval=va_arg(ap, double); //Если это символ f значит //параметр double printf(“%f”, dval); break; //Выводим параметр double на экран } } else printf(“%c”, *p); } va_end(ap); //Завершаем работу с макрокомандами}void main(){ system (“CLS”); Ok(“%d%f”, 8, 9.555); system(“PAUSE”); return; }

Cпециальный тип va_list используется для представления списков параметров неизвестной длины и состава

va_start вызывается непосредственно перед началом работы с неименованными параметрами

ар инициализируется указателем на последний именованный параметр в списке с переменным числом параметров.

Здесь подпараметр является условным обозначением и относится к макрокомандам, а не функциям

После вызова макроопределения va_start, каждый вызов va_arg возвращает значение заказанного типа type себе. Надо заранее указать тип желаемого параметра. В некоторых реализациях с макроопределением va_arg запрещено использовать типы char, unsigned char, float. Даже в макроопределениях, предназначенных для стандартизации языка, многое зависит от реализации.

Работа со списком параметров завершается вызовом макроопределения void va_end(ap); Это макроопределение обеспечивает корректный возврат из функции и вызывается перед выходом из функции.

Примеры обращений к функции с фактическими аргументами:

double k; double v1 = 1.5, v2 = 2.5, v3 = 3.5; // Первый вариант, где 3 – количество аргументов k = fun(3, v1, v2, v3); // Второй вариант, где 0.0 – завершающий нуль списка аргументов k = fun(v1, v2, v3, 0.0);

Ранее было отмечено, что в языке С аргументы передаются в функции по значению и не существует прямого способа изменить переменную вызывающей функции, действуя внутри вызываемой функции. Благодаря аргументам-указателям функция может обращаться к объектам в вызвавшей ее функции, в том числе модифицировать их. В качестве примера рассмотрим функцию swap(), в задачу которой входит обмен элементов местами. Для решения такой задачи необходимо передать из вызывающей программы (например, из главной функции main()) в функцию указатели на переменные, которые нужно изменить. Программный код решения примера:

#include < stdio.h> #include < conio.h> // Прототип функцииvoid swap(int*, int*); int main (void) { int a = 10, b = -20; // Вывод на консоль исходных значений переменных printf(" \n Initial values: \n a = %d, b = %d\n", a, b); // Вызов функции swap() с фактическими параметрами swap(& a, & b); // Результат после обращения функции swap() printf(" \n New values: \n a = %d, b = %d\n", a, b); printf(" \n... Press any key: "); _getch(); return 0; } // Определение функцииvoid swap(int *pa, int *pb){int temp; temp = *pa; *pa = *pb; *pb = temp; }

В программе в качестве фактических параметров функции swap() выступают адреса заданных переменных. Можно было в главной функции определить указатели и инициализировать их адресами заданных переменных, а потом передать эти указатели в функцию swap.

Результат выполнения программы показан нa рис. 12.2.

Рис. 12.2. Результат обмена данными, выполненного функцией swap()

Указатели, передаваемые в функцию, могут быть указателями на указатели. Указатели могут указывать на начало какого-либо массива и т. д. Указатели могут использоваться для защиты массивов, над которыми необходимо произвести некоторые вычисления или преобразования.

Особым свойством указателей можно считать возможность использовать их в качестве возвращаемых значений функций. Поскольку функции возвращают только одно значение, то несколько значений одного типа можно поместить в массив, а затем указатель на этот массив использовать в качестве возвращаемого значения.

Общая форма определения функции, которая возвращает указатель, следующая:

тип *имя_функции (аргументы функции) {// тело функциитип *имя_указателя;? return имя_указателя; }

Рассмотрим пример, в котором осуществляется сложение двух одномерных массивов и результат возвращается через указатель.

Программный код решения примера:

#include < stdio.h> #include < conio.h> #include < stdlib.h> int *out2(int A[], int B[], int); int main (void) { int i, n; int A[] = {1, 2, 3, 4, 5}; int B[] = {2, 2, 2, 2, 2}; int *ptrAB = NULL; n = (sizeof(A)/sizeof(A[0])); puts(" \n The initial arrays: "); for (i = 0; i < n; i++) printf(" %d", A[i]); puts(" "); for (i = 0; i < n; i++) printf(" %d", B[i]); ptrAB = out2(A, B, n); puts(" \n\n Result from function: "); for (i = 0; i < n; i++) printf(" %d", ptrAB[i]); puts(" \n\n Control of the arrays: "); for (i = 0; i < n; i++) printf(" %d", A[i]); puts(" "); for (i = 0; i < n; i++) printf(" %d", B[i]); free(ptrAB); // освобождение выделенной памяти printf(" \n\n... Press any key: "); _getch(); return 0; } int *out2(int A[], int B[], int n){ int i; int *ptr = (int *)calloc(n, sizeof(int)); //выделение памяти for (i = 0; i < n; i++) ptr[i] = A[i] + B[i]; return ptr; }

Программа не требует особых пояснений.

Следует отметить, что никогда не следует возвращать адрес переменной, определенной в теле функции, так как переменные функции являются локальными, и они существуют только во время работы функции.

Указатели возвращаются подобно значениям любых других типов данных. Чтобы вернуть указатель, функция должна объявить его тип в качестве типа возвращаемого значения. Таким образом, если функция возвращает указатель, то значение, используемое в ее инструкции return, также должно быть указателем. В частности, многие библиотечные функции, предназначенные для обработки строк, возвращают указатели на символы.

В языке С существует такой механизм как указатель на функцию. Допустим, существует несколько функций для различных операций с данными. В этом случае оказывается удобным определить указатель на функцию, и использовать его там, где требуется производить расчет для различных функций.

Функции, как и элементы данных, имеют адреса. Адресом функции является адрес памяти, с которого начинается машинный код функции.

Для того, чтобы использовать в программе указатель на функцию, необходимо выполнить следующие действия:

  • принять адрес функции;
  • объявить указатель на функцию;
  • использовать указатель на функцию для вызова этой функции.

Каждая функция характеризуется типом возвращаемого значения, именем и списком типов ее параметров. Если имя функции использовать без последующих скобок и параметров, то оно будет выступать в качестве указателя на эту функцию, и его значение будет определяться как адрес размещения функции в памяти (первый байт). Это значение можно будет присвоить другому указателю. Тогда этот новый указатель можно будет использовать для вызова функции. Указатель на функцию как переменная вводится отдельно от определения и объявления (прототипа) какой-либо функции.

Указатель на функцию – это переменная, содержащая адрес в памяти, по которому расположена функция. Имя функции – это адрес начала программного кода функции. Указатели на функции могут быть переданы функциям в качестве аргументов, могут возвращаться функциями, сохраняться в массивах и присваиваться другим указателям на функции.

Синтаксис определения указателя на функцию:

тип_функции(*имя_указателя)(спецификация_параметров)

где тип_функции – определяет тип возвращаемого функцией значения; имя_указателя – идентификатор; спецификация_параметров – определяет состав и типы параметров функции.

Например:

int (*pf)(); // без контроля параметров вызоваint (*pf)(void); // без параметров, с контролем по прототипуint (*pf)(int, char*); // с контролем по прототипу

В соответствии с принципом контекстного определения типа данных эту конструкцию следует понимать так: pf – переменная, при косвенном обращении к которой вызывается функция с соответствующим прототипом, например int_F(int, char*), то есть pf содержит адрес функции или указатель на функцию. Следует обратить внимание на то, что в определении указателя присутствует прототип – указатель ссылается не на произвольную функцию, а только на одну из функций с заданной схемой формальных параметров и результата.

В определении указателя количество и тип параметров должны совпадать с соответствующими типами в определении функции, на которую ставится указатель.

Например,

int (*func1Prt)(char);

задает определение указателя func1Prt на функцию с параметром типа char, возвращающую значение типа int.

Важнейшим элементом в определении указателя на функцию являются круглые скобки. Так следующий фрагмент:

int *func(char);

это не определение указателя, а объявление (прототип) функции c именем func и параметром типа char, возвращающей значение указателя типа int *. В этом случае указатель указывает на значение функции.

Если же выполнить объявление:

char *(*func2Prt)(char *, int);

то определение указателя func2Prt на функцию с параметрами типа указатель на char и типа int, возвращающую значение типа указатель на char.

Синтаксис вызова функции с помощью указателя:

(*имя_указателя)(список_фактических_параметров);

значением имя_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции.

Арифметические операции над указателями на функции запрещены.

Указатели на функции в основном используются в следующих случаях.

  • Многие библиотечные функции в качестве аргумента получают указатель на функцию. Например, функция сортировки qsort() получает четвертым аргументом указатель на составляемую пользователем функцию сравнения сортируемых элементов.
  • Использование указателей на функции в качестве аргументов позволяет разрабатывать универсальные функции, реализующие известные алгоритмы или методы. Например, функции численного решения уравнений, интегрирования и дифференцирования.
  • Указатели на функции могут использоваться для косвенного вызова резидентных программ, точка входа в которые записана в известное место памяти, например, по одному из неиспользуемых векторов прерываний.

В определении указателя на функцию тип возвращаемого значения, а также типы, количество, последовательность параметров должны совпадать с соответствующими типами и характеристиками параметров тех функций, адреса которых предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания.

Пример 12.1

//Определение и использование указателей на функции#include " stdafx.h" #include < stdio.h> //Определение и использование указателей на функцииvoid f1(); //объявление (прототип)функции f1void f2(); //объявление (прототип)функции f2 int main(void) { void (*ptr)(); //ptr - указатель на функцию f2(); //явный вызов функции f2 ptr=f2; //указателю присваивается адрес функции f2 (*ptr)(); //вызов функции f2 по ее адресу с разыменованием указателя ptr=f1; //указателю присваивается адрес функции f1 (*ptr)(); //вызов функции f1 по ее адресу с разыменованием указателя ptr(); // вызов функции f1 без разыменования указателя system(" pause"); return 0; }//описание функции f1 и f2void f1() { printf(" Выполняется f1\n"); }void f2() { printf(" Выполняется f2\n"); }

Пример 12.2

//Вариант 1 использования указателя на функцию#include " stdafx.h" #include < stdio.h> float plus(float, float); //Объявление (прототип) функции int main(){ float x=2.1, y=4.89; float (*func)(float, float); //определение указателя func на функцию printf(" Сумма равна %.3f\n", plus(x, y)); func=plus; //указателю присвоить адрес func точки входа в функцию plus // Используем указатель на функцию printf(" Сумма = %.3f\n", func(x, y)); system(" pause"); return 0; }//Описание функции сложения двух аргументовfloat plus(float a, float b) { return a+b; } //Вариант 2 использования указателя на функцию#include " stdafx.h" #include < stdio.h> float plus(float, float); //Объявление (прототип)функции int main(void) { float x=2.1, y=4.89; float (*func)(float, float)=& plus; //определение указателя на функцию plus printf(" Сумма равна %.3f\n", plus(x, y)); func=plus; //указателю присвоить адрес точки входа в функцию plus (Используем указатель на функцию) printf(" Сумма = %.3f\n", func(x, y)); system(" pause"); return 0; }//Описание функции сложения двух аргументовfloat plus(float a, float b) { return a+b; }

Указатели на функции как параметры позволяют создавать функции, реализующие тот или иной метод обработки другой функции, которая заранее не определена. Например, можно определить функцию для вычисления определенного интеграла от произвольной функции. Подынтегральная функция может быть передана в функцию вычисления интеграла с помощью параметра-указателя.

Пример 12.3: Вычислите приближенное значение интегралов с помощью формулы прямоугольников, задав пределы интегрирования [a, b] и число интервалов разбиения и .

#include " stdafx.h" #include < stdio.h> //Объявление (прототипы) функций: /*функция rectangle() возвращает значение типа double, ее параметры: *//*pf–указатель на функцию с параметром типа double, возвращающую значение double*//*a, b – пределы интегрирования, величины типа double*/double rectangle(double(*pf)(double), double a, double b); /*функция ratio() возвращает значение типа double, ее параметр типа double*/double ratio(double x); /*функция cos4_2() возвращает значение типа double, ее параметр типа double*/double cos4_2(double v); int main(void){ double a, b, c; printf(" \nВведите значения пределов интегрирования: "); printf(" \na= "); scanf(" %lf", & a); printf(" \nb= "); scanf(" %lf", & b); c=rectangle(ratio, a, b); printf(" Первый интеграл = %f\n", c); printf(" Второй интеграл = %f\n", rectangle(cos4_2, a, b)); system(" pause"); return 0; } double rectangle(double(*pf)(double), double a, double b){/*Вычисление определенного интеграла с помощью формулы прямоугольников*/ int N, i; double h, s=0.0; printf(" \nВведите количество интервалов разбиения: N= "); scanf(" %d", & N); printf(" \na= "); h=(b-a)/N; //Длина интервала разбиения for (i=0; i< N; i++) s+=pf(a+h/2+i*h); return h*s; } double ratio(double x) { //Подынтегральная функция double z; //Вспомогательная переменная z=x*x+1; return x/(z*z); } double cos4_2(double v){ //Подынтегральная функция double w; //Вспомогательная переменная w=cos(v); return 4*w*w; }





© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.