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

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

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

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

Добавлен: 06.12.2023

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

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

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

/*
3.3.2
Ещё раз о способе передачи аргумента Си-функции
По умолчанию передача фактического аргумента СИ-функции всегда происходит по значению (см., например, пункты 3.3.5 и 3.3.6 первого семестра, уже упоминаемые чуть выше). При передаче по значению происходит копирование значения, подставляемого в качестве фактиче- ского аргумента в ячейку формального. Далее вся работа СИ-функции идёт с формальным аргументом, а не с фактическим. Поэтому изменить содержимое фактического аргумента СИ-функция не может никогда (в частности, не может изменить и адрес переменной, передаваемый фак- тическим аргументом). Однако, она может изменить содержимое пере- менной, расположенной по этому адресу.
Типы значений фактического и формального аргументов должны со- ответствовать друг другу. Именно, если в качестве фактического аргу- мента выступает адрес переменной типа int, то в качестве типа фор- мального, который может обеспечить изменение её содержимого должен быть тип int*.
Пример №1.
Рассмотрим работу функции sub1(int,int*) типа void, которая зна- чение первого аргумента типа int присваивает переменной, адрес кото- рой указан во втором аргументе типа int*. В исходный текст функции sub1(int,int*), который приводится ниже специально включены опера- торы вывода значений и адресов формальных аргументов для того, что- бы легче было уяснить суть происходящего при передаче аргументов по значению. В качестве выполняемой части тела функции sub1 выступает один единственный оператор *Y=X. Здесь *Y — операция разыменова- ния указателя Y, т.е. обеспечение доступа к той области памяти, адрес которой содержится в указателе Y (т.е., на которую Y указывает).
#include
void sub1(int,int*);
int main()
{ int A=55, B=1; printf("
main: A=%2d
&A=%p\n",A,&A);
printf("
main: B=%2d
&B=%p\n",B,&B);
sub1(A,&B);
printf("
main: B=%2d
&B=%p\n",B,&B); return 0;
}
101
void sub1(int X, int* Y)
//
Вывод:
{ printf("sub1_1:
X=%d\n",X);
// 1) значения формального аргумента X
//
(т.е. целого значения, которое
//
передано X фактическим аргументом
//
при вызове sub1 из main. В нашей
//
main таким фактическим аргументом
//
служит значение 55 переменной A).
printf("sub1_1:
Y=%p\n",Y);
// 2) значения формального аргумента Y,
// (т.е. адреса памяти, который передан
// формальному аргументу Y фактическим
// аргументом при вызове sub1 из main.
// В нашей main таким фактическим
// аргументом служит адрес переменной B.
printf("sub1_1:
&X=%p\n",&X); // 3) адреса формального аргумента X;
printf("sub1_1:
&Y=%p\n",&Y); // 4) адреса формального аргумента Y;
printf("sub1_1: *Y=%d\n",*Y);
// 5) содержимого той области памяти,
//
на которую ссылается Y (т.е.
//
значение переменной A).
*Y=X; // Изменение содержимого области памяти, на которую ссылается Y.
printf("sub1_1: *Y=%d\n",*Y);
// 6) Вывод этого нового содержимого.
}
Результат работы программы:
main: A=55
&A=0x7fff2a67ce4c // Значение переменной A и её АДРЕС.
main: B= 1
&B=0x7fff2a67ce48 // Значение переменной B и её АДРЕС.
sub1_1:
X=55
// Значение формального аргумента X
// (целое число, скопированное из A)
sub1_1:
Y=0x7fff2a67ce48
// Значение формального аргумента Y
// --- адрес переменной B, ведь
// именно он (&B) служит фактическим
// аргументом и поэтому именно он
// скопирован в Y.
sub1_1:
&X=0x7fffa5c3069c
// Адрес формального аргумента X.
sub1_1:
&Y=0x7fff2a67ce20
// Адрес формального аргумента Y.
// (&X и &Y ОТЛИЧНЫ от &A и &B).
sub1_1: *Y=1
// Значение, хранящееся в области
// памяти, адрес которой указан в Y,
// т.е. изначальное значение B.
sub1_1: *Y=55
// Новое значение области памяти,
// адрес которой хранится в Y,
// полученное посредством оператора
//
*Y=X.
main: B=55
&B=0x7fff2a67ce48 // Действительно, значение B изменено!
102


Пример №2.
Рассмотрим пример, когда первый аргумент функции хранит значе- ние типа int*, а второй аргумент должен передать это значение (имеется ввиду не значение типа int, а именно значение int*, т.е. изменить значе- ние адреса, хранящегося в указателе, описанном в главной программе.
Возражение о том, что главная программа и без этого знает адрес первого аргумента (ведь именно она обеспечила его подачу в качестве первого аргумента функции) — НЕ ПРИНИМАЕТСЯ. Сейчас важно от- ветить на вопрос: ”Как нужно описать формальный аргумент функции,
чтобы, через него можно было передать вызывающей программе адрес,
т.е. значение типа УКАЗАТЕЛЬ?”
Задача похожа на предыдущую (отличие лишь в типах передаваемых аргументов). Первый формальный аргумент хранит указатель на адрес значения целого типа; тип этого указателя — int*.
Второй формальный аргумент не может быть типа int*, т.к.
1) должен адресовать НЕ значение типа int, а значение типа int*;
2) если же всё-таки проигнорировать 1) и сопоставить второму фор- мальному аргументу тип int*, то окажемся у разбитого корыта, посколь- ку значение адреса соответствующего фактического аргумента будет пе- редано формальному по значению, так что изменение формального аргу- мента внутри функции НЕ изменит значения фактического. Что делать?
Поскольку переменная, содержимое которой должно быть изменено,
имеет тип int*, то формальный аргумент функции, обеспечивающий воз- можность такого изменения, должен иметь тип int**, т.е. тип указателя на (указатель типа int*).
Рассмотрим пример, моделирующий ситуацию.
#include
#include
void sub3(int *,int **);
int main()
{ int *v;
// v - рабочий указатель на int int A=55, B=3;
printf(" &v=%p\n",&v);
printf(" v=%p\n",v);
printf(" &A=%p\n",&A);
printf(" A=%d\n",A);
printf(" &B=%p\n",&B);
printf(" B=%d\n",B);
v=&B;
printf(" v=%p\n",v);
sub3(&A,&v);
103
printf("
v=&A=%p\n", v);
printf(" *v= A=%d\n",*v);
return 0;
}
void sub3(int *X, int **Y)
{
printf("sub3_1:
&X=%p\n", &X);
printf("sub3_1:
X=%p\n",
X);
printf("sub3_1:
*X=%d\n", *X);
printf("sub3_1:
&Y=%p\n", &Y);
printf("sub3_1:
Y=%p\n", Y);
printf("sub3_1:
*Y=%p\n",*Y);
// (Разыменование_Y )=*Y=&B.
printf("sub3_1:
**Y=%d\n",**Y); // (Разыменование_*Y)=**Y=B=3
*Y=X;
// Теперь *Y стал указателем на &A.
printf("sub3_2:
*Y=%p\n",*Y);
// (Разыменование_Y )= *Y=&A
printf("sub3_2:
**Y=%d\n",**Y); // (Разыменование_*Y)=**Y=55
}
Результат работы программы:
&v=0x7ffff1ffeca8
// Адрес указателя v.
v=(nil)
// Значение........ v.
&A=0x7ffff1ffeca4
// Адрес переменной A.
A=55
// Значение........ A.
&B=0x7ffff1ffeca0
// Адрес переменной B.
B=3
// Значение........ B.
v=0x7ffff1ffeca0
// Значение v после v=&B
sub3_1:
&X=0x7ffff1ffec88
// Адрес формального аргумента X
sub3_1:
X=0x7ffff1ffeca4
// Значение..........
X = &A
sub3_1:
*X=55
// (Разыменование_X)= *X = 55
sub3_1:
&Y=0x7ffff1ffec80
// Адрес формального аргумента Y
sub3_1:
Y=0x7ffff1ffeca8
// Значение..........
Y = &v sub3_1:
*Y=0x7ffff1ffeca0
// (Разыменование_Y )= *Y = &B
sub3_1:
**Y=3
// (Разыменование_*Y)=**Y =
3
//
После
*Y=X
sub3_2:
*Y=0x7ffff1ffeca4
// (Разыменование_Y )= *Y = &A
sub3_2:
**Y=55
// (Разыменование_*Y)=**Y = 55
v=&A=0x7ffff272a794
// Значение v после отработки sub3
*v= A=55
// (Разыменование_v )= *v = 55
Укажем полезную статью [21] «Нежданчики» языка ФОРТРАН:
https://habr.com/en/company/intel/blog/254235/
104

где излагаются моменты, с которыми сталкивается СИ-программист при переходе на ФОРТРАН, и ФОРТРАН-программист при переходе на СИ.
Пример №3.
Указатели в примерах №1,2 были связаны со значениями элементарно- го типа int. Приведём теперь пример, когда функция должна присвоить фактическому аргументу значение типа struct vec { double x; double y; double z;}
,
где структура используется в качестве модели, задающей координаты точки окончания вектора, исходящего из начала координат.
Напишем функцию addvec(a,b,c), которая находит сумму двух та- ких векторов A и B, и помещает её в структуру C
struct coord
//
Файл tstruct1.h
{
// Имя типа coord должно быть известно и главной double x;
// программе, и функциям. Поэтому помещаем его описание double y;
// отдельном файле, содержимое которого к любой функции double z; }; // можно подсоединить посредством #include .
//
Файл tstvec2.c
#include
#include "tstruct2.h"
// Доступ к имени типа структуры void rdrcoord(struct coord*);
// Указываем прототипы
(интерфейсы)
void prtcoord(struct coord );
//
используемых void addvec(struct coord, struct coord, struct coord*); //
функций.
int main(void)
{
struct coord A, B, C;
// Описываем переменные типа coord и struct coord *PA,*PB,*PC;
// указатели на переменные типа coord.
rdrcoord(&A);
printf("A="); prtcoord(A); // Демонстрация работы с rdrcoord(&B);
printf("B="); prtcoord(B); // переменными типа addvec(A,B,&C); printf("C="); prtcoord(C); //
struct coord.
printf("\n");
PA=&A; printf("A="); prtcoord(*PA);
// Теперь указатели PA, PB и PC
PB=&B; printf("B="); prtcoord(*PB);
// адресуют переменные A, B и C
PC=&C; printf("C="); prtcoord(*PC);
// соответственно, и PA, PB и PC
printf("\n");
// используем для работы с A, B и C.
rdrcoord(PA);
printf("A="); prtcoord(*PA); // При вызове addvec rdrcoord(PB);
printf("B="); prtcoord(*PB); // используем addvec(*PA,*PB,PC); printf("C="); prtcoord(*PC); // разыменование.
printf("\n");
// указателей PA и PB.
PA=&B;
printf("B="); prtcoord(*PA); // При желании можем
105

PB=&C;
printf("C="); prtcoord(*PC); // изменить адрес, храня- addvec(*PA,*PC,PB); printf("B="); prtcoord(*PB); // щийся в указателе.
return 0; }
В программировании часто употребляются термины АДРЕС, ССЫЛ-
КА и УКАЗАТЕЛЬ.
И ссылка, и указатель содержат адрес того объекта, на который ссы- лаются. Различие между ними в том, что ссылка не может изменить тот адрес, на который ссылается, а указатель может.
Например, адреса переменных A, B и С, описанных в главной про- грамме, это ссылки, устанавленные компилятором — их изменить нель- зя, в смысле нельзя изменить адреса этих переменных. Однако можно изменить содержимое тех переменных, чьи адреса хранятся по адресам расположения ссылок А, B и C.
Аналогично, НЕЛЬЗЯ изменить адреса ячеек памяти, установленные компилятором для хранения указателей PA, PB и PC. Однако содержи- мое любого из них изменить можно (ситуация аналогична предыдущей).
Так что после PA=&B, в PA будет храниться адрес структуры B.
//
Файл addvec.c
#include
#include "tstruct2.h"
void rdrcoord(struct coord* a)
{
scanf("%lg
%lg
%lg\n",&(*a).x,&(*a).y,&(*a).z);
}
void prtcoord(struct coord a)
{
printf("%lg
%lg
%lg\n",a.x,a.y,a.z);
}
void addvec(struct coord a, struct coord b, struct coord* c)
{
(*c).x=a.x+b.x; (*c).y=a.y+b.y; (*c).z=a.z+b.z;
}
Результат работы программы:
A=1 2
3
B=1 1
1
C=2 3
4
A=1 2
3 106


B=1 1
1
C=2 3
4
A=1 2
3
B=1 1
1
C=2 3
4
B=1 1
1
C=2 3
4
B=3 4
5 107

3.3.3
Пример построения стека
// Содержимое файла myinter.h typedef struct candidat* link;
// link --- синоним указателя на
//
звено списка candidat struct candidat
//
описание структуры candidat
{
int info;
link next;
};
void prtlist(link);
// прототип функции вывода списка
// Содержимое файла testlist.c
#include
#include
#include "myinter.h"
int main(void)
{
link top,
cur;
int i, k, ier;
top=NULL;
// инициализация головы списка.
i=1;
// --"-- номера вводимого числа do
{
printf(" input %d-st number > 0\n",i);
// подсказка о вводе i-го числа scanf("%d",&k);
// ввод i-го числа printf("%d-st number is %d\n", i,k);
// контрольный вывод i-го числа if (k!=0)
{ cur=(link) malloc(sizeof (struct candidat)); // адрес нового звена
//
списка cur->info= k;
// в поле info помещаем k.
cur->next=top;
// в поле next --"-- адрес того звена, на которое
//
пока ссылается top (голова списка).
top=cur;
// теперь в top помещаем адрес звена, под которое
// выше была выделена память и заполнены её поля.
}
i++;
// увеличение номера вводимого числа.
}
while (k!=0);
prtlist(top);
return 0;
}
108

// Содержимое файла prtlist.c
#include
#include
#include "myinter.h"
void prtlist(link top)
// Функция prtlist выводит содержимое списка,
{
// адрес головы которого указан в top.
link cur;
// Указатель на текущее звено списка int i;
// Номер звена i=0;
// Инициализация номера звена cur=top;
// Инициализация текущего звена while (cur!=NULL)
// Пока указатель текущего звена информирует
{
// о существовании следующего:
i++;
// номер текущего звена printf("%d-е ",i);
// вывод этого номера printf(" число в списке равно %d\n",cur->info); // вывод поля info cur=cur->next;
// теперь в текущем указателе
// адрес следующего звена.
}
}
Содержимое файла input
23 45 67 98 100 0
Содержимое файла result input 1-st number > 0 1-st number is 23
input 2-st number > 0 2-st number is 45
input 3-st number > 0 3-st number is 67
input 4-st number > 0 4-st number is 98
input 5-st number > 0 5-st number is 100
input 6-st number > 0 6-st number is 0 1-е число в списке равно 100 2-е число в списке равно 98 3-е число в списке равно 67 4-е число в списке равно 45 5-е число в списке равно 23 109

3.3.4
Пример добавления нового звена в вершину стека
Если речь идёт о добавлении нового звена главной программой, то до- бавление аналогично соответствующим операторам из тела цикла:
// Содержимое файла testlist.c
#include
#include
#include "myinter.h"
int main(void)
{
link top,
cur;
int i, k, ier;
top=NULL;
// инициализация головы списка.
i=1;
// --"-- номера вводимого числа do
{
printf(" input %d-st number > 0\n",i);
// подсказка о вводе i-го числа scanf("%d",&k);
// ввод i-го числа printf("%d-st number is %d\n", i,k);
// контрольный вывод i-го числа if (k!=0)
{ cur=(link) malloc(sizeof (struct candidat)); // адрес нового звена
//
списка cur->info= k;
// в поле info помещаем k.
cur->next=top;
// в поле next --"-- адрес того звена, на которое
//
пока ссылается top (голова списка).
top=cur;
// теперь в top помещаем адрес звена, под которое
// выше была выделена память и заполнены её поля.
}
i++;
// увеличение номера вводимого числа.
}
while (k!=0);
prtlist(top);
printf(" input %d-st number > 0\n",i);
// подсказка о вводе i-го числа scanf("%d",&k);
// ввод i-го числа printf("%d-st number is %d\n", i,k);
// контрольный вывод i-го числа if (k!=0)
{ cur=(link) malloc(sizeof (struct candidat)); // адрес нового звена списка cur->info= k;
cur->next=top;
top=cur;
}
printf("Добавили ещё одно звено в голову списка\n");
prtlist(top);
return 0;
}
110


Содержимое файла input
23 45 67 98 100 0 777
Содержимое файла result input 1-st number > 0 1-st number is 23
input 2-st number > 0 2-st number is 45
input 3-st number > 0 3-st number is 67
input 4-st number > 0 4-st number is 98
input 5-st number > 0 5-st number is 100
input 6-st number > 0 6-st number is 0 1-е число в списке равно 100 2-е число в списке равно 98 3-е число в списке равно 67 4-е число в списке равно 45 5-е число в списке равно 23
input 7-st number > 0 7-st number is 777
Добавили ещё одно звено в голову списка
1-е число в списке равно 777 2-е число в списке равно 100 3-е число в списке равно 98 4-е число в списке равно 67 5-е число в списке равно 45 6-е число в списке равно 23
Некоторое затруднение может возникнуть, когда добавление нового эле- мента (звена стека) должна выполнить функция типа void, т.е. возвра- щающая результат через свои аргументы. Пусть, например, её вызов име- ет вид addelem0(top,k);. Здесь top — указатель на вершину стека, а k
— целая переменная, хранящая информацию, которая должна оказаться в поле info головного звена. Описание функции addelem0 поместим в тот же файл, что и функцию prtlist (назовём его worklist, отражая тот факт, что в будущем в нём будут собраны все функции, обеспечивающие работу со стеком).
111

// Содержимое файла testlist3.c
#include
#include
#include "myinter.h"
int main(void)
{ link top,
cur; int i, k, ier;
top=NULL;
// инициализация головы списка.
i=1;
// --"-- номера вводимого числа do { printf(" input %d-st number > 0\n",i);
// подсказка о вводе i-го числа scanf("%d",&k);
// ввод i-го числа printf("%d-st number is %d\n", i,k);
// контрольный вывод i-го числа if (k!=0)
{ cur=(link) malloc(sizeof (struct candidat)); // адрес нового звена
//
списка cur->info= k;
// в поле info помещаем k.
cur->next=top;
// в поле next --"-- адрес того звена, на которое
//
пока ссылается top (голова списка).
top=cur;
// теперь в top помещаем адрес звена, под которое
// выше была выделена память и заполнены её поля.
}
i++;
// увеличение номера вводимого числа.
}
while (k!=0);
printf("Первый список:\n"); prtlist(top);
addelem0(top,777); printf("Второй
:\n"); prtlist(top);
return 0;
}
// Содержимое файла prtlist.c
#include
#include
#include "myinter.h"
void prtlist(link top)
// Функция prtlist выводит содержимое списка,
{
// адрес головы которого указан в top.
link cur;
// Указатель на текущее звено списка int i;
// Номер звена i=0;
// Инициализация номера звена cur=top;
// Инициализация текущего звена while (cur!=NULL)
// Пока указатель текущего звена информирует
{
// о существовании следующего:
i++;
// номер текущего звена printf("%d-е ",i);
// вывод этого номера printf(" число в списке равно %d\n",cur->info); // вывод поля info cur=cur->next;
// теперь в текущем указателе
// адрес следующего звена.
}
}
112