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

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

Argument: address=372fd0, value=10

Parameter of product(): address=372fc8, value=100 Argument: address=372fd0, value=10

Parameter of ggg(): address=372fc8, value=100 Parameter of product(): address=372fbc, value=10000 result=10000

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

Обратите внимание на результаты. После первого вызова из тела функции main() функции product() ее параметру выделен участок памяти с адресом (в конкретном случае) 372fc8. После выхода из функции участок с этим адресом свободен и в него помещается значение параметра функции ggg(). Параметру функции product(), вызванной из ggg(), выделяется новый участок памяти (в нашем примере с адресом 372fbc).

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

Чтобы из тела функции можно было влиять на внешние по отношению к ее телу объекты, нужно использовать их адреса (указатели). Адрес объекта, определенного в месте вызова функции, используется в качестве ее аргумента. В теле функции выполняется разыменование соответствующего параметра и тем самым обеспечивается доступ к объекту, адрес которого использован в качестве аргумента. Обратите внимание, что НИКАКИХ изменений аргумента в этом случае не происходит. Аргумент продолжает адресовать тот же самый объект, состояние (значение) которого изменяется за счет выполнения операндов тела функции.

281

ЗАДАЧА 08-06. Определите функцию для вычисления площади круга и длины окружности заданного радиуса. В основной программе вводите значения радиуса и, обращаясь к функции, печатайте вычисленные с ее помощью значения площади круга и длины окружности. Окончание работы – ввод нулевого или отрицательного значения радиуса.

Решение:

/* 08_06.c – функция вычисляет площадь круга и длину окружности */

#define PRINTF(EXPRESSION) \ printf(#EXPRESSION"=%f\n",EXPRESSION)

#include <stdio.h>

void circle(double r, double * pArea, double * pLength)

{

*pArea = 3.1415 * r * r; *pLength = 2 * 3.1415 * r;

}

int main()

{

double radius, circleArea, circleLength; while (1)

{printf("radius="); scanf("%le",&radius); if (radius <= 0) break;

circle(radius, &circleArea, &circleLength); PRINTF(circleArea);

PRINTF(circleLength);

}

printf("\nThe End!"); return 0;

}

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

radius=5<ENTER>

circleArea=78.537500

circleLength=31.415000

radius=1<ENTER>

circleArea=3.141500

282

circleLength=6.283000

radius=0<ENTER>

The End!

Функция circle() получает в качестве значений параметров величину радиуса и адреса pAarea, pLength двух переменных (объектов), которым в теле функции присваиваются значения площади круга и длины окружности заданного радиуса. В основной программе определены переменные circleArea, circleLength, адреса которых используются в качестве аргументов при обращении к функции. Цикл с предусловием завершается при вводе неположительного значения радиуса оператором break. Остальное не требует пояснений.

Распространенная ошибка при использовании функций с пара- метром-указателем – отсутствие знака операции & перед именем объекта в аргументе. Особенно часто эту ошибку допускают начинающие программисты, обращаясь к библиотечной функции scanf().

ЭКСПЕРИМЕНТ. В предыдущей программе уберите знак операции получения адреса (&) перед именами переменных circleArea и circleLength в обращении к функции circle(). Откомпилируйте программу. Проанализируйте результаты.

В программе 08_06_1.с проделан этот эксперимент. Компиляция будет прервана со следующими сообщениями:

08_06_1.c: In function `main':

08_06_1.c:20: incompatible type for argument 2 of `circle'

08_06_1.c:20: incompatible type for argument 3 of `circle'

ЭКСПЕРИМЕНТ. В предыдущей неверной программе 08_06_1.с перенесите определение функции circle() в конец текста программы.

При компиляции полученной программы 08_06_2.с будут выданы только следующие предупреждения:

283

08_06_2.c:22: warning: type mismatch with previous implicit declaration

08_06_2.c:14: warning: previous implicit declaration of `circle'

