Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

g30zAZLYUG

.pdf
Скачиваний:
3
Добавлен:
15.04.2023
Размер:
1.89 Mб
Скачать

setlocale(LC_ALL, "RUS");

cout << "Нажмите клавишу ENTER, чтобы закрыть программу\n"; getchar();

return 0;

6.2. В конец раздела подключения библиотек добавьте ещё один include:

#include <vector>

6.3. В начале процедуры main объявите целочисленный вектор с именем proba:

vector <int> proba;

Обратите внимание, что числовой ограничитель на количество элементов (как в случае со статическими массивами) задавать не надо.

6.4. Добавим в вектор proba три целых числа: 20, 30, 40. Это можно сделать с помощью функции push_back, которая вставляет новый элемент в конец вектора:

proba.push_back(20); proba.push_back(30); proba.push_back(40);

6.5. С помощью функции size убедимся, что вектор proba содержит три элемента. Для этого добавьте оператор

cout << "Размер массива" << proba.size() << "\n" ;

и запустите программу на выполнение. Как видите, добавленные нами элементы учтены.

6.6. Добавьте ещё пару элементов

proba.push_back(50); proba.push_back(60);

и, запустив программу на выполнение, посмотрите результат. Всё работает правильно.

6.7. Ещё один маленький тест – вычислим сумму элементов вектора proba.

int i, sum; sum=0;

for (i=0; i<proba.size(); i++) sum=sum+proba[i];

cout << "Сумма элементов массива " << sum << "\n" ;

Запустите программу на выполнение. Легко проверить, что, в самом деле, 20 + 30 + 40 + 50 + 60 = 200.

21

С другими функциями контейнера vector Вы можете ознакомиться, обратившись к соответствующей справочной литературе.

п. 7. Динамический массив записей

Решим ту же задачу, что и в пункте 5, но при этом будем более эффективно и грамотно использовать компьютерную память.

7.1. Закомментируйте или удалите содержимое процедуры main, оставив следующий фрагмент:

setlocale(LC_ALL, "RUS");

cout << "Нажмите клавишу ENTER, чтобы закрыть программу\n"; getchar();

return 0;

7.2. Сразу после строки

#define MAX_NODES 100 // Максимально возможное число записей (которую, кстати говоря, можно закомментировать или удалить, так как константа нам больше не понадобится), в области объявления глобальных типов и переменных, используя оператор typedef, создадим новый тип данных MyRecord:

// Определяем новый тип данных с именем MyRecord typedef struct MY_NODE

{

char

surname[24]; // Фамилия

char

name[18];

// Имя

int

ball;

// Оценка

}

MyRecord;

Другими словами, теперь Вы можете объявить не только целочисленную переменную (тип int), булеву переменную (тип bool), действительную переменную (типы float, double) и т.д., но и переменную типа

MyRecord.

7.3. Строкой ниже объявим динамический массив записей типа MyRecord. Назовём его, как и в пункте 4, myArray:

vector <MyRecord> myArray;

7.4. В начале процедуры main объявите переменную node:

MyRecord node;

Переменная node, имеющая тип MyRecord, представляет собой одну запись с тремя полями, описанными в структуре MY_NODE. Она нам понадобится на этапе чтения данных из файла.

22

7.5. В процедуре main читаем записи из файла:

ifstream f; f.open("StudPmi.txt"); int i;

char lin[81];

f.getline(lin,sizeof(lin)); // пропускаем 1-ю строку, содержащую заголовок while (!f.eof()) // пока не достигнут конец файла

{ // читаем строку из файла и формируем очередную запись f >> node.surname >> node.name >> node.ball; myArray.push_back(node);

};

f.close();

По сравнению с чтением данных в статический массив, изменений немного. В цикле мы считываем очередную строку файла, помещаем её в запись node, после чего следующим оператором сформированную запись добавляем в динамический массив myArray. Счётчик count здесь не понадобился, так как размер динамического массива можно определить с помощью функции size().

