Файл: А. Б. Шнейвайс основы программирования.pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 06.12.2023

Просмотров: 338

Скачиваний: 6

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

4.4.3
Заимствование размера фактического аргумента
Имеется ввиду, что массив, являющийся формальным параметром, за- имствует у фактического параметра только его размер, при возможном отличии формы.
Признаком, определяющим рассматриваемый способ — звездочка при описании размера последнего измерения формального параметра.
Этот способ сохранён для совместимости с ФОРТРАНом-77 (см. [2]).
Пользоваться им не рекомендуется (см. [4, 2]).
4.4.4
Выводы
1. При описании массива в качестве формального параметра внеш- ней процедуры используем явное задание его формы, добавляя (по необходимости) в список параметров переменные, хранящие его за- явленный и/или требуемый размеры.
2. Явное задание формы (конфигурации) массива, служащим фор- мальным парметром процедуры, работает и в старых, и в совре- менных версиях ФОРТРАНа.
3. Объявление массива формальным параметром, заимствующим фор- му фактического, выгодно
(a) либо при описании начального индекса по каждому измерению единичным (по умолчанию);
(b) либо при безукоризненной и удобной для пользователя работе функций lbound и ubound, которая в старых версиях компи- лятора gfortran вызывала определённые нарекания.
4. Стараемся не использовать режим заимствования размера.
192

