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

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

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

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

Добавлен: 06.12.2023

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

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

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

1. Компилятор gfortran в качестве разделителя имён структурного объекта и его поля использует значок процента (%), а не точки, хотя некоторые ФОРТРАН-компиляторы имеют опции, допускающие в качестве такого разделителя и точку.
2. Правильный выбор производного типа может существенно упро- стить тексты исходных файлов, из которых генерируется исполни- мый код, если описание новых типов помещать в модуль, подклю- чаемый к нужным единицам компиляции через оператор use.
3. В тот же модуль удобно включать и описания процедур, формаль- ные аргументы которых являются объектами указанных производ- ных типов. Так вывод объекта производного типа удобно оформить процедурой, с тем, чтобы главная программа имела дело только с именем этого объекта, а работой с именами его полей ведала бы процедура:
module modcoor1; implicit none type coord;
real x, y, z;
end type coord type cube ;
type (coord) v(8);
end type cube contains subroutine prtcoor(a)
type (coord) a write(*,’(3e15.7)’) a%x, a%y, a%z end subroutine prtcoor subroutine prtcube(t)
integer j type (cube) t do j=1,8;
call prtcoor(t%v(j));
enddo end subroutine prtcube end module modcoor1
program tstcoor1; use modcoor1
implicit none type (coord) :: a, b, c, w;
type (cube) ::
t integer j a=coord(1.0, 2.0, 3.0); b=coord(4.0, 5.0, 6.0); c=coord(7.0, 8.0, 9.0)
call prtcoor(a);
call prtcoor(b); call prtcoor(c); write(*,*)
w=a; a=c; c=w call prtcoor(a);
call prtcoor(b); call prtcoor(c); write(*,*)
t=cube ((/ coord(4,4,4), coord(4,4,5), coord(4,5,5), coord(4,5,4),&
coord(5,4,4), coord(5,4,5), coord(5,5,5), coord(5,5,4)/))
call prtcube(t)
end
92

3.2.2
СИ
Для первого знакомства с описанием и инициализацией переменных типа struct в СИ используем предыдущий пример: задание положения точ- ки в пространстве тремя её координатами. Тогда оно описывалось од- номерным вектором из трёх элементов типа float. Сейчас для описания используем тип данных struct с тремя полями x, y и z типа float. Рас- смотрим соответствующий аналог программы tvector.c:
#include
int main(void)
//
Файл tstruct.c
{
struct coord
// В качестве нового имени типа определено слово coord.
{ float x;
// x, y, z - его поля типа float. Некоторые компиляторы float y;
// не допускали объявления однотипных полей одним float z;
// оператором float x, y, z; (gcc и g++ допускают).
};
struct coord a;
// Простое описание переменной типа coord.
struct coord b={1,2,3};
// Полная инициализация.
struct coord c={.x=8, .z=88.0};
// Частичная инициализация (только в СИ,
//
но не в С++ !!!).
printf(" a=%e %e %e\n", a.x, a.y, a.z);
a.x=10.0; a.y=11.0; a.z=12.0;
printf(" a=%e %e %e\n"
, a.x, a.y, a.z);
printf(" b=%e %e %e\n\n", b.x, b.y, b.z);
printf("Адрес a.x=%p, а содежимое a.x=%e \n"
, &a.x, a.x);
printf("Адрес a.y=%p, а содежимое a.y=%e \n"
, &a.y, a.y);
printf("Адрес a.z=%p, а содежимое a.z=%e \n\n"
, &a.z, a.z);
printf("Адрес c.x=%p, а содежимое c.x=%e \n"
, &c.x, c.x);
printf("Адрес c.y=%p, а содежимое c.y=%e \n"
, &c.y, c.y);
printf("Адрес c.z=%p, а содежимое c.z=%e \n"
, &c.z, c.z);
return 0;
}
Результат её работы:
a=1.048576e+06 1.883795e-305 0.000000e+00
a=1.000000e+01 1.100000e+01 1.200000e+01
b=1.000000e+00 2.000000e+00 3.000000e+00
Адрес a.x=0xfee27730, а содежимое a.x=1.000000e+01
Адрес a.y=0xfee27734, а содежимое a.y=1.100000e+01
Адрес a.z=0xfee27738, а содежимое a.z=1.200000e+01
Адрес c.x=0xfee27710, а содежимое c.x=8.000000e+00
Адрес c.y=0xfee27714, а содежимое c.y=0.000000e+00
Адрес c.z=0xfee27718, а содежимое c.z=8.800000e+01 93