7.6. Чтобы убедиться, что информация из файла считана и динамический массив сформирован, выведем его на экран:

if (myArray.size()>0)

{for (i=0; i<myArray.size(); i++)

{cout << myArray[i].surname << "\t" << myArray[i].name << "\t" << myArray[i].ball << "\n";

}

};

ЗАДАНИЕ № 2. Динамический массив записей

Разработайте консольное приложение, в котором решается задача, сформулированная в Вашем варианте (см. задание № 1). Вместо статического массива на этот раз используйте массив динамический.

Работающую версию программы, а также программный код, предъявите преподавателю.

Контрольные вопросы

1.Что означает применительно к массиву понятие «структура прямого доступа»?

2.Какие операции определены над массивом?

3.Чем отличаются статический и динамический массивы?

23

4.Каким образом организовать вывод на консоль русскоязычной информации?

5.Для чего предназначены операторы cin и cout?

6.С помощью какой функции можно сравнивать строковые переменные?

7.Какая функция позволяет копировать строковые переменные?

8.Как осуществить ввод информации из текстового файла?

9.Каким образом осуществить вывод информации в текстовый файл?

10.Как в С++ организовать статический массив записей?

11.Какой оператор в С++ позволяет объявить новый тип данных?

12.Как организовать динамический массив записей?

24

Лабораторная работа № 2. Линейные списки (начало) – стек, очередь

Линейные списки являются структурой данных последовательного доступа, т.е. для каждого элемента может быть осуществлён переход только к соседнему для него элементу.

К линейным спискам, в частности, относятся: стек, очередь, список с двумя связями.

Рис. 2.1. Цепное представление стека

Стек – это структура данных, в которой включение и исключение производится только с одного конца, называемого вершиной (верхушкой) стека. Когда элемент помещается в стек, то элемент, ранее находившийся на вершине стека, становится временно недоступным (рис. 2.1). Стек называют также списком типа

LIFO (Last In, First Out, что означает: “последним при-

шел – первым ушел”).

Над стеком определены следующие операции:

создать пустой стек;

добавить элемент сверху;

удалить верхний элемент;

получить верхний элемент;

проверка: является ли стек пустым?

Очередь – это структура данных, в которой элементы добавляются всегда с одного края, а удаляются с другого. Край, в котором выполняется включение, называется концом (хвостом) очереди. Край, в котором производится исключение элемента, называется началом (головой) очереди

(рис. 2.2).

Очередь называют списком FIFO (First In, First Out, что означает:

“первым пришел – первым ушел”).

Над очередью определены следующие операции:

создать пустую очередь;

добавить элемент в хвост;

удалить головной элемент;

получить первый (головной) элемент;

последний (хвостовой) элемент.

проверка: является ли очередь пустой?

Рис. 2.2. Цепное представление очереди

На первый взгляд кажется, что очередь ничем не отличается от стека. Это не так. Мысленно поверните рисунок 2.2 на 90 градусов против часо-

25

вой стрелки (так, чтобы последний добавленный элемент оказался сверху – как у стека). После такого поворота стрелки будут направлены вверх, а не вниз, как это было у стека.

Другими словами, если у стека вновь добавленный элемент хранит в своём поле указателя адрес элемента, который был добавлен раньше, то у очереди всё наоборот. Вновь добавленный в очередь элемент имеет нулевое значение в поле указателя, а вот в поле указателя ранее добавленного элемента заносится адрес «новичка».

п. 1. Указатели

Если переменная объявлена статично, то она остаётся в оперативной памяти компьютера до того момента, когда программа завершит свою работу. Такой подход не годится для крупномасштабных проектов. Если бы разработчики компьютерных игр использовали статичные переменные и массивы, то игрокам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset через несколько секунд после начала игры. Проще говоря, если не уничтожать объекты, которые в дальнейшем использоваться не будут, то очень скоро они заполнят весь объём компьютерной памяти.

