Лекции Булатицкий Дмитрий Иванович (во многом по материалам Прасолова А. Н.)
Скачать 319.62 Kb.
|
Механизм вызова функций в СиПередача параметров в функциюВ правильно организованной функции использование списка аргументов (параметров) и возвращаемого значения является единственным способом связи этой функции с остальными. При вызове функции в стеке создаются локальные переменные соответствующие по типу формальным параметрам функции. Затем они инициализируются значениями фактических параметров, задаваемых при вызове функции в строгом соответствии с последовательностью формальных параметров. При необходимости производится допустимое преобразование типов. Соответствие фактических и формальных параметров устанавливается не по именам параметров, а по их местоположению в списках параметров. Рассмотренный алгоритм вызова функции гарантирует сохранение значений фактических параметров независимо от того, что делала функция с соответствующими формальными параметрами. Рассмотрим функцию Sum(), вычисляющую значение суммы элементов массива: #include double Sum(double A[], int nA) { double s = 0; while(nA) s += A[--nA]; return s; } void main (void) { double B[] = { 1, 2, 3, 4, 5 }; int nB = sizeof(B)/sizeof(B[0]); printf("Сумма = %lf\n", Sum(B,nB)); printf("nB = %d\n", nB); } Функция при своей работе изменяет значение формального параметра nA, в котором передается размер массива. Однако, если запустить программу, то можно увидеть, что значение соответствующего фактического параметра nB осталось неизменным, ввиду того, что функция работает с копиями параметров. При фактическом параметре-массиве в функцию передается значение его имени, то есть адрес его первого элемента, для которого создается копия в стеке, которая уже не является адресом-константой и допускает изменение своего значения внутри функции. Поэтому функцию Sum можно реализовать и следующим образом: double Sum(double A[], int nA) { double s = 0, *Aend = A + nA; while( A < Aend ) s += *(A++); return s; } Соответствующий фактический параметр - константный адрес массива B в вызывающей функции не меняет своего значения, то есть никаких противоречий в предыдущей функции нет. Вообще говоря, для формальных параметров-массивов описания в виде A[] и *A совершенно идентичны и обозначают локальную копию адреса соответствующего типа. Какое из этих описаний использовать, зависит от смысла параметра. Если это массив, то более наглядно использовать описание вида A[]. Возврат значенийВозврат результата работы функции в вызывающую программу в виде единственного значения можно осуществить с помощью оператора return. При этом результат возвратится как значение самой функции и должен иметь соответствующий тип. Типы возвращаемых значений могут быть любыми, кроме массивов. Тип void означает, что функция не возвращает никакого значения. Тип void* означает, что функция возвращает указатель на произвольный тип данных. Если необходимо изменить содержимое массива, его адрес нужно передать в функцию и обычным способом, с помощью операции индексации изменить нужные элементы массива. В следующем примере функция FillArray() заполняет массив указанным значением: void FillArray(double A[], int nA, double val) { int i; for (i=0; i } void main (void) { double B[100]; FillArray(B, 40, 35.4); /* ... */ FillArray(&B[60], 20, 15.4); /* ... */ } Первый вызов FillArray() заполняет 40 первых элементов массива B значением 35.4, второй вызов заполняет 20 элементов массива B, начиная с элемента B[60], значением 15.4. При возврате из функции массив будет изменен, т. к. занесение значения val происходит непосредственно по нужному адресу. Эту же функцию можно использовать для заполнения строк двумерного массива: void main (void) { double a[10][20]; int n = sizeof(a) / sizeof(a[0]); int m = sizeof(a[0]) / sizeof(a[0][0]); int i; /* ... */ for(i=0; i FillArray(a[i], m, 14.6); /* ... */ } В примере следует обратить внимание на соответствие типов передаваемых параметров и на способ вычисления числа строк и числа столбцов двумерного массива. Возврат из функции нескольких значений, которые не являются элементами массива, можно организовать, используя указатели. В следующем примере функция Decart() осуществляет перевод пары полярных координат в декартовые: void Decart(double *px, double *py, double r, double f) { (*px) = r * cos(f); (*py) = r * sin(f); } При обращении к данной функции для параметров px и py нужно передавать адреса: void main(void) { double x, y, r=5, f=0.5; /* ... */ Decart( &x, &y, r, f ); /* ... */ } В данном примере при вызове функции создаются локальные копии адресов переменных x и y, а внутри функции происходит обращение к переменным x и y через их адреса (как и в случае массивов), поэтому значения x и y после вызова функции будут изменены. |