4.5
Массив в СИ как формальный аргумент функции
В первом семестре (см. часть I (3.3.7) сообщалось, что передача аргу- мента функции в ФОРТРАНе происходит по адресу, а в СИ – по значе- нию (там же проводилось обоснование выгоды передачи по значению).
Время на передачу одного значения простого типа (int, float или double) в ячейку формального аргумента, невелико. Копирование же всех элементов массива требует гораздо больше времени. Поэтому при использовании массива в качестве фактического аргумента передаётся лишь указатель на его начальный элемент.
Содержимое i-го элемента массива a доступно или по записи a[i], или
(см. часть II (4.2.2)) по записи *(a+i), обозначающей операцию разы- менования указателя на элемент a[i]. В соответствие со сказанным опи- сание одномерного массива в качестве формального аргумента можно выполнить и в терминах конструктора массива, и на языке указателей.
1. Описание без указания количества элементов в массиве:
#include
void c0(int [], int);
int main()
{ int i, n=100, a[100];
// В main значение переменной n равно 100.
printf(" main 1: n=%d\n",n);
// Убедимся в этом до вызова функции.
c0(a,n);
// Функция не изменяет фактический аргумент printf(" main 2: n=%d\n", n); // n, хотя формальный меняется (n=5).
for (i=0;i<=4;i++)
// Убедимся в этом после вызова функции.
printf(" %d
%d\n", i, a[i]); // В отличие от n cодержимое пяти элементов return 0; }

1   ...   5   6   7   8   9   10   11   12   ...   23

// (a) изменено. Почему?
void c0(int a[],int n)
// По значению переданы: адрес начального
{ int i;
// элемента массива и значение
(n)
printf(" c0 1: n=%d\n",n);
// Вывод формального n при работе функции n=5; printf(" c0 2: n=%d\n",n); //
(до и после изменения n).
for(i=0; i<=n-1; i++)
a[i]=i+1;
}
main 1: n=100
// Результат пропуска программы c0.c c0 1: n=100
c0 2: n=5
main 2: n=100 0
1 1
2 2
3 3
4 4
5 193

2. Описание с указанием количества элементов в массиве:
#include
void c2(int [100], int);
// Размер массива, указанный в конструкторе int main()
// формального аргумента, НЕ ПЕРЕДАЁТСЯ
{ int na, a[100];
// внутрь функции.
na=sizeof(a)/sizeof(a[0]);
// Здесь printf(" main:
na=%d\n",na);
//
na=100
c2(a,na); return 0;
}
// Но внутри функции отношение void c2(int a[100],int n)
//
sizeof(a)/sizeof(a[0])=1,
{ int nac2;
// т.к. компилятор gcc отводит nac2=sizeof(a)/sizeof(a[0]);
// для хранения указателя 4 байта, т.е.
printf(" c2 : nac2=%d\n", nac2);
// столько же, сколько и для хранения printf(" c2 :
n
=%d\n", n
); }// одного элемента типа int.
main:
na=100
// Как и предупреждалось, размер массива, вычисленный c2 : nac2=1
// внутри процедуры с2, равен единице, а переданный c2 :
n
=100
// через параметр равен хранимому в нем значению.
Так что, если размер массива, необходим внутри функции, то его нужно передать дополнительным параметром.
3. В качестве описателя одномерного массива int [], служащего фор- мальным параметром, не менее элегантно выглядит и простое int*.
#include
void c1(int*, int);
// В СИ имя массива -- указатель на его начальный int main()
// элемент. Поэтому при описании формального
{ int i, n=100, a[100]; // аргумента вместо int[] можно использовать int*
printf(" main 1: n=%d\n", n);
c1(a,n);
printf(" main 2: n=%d\n", n);
for (i=0;i<=4;i++) printf(" %d
%d\n", i, a[i]);
return 0;
}
void c1(int *a,int n)
// Здесь описание int a[100]
{ int i;
printf(" c0 1: n=%d\n", n);
// возможно, но размер формального n=5;
printf(" c0 2: n=%d\n", n);
// аргумента (вектора a)
for(i=0; i<=n-1; i++)
a[i]=i+1;} // в процедуру передан не будет.
4. Иногда описывать массив, указывая атрибуты его формы только через конструктор или указатель, неудобно. Часто выгодно сопо- ставить безымянному типу int [] или int* синоним, отражающий проблемное назначение массива. Так, если одномерные массивы a,
b, и c хранят коэффициенты полиномов, то может оказаться удоб- ным их описание в виде
194
coef a, b, c;
// Здесь coef - имя, придуманное нами для обозначения
// типа указателя на одномерный массив.
Используя СИ-оператор typedef, нацеленный на определение по- добных синонимов, программа вызова функции, суммирующей ко- эффициенты двух полиномов (при одинаковых степенях аргумента),
и описание функции могут иметь вид:
#include
typedef double coef[100] ;
// coef - синоним типа double [100]
void add(coef, coef, coef, int);
// описание прототипа (интерфейса) add int main()
{ int i,n, nw=6;
coef a, b, c;
for(i=0; i<=nw-1; i++)
// пример заполнения векторов a и b
{
a[i]=i; *(b+i)=10*i; }
// обеими формами доступа к элементу n =sizeof(a)/sizeof(a[0]); printf(" n=%d\n",n);
add(a,b,c, nw);
for (i=0;i<=nw-1;i++) printf("%d
%le
%le
%le\n",i,a[i], b[i],c[i]);
return 0;}
void add(coef a, coef b, coef c, int n)
{ int i;
printf(" add:
n=%d\n", n);
for(i=0; i<=n-1; i++) c[i]=a[i]+b[i]; }
n=100
// Результат работы программы add:
n=6
//
с3.c
0 0.000000e+00 0.000000e+00 0.000000e+00 1
1.000000e+00 1.000000e+01 1.100000e+01 2
2.000000e+00 2.000000e+01 2.200000e+01 3
3.000000e+00 3.000000e+01 3.300000e+01 4
4.000000e+00 4.000000e+01 4.400000e+01 5
5.000000e+00 5.000000e+01 5.500000e+01 5. В пункте 4 главная программа и функция описаны в одном файле.
Поэтому описание типа coef видно обеим. Если бы описание функ- ции находилось в файле add.c, то void add(coef a, coef b, coef c, int n)
// при компиляции gcc add.c -c
{ int i;
printf(" add:
n=%d\n", n);
//
for(i=0; i<=n-1; i++) c[i]=a[i]+b[i];} // получили бы сообщения:
add.c:1: error: syntax error before "a"
add.c: In function ‘add’:
add.c:2: error: ‘n’ undeclared (first use in this function) <--=
что n не add.c:2: error: (Each undeclared identifier is reported only once объявлено add.c:2: error: for each function it appears in.)
ошибка add.c:3: error: ‘c’ undeclared (first use in this function)
наведена add.c:3: error: ‘a’ undeclared (first use in this function) отсутствием add.c:3: error: ‘b’ undeclared (first use in this function) описания типа coef
195


6. Обычно описание прототипов функций и имен синонимов типов дан- ных помещают в заголовочный файл, подсоединяемый к нужным файлам директивой #include (см. частI (1.6.2)), например:
typedef double coef [100];
// Файл mytype.h
#include
// Файл tstadd1.c
#include "mytype.h"
void add1(coef, coef, coef, int);
int main()
{ int i,n, nw; coef a, b, c;
nw=6;
for(i=0; i<=nw-1; i++) {
a[i]=i; b[i]=10*i; }
n =sizeof(a)/sizeof(a[0]);
printf(" n=%d\n",n);
add1(a,b,c, nw);
for (i=0;i<=nw-1;i++)
printf("%d
%le
%le
%le\n",i,a[i], b[i],c[i]);
return 0; }
#include "mytype.h"
// Файл add1.c void add1(coef a, coef b, coef c, int n)
{ int i; // printf(" add1:
n=%d\n", n);
for(i=0; i<=n-1; i++) c[i]=a[i]+b[i];
}
7. Если при описании типа coef не указать (по невнимательности) в операторе typedef тип значения, на которое должен ссылаться ука- затель типа coef (т.е. вместо typedef double coef[100]; написать typedef coef[100];, то компилятор gcc не выдал бы никаких оши- бок, но результат мог бы иметь вид:
n=100
// СИ при описании функции
0 0.000000e+00
-5.412876e+303 1.484567e-313
// без указания ее типа
1 2.121996e-313
-5.412876e+303 1.484567e-313 // полагает, что её тип
2 4.243992e-313
-5.412876e+303 1.484567e-313 // int. Видимо, это же
3 6.365987e-313
-5.412876e+303 1.484567e-313 // относится и к описанию
4 8.487983e-313
-5.412876e+303 1.484567e-313 // типа массива, т.к. при
5 1.060998e-312
-5.412876e+303 1.484567e-313 // замене %le на %d
// получаем нужный ответ.
g++-компиляция файла tstadd1.c приводит к сообщению:
mytype2.h:1: error: ISO C++ forbids declaration of ‘coef’ with no type
196

4.6
О чем узнали из четвёртой главы (второй семестр).
1. Массив – именованный набор конечного количества нумерованных элементов одинакового типа, располагаемых в оперативной памя- ти непосредственно друг за другом в порядке их следования.
2. Массив называют одномерным (или вектором), если в его описа- нии указано о распределении его элементов вдоль одного измерения.
3. Массив называют двумерным (или матрицей), если в его описании указано, что все элементы распределены вдоль двух измерений.
4. Для доступа к элементу вектора нужен порядковый номер элемента.
5. Для выборки элемента двумерного массива (матрицы) нужно задать два числа – номер строки и номер столбца.
6. Номер элемента часто называют индексом.
7. ФОРТРАН-индекс начального элемента вектора по умолчанию =1.
СИ-индекс начального элемента вектора всегда =0. Поэтому СИ- индекс последнего элемента вектора на 1 меньше числа элементов.
8. В ФОРТРАНе можно явно задать диапазоны изменения индексов.
9. Размер статического массива задается при его описании константой.
10. Размер динамического определяется при работе программы
11. При описании статического массива по умолчанию всегда указыва- ется количество элементов по соответствующему измерению.
12. Имя СИ-массива — указатель на его начальный элемент.
13. Конструктор массива в СИ обозначается [].
14. Если формальный параметром СИ-функции является массив, то при вызове ей передаётся адрес начального элемента соответствую- щего фактического аргумента.
15. В СИ обозначения a[i] и *(a+i) – синтаксически эквивалентны.
16. В современном ФОРТРАНЕ есть много встроенных функций, наце- ленных на работу с массивами. Одна из них функция sum, находя- щая сумму элементов массива.
197


4.7
Четвёртое домашнее задание (второй семестр).
• Решения дать на ФОРТРАНе-95 и С (fprintf, fscanf ).
• ФОРТРАН-процедуры из задач под номерами с 1-го по 4-ый разместить в модуле poly.
c 5 по 6 разместить в модуле binrad.
• Главная программа должна подсоединять модуль, вводить исходные данные из файла, вызывать нужную процедуру и выводить резуль- тат в файл вывода.
• Инициировать запуск каждой программы должен make- файл.
1. Разработать процедуру, которая по заданным n+1 коэффициентам полинома n-й степени и его аргументу x вычисляет значение поли- нома P(x).
2. Разработать процедуру, которая по заданным n+1 коэффициентам полинома n-й степени и его аргументу вычисляет два значения по- линома (P(x) и P(-x)) практически за время расчёта одного значе- ния P(x) ( [14]).
3. Первые n элементов одномерного массива содержат n целых чисел из диапазона [-16,16]. Разработать процедуру, которая подсчиты- вает количество встреч в массиве каждого из чисел соответственно.
4. Разработать процедуру, которая по заданному целому находит его двоичное представление, помещая очередную двоичную цифру в со- ответствующий элемент вектора (для отрицательных чисел пред- ставление должно быть получено в дополнительном коде).
5. Разработать процедуру, которая набор нулей и единиц, размещён- ных в элементах вектора переводит в значение типа integer
6. Разработать процедуру, преобразующую набор двоичных цифр, раз- мещённых в элементах одномерного массива, в набор восьмеричных цифр так, чтобы целые значения, изображаемые указанными набо- рами, численно были равны.
198

7. Значение интеграла по промежутку [a,b] от подинтегральной функ- ции f(x) приближенно вычисляется через квадратурную сумму фор- мулы средних прямоугольников (подробнее см. учебное пособие
«ПРАКТИКА ПРОГРАММИРОВАНИЯ» (приближённое вычисле- ние интегралов) (сайт HИАИ им. В.В. Соболева WWW-ресурсы)):
˜
S = h ·
n
X
i=1
f (x i
), где h =
b − a n
; x i
= a + h · (i −
1 2
); i ∈ [1, n].
n – количество промежутков равномерного дробления отрезка [a,b].
Разработать функцию rectan(y,a,b,n), которая по набору из n зна- чений подинтегральной функции, хранящихся в первых n элемен- тах одномерного массива y вычисляет значение квадратурной сум- мы формулы средних прямоугольников. Описание функции rectan поместить в модуль quarda.
199

5
Одномерный динамический массив.
До ФОРТРАНа-90 динамические массивы в ФОРТРАНе не допускались.
5.1
ФОРТРАН
Современный стандарт ФОРТРАНа допускает несколько механизмов ди- намического выделения памяти под массив [2]:
1. Автоматические – массивы, память под которые выделяется при входе в процедуру и освобождается при выходе из неё.
2. Размещаемые – массивы объявленной фиксированной размерно- сти, но с границами, вычисляемыми при выполнении программы.
3. Создаваемые – массивы, создаваемые без их явного объявления.
5.1.1
Автоматические массивы
Нередко внутри процедуры требуется локальный рабочий массив, кото- рый не хочется включать в список её формальных параметров. В то же время его размер зависит от формального параметра. Например, следуя
[2, 4] рассмотрим процедуру обмена содержимым двух векторов:
subroutine obmen(u,v) ! Здесь w - локальный внутренний массив implicit none
! процедуры obmen. В ФОРТРАНе-77 начало real u(:), v(:)
! описания процедуры выглядело бы так:
real w(size(u))
! subroutine obmen(u,v,w,n) real u(n), v(n), w(n)
w=u; u=v; v=w
! что удвоило бы число аргументов end
Ясно, что выгода от использования автоматических массивов налицо.
program testobmen; implicit none interface subroutine obmen(u, v);real u(:), v(:); end subroutine obmen end interface integer j; real u(5) / 1.7, 2.7, 3.7, 4.7, 5.7 /,&
&
v(5) / 1.3, 2.3, 3.3, 4.3, 5.3 /
call obmen(u,v)
write(*,’(i3,f10.3,f10.3)’) (j,u(j),v(j),j=1,5)
end program testobmen
1. Автоматические массивы не должны быть формальными аргумен- тами или элементами общих областей (COMMON-блоков).
200


2. size — встроенная справочная функция, получающая (в случае фак- тического аргумента-массива) общее число его элементов.
3. Автоматические массивы видны лишь той процедуре, в которой опи- саны.
4. Их нельзя использовать в операторах data, namelist, equivalence.
5. Интерфейсный блок в программе, вызывающей процедуру с фор- мальным аргументом-массивом, заимствующим форму, обязателен.
Так, если закомментировать описание интерфейса program testobmen; implicit none
!
interface
!
subroutine obmen(u, v);real u(:), v(:); end subroutine obmen
!
end interface integer j; real u(5) / 1.7, 2.7, 3.7, 4.7, 5.7 /,&
&
v(5) / 1.3, 2.3, 3.3, 4.3, 5.3 /
call obmen(u,v)
write(*,’(i3,f10.3,f10.3)’) (j,u(j),v(j),j=1,5)
end program testobmen то результат работы исполнимого файла оказался бы следующим:
Program received signal SIGSEGV: Segmentation fault - invalid memory reference. Backtrace for this error:
#0 0x7FB4012E4467
#1 0x7FB4012E4AAE
#2 0x7FB4007EB65F
#3 0x400BC9 in obmen_

#4 0x4008DE in MAIN__ at tstobmen.f90:?
Ошибка сегментирования (core dumped)
6.
subroutine obmen1(u,v, n) ! В gfortrane для указания числа real u(n), v(n)
! элементов автоматического массива w real w(n)
! можно использовать не только функцию w=u; u=v; v=w
! size, но и формальный аргумент n.
end subroutine obmen1

! Какова может быть критика исходного текста?
program testobmen1; implicit none
!
Результат работы:
integer j, n
!
1 1.300 1.700
real u(5) / 1.7, 2.7, 3.7, 4.7, 5.7 /,& !
2 2.300 2.700
&
v(5) / 1.3, 2.3, 3.3, 4.3, 5.3 /
!
3 3.300 3.700
n=3
! Почему эта программу проходит call obmen1(u,v,n)

! без явного описания интерфейса?
write(*,’(i3,f10.3,f10.3)’) (j,u(j),v(j),j=1,n)
end program testobmen1 201

5.1.2
Размещаемые массивы
Размещаемые массивы объявляются с атрибутом allocatable (размеща- емый). Их создание и уничтожение выполняется операторами allocate и deallocate соответственно, в отличие от автоматических массивов, ко- торые создаются и уничтожаются при вызове процедуры автоматически.
program allocat0; implicit none real(8), allocatable, dimension(:) :: a ! объявление размещаемого массива integer n, n1, n2, ier, k write(*,*) ’ введи размер массива:’
read (*,*) n; write(*,*) ’ n=’, n
! Значение n можно и вычислить.
allocate (a(n), stat=ier)
! Выделяем память под массив.
if (ier.ne.0) stop ’Не могу выделить!’
do k=1,n; a(k)=dsqrt(dfloat(k)); enddo
! Моделирование расчета.
write(*,1001) (k, a(k), k=1,n)
! Вывод результата.
deallocate(a, stat=ier)
! Высвобождение памяти.
if (ier/=0) stop ’Не могу высвободить!’
write(*,*) ’введи граничные индексы:’
read (*,*) n1, n2
! Значения n1 и n2
программа write(*,*) ’ n1=’, n1, ’ n2=’,n2
! может и вычислить.
allocate(a(n1:n2), stat=ier)
! Выделяем память под массив.
if (ier.ne.0) stop ’Не могу выделить!’
write(*,*) allocated(a)
do k=n1,n2; a(k)=dsqrt(dabs(dfloat(k))); enddo ! Моделирование расчёта.
write(*,1002) (k, a(k), a(k)**2, k=n1,n2)
! Вывод результата.
deallocate(a, stat=ier)
! Высвобождение памяти.
if (ier/=0)
stop
’Не могу высвободить!’
if (.not.allocated(a)) write(*,*) ’Массив a не размещен!!!’
1001 format(1x,i3,3x, d23.16,3x); 1002 format(1x,i3,3x, d23.16,3x, d23.16)
end program allocat0 1. 2-я строка. Объявляется размещаемый массив a, который дол- жен обладать следующими атрибутами:
элементы типа real(8); размещаемый allocatable; одномерный dimension (:) (одно двоеточие в круглых скобках).
Атрибуты разделяются запятыми.
2. 6-я строка. Оператор allocate a(n) фрахтует память под необ- ходимые n элементов (индекс начального элемента массива равен единице, т.е. определяется умолчанием начального индекса). Общий вид оператора allocate:
allocate(a, b, · · · , z[, stat = имя_переменной])
202