08_06_2.c:22: warning: `circle' was previously implicitly declared to return `int'

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

radius=5<ENTER>

circleArea=0.000000

circleLength=0.000000

radius=1<ENTER>

circleArea=0.000000

circleLength=0.000000

radius=0<ENTER>

The End!

ЗАДАНИЕ. Включите в функцию с параметрамиуказателями, приведенную в программе 08_06.с, печать значений адресов параметров. В основной программе напечатайте значения адресов, используемых в качестве аргументов.

Решение дано в программе 08_06_3.с. Проанализируйте результаты.

ЗАДАЧА 08-07. Напишите функцию, которая упорядочивает значения трех переменных, адреса которых передаются ей в качестве параметров. В основной программе продемонстрируйте применение написанной функции.

Решим предлагаемую задачу, упорядочивая целочисленные переменные по возрастанию их значений. Для решения основной задачи удобно определить и использовать вспомогательную функцию, которая упорядочивает значения только двух переменных, адресуемых ее двумя аргументами. (Такой прием мы уже использовали в программе 08_02_2.с.) Программа может быть такой:

284

/* 08_07.c – функция сортировки значений трех переменных */

#include <stdio.h>

void rank2(int * x, int * y)

{

int m;

if (*x > *y)

{ m=*x; *x=*y; *y=m; }

}

void rank3(int * x, int * y, int * z)

{

rank2(x,y);

rank2(y,z);

rank2(x,y);

}

int main()

{

int i=56, j=24, k=12; rank3(&i,&j,&k);

printf("\ni=%d, j=%d, k=%d.",i,j,k); return 0;

}

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

i=12, j=24, k=56.

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

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

285

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

ЗАДАЧА 08-08. Напишите функцию, параметрами которой служат адреса трех переменных типа double. Функция должна возвращать адрес (значение указателя) той из переменных, адресуемых параметрами, которая имеет максимальное значение. В основной программе с помощью обращения к функции поменяйте знак значения максимальной из трех переменных.

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

/* 08_08.c - функция, возвращающая значение адреса-аргумента */

#include <stdio.h>

/* Вспомогательная функция с двумя параметрами */ double * maxParam2(double * x, double * y)

{

return (* x > * y) ? x : y;

}

double * maxParam3(double *x, double *y, double *z)

{

return maxParam2(maxParam2(x,y),z);

}

int main()

{

double x=56, y=24, z=92;

printf("max value=%f.", * maxParam3(&x,&y,&z)); * maxParam3(&x,&y,&z) = - * maxParam3(&x,&y,&z); printf("\nx=%f, y=%f, z=%f.",x,y,z);

return 0;

}

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

max value=92.000000.

x=56.000000, y=24.000000, z=-92.000000.

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

286

maxParam3() обращение к функции maxParam2() используется в качестве аргумента при вызове той же функции maxParam2(). Параметры функции maxParam3() – это указатели на объекты, поэтому в теле функции при обращении к функции maxParam2() не требуется операция получения адреса (&). При обращении к maxParam3() в основной программе используются в качестве аргументов адреса переменных (&x, &y, &z). Остальное очевидно из текста программы и результатов ее выполнения.

ЗАДАНИЕ. Модифицируйте программу 08_08.с. Введите функцию, которая возвращает адрес минимальной из четырех переменных, адресуемых ее параметрами.

ЗАДАЧА 08-09. Определите и инициализируйте одномерный массив с фиксированным количеством вещественных элементов. Используя многократное обращение к функции maxParam2() из программы 08_08.с, определите адрес максимального элемента массива и присвойте этому элементу нулевое значение, используя разыменование адреса (указателя).

Задачу решает следующая программа:

/* 08_09.c - адрес максимальной из переменных, адресуемых аргументами */

#include <stdio.h>

/* Вспомогательная функция с двумя параметрами */ double * maxParam2(double * x, double * y)

