Главная страница
Навигация по странице:

  • Описание функции и ее тип.

  • Вызов функции .

  • Указатели в параметрах функций Указатель-параметр.

  • Массивы и строки как параметры функций

  • Курс на Си. Подбельский. Курс программирования на Си. В., Фомин С. С. Курс программирования на языке Си Учебник


    Скачать 1.57 Mb.
    НазваниеВ., Фомин С. С. Курс программирования на языке Си Учебник
    АнкорКурс на Си
    Дата18.02.2023
    Размер1.57 Mb.
    Формат файлаdocx
    Имя файлаПодбельский. Курс программирования на Си.docx
    ТипУчебник
    #943863
    страница14 из 42
    1   ...   10   11   12   13   14   15   16   17   ...   42
    Глава 5

    ФУНКЦИИ

    5.1. Общие сведения о функциях

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

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

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

    Первая строка - это заголовок функции.

    Здесь тип - либо void (для функций, не возвращающих значе­ния), либо обозначение типа возвращаемого функцией значения. В предыдущих главах рассмотрены функции, возвращающие значе­ния базовых типов (char, int, double и т. д.).

    Имя_функции - либо main для основной (главной) функции про­граммы, либо произвольно выбираемое программистом имя (иден­тификатор), не совпадающее со служебными словами и с именами других объектов (и функций) программы.

    Спецификация_параметров - это либо пусто, либо список пара­метров, каждый элемент которого имеет вид:

    обозначение_типа имя_параметра

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

    параметров функции может заканчиваться запятой с последующим многоточием «...». Многоточие обозначает возможность обращаться к функции с большим количеством параметров, чем явно указано в спецификации параметров. Такая возможность должна быть «под­креплена» специальными средствами в теле функции. В следующих параграфах этой главы особенности подготовки функций с перемен­ным количеством аргументов будут подробно рассмотрены. Сейчас отметим, что мы уже хорошо знакомы с двумя библиотечными функциями, допускающими изменяемое количество аргументов. Вот их заголовки:

    int printf (char * format, ...)

    int scanf (char * format, ...)

    Указанные функции форматированного вывода и форматиро­ванного ввода позволяют применять теоретически неограниченное количество аргументов. Обязательным является только параметр char * format - «форматная строка», внутри которой с помощью спецификаций преобразования определяется реальное количество аргументов, участвующих в обменах.

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

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

    return;

    return выражение;

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

    Мы отметили обязательность оператора возврата из тела функ­ции, однако оператор return программист может явно не использо­вать в теле функции, возвращающей значение типа void (ничего не возвращающей). В этом случае компилятор автоматически добав­ляет оператор return в конец тела функции перед закрывающейся фигурной скобкой «}».

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

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

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

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

    double f (int n, float x);

    double f (int, float);

    Вызов функции. Для обращения к функции используется выра­жение с операцией «круглые скобки»:

    обозначение_функции (список_аргументов)

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

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

    Между параметрами и аргументами должно быть соответствие по типам. Лучше всего, когда тип аргумента совпадает с типом па­раметра. В противном случае компилятор автоматически добавляет команды преобразования типов, что возможно только в том случае, если такое приведение типов допустимо. Например, пусть опреде­лена функция с прототипом:

    int g(int, long);

    Далее в программе использован вызов:

    g(3.0+m, 6.4e+2)

    Оба аргумента в этом вызове имеют тип double. Компилятор, ориентируясь на прототип функции, автоматически предусмотрит такие преобразования:

    g((int)(3.0+m), (long) 6.4e+2)

    Так как вызов функции является выражением, то после выполне­ния операторов тела функции в точку вызова возвращается некото­рое значение, тип которого строго соответствует типу, указанному перед именем функции в ее определении (и прототипе). Например, функция

    float ft(double x, int n) {

    if (x
    return n;

    }

    всегда возвращает значение типа float. В выражения, помещенные в операторы return, компилятор автоматически добавит средства для приведения типов, то есть получим (невидимые программисту) операторы:

    return (float) x;

    return (float) n;

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

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

    1. При компиляции функции (точнее, при подготовке к ее вы­полнению) выделяются участки памяти для параметров, то есть параметры оказываются внутренними объектами функ­ции. При этом для параметров типа float формируются объ­екты типа double, а для параметров типов char и short int соз­даются объекты типа int. Если параметром является массив, то формируется указатель на начало этого массива и он служит представлением массива-параметра в теле функции.

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

    3. Значения выражений-аргументов заносятся в участки памяти, выделенные для параметров функции. При этом float преоб­разуется в double, а char и short int - в тип int (см. п. 1).

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

    5. Никакого влияния на аргументы (на их значения) функция не оказывает.

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

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

    имя_функции (список_аргументов);

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

    void print(int gg, int mm, int dd)

    {

    printf("\n год: %d",gg);

    printf(",\t месяц: %d,",mm);

    printf(",\t день: %d.", dd);

    }

    Обращение к ней

    print(1966, 11, 22);

    приведет к такому выводу на экран:

    год: 1966, месяц: 11, день: 22.

    Может оказаться полезной и функция, которая не только не воз­вращает никакого значения (имеет возвращаемое значение типа void), но и не имеет параметров. Например, такая:

    #include

    void Real_Time (void)

    { printf("\n Текущее время: %s", _ _TIME_ _ " (час: мин: сек.)");

    }

    При обращении

    Real_Time ( );

    в результате выполнения функции будет выведено на экран дисплея сообщение:

    Текущее время: 14:16:25 (час: мин: сек.)

      1. Указатели в параметрах функций

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

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

    Продемонстрируем изложенную схему на простом примере:

    #include

    void positive(int * m) /* Определение функции */ {

    *m = *m > 0 ? *m : -*m;

    }

    void main()

    {

    int k=-3;

    positive(&k);

    printf("\nk=%d", k);

    }

    Результат выполнения программы:

    k=3

    Параметр функции positive( ) - указатель типа int *. При обра­щении к ней из основной программы main( ) в качестве аргумента используется адрес &k переменной типа int. Внутри функции значе­ние аргумента (то есть адрес &k) «записывается» в участок памяти, выделенный для указателя int *m. Разыменование *m обеспечивает доступ к тому участку памяти, на который в этот момент «смотрит» указатель m. Тем самым в выражении

    *m = *m>0 ? *m :- *m

    все действия выполняются над значениями той переменной основ­ной программы (int k), адрес которой (&k) использован в качестве аргумента.

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



    Рис. 5.1. Схема «настройки» параметра-указателя

    Пояснив основные принципы воздействия с помощью указате­лей из тела функции на значения объектов вызывающей програм­мы, напомним, что этой возможностью мы уже неоднократно поль­зовались, обращаясь к функции scanf( ). Функция scanf( ) имеет один обязательный параметр - форматную строку - и некоторое количество необязательных параметров, каждый из которых должен быть адресом того объекта, значение которого вводится с помощью функции scanf( ). Например:

    long l;

    double d;

    scanf("%ld%le", &l, &d);

    Здесь в форматной строке спецификаторы преобразования %ld и %le обеспечивают ввод (чтение) от клавиатуры соответственно значений типов long int и double. Передача этих значений перемен­ным long l и double d обеспечивается с помощью их адресов &l и &d, которые используются в качестве аргументов функции scanf( ).

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

    void print(int gg, int mm, int) и void

    Real_Time(void),

    каждая из которых очень похожа на подпрограммы других языков программирования.

    К сожалению, в языке Си имеется еще одно препятствие для не­посредственного использования функции в роли подпрограммы - это рассмотренная выше передача параметров только значениями, то есть передаются значения переменных, а не их адреса. Другими словами, в результате выполнения функции нельзя изменить зна­чения ее аргументов. Таким образом, если Z( ) - функция для вы­числения периметра и площади треугольника по длинам сторон E, F, G, то невозможно, записав оператор-выражение Z(E, F, G, PP, SS);, получить результаты (периметр и площадь) в виде значений пере­менных PP и SS. Так как параметры передаются только значениями, то после выполнения функции Z( ) значения переменных PP и SS останутся прежними.

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

    #include void main()

    {

    float x,y;

    /* Прототип функции */

    void aa(float *, float *);

    printf("\n Введите: x=");

    scanf("%f",&x);

    printf(" Введите: y=");

    scanf("%f",&y);

    /* Вызов функции */

    aa(&x,&y);

    printf(" \n Результат: x=%f y=%f", x, y);

    }

    /* Функция, меняющая местами значения переменных,

    на которые указывают фактические параметры: */

    void aa(float * b, float * c) /* b и c - указатели */

    {

    float e; /* Вспомогательная переменная */

    e=*b;

    *b=*c;

    *c=e;

    }

    В основной программе описаны переменные x, y, значения кото­рых пользователь вводит с клавиатуры. Параметрами функции aa( ) служат указатели типа float *. Задача функции - поменять местами значения тех переменных, на которые указывают ее параметры-ука­затели. При обращении к функции aa( ) адреса переменных x, y используются в качестве аргументов. Поэтому после выполнения программы возможен, например, такой результат:

    Введите: x=33.3

    Введите: y=66.6

    Результат: x=66.600000 y=33.300000

    Имитация подпрограммы (функция) для вычисления периметра и площади треугольника:

    #include /* для функции sqrt( ) */

    #include void main( )

    {

    float x,y,z,pp,ss;

    /* Прототип: */

    int triangle(float, float, float, float *, float *);

    printf("\n Введите: x=");

    scanf("%f",&x);

    printf("\t y=");

    scanf("%f",&y);

    printf("\t z=");

    scanf("%f",&z);

    if (triangle(x,y,z,&pp,&ss) == 1)

    {

    printf(" Периметр = %f",pp);

    printf(", площадь = %f",ss);

    } else printf("\n Ошибка в данных ");

    }

    /* Определение функции: */

    int triangle(float a,float b, float c,float * perimeter, float * area)

    {

    float e;

    *perimeter=*area=0.0;

    if (a+b<=c || a+c<=b || b+c<=a)

    return 0;

    *perimeter=a+b+c;

    e=*perimeter/2;

    *area=sqrt(e*(e-a)*(e-b)*(e-c)); return 1 ;

    }

    Пример результатов выполнения программы:

    Введите

    х=3






    y=4






    z=5



    Периметр = 12.000000, площадь = 6.000000.

      1. Массивы и строки

    как параметры функций
    1   ...   10   11   12   13   14   15   16   17   ...   42


    написать администратору сайта