По этим причинам в большинстве языков программирования, в том числе и C/C++, имеется возможность обращения к переменной не напрямую, а через адрес памяти, по которому хранится значение этой переменной. Такая возможность динамического размещения данных реализуется посредством указателей.

Указатель − это переменная, хранящая в себе адрес ячейки оперативной памяти.

Например, можно обращаться к массиву данных через указатель, который будет содержать адрес начала диапазона ячеек памяти, хранящих этот массив. Когда этот массив станет не нужен для выполнения остальной части программы, память по адресу этого указателя с помощью специального оператора освобождают, и она снова становится доступной для других переменных.

Рассмотрим простой пример работы с указателями.

1.1.Запустите Microsoft Visual Studio C++.

1.2.В открывшемся окне выберите: «Создать проект»

1.3.Затем выберите: «Консольное приложение Win32»

ина этой странице в качестве имени проекта укажите lab_02, расположение – выберите свою папку или рабочий стол.

1.4.В появившемся окне нажмите «Далее» (а не «Готово»),

1.5.Снимите галочку с пункта «Предварительно скомпилированный заголовок» и теперь уже нажмите «Готово».

26

1.6. Замените весь изначально сгенерированный код следующим фрагментом:

#include "stdafx.h" #include <iostream> #include <locale.h> using namespace std;

int main ()

{

setlocale(LC_ALL, "RUS");

cout << "Нажмите клавишу ENTER, чтобы закрыть программу\n"; getchar();

return 0;

}

1.7.Запустите программу на выполнение, чтобы убедиться, что подключение прошло нормально.

1.8.В начале процедуры main объявим указатель на целочисленную переменную. Делается это так:

int *pn;

Признаком того, что объявлена не переменная, а указатель на неё, является звёздочка «*» перед именем указателя. По традиции имя указателя начинается с «p» (от английского pointer − указатель). Не обязательно, но желательно − для того, чтобы программисту не забыть, где у него статическая переменная, а где динамическая.

Обратите внимание, что пробела между звёздочкой и именем переменной быть не должно!

1.9. Выделить память под переменную можно непосредственно перед её использованием. Делается это с помощью оператора new<type>.

После оператора setlocale добавьте оператор:

pn=new int;

Тип int, указанный после служебного слова new, сообщает компьютеру, какой именно объём памяти необходимо выделить под новую переменную. После выполнения этого оператора будет выделена ячейка памяти, необходимая для хранения целочисленной переменной, а адрес этой ячейки сохранится в указателе pn.

1.10. Присвоим целочисленной переменной, расположенной по адресу pn, значение 10, удвоим это значение, выведем результат на экран, по-

27

сле чего с помощью оператора free выделенную под динамическую переменную память освободим для дальнейшего использования.

*pn = 10; *pn = *pn + *pn;

cout << "Результат=" << *pn << endl; free(pn);

Служебное слово endl в операторе cout – это аналог "\n", который, как Вы помните, означает переход на новую строку.

Запустите программу на выполнение. Если Вы всё сделали правильно, то результат, разумеется, равен 20.

1.12. В порядке эксперимента поменяйте местами операторы cout и free, т.е. сначала очистите память, а уж потом распечатайте значение, находящееся в этом участке памяти. Запустите программу на выполнение. Видите, как изменился результат?

Итак, pn – это не сама целочисленная переменная, а указатель на неё, т.е. адрес той ячейки памяти, в которой переменная расположена. Как почтальон, зная адрес получателя, находит этого получателя и доставляет ему письмо, так и компьютер, зная адрес переменной, получает доступ к этой переменной и может, например, считывать или изменять её значение.

п. 2. Реализация стека с помощью указателей

2.1. Закомментируйте или удалите последние добавленные строки, вернувшись к состоянию, описанному в пункте 1.6.

