Характерная особенность основного алгоритма при работе с подпрограммой с несколькими возвращаемыми результатами – организация каждого обращения к ней отдельным блоком «предопределённый процесс». Эта особенность учтена в схеме основного алгоритма. В дополнительном алгоритме локальная переменная i используется для организации цикла перебора текущих значений формального параметра Z. Схемы основного и дополнительного алгоритмов представлены на рис. 10.13.
Программирование задачи
Программирование подобного класса задач опирается на структуры предыдущего раздела, с учетом особенностей, связанных с использованием входных и выходных параметров.
Каждая из функций Си/Си++ представляется в этом случае одной из упрощенных структур
[тип] имя([тип b1,...,тип bi,...,тип bn,тип *d1,...,тип *dj,..., тип *dm])
{
тело
функции
[return РВ;]
}
где имя – идентификатор (название) функции;
тип – описатель типа функции (результата);
bi – список входных формальных параметров с указанием типа каждого;
dj – список выходных формальных параметров – указателей, определяющих возвращаемые результа-
Рис. 10.13. Схемы основного и дополнительного алгоритмов задачи о суммах и произведениях
ты, с указанием типа каждого и признака указателя при описании (*);
() – ограничители списка формальных параметров;
тело функции – основная часть (совокупность операторов), реализующая вынесенные в отдельный алгоритм вычисления (действия);
return РВ; – оператор возврата в вызывающую функцию результата вычислений РВ (выражения);
[ ] – признак необязательности содержимого;
{ } – ограничители тела функции.
Первая строка структуры – заголовок функции.
Структура вызова функции:
имя ([a1,..., ai,..., an,c1,..., cj,..., cm])[;]
где имя – идентификатор функции;
a1, …,ai, …,an – список входных фактических параметров (аргументов), численные значения которых требуется передать в дополнительную функцию (подпрограмму);
c1, …, cj,…, cm – список адресов выходных фактических параметров (аргументов), численные значения которых требуется получить из дополнительной функции (подпрограммы);
() – ограничители аргументов;
[ ] – признак необязательности содержимого.
Прототип функции аналогичен ее заголовку и имеет структуру:
[тип] имя([тип b1,...,тип bi,...,тип bn,тип *d1,...,тип *dj,...,тип *dm]);
где имя – идентификатор (название) функции;
тип – описатель типа функции (результата);
тип bi – список входных формальных параметров с указанием типа каждого;
тип *dj – список выходных формальных параметров – указателей, определяющих возвращаемые результаты, с записью типа и признака указателя каждого (*);
() – ограничители списка формальных параметров;
[ ] – признак необязательности содержимого;
; – признак оператора.
Правила записи и использования функций с несколькими возвращаемыми значениями совпадают с указанными ранее для функций с одним результатом со следующими дополнениями:
· количества и типы входных и выходных формальных параметров должны соответствовать аналогичным фактическим, т.е. каждый ai имеет свой bi, а каждый cj – свой dj, при этом тип и взаимное расположение их в списке определяется программистом;
· в качестве входных формальных параметров используются переменные;
· в качестве выходных формальных параметров используются указатели;
· в качестве входных фактических параметров используются константы, переменные, вызовы функций, выражения и адреса массивов;
· в качестве выходных фактических параметров используются адреса переменных и адреса массивов;
· каждое обращение к подпрограмме, как правило, оформляется как отдельный оператор, в соответствии с требованиями алгоритма;
· использование выходных параметров освобождает от ограничения на возвращение только одного результата оператором return, основной результат рекомендуется возвращать оператором return, все остальные в качестве выходных параметров.
· при использовании структуры с оператором return, вызов функции может служить операндом выражения в вызываемой функции;
· если логика задачи не позволяет выделить основной результат, то использование оператора return необязательно. В этом случае в заголовке дополнительной функции в качестве описателя типа необходимо указывать ключевое слово void;
· вызов функции, оформленный простым оператором, заканчивается символом «;», если же он является операндом выражения – признак оператора не указывается;
· формальные значения (кроме главного) возвращаются в ячейки вызывающей функции, адреса которых были переданы в вызываемую;
· в списке формальных параметров прототипа допускается опускать их имена, оставляя для входных их типы, а для выходных типы с последующими символами «*».
Программная реализация задач с использованием входных и выходных параметров опирается на закономерности, описанные при алгоритмизации.
В качестве выходных параметров используются адреса операндов и указатели на них.
Внимание! Обращение по адресу определяет, что в дополнительной функции используется в качестве формального фактический параметр головной функции. Поэтому любые корректировки формального параметра в дополнительной функции есть автоматическое изменение фактического параметра в вызывающей функции.
Принципиальные различия в обработке формальных параметров, заданных переменными или указателями, иллюстрируются схемами рис. 10.14, 10.15.
Рис. 10.14. Схема 1 – передача параметра по значению
Схема 1 (передача по значению) показывает параллельное существование отдельных ячеек хранения формального и фактического параметра (переменной).
Рис. 10.15. Схема 2 – передача параметра по адресу
Схема 2 (передача по адресу) определяет существование единой ячейки для хранения фактического параметра, обрабатываемого в дополнительной функции как формальный с использованием адреса.
Передача по значению позволяет перемещать простые переменные в качестве входных параметров. В этом случае используются две различные ячейки (в вызывающей и вызываемой функции) для хранения значений локальных переменных с разными или одинаковыми именами.
Передача по адресу позволяет работать с переменными в качестве выходных (формальных и фактических) параметров. В этом случае для хранения их значений используются единые ячейки, выделяемые и описываемые в вызывающей функции, с разными при желании именами.
Аналогично передача по адресу позволяет работать с массивами в качестве входных и выходных параметров. В этом случае в вызывающей функции описывается массив (выделяются ячейки для хранения его элементов), используемый как фактический в основной (вызывающей) и формальный – в дополнительной функции. При этом доступ к единому массиву из основной и дополнительной функции возможен под разными именами.
Достоинство передачи по адресу – возможность возвращения в вызывающую функцию значения выходного формального параметра.
Недостаток – любые изменения входного формального параметра есть автоматическое изменение аналогичного фактического, что, как правило, нежелательно (исходные значения входных фактических параметров в процессе счета изменять не рекомендуется).
... main() { int j = 1, i = 1; ... printf("j=%d i=%d", j, i); func(j, &i); printf("j=%d i=%d", j, i); ... } void func(int j, int *pi) {... *pi = 0.5 * j + 1.; j = 2; ... } |
Так, фрагмент программы в качестве фактических параметров использует входной – переменную j и выходной – адрес переменной i. Этим параметрам в вызываемой подпрограмме соответствуют одноименный входной формальный параметр – переменная j и выходной – указатель pi. В теле подпрограммы численные значения обрабатываемых переменных i и j изменены (i – с помощью разадресации указателя pi, j – напрямую). Выполнение программы приведет к выводу исходных значений j и i (j=1 i=1), а затем их же значений после обращения к дополнительной функции (j=1 i=1.5). Значение входного фактического параметра j не изменилось, а выходного (i) – приняло новое значение. Это произошло потому, что обработка параметра j велась по схеме 1 (разные ячейки), а обработка параметра i – по схеме 2.
С учетом изложенного выполним программирование задачи о суммах и произведениях.
Идентификация переменных представлена в табл. 10.3.
Таблица 10.3
Имя в алгоритме | n | m | i | j | xi | yj | SX | PX | SY | PY | d | k | z i | SZ | PZ |
Имя в программе | n | m | i | j | x[i] | y[j] | sx | px | sy | py | d | k | z[i] | sz | pz |
Предусмотренная алгоритмом обработка двух массивов (на 30 и 50 элементов) позволяет в программе первый из них использовать как фактический под именем x и формальный под именем z, второй, аналогично, под именами y и z.
Выделить главный из выходных параметров невозможно, т. к. они равнозначны. Поэтому составленные при выборе метода решения обращения к дополнительной функции, с учетом таблицы идентификации и правил работы с выходными параметрами, примут вид:
sp(x, n, &sx, &px); и sp(y, m, &sy, &py);
Заголовок дополнительной функции преобразуется к виду:
void sp(float *z, int k, float *sz, *pz)
т. е. входные параметры передаются по значению, а выходные через адреса. Равнозначность возвращаемых значений определяет отсутствие оператора return.
Классический вариант программирования задачи
#include<stdio.h>/*файл с прототипами функций ввода-вывода*/
#include<conio.h>/*файл с прототипом функции getch(), clrscr()*/
#include<math.h> /*файл с прототипами математич. функций*/
#include <windows.h> /*файл с прототипом функции CharToOem*/
void sp(float *z, int k, float *sz, float *pz);/*прототип функции sp*/
main() /* заголовок головной функции */
{
float d, sx, px, sy, py, x[30], y[50]; /*описатели локальных */
int i, j, n, m; /* переменных и массивов */
char buf[30];
clrscr();
CharToOem(" Введите значения n, m: ",buf);
printf("\n %s ",buf);
scanf("%d%d", &n, &m);
printf("\n n=%d m=%d\n", n, m);
for(i = 0; i < n; i++)/* заголовок цикла ввода x[ i ] */
{
CharToOem(" Введите значение x ",buf);
printf("\n %s (%d): ",buf,i+1);
scanf("%f", &x[i]);
}
for(i = 0; i < n; i++) /* заголовок цикла вывода x[ i ] */
printf(" %.2f",x[i]);
printf("\n"); /* перевод курсора в начало следующей строки */
for(j = 0; j < m; j++) /* заголовок цикла ввода y[ j ] */
{
CharToOem(" Введите значение y ",buf);
printf("\n %s (%d): ",buf,j+1);
scanf("%f", &y[j]);
}
for(j = 0; j < m; j++) /* заголовок цикла вывода y[ j ] */
printf(" %.2f", y[j]);
sp(x, n, &sx, &px); /* вызовы дополнительной функции */
sp(y, m, &sy, &py); /* оформленные простыми операторами */
d = (sx + sqrt(fabs(py))) / (log(px) - sy); /* вычисление d*/
printf("\n sx=%.2f px=%.2f sy=%.2f py=%.2f d=%.2f \n",
sx,px, sy, py, d);
getch();
}
void sp(float *z, int k, float *sz, float *pz)/*определение ф-ции sp*/
{
int i; /* описание локальной переменной i */
*sz = 0;
*pz = 1;
for(i = 0; i < k; i++) /* заголовок цикла расчета SZ и PZ */
{
*sz = *sz + z[ i ];
*pz = *pz * z[ i ];
}
}
5 6 – реальные размеры массивов Х и У;
1 1.6 1.8 15 23 – значения элементов массива Х;
0.6 0.76 0.99 180 67.7 200 – значения элементов массива Y;
Под закрывающей скобкой приведены исходные данные для решения задачи.
Результаты решения представлены в приложении 10.5.
Программирование задачи с графическим интерфейсом
Программирование задачи при использовании графического интерфейса предварим его разработкой. Для ввода значений n, m планируем однострочные поля редактирования (EditN, EditM). Для ввода элементов массивов X и Y используем многострочные поля редактирования (EditХ, EditY).
Вывод расчетных значений sx, px, sy, py, d реализуем в однострочные поля редактирования (EditSx, EditPx, EditSy, EditPy, EditD).
Управление процессом решения реализуем двумя командными кнопками, расположенными в нижней части окна. Назначение каждой определяется ее названием.
С учетом планируемого интерфейса выполним программирование задачи.
#include<stdio.h>/*файл с прототипами функций ввода-вывода*/
#include<conio.h>/*файл с прототипом функции getch(), clrscr()*/
#include<math.h>/*файл с прототипами математич. функций*/
void sp(float *z, int k, float *sz, float *pz);/*прототип функции sp*/
…
void TSumprDlgClient::BNClickedOk()
{
// INSERT>> Your code here.
float d, sx, px, sy, py, x[30], y[50]; /*описатели локальных*/
int i, j, n, m; /* переменных и массивов */
char buf[30];
EditN->GetText(buf,10); /*ввод */
n=atoi(buf); /*значения n*/
EditM->GetText(buf,10); /*ввод */
m=atoi(buf); /*значения m*/
for(i = 0; i < n; i++) /* заголовок цикла ввода x[ i ] */
{
EditX->GetLine(buf, sizeof(buf),i);
x[i]=atof(buf);
}
for(j = 0; j < m; j++) /* заголовок цикла ввода y[ j ] */
{
EditY->GetLine(buf, sizeof(buf),j);
y[j]=atof(buf);
}
sp(x, n, &sx, &px); /* вызовы дополнительной функции */
sp(y, m, &sy, &py); /* оформленные простыми операторами */
d = (sx + sqrt(fabs(py))) / (log(px) - sy); /* вычисление d*/
sprintf(buf,"%.2f ",sx); /* вывод */
EditSx->SetText(buf); /*значения sx*/
sprintf(buf,"%.2f ", px); /* вывод */
EditPx->SetText(buf); /*значения px*/
sprintf(buf,"%.2f ",sy); /* вывод */
EditSy->SetText(buf); /* значения sy*/
sprintf(buf,"%.2f ", py); /* вывод */
EditPy->SetText(buf); /* значения py*/
sprintf(buf,"%.2f",d); /* вывод */
EditD->SetText(buf); /* значения d*/
}
/* определение функции sp */
void sp(float *z, int k, float *sz, float *pz)
{
int i; /* описание локальной переменной i */
*sz = 0;
*pz = 1;
for(i = 0; i < k; i++) /* заголовок цикла расчета SZ и PZ */
{
*sz = *sz + z[ i ];
*pz = *pz * z[ i ];
}
}
5 6 – реальные размеры массивов Х и У;
1 1.6 1.8 15 23 – значения элементов массива Х;
0.6 0.76 0.99 180 67.7 200 – значения элементов массива Y;
Под закрывающей скобкой приведены исходные данные для решения задачи.
Результаты решения представлены в приложении 10.6.
10.4. Подпроцесс с результатом – массивом
Рассмотренные примеры позволяли возвращать результаты в виде одного или нескольких разрозненных значений. При этом входными параметрами подпрограмм были переменные и одномерные массивы. В некоторых задачах необходима обработка в подпрограмме многомерных массивов, а также возвращение значений в виде одно- и многомерных массивов. Рассмотрим особенности программирования таких вычислительных процессов на конкретной задаче (10.4) о суммах строк двумерных матриц.
Постановка задачи
Вычислить суммы элементов каждой строки двумерных массивов A(m x n) и B(t x s). Положительные суммы каждой исходной матрицы сформировать в одномерные массивы.