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

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

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

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

Добавлен: 06.12.2023

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

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

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
void addelem0(link top, int num) // Функция ADDELEM0 добавляет (???)
{
// новое звено к вершине стека.
link cur;
cur=(link) malloc(sizeof(struct candidat));
cur->info=num;
cur->next=top;
top=cur;
}
Содержимое файла 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
// Видим, что несмотря на то, что в тексте
Второй
:
// главной программы явно указано добавление
1-е число в списке равно 100 // нового звена посредством вызова addlist0,
2-е число в списке равно 98
// функция prtlist выводит тот же самый
3-е число в списке равно 67
// список, который был и до вызова
4-е число в списке равно 45
// addlist0 (т.е. addlist0 работает не так.
5-е число в списке равно 23
// как требуется.
П О Ч Е М У
???
Для уяснения происходящего поместим в тело addlist0 вспомогательные печати. Назовём копию функции addlist0 c этими печатями addlist1.
// Содержимое файла testlist4.c
#include
#include
113

#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);
addelem1(top,555); 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;
// теперь в текущем указателе
// адрес следующего звена.
}
}
114
void addelem1(link top, int num)
{
link cur;
cur=(link) malloc(sizeof(struct candidat)); printf("%p\n",cur);
cur->info=num;
printf("%d\n",cur->info);
cur->next=top;
printf("%p\n",cur->next);
top=cur;
printf("%p\n",top);
}
Содержимое файла 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 0x18310b0
// Это адрес нового текущего звена.
555
// Это содержимое его info-поля.
0x1831090
// Это адрес старого головного звена.
0x18310b0
// Это адрес нового головного звена.
Второй
:
// И, тем не менее, для главной
1-е число в списке равно 100
// программы функция addlist1 2-е число в списке равно 98
// отработала вхолостую ---
3-е число в списке равно 67
//
ничего не изменилось !!!
4-е число в списке равно 45
//
П О Ч Е М У
???
5-е число в списке равно 23
Дело в том, что мы забыли, что в СИ аргументы функций всегда передаются по значению. Это означает, что значение любого факти- ческого аргумента сначала копируется в ячейку формального, и далее
115

