Наследование, полиморфизм, инкапсуляция, исключения 4 страница
параметров):
copy (istream_iterator<int>(cin), istream_iterator<int>(),
back_inserter(v)); // считать элементы в вектор v
unique_copy (istream_iterator<int>(cin), istream_iterator<int>(),
back_inserter(v)); // считать уникальные элементы в вектор v
ЧАСТЬ 2
ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ
Создать контейнер объектов классов, реализованных в лабораторной работе 5 (студентов, общежитий, преподавателей и т.д.) Контейнер реализовать как вектор. Произвести определенные действия с этим контейнером. Не использовать циклы, использовать только подходящие стандартные алгоритмы. Преобразовать классы для решения данной задачи.
ВАРИАНТ 1
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по фамилии студента (это должна быть сортировка по умолчанию), а также сортировку по его среднему баллу за последнюю сессию.
3. вынести в отдельный вектор всех студентов, начиная со студента со средним баллом больше, чем 4
4. сохранить все средние баллы студентов из вектора шага 3 в отдельном векторе
5. узнать, сколько в векторе шага 4 значений среднего балла выше заданного
6. найти средний балл для всех студентов с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 2
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по улице и номеру дома общежития (это должна быть сортировка по умолчанию), а также сортировку по его средней заселенности.
3. вынести в отдельный вектор все общежития, начиная с общежития с числом свободных комнат больше, чем 4
4. сохранить все номера домов из вектора шага 3 в отдельном векторе
5. узнать, сколько в векторе шага 4 номеров домов больше заданного
6. найти среднюю заселенность всех общежитий с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 3
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по полному названию факультета (это должна быть сортировка по умолчанию), а также сортировку по суммарному числу студентов.
3. вынести в отдельный вектор все факультеты, начиная с факультета со средним баллом по всем группам выше, чем 4
4. сохранить все средние баллы из вектора шага 3 в отдельном векторе
5. найти в векторе шага 4 число факультетов со средним баллом выше заданного
6. найти средний балл для всех факультетов с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 4
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по фамилии преподавателя (это должна быть сортировка по умолчанию), а также сортировку по его суммарному количеству часов.
3. вынести в отдельный вектор всех преподавателей, начиная с преподавателя, выставившего средний балл больше, чем 4
4. сохранить все количества часов преподавателей из вектора шага 3 в новом векторе.
5. найти число преподавателей с количеством часов больше заданного в векторе, полученном на шаге 4.
6. найти средний балл по всем предметам с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 5
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по фамилии автора (это должна быть сортировка по умолчанию), а также сортировку по числу выдач.
3. вынести в отдельный вектор все пособия, начиная с пособия с числом выдач больше 4
4. сохранить все имена студентов из вектора шага 3 в новом векторе.
5. найти число студентов с именами идущими по алфавиту за заданным в векторе, полученном на шаге 4.
6. найти среднее число выдач по всем пособиям с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 6
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по названию книги (это должна быть сортировка по умолчанию), а также сортировку по числу авторов.
3. вынести в отдельный вектор все книги, начиная с книги, первый автор которой написал более 4 книг
4. сохранить все имена авторов из вектора шага 3 в новом векторе.
5. найти число авторов с именами идущими по алфавиту за заданным в векторе, полученном на шаге 4.
6. найти среднее число авторов для всех книг с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 7
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по названию группы (это должна быть сортировка по умолчанию), а также сортировку по среднему баллу для всех студентов.
3. вынести в отдельный вектор все группы, начиная с группы, число студентов которой больше 4
4. сохранить все имена студентов из вектора шага 3 в новом векторе.
5. найти число студентов с именами идущими по алфавиту за заданным в векторе, полученном на шаге 4.
6. найти средний балл для всех групп с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 8
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по названию университета (это должна быть сортировка по умолчанию), а также сортировку по общему числу студентов.
3. вынести в отдельный вектор все университеты, начиная с университета с числом факультетов, больше чем 4
4. сохранить все названия факультетов из вектора шага 3 в новом векторе
5. найти число факультетов с именами идущими по алфавиту за заданным в векторе, полученном на шаге 4.
6. найти среднее число студентов для всех университетов с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 9
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по названию курса (это должна быть сортировка по умолчанию), а также сортировку по общему числу студентов, посетивших курс
3. вынести в отдельный вектор все курсы, начиная с курса, для которого отмечена 100% посещаемость
4. сохранить все названия групп из вектора шага 3 в новом векторе.
5. найти число групп с именами идущими по алфавиту за заданным в векторе, полученном на шаге 4.
6. найти среднее число студентов для всех курсов с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
ВАРИАНТ 10
Необходимые задачи таковы:
1. обеспечить для контейнера возможность чтения из файла и записи в файл с использованием итераторов потоков.
2. обеспечить для контейнера сортировку по названию корпуса (это должна быть сортировка по умолчанию), а также сортировку по числу свободных аудиторий.
3. вынести в отдельный вектор все корпуса, начиная с корпуса, в котором более 4 аудиторий
4. сохранить все номера аудиторий из вектора шага 3 в новом векторе.
5. найти число аудиторий с номерами больше заданного в векторе, полученном на шаге 4.
6. найти среднее число аудиторий для всех корпусов с использованием accumulate()
7. проверить, входит ли вектор, полученный на шаге 3, в исходный вектор как подпоследовательность.
5 Работа с элементами стандартной библиотеки С++
ЧАСТЬ 1. ПРЕДВАРИТЕЛЬНЫЕ СВЕДЕНИЯ
Необходимые сведения о доступе к элементам объектов классов.
В данной работе может возникнуть необходимость получить доступ к элементу объекта класса, на который указывает указатель, т.е. сделать что-то типа
(*p).elem
где p - указатель на класс, содержащий элемент elem.
Для этого есть более простая запись:
указатель->элемент
т.е. в нашем случае можно написать:
p->elem
1. Основные компоненты стандартной библиотеки С++.
Стандартная библиотека C++ состоит из компонент трех основных видов:
1. контейнеров (классов, которые могут содержать данные различных типов, например, vector)
2. итераторов (классов, необходимых для перемещения по контейнерам и получения доступа к текущим элементам в них, подобно тому, как мы перемещаемся по массиву с помощью указателей)
3. алгоритмов, которые производят действия над контейнерами или их элементами, получив к ним доступ через итераторы, например, sort() или reverse().
2. Итераторы.
Важнейшей концепцией стандартной библиотеки С++ является концепция
итераторов.
Итераторы похожи на указатели, с помощью которых производится перемещение по массиву. Для них допустимы в первую очередь операции ++ (перемещается на следующий элемент), сравнения ==, != и операция * (разыменование), которое дает элемент контейнера. Различают ряд классов итераторов:
- входные и выходные (input, output) итераторы (допустимы только указанные операторы, input не может писать через разыменование, output не может читать),
- однонаправленные (могут записывать и считывать данные)
- двунаправленные (добавляется еще операция --),
- итераторы с произвольным доступом (+=, -=, +, -, сравнения на
больше-меньше).
Разные контейнеры определяют итераторы разного класса, то, какой класс итераторов может предоставить контейнер, является свойством этого контейнера. Например, vector или string предоставляют итераторы с произвольным доступом, а list (список) - только двунаправленные.
Тип контейнера (vector<int> или vector<string>) предоставляет итераторы, описывая их типы внутри себя как тип-контейнера::тип-итератора где тип-итератора может быть iterator, reverse_iterator, const_iterator и const_reverse_iterator. Далее можно будет определять переменные такого типа и перемещаться по экземплярам контейнера.
Возьмем контейнер vector<int>. Вот какие итераторы он может предоставлять:
- vector<int>::iterator - итератор, реализующий прямой проход по контейнеру (именно такие итераторы возвращаются begin() и end())
- vector<int>::reverse_iterator - итератор, реализующий обратный проход по контейнеру (такие итераторы возвращаются rbegin() и rend())
- vector<int>::const_iterator - итератор, через который нельзя менять элементы контейнера (есть и const_reverse_iterator, но в Visual C++ он не работает).
Непосредственно как объект итератор внутри контейнера не содержится, там описан только его тип. Переменную-итератор нужно определять отдельно:
vector<int>::iterator i;
Раз определенный, такой итератор может перемещаться по разным экземплярам вектора целых.
Вот как работать с итераторами для вектора:
vector<int>v; // определяем вектор
// заполняем вектор
// определяем итератор для перемещения по этому вектору
vector<int>::iterator i;
// v.begin() возвращает итератор, указывающий на начало вектора
// v.end() - указывающий за конец вектора
// i++ продвигает итератор на следующий элемент
for (i = v.begin(); i != v.end(); i++) {
cout << *i; // *i возвращает элемент вектора, на который
// ссылается итератор
*i = 200; // изменяем значение через итератор
}
iterator и reverse_iterator являются итераторами одного класса (для вектора - итераторами с произвольным доступом), они только по-разному интерпретируют последовательность. Вот как работает reverse_iterator:
vector<int>::reverse_iterator ri;
// v.rbegin() возвращает итератор для обратного прохода,
// указывающий на конец вектора
// v.rend() - указывает на позицию перед началом вектора
// это - конец последовательности для обратного итератора
// ri++ продвигает итератор на предыдующий элемент
for (ri = v.rbegin(); ri != v.rend(); ri++)
cout << *i; // проход от конца в начало
Если в векторе были данные 1 2 3, то программа выдаст 3 2 1
const_iterator работает аналогично обычному, за исключением того, что через него нельзя менять элементы массива.
vector<int>::const_iterator ci = v.begin();
*ci = 200; // ошибка
Как мы уже видели, стандартные алгоритмы (такие, как sort() или reverse()) работают с последовательностями, заданными двумя итераторами - указывающим на первый элемент последовательности и за последний.
sort(v.begin(), v.end());
iterator - тип, который описан внутри класса vector. В Visual C++ для того, чтобы можно было описывать переменные такого типа, ошибочно необходимо, помимо
using std::vector;
подключать конкретную специализацию параметризованного класса так:
using std::vector<int>;
Это не требуется по стандарту С++, более того, это даже запрещено. Учитывая эти сложности, можно до улучшения ситуации использовать просто
using namespace std;
3. Ассоциативные массивы.
В стандартной библиотеке С++, помимо векторов, есть ряд других классов контейнеров. Одним из наиболее важных является ассоциативный массив - массив, который индексируется не идущими последовательно целыми числами (как обычный массив), а произвольными данными (чаще всего строками). Такой массив состоит из набора пар "ключ-значение", и если ключи в массиве не повторяются, то любое значение всегда можно получить по соответствующему ключу как
имя-массива[ключ].
Для реализации такого массива в библиотеке задано два класса: map и multimap. Они отличаются тем, что в контейнере map ключи должны быть уникальными, а в контейнере multimap - нет (ключи могут повторяться).
Для работы с map и multimap необходимо подключать заголовочный файл
<map> и пространство имен std.
Сначала рассмотрим класс map.
3.1. Класс map
Класс map, как и vector, является параметризованным, при его задании в угловых скобках нужно задать два параметра - тип ключа и тип значения:
map<string,int> m; // индексируется строками
map<int,int> mi; // индексируется произвольно расположенными целыми
Дальнейшая работа достаточно проста. Для задания элемента массива достаточно простого присваивания:
m["key1"] = 100;
После этого в массиве появляется пара значений "key1" => 100
mi[-2] = 100; // пара -2 => 100
Доступ к элементу массива производится аналогично:
int i = m["key1"];
Следует отметить, что если ключ не найден, то в массив помещается пара "ключ" - "значение по умолчанию для типа значения". Для целочисленных типов таким значением считается 0.
Таким образом, если в m есть пара с ключом "key1", то i будет присвоено значение, соответствующее этому ключу, а если нет - в массив будет добавлена пара "key1" => 0 и i присвоится 0.
Это далеко не всегда удобно, как этого избежать - мы увидим чуть позже.
А пока вкратце остановимся на реализации map. Для примера возьмем map<string,int>. Такой массив не содержит отдельно ключи, а отдельно значения, он содержит экземпляры класса pair<string,int>, определенного в стандартной библиотеке. Этот класс содержит два элемента - first и second. Тип first будет string, а second - int, first будет соответствовать ключу, а second - значению. Поместить новую пару элементов в массив можно еще и так:
m.insert(make_pair(string("key1"),100));
где make_pair() - стандартная функция, создающая пару pair из двух
элементов, которые переданы в нее как аргументы.
Рассмотрим, как обойти обязательную вставку элемента при доступе по индексу. Если мы хотим просто проверить наличие ключа в массиве без обязательного присваивания, то, если нам важен только сам факт наличия элемента с данным ключом, нужно воспользоваться методом count(), который возвращает число вхождений пары элементов с данным ключом (для
map count() вернет 0 или 1, для multimap - число вхождений может быть
больше 1).
if (m.count("key1") != 0) {
// элемент с данным ключом есть в массиве
}
Если же нам нужно получить значение по ключу, но мы хотим избежать вставки, нам нужно воспользоваться функцией-элементом find(), который возвращает итератор, который указывает на найденный элемент (объект типа pair), если он есть или который равен итератору, который
возвращает end(), если элемент с заданным ключом не найден:
map<string,int>::iterator mi;
if ((mi = m.find("key1")) != m.end()) {
// элемент с данным ключом есть в массиве
// ключ этого элемента даст mi->first, значение - mi->second
cout << "значение=" << mi->second << endl;
}
С другой стороны, автоматическая вставка при индексации может быть и весьма удобной, например, этот эффект дает возможность написать так:
m["key1"]++;
не заботясь о том, есть ли в данный момент в массиве элемент с ключом key1 или нет:
- если он есть - соответствующее ему значение увеличится на 1 (это может быть, например, число вхождений данного слова),
- если его нет - попытка получить значение с тем, чтобы применить к нему инкремент приведет к вставке нового элемента с данным ключом и значением 0, так что инкремент будет тут же применен к только что вставленному элементу и значение станет 1.
Вот так производится обход ассоциативного массива:
map<string,int>::iterator wi;
for (wi = m.begin(); wi != m.end(); wi++)
cout << "key=" << wi->first << " value=" << wi->second << endl;
Итераторы для map являются двунаправленными, для них, например, не определены операции - или +=.
3.2. Класс multimap.
multimap отличается от map тем, что значение ключа в нем может повторяться. Для него не задана операция индексации [] и для получения всех элементов с данным ключом необходимо применять функции-элементы equal_range(), lower_bound() и upper_bound() equal_range() принимает значение ключа и возвращает pair из двух итераторов, первый из которых указывает на первый элемент с заданным ключом (то же возвращает lower_bound()), а второй - после последнего элемента (то же возвращает upper_bound()). Вот так можно вывести все значения для данного ключа:
multimap<string,int> mm;
multimap<string,int>::iterator mi;
string key = "key1";
cout << "key=" << key;
for (mi = mm.lower_bound(key); mi != mm.upper_bound(key); mi++)
cout << "value=" << mi->second << endl;
Помещать элементы в multimap можно только по insert() (которая реализована точно так же, как и для map). Функция-элемент count() может вернуть значение > 1.
3.3. Случай множества значений, соответствующих одному ключу.
Часто встречается ситуация, когда одному ключу соответствует множество значений, например, когда нужно сохранить набор синонимов для данного слова. Есть два подхода к этой ситуации:
1) использовать map, для которого типом значения является вектор
map<string, vector<string> > и накапливать нужные значения в этом векторе
2) использовать multimap - массив с повторяющимися ключами multimap<string, string> накапливать нужные значения как разные значения для одинаковых ключей и пользоваться для доступа к ним функциями-элементами типа equal_range()
4. Множества (set и multiset)
Множества до определенной степени похожи на ассоциативные массивы, но они содержат только ключи и не содержат значений. Их можно рассматривать как автоматически сортируемый набор элементов, в котором элементы не индексируются, но всегда можно проверить, есть ли элемент в наборе или его там нет.
Есть два вида множеств: те, которые могут содержать только уникальные элементы (тип set) и те, в которых элементы могут повторяться (тип multiset). Мы будем рассматривать только тип set.
Для работы с множествами нужно подключать заголовочный файл <set>. При определении переменных типа множество нужно указывать только один тип - тип элемента множества:
set<string> words;
Добавление элементов производится с помощью функции-элемента insert():
words.insert("word1");
words.insert("word2");
Определение числа вхождений элемента и поиск элемента аналогичны map:
if (words.count("word1") != 0) {
// элемент во множестве есть
}
set<string>::iterator si;
if ((si = words.find("word1")) != words.end()) {
// данный элемент есть во множестве
cout << "элемент=" << *si << endl;
}
// вывод множества
for (si = words.begin(); si != words.end(); si++)
cout << *si << " ";
ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ
ВАРИАНТ 1
Разработать программу, которая считывает текст и подсчитывает число повторений каждого слова, за исключением слов, указанных в списке. Слова в тексте разделяются пробелами. Подсчет числа повторений слова реализовывать с помощью ассоциативного массива, список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
ВАРИАНТ 2
Разработать программу, которая считывает текст и сохраняет номера всех строк, в которых встретилось данное слово, за исключением слов, указанных в списке. Слова в тексте разделяются пробелами. Накопление номеров строк реализовывать с помощью ассоциативного массива, список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Если слово встретилось в одной строке дважды - выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл.
Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
ВАРИАНТ 3
Разработать программу, которая считывает текст и сохраняет номера всех позиций в строках, в которых встретилось данное слово, за исключением слов, указанных в списке. Слова в тексте разделяются пробелами. Накопление позиций в строках реализовывать с помощью ассоциативного массива (map или multimap), список слов для исключения - с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Если слово встретилось в одной и той же позиции дважды - выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
----- вход
уж небо осенью дышало
уж реже солнышко блистало
короче становился день
лесов таинственная сень
-----
список исключения (день осенью лесов короче Пушкин)
----- выход
осенью - из списка
короче - из списка
день - из списка
лесов - из списка
уж - дважды в позиции 1
уж -> 1 1
небо -> 4
осенью -> 9
и т.д.
ВАРИАНТ 4
Разработать программу, которая считывает текст и вычисляет среднюю частоту вхождения каждого слова, за исключением слов, указанных в списке (число вхождений / общее число слов кроме слов из списка). Слова в тексте разделяются пробелами. Подсчет частоты вхождения реализовывать с помощью ассоциативного массива, список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
ВАРИАНТ 5
Разработать программу, которая считывает текст и вычисляет отношение длины каждого слова к средней длине слов текста, за исключением слов, указанных в списке (длина / средняя длина слов, не учитывая слова из списка). Слова в тексте разделяются пробелами. Подсчет относительной длины реализовывать с помощью ассоциативного массива, список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявлени функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
ВАРИАНТ 6
Разработать программу, которая считывает текст и вычисляет среднюю частоту встречаемости букв в данном тексте, за исключением слов, указанных в списке (число вхождений буквы / длину текста без длины слов из списка). Слова в тексте разделяются пробелами (при подсчете частоты пробелы не учитывать). Подсчет частоты реализовывать с помощью ассоциативного массива, список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка, выводить сообщение. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
ВАРИАНТ 7
Разработать программу, которая считывает текст и подсчитывает число слов на данную букву в этом тексте, за исключением слов, указанных в списке. Слова в тексте разделяются пробелами. Подсчет числа слов реализовывать с помощью ассоциативного массива (map или multimap), список слов для исключения реализовывать с помощью множества. Буквы должны идти в списке в порядке расположения в тексте слов, которые с них начинаются. Каждый раз, когда встретилось слово из списка исключений, выводить сообщение. Вместе с числом слов выводить и список этих слов. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл. Исходные данные считать из файла и вывести в файл.
----- вход
уж небо осенью дышало
уж реже солнышко блистало
короче становился день
лесов таинственная сень
-----
список исключения (день осенью лесов короче Пушкин)
----- выход
осенью - из списка
короче - из списка
день - из списка
лесов - из списка
у -> уж уж (2)
н -> небо (1)
д -> дышало (1)
р -> реже (1)
с -> солнышко становился сень (3)
б -> блистало (1)
т -> таинственная (1)
ВАРИАНТ 8
Разработать программу, которая считывает текст, находит число всех слов, начинающихся на ту же букву, с которой начинается данное слово. Слова в тексте разделяются пробелами. Слова из списка исключения не учитывать. Если ни одного слова на ту же букву нет - данные по слову не выводить. Подсчет числа слов реализовывать с помощью ассоциативного массива (map или multimap), список слов для исключения реализовывать с помощью множества. Каждый раз, когда встретилось слово из списка исключений, выводить сообщение. Вместе с числом слов выводить и список этих слов. Создать набор функций для вывода результатов и вынести их в отдельный файл. Объявления функций вынести в заголовочный файл.