{

return (* x > * y) ? x : y;

}

int main()

{

double array[] = {5.6, 2.4, 9.2, 1.3, 8.6, 3.5}; int sizeArray = sizeof(array)/sizeof(array[0]); int i;

double * pMax = array;

for (i=1; i<sizeArray; i++)

pMax = maxParam2(pMax,&array[i]); printf("max value: array[%d] = %f.",

287

(pMax - array), * pMax); * pMax =0.0;

printf("\nvalue: array[%d] = %f.", (pMax - array), * pMax);

return 0;

}

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

max value: array[2] = 9.200000. value: array[2] = 0.000000.

В основной программе определен указатель pMax, инициализированный адресом начала массива, т.е. значением &array[0]. Далее в цикле вызывается функция maxParam2(), в которой сравниваются элементы массива: адресуемый указателем pMax и адресом &array[i]. Индекс найденного элемента с максимальным значением равен значению выражения (pMax-array). К этому элементу равноправно можно обратиться с помощью выражений array[pMax-array] или *pMax. Остальные особенности очевидны из текста программы и результатов ее исполнения.

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

ЗАДАНИЕ. В программе 08_09.с после вычисления значения указателя pMax напечатайте разность указателей pMax-array и разность их целочисленных значений.

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

08_09_1.с):

printf("\n pMax-array=%d",pMax-array); printf

("\n (int)pMax-(int)array=%d",(int)pMax-(int)array);

Приведенным операторам соответствует печать:

pmax-array=2 (int)pMax-(int)array=16

288

ЗАДАНИЕ. Замените приведение значений указателей к типу (int) приведением к типу (char*).

Будет выведено:

(char*)pMax-(char*)array=16

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

ЗАДАЧА 08-10. Напишите функцию с двумя параметрами типа void * и параметром int n. Функция должна копировать n байт из области, адресуемой первым указателем void *, в область, адресуемую вторым указателем void *. В основной программе определите и инициализируйте две переменные типа double и, обратившись к функции, поменяйте их значения. Затем выполните то же для переменных типа long.

/* 08_10.c - функция копирования участка памяти с параметрами типа void * */

#include <stdio.h>

void copy(void * new, void * old, int n)

{

int i;

for (i=0; i<n; i++)

*((char *)new+i)=*((char *)old+i);

}

int main()

{

double x=56, z=24, dRab;

long kon=245L, son=400L, lRab; printf("\nBegin: x=%f, z=%f.",x,z); copy(&dRab,&x, sizeof(double)); copy(&x,&z, sizeof(double)); copy(&z,&dRab, sizeof(double));

289

printf("\nResult: x=%f, z=%f.",x,z); printf("\nBegin: kon=%ld, son=%ld.",kon,son); copy(&lRab, &kon, sizeof(long));

copy(&kon, &son, sizeof(long)); copy(&son, &lRab, sizeof(long));

printf("\nResult: kon=%ld, son=%ld.",kon,son);

}

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

Begin: x=56.000000, z=24.000000.

Result: x=24.000000, z=56.000000.

Begin: kon=245, son=400.

Result: kon=400, son=245.

В теле функции copy() с двумя нетипизированными параметра- ми-указателями new и old каждый из них вначале приведен к типу char *. Прибавление к полученному адресу целого значения переменной i изменяет адрес на i байт. Последующее разыменование обеспечивает доступ к соответствующему байту. Цикл с параметром, изменяющимся от 0 до (n-1), обеспечивает копирование n байт из участка, адресованного указателем void * old в участок, адресованный указателем void * new. В основной программе для организации обменов введены две вспомогательные переменные dRab (типа double) и lRab (типа long). При обращении к функции copy() первые два параметра заменены адресами объектов (переменных), а третий параметр задает размеры участков памяти, занимаемых переменными. Обратите внимание, что приведение адресов-аргументов к типу void * выполняется автоматически.

8.3. Массивы и функции

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

290