2.2. Каждый элемент стека представляет собой запись, как минимум,

сдвумя полями, а именно:

поле хотя бы одно той смысловой информации, ради которой, собственно, мы и организуем стек;

поле указателя.

Для простоты в этом учебном примере будем считать, что полей

ровно два, причём первое поле (поле смысловой информации) целочисленное.

В области объявления глобальных типов и переменных (после строки “using namespace std;”) вставьте объявление нового типа данных:

typedef struct LIFO

 

{

 

int data;

// поле целочисленных данных

struct LIFO *ptr; // указатель на запись типа NODE } NODE;

28

Здесь используется такое мощное средство языка С++, как рекурсивное описание типа, а именно: второе поле записи LIFO – это указатель на запись LIFO. Другими словами, структура определяется через саму себя.

2.3. Сразу под объявлением нового типа NODE задайте объявление двух указателей на запись этого типа:

NODE *ptop, *p1;

В указателе ptop будет храниться адрес верхушки стека, а указатель p1 понадобится нам в качестве вспомогательной переменной. Мы задаём их в области объявления глобальных переменных для того, чтобы все процедуры имели к ним доступ.

2.4. Чтобы создать пустой, не содержащий ни одного элемента, стек, надо обнулить указатель на его верхушку. Для этого в начало процедуры main добавьте оператор:

ptop=NULL;

// Создание пустого стека

 

 

2.5. Внесём информацию о добавляемом в стек числе. В начале процедуры main объявите целочисленную переменную k:

int k;

2.6. Перед процедурой main вставьте три так называемые заглушки для будущих процедур:

вывода содержимого стека на экран;

добавления элемента в стек;

удаления элемента из стека.

void Print_LIFO()

{

cout << "*****Print_LIFO*****\n";

};

void Add_LIFO(int number)

{

cout << "*****Add_LIFO*****\n";

};

void Del_LIFO()

{

cout << "*****Del_LIFO*****\n";

};

Как видите, пока эти процедуры лишь выдают отладочную печать. Процедура печати Print_LIFO и процедура удаления Del_LIFO не имеют

29

int choice; label_1: cout <<

параметров, у процедуры Add_LIFO имеется один целочисленный параметр number – то число, которое необходимо положить в стек.

2.7. Организуем простейшее пользовательское меню. Для этой цели воспользуемся оператором выбора switch. В процедуру main после оператора setlocale вставьте следующий фрагмент:

"Введите цифру:" << "\n" <<

"Добавить элемент в стек" << "\t" << "0" << "\n" << "Удалить элемент из стека" << "\t" << "1" << "\n" << "Распечатать содержимое стека" << "\t" << "2" << "\n" << "Закончить" << "\t" << "3" << "\n";

cin >> choice; cin.ignore(); switch (choice)

{

case 0: cout << "Введите добавляемое число\n"; cin >> k; cin.ignore();

Add_LIFO(k); break; case 1: Del_LIFO(); break;

case 2: Print_LIFO(); break; case 3: break;

// default: break;

};

if (choice != 3) goto label_1;

Здесь оператор cout выводит пользовательское меню на экран, оператор cin считывает целочисленную переменную choice, а затем, в зависимости от значения переменной choice, в операторе switch производится выбор той процедуры, которую следует вызвать.

Если пользователь ввёл «0», то последует запрос пользователю о добавляемом в стек числе. После ввода этого числа оно будет передано в процедуру добавления элемента в стек.

Если пользователь ввёл «1», то будет вызвана процедура удаления элемента из стека.

Если пользователь ввёл «2», то последует вызов процедуры печати

стека.

Если пользователь ввёл «3», процесс завершается; в противном случае оператор goto отправляет программу на метку label_1 и на экран снова выводится пользовательское меню.

Запустите программу на выполнение. Вводя поочередно 0, 1, 2, 3, убедитесь, что выбор процедуры осуществляется верно:

30

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]