вся работа внутри функции ведётся исключительно с формальным ар- гументом. В частности, передача внутрь функции адреса вершины стека тоже происходит по значению, т.е. новое значение адреса вершины, ока- завшееся по адресу формального аргумента, никогда не будет передано вызывающей программе. Что же делать? Сделать надо то же самое, что мы уже делали в первом семестре, когда требовалось изменить значение фактического аргумента. Вспомним программу testsub1.c и функцию subfun1.c из пункта 6.3.5 первого семестра:
#include
// файл testsub1.c void subfun(double,double*);
int main()
{ double x, y, z, a, b;
double resx, resy, resz, resa, resb;
x=0.51; y=1; z=0.75; a=1.5; b=1.499999999999999;
subfun(x,&resx);
printf("
x=%23.16e resx=%23.16e\n",x,resx);
subfun(y,&resy);
printf("
y=%23.16e resy=%23.16e\n",y,resy);
subfun(z,&resz);
printf("
z=%23.16e resz=%23.16e\n",z,resz);
subfun(a,&resa);
printf("
a=%23.16e resa=%23.16e\n",a,resa);
subfun(b,&resb);
printf("
b=%23.16e resb=%23.16e\n",b,resb);
return 0;
}
void subfun(double x, double* y)
// файл subfun1.c
{ (*y)=2*x-3;
printf(" subfun: x=%23.16le y=%23.16le\n",x,*y);
}
Вызов функции subfun1(x,&res) должен изменить значение res пере- менной главной программы. Другими словами, в качестве соответству- ющего фактического аргумента следует использовать адрес переменной res, а в качестве соответствующего формального — указатель на об- ласть памяти, где расположен фактический аргумент.
Аналогично следует поступить и для изменения адреса вершины сте- ка: подать в качестве фактического аргумента не адрес вершины стека
(он будет передан по значению), а адрес ячейки, в которой адрес верши- ны стека записан. Адрес этой ячейки тоже будет передан по значению и его изменить функция не сможет, но сможет изменить содержимое ячейки, в которой записан адрес вершины стека. Неважно, значение ка- кого типа хранится в области памяти, на которую указывает указатель
— важно, что для изменения её содержимого в качестве фактического аргумента следует подать её адрес:
116

Имя
Тип
Тип
Вызов переменной факти- формаль- ческого ного аргумента аргумента double res;
&res double*
subfun(x,&res);
link top;
& top link*
addelem(&top,int);
// Содержимое файла 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);
printf("Первый список:\n"); prtlist(top);
addelem(&top,555); printf("Второй
:\n"); prtlist(top);
addelem(&top,444); printf("Третий
:\n"); prtlist(top);
addelem(&top,333); printf("Четвёртый
:\n"); prtlist(top);
return 0;
}
117


// Содержимое файла worklist.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;
// теперь в текущем указателе
// адрес следующего звена.
}
}
void addelem(link* top, int num) // Функция ADDELEM добавляет к вершине
{
// стека новое звено, помещая в его
// поле INFO число NUM.
// Важно осознать, что добавление нового звена должно привести к
// изменению содержимого указателя на вершину стека.
// Если в качестве первого аргумента ADDELEM использовать просто
// указатель на вершину стека, то (в силу того, что передача аргументов
// в СИ происходит по значению) изменение содержимого этого указателя
// (как формального аргумента) не приведёт к изменению фактического.
// Для изменения фактического аргумента-указателя необходимо в качестве
// типа первого аргумента ADDELEM использовать тип
// <указатель_на_указатель>. Тогда через операцию разыменования первого
// указателя можем осуществить изменение второго, т.е. определить тем
// самым адрес новой вершины стека.
link cur;
cur=(link) malloc(sizeof(struct candidat));
cur->info=num;
cur->next=(*top);
(*top)=cur;
}
Содержимое файла input
23 45 67 98 100 0 118

Содержимое файла 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
Второй
:
1-е число в списке равно 555 2-е число в списке равно 100 3-е число в списке равно 98 4-е число в списке равно 67 5-е число в списке равно 45 6-е число в списке равно 23
Третий
:
1-е число в списке равно 444 2-е число в списке равно 555 3-е число в списке равно 100 4-е число в списке равно 98 5-е число в списке равно 67 6-е число в списке равно 45 7-е число в списке равно 23
Четвёртый
:
1-е число в списке равно 333 2-е число в списке равно 444 3-е число в списке равно 555 4-е число в списке равно 100 5-е число в списке равно 98 6-е число в списке равно 67 7-е число в списке равно 45 8-е число в списке равно 23 119

3.3.5
Пример удаление звена из вершины стека
Решать вопрос о изменении адреса вершины стека приходится и при удалении головного звена.
// Содержимое файла testlist6.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");
printf("АДРЕС ВЕРШИНЫ СТЕКА: %p\n",top); prtlist(top);
printf("ДОБАВЛЕНИЕ звеньев:\n");
addelem(&top,555); printf("АДРЕС ВЕРШИНЫ СТЕКА: %p\n",top);
printf("Содержимое стека
:\n"); prtlist(top);
printf("Второй список
:\n");
addelem(&top,444); printf("АДРЕС ВЕРШИНЫ СТЕКА: %p\n",top);
printf("Содержимое стека
:\n"); prtlist(top);
printf("Третий список
:\n");
addelem(&top,333); printf("АДРЕС ВЕРШИНЫ СТЕКА: %p\n",top);
printf("Содержимое стека:\n"); prtlist(top);
printf("УДАЛЕНИЕ звеньев:\n");
do { delelem(&top); printf("АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: %p\n",top);
printf("Содержимое стека:\n"); prtlist(top); }
while (top!=NULL);
return 0;
}
120


// Содержимое файла worklist.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;
// теперь в текущем указателе
// адрес следующего звена.
}
}
void addelem(link* top, int num) // Функция ADDELEM добавляет к вершине
{
// стека новое звено, помещая в его
// INFO-поле целое число NUM, и link cur;
// устанавливая НОВЫЙ адрес вершины.
cur=(link) malloc(sizeof(struct candidat));
cur->info=num;
cur->next=(*top);
(*top)=cur;
}
void delelem(link* top)
{ link cur;
if ((*top)==NULL) printf("Стек пуст.\n");
else { cur=(*top);
(*top)=cur->next; free(cur);}
}
Содержимое файла input
23 45 67 98 100 0
121

Содержимое файла 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
Первый список
:
АДРЕС ВЕРШИНЫ СТЕКА: 0x8ae090 1-е число в списке равно 100 2-е число в списке равно 98 3-е число в списке равно 67 4-е число в списке равно 45 5-е число в списке равно 23
ДОБАВЛЕНИЕ звеньев:
АДРЕС ВЕРШИНЫ СТЕКА: 0x8ae0b0
Содержимое стека
:
1-е число в списке равно 555 2-е число в списке равно 100 3-е число в списке равно 98 4-е число в списке равно 67 5-е число в списке равно 45 6-е число в списке равно 23
Второй список
:
АДРЕС ВЕРШИНЫ СТЕКА: 0x8ae0d0
Содержимое стека
:
1-е число в списке равно 444 2-е число в списке равно 555 3-е число в списке равно 100 4-е число в списке равно 98 5-е число в списке равно 67 6-е число в списке равно 45 7-е число в списке равно 23
Третий список
:
АДРЕС ВЕРШИНЫ СТЕКА: 0x8ae0f0
Содержимое стека:
1-е число в списке равно 333 2-е число в списке равно 444 3-е число в списке равно 555 4-е число в списке равно 100 5-е число в списке равно 98 122

6-е число в списке равно 67 7-е число в списке равно 45 8-е число в списке равно 23
УДАЛЕНИЕ звеньев:
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae0d0
Содержимое стека:
1-е число в списке равно 444 2-е число в списке равно 555 3-е число в списке равно 100 4-е число в списке равно 98 5-е число в списке равно 67 6-е число в списке равно 45 7-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae0b0
Содержимое стека:
1-е число в списке равно 555 2-е число в списке равно 100 3-е число в списке равно 98 4-е число в списке равно 67 5-е число в списке равно 45 6-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae090
Содержимое стека:
1-е число в списке равно 100 2-е число в списке равно 98 3-е число в списке равно 67 4-е число в списке равно 45 5-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae070
Содержимое стека:
1-е число в списке равно 98 2-е число в списке равно 67 3-е число в списке равно 45 4-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae050
Содержимое стека:
1-е число в списке равно 67 2-е число в списке равно 45 3-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae030
Содержимое стека:
1-е число в списке равно 45 2-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: 0x8ae010
Содержимое стека:
1-е число в списке равно 23
АДРЕС НОВОЙ ВЕРШИНЫ СТЕКА: (nil)
Содержимое стека:
123


3.3.6
Задача о считалке
При её решении полезно сочетать и тип данных struct, и указатель на значение типа struct)
n детей собираются играть в прятки и выясняют кому водить, произ- нося считалку из m слов. Для простоты каждому участнику сопоставим номер. Требуется написать программу, которая моделирует процесс вы- яснения номера водящего. Есть несколько подходов к решению:
1. Найти формулу, которая по m и n находит номер водящего (нас она пока не интересует). Нам интересно как можно ближе к реальности смоделировать процесс выявления водящего.
2. Описать массив из n элементов булева типа. Инициализировать их значением true (любой из играющих может оказаться водящим).
Затем, перебрав m слов считалки, поместить в m-ый элемент зна- чение false, что будет признаком выбывания из списка кандидатов на водящего. При подобном моделировании всё равно придётся про- верять: “А не выбыл ли уже играющий из списка кандидатов?”.
В реальности этого нет. Говорящий считалку просто ориентруется только на тех, кто ещё остался в круге из кандидатов на водящего.
3. Смоделировать процесс, используя указатели на структуру данных struct candidat
{ int number;
// поле с номером кандидата на вождение;
struct candidat* next;
// поле с указателем на соседнего кандидата.
};
одно звено которой состоит из двух полей:
(a) первое типа int с номером участника;
(b) второе типа указателя на соседа (ещё не выбывшего) из списка
Внимание! При описании поля next типа struct candidat указа- но, что значение, помещаемое в next, есть указатель на тип struct candidat. Другим словами, описание структуры struct candidat
— рекурсивное, т.е. при описании типа struct candidat используем ссылку на сам описываемый тип. Подобная рекурсивность допуска- ется только при описании указателей на него.
124

#include
#include
typedef struct candidat* link; // Тип link - указатель на данное типа candidat struct candidat
// Тип candidat - структура из двух полей:
{ int number;
//
number - для данного целого типа и link next;
//
next - для хранения указателя на тип
};
//
candidat.
int main(void)
{
int j, k, m, n;
link first, tek, nov;
n=5; m=3;
// число кандидатов и число слов в считалке
//
СОЗДАНИЕ КОЛЬЦЕВОГО СПИСКА ИГРАЮЩИХ.
first=(link) malloc(sizeof(struct candidat)); // выделение памяти под
// значение типа candidat
// её начальный адрес хранится в указателе first,
first->number=1;
// а в её поле number запомнен номер первого игрока.
tek=first;
// Сейчас tek смотрит туда же куда и first.
for (k=2;k<=n;k++)
// Фрахтуем ячейку под
{
nov=(link) malloc(sizeof(struct candidat)); // k-го участника.
nov->number=k;
// Запоминаем его номер.
tek->next=nov;
// Запоминаем в поле next (предыдущего игрока)
// указатель на зафрахтованную ячейку k-го.
tek=nov;
// теперь tek смотрит на k-го игрока.
}
tek->next=first;
// После последнего игрока следует первый.
//
КОЛЬЦЕВОЙ СПИСОК ИГРАЮЩИХ СОЗДАН.
// ПОИСК НОМЕРА ВОДЯЩЕГО:
for (k=n;k>=2; k--)
// Пока в списке не останется один человек
{
// (водящий) повторяем:
for (j=1;j<=m-1;j++) // проговор m-1 слова считалки, получая в tek
{
// указатель на игрока, который должен выбыть.
tek=tek->next;
//
}
// Выбытие достигается присваиванием полю
// tek->next соседа предшествующего выбывающему
// указателя, хранящегося в поле next tek->next=tek->next->next; // выбывающего. При этом звено
}
// выбывающего не отдаётся операционной
//
системе, хотя и становится недоступным данной программе (ведь его
// адрес, хранящийся в next-поле предыдущего игрока, уже затёрт).
printf(" Водит игрок под номером %d\n",
// Вывод номера tek->number);
// водящего игрока return 0;
}
125