1. После фигурной скобки, завершающей описание полей структуры,
необходим символ
;
(точка с запятой).
2. СИ допускает разные варианты именования структурных типов и описания переменных типа struct. Например,
struct coord
// Описание имени типа coord и его структуры.
{ float x,y,z;};
// (именно так тип coord описан в tstruct.c)
struct coord
{ float z,y,z;} a,b;
// Вместе с типом описаны и переменные a и b
// типа coord, под которые выделяется память
3. Помещать описание структурного типа в тело главной программы невыгодно. Если переменные типа coord в дальнейшем окажутся фактическими аргументами каких-то функций, то он потребуется и при описании их прототипов. Выгодно обеспечить доступ к нему из файлов c исходными текстами этих функций, описав тип coord в отдельном файле (например, tstruct1.h)
struct coord //
Файл tstruct1.h
{
// Имя типа coord должно быть известно и главной программе,
float x;
// и функциям. Помещать описания этих функций в этом файле float y;
// tstruct1.h можно, но невыгодно, так как, компилируя главную float z;
// программу, компилятор будет их перекомлировать заново.
};
Посредством директивы #include “tstruct1.h” содержимое файла tstruct1.h подключается к нужной функции.
4. Аналогичную директиву включим и в файл tstrfun1.c c функция- ми, которые реализуют ввод и вывод данных типа coord:
#include
// Файл tstrfun1.c
#include "tstruct1.h"
void rdrcoord0(struct coord *a)
{ scanf("%e %e %e", &((*a).x), &((*a).y), &(a->z));}
void rdrcoord1(struct coord *a)
// При желании можно распределить работу
{ float u, v, w;
// на два этапа:
scanf("%e%e%e", &u, &v, &w);
// 1) ввод значений простых переменных;
(*a).x=u;
a->y=v;
a->z=w;
} // 2) их присваивание полям структуры.
void prtcoord(struct coord a)
{ printf("%e %e %e\n",a.x,a.y,a.z);
a.x=555; a.y=666; a.z=777; printf("%e %e %e\n",a.x,a.y,a.z);
}
94

5. Внимание! Тип формального аргумента и функции rdrcoord0 и функции rdrcoord1 есть указатель на структуру. В этом случае возможны две эквивалентных формы доступа к её полям:
• через разыменование указателя перед .имя_поля:
(*a).x, (*a).y, (*a).z
• посредством конструкции имя_указателя->имя_поля:
a->x, a->y, a->z
6. Функции scanf (по её интерфейсу) при указании списка ввода тре- буются адреса вводимых переменных, т.е. &((*a).x) или &(a->x)).
7. В отличие от rdrcoord0 и rdrcoord1 формальный аргумент проце- дуры prtcoord является значением типа coord, а не адресом. Про- цедура prtcoord нацелена лишь на вывод значения, переданного фактическим аргументом формальному, а не на измененеие значе- ния фактического. В предпоследней строке prtcoord моделируется изменение формального аргумента, но фактический не изменяется:
#include
#include "tstruct1.h"
// Указываем имя используемой структуры.
void rdrcoord0(struct coord *);
// Указываем прототипы (интерфейс)
void rdrcoord1(struct coord *);
// используемых функций.
void prtcoord (struct coord );
int main(void)
{ struct coord a, x, p;
// Описываем переменные типа coord struct coord b={1,2,3};
// Инициализация полная struct coord c={.x=8, .z=88.0};//
и частичная.
a.x=10.0; a.y=11.0; a.z=12.0;
prtcoord(a); printf("%f
%f
%f\n", a.x, a.y, a.z);
prtcoord(b); printf("%f
%f
%f\n", b.x, b.y, b.z);
prtcoord(c); printf("%f
%f
%f\n", c.x, c.y, c.z);
printf("введите x\n"); rdrcoord0(&x);
prtcoord(x);
printf("%f
%f
%f\n", x.x, x.y, x.z);
printf("введите p\n"); rdrcoord1(&p);
prtcoord(p);
printf("%f
%f
%f\n", p.x, p.y, p.z);
return 0;
}
95


8. Результаты пропуска этой программы:
1.000000e+01 1.100000e+01 1.200000e+01 //prtcoord: 1-ый вывод a.x, a.y, a.z
5.550000e+02 6.660000e+02 7.770000e+02 //
: 2-ой вывод
10.000000 11.000000 12.000000
// main
:
a.x, a.y, a.z
1.000000e+00 2.000000e+00 3.000000e+00 //prtcoord: 1-ый вывод b.x, b.y, b.z
5.550000e+02 6.660000e+02 7.770000e+02 //
: 2-ой вывод
1.000000 2.000000 3.000000
// main
:
b.x, b.y, b.z
8.000000e+00 0.000000e+00 8.800000e+01 //prtcoord: 1-ый вывод c.x, c.y, c.z
5.550000e+02 6.660000e+02 7.770000e+02 //
: 2-ой вывод
8.000000 0.000000 88.000000
// main
:
c.x, c.y, c.z введите x
4 44 444 4.000000e+00 4.400000e+01 4.440000e+02 //prtcoord: 1-ый вывод x.x, x.y, x.z
5.550000e+02 6.660000e+02 7.770000e+02 //
: 2-ой вывод
4.000000 44.000000 444.000000
// main
:
x.x, x.y, x.z введите p
9 99 999 9.000000e+00 9.900000e+01 9.990000e+02 //prtcoord: 1-ый вывод p.x, p.y, p.z
5.550000e+02 6.660000e+02 7.770000e+02 //
: 2-ой вывод
9.000000 99.000000 999.000000
// main
:
p.x, p.y, p.z
Ещё раз: передача формального аргумента по значению не может изменить значения соответствующего фактического аргумента.
9. Правда, есть объективное основание для описания формального ар- гумента процедуры prtcoord указателем на значение типа coord
(при замене списка вывода printf либо на a->x, a->y, a->z, либо на (*a).x, (*a).y, (*a).z. Обоснование заключается в том, что пе- редача одного адреса начала структуры занимает гораздо меньше времени чем копирование значений всех полей структуры. Короче,
“ Думаем сами, решаем сами: иметь или не иметь” .
10. В СИ и С++ есть оператор typedef, сопоставляющий имени, при- думанному нами, любой уже существующий тип, что может суще- ственно упростить модификацию и унифицировать описание про- граммы в целом без описания придуманного имени в директиве пре- процессора.
96

3.2.3
Об операторе typedef.
Оператор typedef определяет псевдоним имени существующего типа:
1. typedef float real позволит вместо float a, h; использовать real a,
h;, и переход на тип double сведётся лишь к typedef double real
#include
//
Файл tsttrap.c using namespace std;
//
typedef long double real;
//
real
Результат:
int main()
//
float inf
{ real a=1.0e250, h=1.0e303, s=a*h;
//
double inf cout <}
//
long double
1e+553 2. При описании переменных типа массив бывает неудобно после каж- дого имени явно указывать структуру массива посредством кон- структора []. Можно поступить так:
typedef double xyz[3];
// Определили имя типа xyz int main()
// и описали два массива a и b типа xyz, хотя
{ xyz a={1.2,2.3,3.7}, b ; } // могли бы объявить и так: double a[3], b[3].
3. Аналогично вместо директивы препроцессора, подключающей за- головочный файл с описанием типа структуры, можно определить синоним нужной структуры посредством оператора typedef:
typedef struct
{ float x, y, z;}
coord;
Теперь слово coord — эквивалент типа структуры с тремя полями типа float, и описание переменных типа coord выглядит проще:
coord a, b, c;
//
(вместо struct coord a, b, c;)
4. Через typedef определяется псевдоним указатель на функцию,
имя которой служит аргументом другой функции. Так, прототип double trap (double*(double), double, double, double);
после описания псевдонима pf: typedef double (*pf )(double); —
запишется проще:
double trap (pfun, double, double, int);
97


3.3
Указатели в СИ
В предыдущих пунктах уже касались понятия указатель (это производ- ный тип, значением которого служит адрес ячейки, предназначенной для хранения данного нужного типа). В СИ по умолчанию типы указателей на объекты разных типов различаются. Например,
#include
int main(void)
{ int u, *a, k; double *b;
a=b; return 0;
}
1. Звёздочка перед именем переменной при описании типа указы- вает, что a и b — указатели, т.е. будут хранить адреса ячеек,
отведённых под значения типов int и double соответственно, а u и k — просто для значений типа int.
2. Для хранения адреса значения любого типа ячейки всегда доста- точно 8 байтов. Однако, прямая попытка присвоить указателю, ссы- лающемуся на значение int, значение указателя, ссылающегося на double, приведёт к сообщению компилятора:
tpointm1.c: In function ‘main’:
tpointm1.c:4: warning: assignment from incompatible pointer type т.е. тип указателя из левой части оператора присваивания отличен от типа указателя из правой части. При желании возможна опера- цию приведения типа: a=(int*)b;, но нужно ли? — решать нам.
3. Для выделения области оперативной памяти нужного размера есть функция malloc, которая (при успешном вызове) возвращает че- рез своё имя адрес начального байта области. Освобождение памяти осуществляет функция free. Прототипы обеих функций обычно из stdlib.h:
void *malloc(size_t size);
void free(void *ptr);.
4. malloc выделяет size байтов под переменную типа size_t и воз- вращает через имя malloc указатель (типа void) на выделенную память. Поэтому при необходимости приписать указателю нужный тип используем любую из операций приведения типа (например,
(int*), (double*)). При невозможности выделить память malloc возвращает указатель NULL.
98

5. Область оперативной памяти, находимая malloc или освобождае- мая free при их вызове, не имеет имени. Для доступа к содержимо- му этой области используется операция разыменования указателя.
6. Переменная, описанная в программе в качестве указателя, имеет имя, память под неё выделяется при компиляции и находится пол- ностью в ведении программы. Область же памяти, адресуемая через указатель, определяется и высвобождается в процессе работы про- граммы (и в этом смысле является динамической).
#include
#include
// Не забывать при использовании malloc!!!
int main(void)
{ int *pa; double *pb; char *pc; pa=NULL; pb=NULL; pc=NULL;
printf("&pa=%p
&pb=%p
&pc=%p \n",&pa, &pb, &pc);// What now in printf(" pa=%p pb=%p pc=%p \n", pa,
pb,

1   2   3   4   5   6   7   8   9   ...   23

pc);// pa, pb, pc?
pa=(int*) malloc(sizeof(int));
pb=(double*) malloc(sizeof(double));
pc=(char*)
malloc(sizeof(char));
printf("&pa=%p
&pb=%p
&pc=%p\n",&pa, &pb, &pc); // What now in printf(" pa=%p pb=%p pc=%p\n", pa,
pb,

pc); // pa, pb, pc?
*pa=5; *pb=1.234567890123456789l; *pc=’q’;
printf(" &pa=%p
&pb=%p
&pc=%p\n",&pa, &pb, &pc);
// What now in printf("*pa=%d

*pb=%30.18le pc=%c\n",*pa,*pb,*pc);//*pa,*pb,*pc?
free((void*)pa);
printf("&pa=%p &pb=%p
&pc=%p\n",&pa, &pb, &pc);
printf(" pa=%p pb=%p pc=%p\n", pa,
pb,
pc);
//What now in printf("*pa=%d *pb=%30.18le
*pc=%c\n",*pa, *pb, *pc);//
pa?
return 0;
}
7. Небрежное использование функии malloc может привести к обра- зованию в оперативной памяти областей недоступных программе из-за ошибочной потери содержимого соответствующих указателей.
В итоге, и сами не используем динамическую память, захваченную своей же программой, и другим не даём.
8. Напомним (см., пункты 3.3.5 и 3.3.6; первый семестр), что в СИ
для изменения содержимого по адресу фактического аргумента соответствующий формальный по типу должен быть только указа- телем. В С++ для той же цели используем описание формального аргумента ссылкой (скрытым указателем), переходя к обычному
СИ-указателю лишь по необходимости.
99

3.3.1
СИ
#include
#define pintdef int*
// pintdef
- имя последовательности int*
typedef int* pinttyp; // pinttype - синоним типа указатель на целое.
int main(void)
{ int*
pa, a;
// pa - указатель;
a - переменная типа int.
pintdef pb, b;
// pb - указатель;
b - переменная типа int.
pinttyp pc, c;
// pc и c - указатели.
printf(" &pa=%p pa=%p\n"
,&pa, pa); // У переменных pa и pb адреса есть,
printf(" &pb=%p &pb=%p\n\n",&pb, pb); // но содержимое pa и pb не задаётся.
printf(" &a=%p a=%d\n",
&a, a);
// У переменных a и b адреса есть,
printf(" &b=%p b=%d\n\n", &b, b);
// но содержимое a и b не задаётся.
a=5; b=7;
printf(" &a=%p a=%d\n"
, &a, a);
// Переменным a и b присвоены значения.
printf(" &b=%p b=%d\n\n", &b, b);
pa=&a;
// переменной pa присвоено значение - это адрес переменной a;
pb=&b;
// переменной pb присвоено значение - это адрес переменной b;
printf(" &pa=%p pa=%p
*pa=%d\n",
&pa, pa, *pa); // *pa - операция printf(" &pb=%p pb=%p
*pb=%d\n\n",&pb, pb, *pb); //
разыменования pc=pa; c=pb;
printf(" &pc=%p pc=%p
*pc=%d\n", &pc, pc, *pc);
printf("
&c=%p c=%p
*c=%d\n\n",&c, c,
*c);
c=NULL; printf(" c%p
&c%p\n",c, &c);
return 0;
}
&pa=0xbfdb30dc pa=0x64fff4
&pb=0xbfdb30d4 &pb=0x8048310
&a=0xbfdb30d8
a=134514059
&b=0xbfdb30d0
b=134514048
&a=0xbfdb30d8
a=5
&b=0xbfdb30d0
b=7
&pa=0xbfdb30dc pa=0xbfdb30d8
*pa=5
&pb=0xbfdb30d4
pb=0xbfdb30d0
*pb=7
&pc=0xbfdb30cc pc=0xbfdb30d8
*pc=5
&c=0xbfdb30c8
c=0xbfdb30d0
*c=7
c(nil)
&c0xbfdb30c8
NULL — адресная константа (нулевой указатель). Совпадение указателя с NULL означает, что указателю уже не на что указывать (например,
список окончился).
100