Программирование

Материалы для 1-го курса

2-й семестр: список задач

Задачи даются по темам. Номера задач для каждого студента определяются следующим образом. Пусть N — номер студента по журналу, и пусть в теме даны k задач. Тогда студенту нужно сделать задачу с номером r, где r == N (mod k), т.е. выбрать ту задачу, номер которой при делении на k дает тот же остаток, что и номер студента. Например, если в теме 4 задачи, а номер студента равен 10, то нужно сделать задачу 2; если номер студента 16, то нужно сделать задачу 4.

1. Задачи на тему
"Вычисление стандартных функций с помощью суммирования рядов"

Во всех задачах надо вычислить стандартную функцию с точностью ε=1e-12. Для небольших значений x это делается с помощью суммирования степенного ряда. Для значений x, при которых ряд не сходится или сходится очень медленно, надо воспользоваться какой-либо формулой, позволяющей свести вычисление нужной функции к вычислению ее или других функций при "хороших" значениях x. Например, формула

arctg(x) = 2*arctg(y),
y = (sqrt(1 + x*x) - 1)/x,
позволяет вычислить arctg(1)=π/4, сведя задачу к суммированию степенного ряда для
y = sqrt(2) - 1.

Список задач

  1. Вычислить arctg(x).
  2. Вычислить arcsin(x).
  3. Вычислить arccos(x).
  4. Вычислить ln(x).
  5. Вычислить xα

2. Матрицы и метод Гаусса

В качестве образца дается файл "gauss.cpp", в котором реализована функция приведения матрицы к ступенчатому виду:

int gauss(double *a, int m, int n, double eps = 1e-12);
Функция приводит матрицу элементарными преобразованиями к ступенчатому виду (сохраняя инвариантным ее определитель) и возвращает ранг матрицы, т.е. число ненулевых строк ступенчатой матрицы. Элементы прямоугольной матрицы размера m×n (m строк, n столбцов) хранятся в линейном массиве a по строкам. Элемент матрицы с индексами i, j записывается как
    a[i*n + j]

Также в файле "gauss.cpp" реализованы функции чтения матрицы из текстового входного потока и записи в выходной поток:

bool readMatrix(std::istream &s, int &m, int &n, double* &a);
bool writeMatrix(std::ostream &s, int m, int n, const double *a);
Тестовая программа читает матрицу из файла "input.txt", печатает исходную матрицу на консоль, приводит ее к ступенчатому виду, печатает ранг матрицы и ступенчатую матрицу, а также записывает ее в файл "output.txt".

Пример выполнения программы:

/d/home/vvb>g++ -o gauss gauss.cpp
/d/home/vvb>./gauss
Initial matrix:
3 3
    1.000000     2.000000     3.000000
    4.000000     5.000000     6.000000
    7.000000     8.000000     9.000000
Rank of matrix: 2
Echelonized matrix:
3 3
    7.000000     8.000000     9.000000
    0.000000    -0.857143    -1.714286
    0.000000     0.000000    -0.000000

Задачи по теме "Матрицы и метод Гаусса"

Студент должен сделать одну задачу, номер которой совпадает по модулю 3 с номером студента по журналу.

Список задач

  1. Для квадратной невырожденной матрицы вычислить обратную матрицу. Исходная матрица читается из файла "input.txt", обратную матрицу нужно записать в файл "output.txt". Исходные данные записаны в файле "input.txt" в следующем формате: в первой строке целые числа m и n (m строк, n столбцов, поскольку матрица квадратная, m=n), затем m·n элементов матрицы (вещественных чисел). Числа в файле разделяются пробелами или переводами строк, каждая строка матрицы начинается с новой строки в файле. Результат работы программы надо записать в том же формате в файл "output.txt".
  2. Решить систему линейных уравнений с невырожденной квадратной матрицей. В файле "input.txt" записана расширенная матрица системы, содержащая n строк и n+1 столбец (формат файла как в задаче 1), последний столбец содержит свободные члены уравнений:
         ai1 x1 + ai2 x2 + ... + ain xn = bi,    i = 1, 2, ..., n.
    Ответ (набор чисел x1, x2, ... xn) надо записать в файл "output.txt".
  3. Дана однородная линейная система уравнений (свободные члены уравнений равны нулю) с вырожденной квадратной матрицей A размера n×n:
         A X = 0
    (здесь X — столбец неизвестных x1, x2, ... xn). Матрица A записана в файле "input.txt", формат файла как в задаче 1. Найти какое-нибудь ненулевое решение системы. Ответ (набор чисел x1, x2, ... xn) надо записать в файл "output.txt".

4. Вычисление определенных интегралов

Описание квадратурных формул.

Задачи можно выполнять в одном из двух вариантов:

  1. Вычислить значение интеграла от некоторой функции y=f(x) по отрезку [a, b], используя формулу трапеций.
  2. Вычислить значение интеграла от некоторой функции y=f(x) по отрезку [a, b], используя формулу Симпсона.

5. Геометрия и оконная графика

При решении геометрических задач следует использовать классы R2Vector и R2Point, которые представляют вектор и точку на 2-мерной плоскости R2. Определения этих типов (т.е. классы языка C++) даны в файле "R2Graph.h", который надо переписать в текущую директорию и подсоединять с помощью директивы

#include "R2Graph.h"
Также надо переписать в текущую директорию файл "R2Graph.cpp", содержащий реализацию некоторых методов и глобальных функцию, использующих классы R2Vector и R2Point (например, найти пересечение двух прямых, каждая прямая задается точкой и направляющим вектором). При компиляции проекта надо подключить также этот файл, например:
    g++ -o myTask myTask.cpp R2Graph.cpp

Используйте в качестве образца программу "inCircle.cpp", архив всех необходимых файлов: InCircle.zip.

5.1. Геометрические задачи, не требующие рисования в окне

Во всех задачах точки вводятся с консоли, результат также печатается на консоли. В реализации программы запрещено пользоваться любыми вычислениями с координатами точек и векторов, следует пользоваться только методами классов и функциями, определенными в пакете R2Graph. Используйте в качестве образца программу "inCircle.cpp", архив всех необходимых файлов: InCircle.zip.

  1. Вычислить центр и радиус окружности, описанной вокруг треугольника.
  2. Вычислить точку пересечения высот треугольника.
  3. Вычислить точку Жергона:

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

5.2. Геометрические задачи с графической иллюстрацией

Для оконной графики мы используем библиотеку классов Qt и среду для разработки Qt-проектов "qtcreator". К достоинствам Qt относится то, что исходный код Qt-проекта не зависит от операционной системы (если не использовать специфические функции различных ОС и оставаться только в рамках Qt, то исходный код одинаковый для Unix, MS-Windows, MAC OS-X и т.д.). Кроме того, программировать на Qt очень удобно, в Qt предусмотрено огромное множество функций, классов и других инструментов для создания самых разнообразных программ.

Недостаток Qt в том, что необходимо изучить эту систему разработки в дополнение к изучению языков C и C++. Впрочем, библиотека Qt очень естественна и, наверно, по сравнению с другими средами разработки оконых приложений наиболее проста (насколько вообще может быть проста система разработки графических оконных приложений).

5.2.1. Минимальные сведения об оконных графических системах и оконных приложениях

Любая оконная программа занимается обработкой сообщений об оконных событиях. В центре программы — цикл обработки событий:

    цикл до бесконечности выполнять
    | получить сообщение о событии(вых: event)
    | обработать событие(вход: event)
    конец цикла

Таким образом, классическая оконная программа большую часть времени ничего не делает, ожидая, когда произойдет событие. Когда происходит событие, операционная система добавляет сообщение о нем в очередь; программа обрабатывает события (т.е. совершает действия, логически связанные с событием) в порядке их поступления в очередь. Отметим, что цикл обработки событий может быть спрятан внутри стандартных функций или методов и явно не присутствать в программе (так, в Qt он стартует при вызове метода QApplication::exec()).

В различных операционных системах набор оконных событий может различаться, начиная от небольшого количества событий (около 15) в первых версиях Mac OS и заканчивая чрезвычайно детализированным их набором в Win32. Для программирования несложных оконных приложений на Qt достаточно обрабатывать следующие события (укажем также методы, которые обрабатывают эти события — это методы класса QWidget, который в Qt представляет базовое окно).

Перерисовка окна
void paintEvent(QPaintEvent *event);
Нажатие на клавишу мыши
void mousePressEvent(QMouseEvent* event);
Отпускание клавиши мыши
void mouseReleaseEvent(QMouseEvent* event);
Перемещение курсора мыши
void mouseMoveEvent(QMouseEvent* event);
Изменение размера окна
void resizeEvent(QResizeEvent *event);
(В этот список не включены события, связанные с клавиатурой.)

Технология обработки этих событий при программировании на Qt/C++ состоит в перекрытии (т.е. переопределении) указанных методов для класса, реализующего в Qt-программе окно, в котором выполняется рисование и работа с различными графическими объектами с помощью мыши. Окно,в котором происходит рисование, наследуется из базового типа QWidget (базовое окно в Qt).

Однако, помимо событий "базового" уровня (перерисовка окна, нажатие или отпускание клавиш мыши, ее движение), можно рассматривать и более сложные события, которые являются комбинациями базовых — например, mouse click (щелчок мыши), double click и т.п. Рассмотрим, например, окно типа QPushButton, представляющее кнопку. Комбинация базовых событий нажатия и отпускания клавиши мыши, когда курсор находится в этом окне, составляет сложное событие "clicked". В Qt для обработки таких "составных" событий используется механизм сигналов и слотов. Считается, что при наступлении такого сложного события окно (в нашем примере кнопка) посылает сигнал о событии (в тексте Qt-программы сигнал посылается с помощью ключевого слова emit). Этот сигнал может обрабатываться другими объектами, методы, обрабатывающие сигналы, называются слотами (slot). Связь между сигналами и обрабатывающими их слотами в Qt устанавливается с помощью метода QObject::connect. Например, следующий фрагмент программы

    QObject::connect(
        &clearButton, SIGNAL(clicked()),        
        &window, SLOT(clear())    
    );
устанавливает связь между сигналом clicked() кнопки clearButton и слотом clear() объекта window, который обрабатывает нажатие на эту кнопку.

5.2.2. Учебный проект на Qt: рисование графика функции и ввод массива точек

Рассмотрим проект, который можно использовать как шаблон для написания простых графических Qt-программ: "PlotFunc.tgz". Для распаковки его в Unix'е сохраните этот файл в текущей директории и выполните команду

    tar xzvf PlotFunc.tgz
Будет создан каталог "PlotFunc" с исходными файлами проекта. Нужно перейти в него и запустить qtcreator:
    cd PlotFunc
    qtcreator PlotFunc.pro &
(последняя команда запускает систему разработки qtcreator для проекта "PlotFunc.pro"; в Qt файлы с расширением .pro используются для описания проектов).

Вот как выглядит работающая программа:

Программа рисует график функции, заданной непосредственно в коде программы:

    double f(double x) {
        return 4.*sin(2.*x)/(1. + 0.2*x*x);
    }
Помимо графика функции, рисуются оси координат и координатная сетка ("клетчатая бумага"). Пользователь может отмечать кликами мыши точки, в ответ программа рисует крестики в отмеченных точках; цвет крестиков соответствует клавишам мыши (левая клавиша — синий крестик, правая — красный, средняя — малиновый).

Все крестики можно стереть, нажав на кнопку Clear в окне. Можно также изменять масштаб изображения и положение начала координат, редактируя значения в текстовых полях xmin, xmax, ymin (значение ymax определяется автоматически так, чтобы масштабы по осям X и Y были одинаковы). После изменения значений следует нажать кнопку Draw для перерисовки окна. По нажатию кнопки Close программа завершает работу.

5.2.3. Устройство проекта PlotFunc

Проект состоит из следующих файлов:

PlotFunc.pro
Файл описания проекта.
main.cpp
Запускающая функция (стартер).
mainwindow.h, mainwindow.cpp
Окно вернего уровня (форма), содержащее кнопки, текстовые поля и дочернее окно для рисования. h-файл содерит описание класса MainWindow, cpp-файл содержит реализацию методов класса.
mainwindow.ui
Описание оконной формы в виде XML-файла (редактируется в оболочке qtcreator с помощью программы qtdesigner; используется для автоматической генерации С++ кода, создающего эту форму при работе программы).
drawarea.h, drawarea.cpp
Дочернее окно, в котором выполняется рисование и ввод точек кликами мыши. h-файл содерит описание класса DrawArea, cpp-файл содержит реализацию методов класса.
R2Graph.h, R2Graph.cpp
Файлы, содержащие описание и реализацию классов для поддерки двумерной графики, в частности, классов R2Vector и R2Point, представляющих вектор и точку с вещественными координатами на двумерной плоскости R2.
Отметим, что для решения любых задач из приведенного ниже списка достаточно вносить изменения только в файлы drawarea.h и drawarea.cpp, другие файлы трогать не нужно!

5.2.4. Класс DrawArea

Класс DrawArea представляет собой дочернее окно, в котором происходит рисование и ввод точек кликами мыши. Оно является частью окна вернего уровня и меняет свой размер синхронно с изменением размера родительского окна. Рисование в окне выполняется в методе paintEvent, который вызывается при получении сообщения о событии типа QPaintEvent (перерисовка окна). Вот реализация этого метода:

void DrawArea::paintEvent(QPaintEvent* /* event */) {
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(0, 0, width(), height(), bgColor);

    drawCoordSystem(&painter);
    plotFunction(&painter, &f);
    drawPoints(&painter);
}
Комментарии к тексту. Отметим, что любое рисование в окне совершается с помощью объекта типа QPainter (рисователь, художник), иногда в оконных системах подобные объекты называют "графическим контекстом" или "контекстом устройства" (device context). Так что перед рисованием сначала создается объект painter, указатель на который затем передается во все функции и методы, выполняющие рисование.
    QPainter painter(this);
Параметр this в скобках означает указатель на текущее окно. Отметим, что рисователь можно создавать не только для окна, но и для объекта типа QImage, который в Qt используется для работы с изображениями в памяти компьютера. В последнем случае рисование выполняется внутри изображения (т.е. непосредственно не отображается в окне); изображение потом можно отобразить в окне или, например, записать в файл в любом из популярных графических форматов.

Следующие две строки устанавливают режим сглаживания линий при рисовании (Antialiasing) и очищают прямоугольник окна (заполняют его цветом фона bgColor, bg от слова background; переменная bgColor инициализирутся светло-серым цветом Qt::lightGray в конструкторе класса DrawArea):

    painter.setRenderHint(QPainter::Antialiasing);
    painter.fillRect(0, 0, width(), height(), bgColor);

И, наконец, программа рисует сначала систему координат с координатной сеткой ("бумага в клеточку"), затем график функции и множество точек, отмеченных мышкой:

    drawCoordSystem(&painter);
    plotFunction(&painter, &f);
    drawPoints(&painter);
Эти три действия оформлены в виде трех отдельных методов. Во все методы передается указатель на объект painter, с помощью которого выполняется рисование.

Разберем для примера метод plotFunction, рисующий график функции y=f(x).

void DrawArea::plotFunction(QPainter* painter, double (*f)(double)) {
    QPen greenPen(Qt::darkGreen);
    greenPen.setWidth(2);
    painter->setPen(greenPen);

    // double dx = 0.01;
    // Compute dx that corresponds to 1 pixel
    R2Point h0, h1;
    h0 = invMap(QPoint(0, 0));
    h1 = invMap(QPoint(1, 0));
    double dx = h1.x - h0.x;

    R2Point p0(xmin, (*f)(xmin));
    R2Point p1 = p0;
    while (p1.x <= xmax) {
        p0 = p1;
        p1.x += dx;
        p1.y = (*f)(p1.x);
        painter->drawLine(
            map(p0), map(p1)
        );
    }
}

В начале метода создается темно-зеленое перо (объект типа QPen) толщиной 2 пикселя и устанавливается как текущее перо рисователя:

    QPen greenPen(Qt::darkGreen);
    greenPen.setWidth(2);
    painter->setPen(greenPen);

Затем определяется шаг dx по оси абсцисс, который соответствует ширине одного пикселя экрана:

    // Compute dx that corresponds to 1 pixel
    R2Point h0, h1;
    h0 = invMap(QPoint(0, 0));
    h1 = invMap(QPoint(1, 0));
    double dx = h1.x - h0.x;

Наиболее важная часть этого метода — рисование ломаной, аппроксимирующей кривую графика функции:

    R2Point p0(xmin, (*f)(xmin));
    R2Point p1 = p0;
    while (p1.x <= xmax) {
        p0 = p1;
        p1.x += dx;
        p1.y = (*f)(p1.x);
        painter->drawLine(
            map(p0), map(p1)
        );
    }

5.2.5. Две системы координат в окне: математическая и пиксельная

В Qt по умолчанию используется пиксельная система координат в окне. В ней ось Y направлена вниз, а начало координат находится в левом верхнем углу окна. Единица в пиксельной системе координат соответствует размеру одного пикселя. Чтобы нарисовать линию, мы указываем две точки с пиксельными координатами — объекты типа QPoint, который в Qt представляет точки с целочисленными координатами. Например, данный фрагмент кода

    painter->drawLine(
        QPoint(20, 50), QPoint(200, 150)
    );
рисует отрезок с координатами концов (20, 50) и (200, 150) в пиксельной системе координат:

(20, 50) (200, 150)

Пиксельная система координат не очень удобна для рисования математических объектов, поэтому мы все вычисления проводим в математической системе координат. Она использует вещественные числа вместо целых для представления координат точек и векторов. При рисовании объектов ось Y направлена вверх, как это принято в математике, а начало координат может находиться в произвольной точке окна. Также можно выбрать и произвольный масштаб, он никак не связан с размером пикселя или разрешением окна. В нашем проекте мы задаем минимальное и максимальное значения координаты x от -8.0 до 8.0, начало координат находится в центре окна, минимальное и максимальное значения координаты y вычисляются, исходя из геометрии реального окна так, чтобы масштабы по осям были одинаковы.

x y

Для работы с точками и векторами на плоскости в математической системе координат мы используем классы R2Point и R2Vector, которые определены и реализованы в файлах "R2Graph.h" и "R2Graph.cpp". Основная идея в том, чтобы все определения геометрических объектов, операции с ними, вычисления и т.п. производить в терминах математических точек и векторов, и лишь в самый последний момент перед рисованием отображать точки в математической системе координат на точки в пиксельных координатах. Для этого в классе DrawArea имеется метод map:

    class DrawArea: public QWidget {
        . . .
        QPoint map(const R2Point& p) const;
        . . .
    };
С его помощью для точки p с математическими координатами можно получить ее пиксельные координаты, т.е. объект q типа QPoint:
    R2Point p;
    . . .
    QPoint q = map(p);
Пиксельные координаты можно получать непосредственно перед рисованием, например, при вызове метода DrawLine. Так, фрагмент кода
    R2Point p0(0.5, 2.3);
    R2Point p0(3.75, -1.5);
    painter->drawLine(
        map(p0), map(p1)
    );
рисует отрезок с концами в точках с математическими координатами (0.5, 2.3) и (3.75, -1.5):

x y (0.5, 2.3) (3.75, -1.5)

Наряду с методом map, который отображает точку с математическими координатами на соответствующую точку с пиксельными координатами, в классе DrawArea реализовано также и обратное отображение invMap:

    class DrawArea: public QWidget {
        . . .
        QPoint map(const R2Point& p) const;
        R2Point invMap(const QPoint& q) const;
        . . .
    };
С его помощью по точке точке q с пиксельными координатами получаем математические координаты этой точки:
    QPoint q;
    . . .
    R2Point p = invMap(q);
Метод invMap используется, например, при получении математических координат точки, отмеченной кликом мыши:
    void DrawArea::mousePressEvent(QMouseEvent* event) {
        . . .
        R2Point p = invMap(event->pos());
        points[numPoints] = p;
        ++numPoints;
        . . .
    }
Здесь переменная event описывает событие, состоящее в нажатии на клавишу мыши. Точку, в которой это произошло, можно получить, применив к объекту, на который ссылается указатель event, метод pos(), возвращающий точку в пиксельных координатах ("pos" от слова позиция). Можно было бы написать:
        QPoint q = event->pos();
К точке в пиксельных координатах, возвращенной методом pos() класса QMouseEvent, затем применяется метод invMap класса DrawArea, и мы получаем ту же точку в математических координатах.
        R2Point p = invMap(event->pos());

5.2.6. Список задач для решения

Во всех задачах точки указываются кликами левой клавиши мыши. По клику левой клавиши мыши программа должна нарисовать крестик в отмеченной точке. После того, как пользователь отметит последнюю точку (во всех задачай — третью) и нажмет кнопку Draw в окне, программа должна нарисовать требуемую кривую, не стирая отмеченные точки.

По нажатию кнопки Clear программа должна стереть все ранее определенные точки и кривую, очистить окно и начать все заново.

  1. Нарисовать эллипс по двум его фокусам и точке на эллипсе. (Эллипс — это геометрическое место точек, сумма расстояний от которых до фокусов эллипса постоянна.)
  2. Нарисовать ветвь гиперболы по двум ее фокусам и точке на этой ветви. (Гипербола — это геометрическое место точек, модуль разности расстояний от которых до фокусов гиперболы постоянен.)
  3. Нарисовать параболу по ее фокусу и директриссе. Директрисса (прямая) задается двумя точками. (Парабола — это геометрическое место точек, расстояния от которых до фокуса и директриссы параболы равны между собой.)

6. Интерполяция

  1. Нарисовать график интерполяционного многочлена степени n по заданным точкам (число точек n+1). Точки отмечаются кликами левой клавиши мыши, программа должна рисовать крестики в указанных точках. По нажатию но кнопку Draw программа должна нарисовать график интерполяционного многочлена, построенного по заданным точкам. Координаты x точек должны быть различны. Число точек ограничено: n<16. Для вычисления многочлена следует использовать интерполяционную формулу Ньютона.

    По нажатию на кнопку Clear программа должна стереть все точки, очистить окно и начать все заново.

  2. Пользователь отмечает кликами мыши 4 точки A, B, C, D, координаты x которых строго возрастают. Построить график кубического многочлена, обладающего следующими свойствами:
    • график проходит через первую и последнюю точки A и D;
    • прямая AB задает производную многочлена в точке A, т.е. является касательной к графику в точке A;
    • прямая CD задает производную многочлена в точке D, т.е. является касательной к графику в точке D.
    Указание: такой кубический многочлен удобно вычислять в форме Ньютона. Обозначим A.x=x0, D.x=x1. Нужно найти коэффициенты ai в представлении многочлена
         p(x) = a0 + a1(x-x0) + a2(x-x0) (x-x1) + a3(x-x0)2 (x-x1).
  3. Нарисовать кривую Безье порядка n. Кривая Безье порядка n строится по n+1-ой точке
         p0, p1, ..., pn
    на плоскости, это отображение отрезка [0, 1] в плоскость R2:
         βn: [0, 1] → R2
    Определение рекурсивное:
         β0(t) = p0,
         β1(t) = βp0(t) (1-t) + βp1(t) (t),
         . . .
         βn+1(t) = βp0,...,pn (t) (1-t) + βp1,...,pn+1 (t) t.
    Кривая Безье нулевого порядка — это просто точка, первого порядка — отрезок, второго порядка — парабола, третьего — кубическая парабола и т.д.
  4. На плоскости R2 задана n+1 точка
         P0, P1, ..., Pn
    где n>1 и координаты x разных точек различны. Эти точки представляют собой измерения некоторой неизвестной линейной функции
         f(x) = a x + b,
    причем измерения потенциально неточные (со случайными ошибками). Вычислить коэффициенты a, b с помощью метода наименьших квадратов (так, чтобы сумма квадратов отклонений функции от измеренных значений была бы минимальной для данного набора измерений) и нарисовать искомую прямую.

7. Геометрия (множество точек на плоскости)

Во всех задачах отмечается множество точек кликами левой или правой клавиш мыши. Отмеченные точки изображаются крестиками синего или красного цвета. Для окончания ввода надо нажать кнопку Draw в окне. После этого программа должна вычислить и нарисовать требуемую фигуру. При нажатии на кнопку Clear в окне программа должна стереть все точки и фигуру и начать все заново.

  1. Построить выпуклую оболочку точек на плоскости.
  2. Построить круг минимального радиуса, содержащий все заданные точки.
  3. Пользователь отмечает n синих и n красных точек. По окончанию ввода соединить красные точки с синими так, чтобы построенные отрезки не пересекались.
  4. Пользователь вводит кликами левой клавиши мыши последовательность вершин замкнутого многоугольника, возможно, не выпуклого. По окончанию ввода вершин нарисовать многоугольник и его разбиение на треугольники диагоналями (площадь пересечения любой пары треугольников должна быть нулевой).

8. Работа с текстами

Во всех задачах следует использовать командную строку, в которой задаются аргументы программы. Последний аргумент командной строки — это всегда имя входного файла, из которого считывается текст. Результат программы должен выводится в выходной поток (т.е. по умолчанию печататься в консольном окне). Заголовок функции main выглядит так:

    int main(int argc, char *argv[]) {
        . . .
Здесь argc — число аргументов командной строки, argv — массив аргументов. Каждый элемент массива argv является указателем на строку, терминированную нулевым байтом. Первый аргумент, т.е. argv[0] — это всегда имя самой программы (точнее, путь к файлу с программой). Вот пример небольшой программы, которая печатает аргументы своей командной строки (файл "args.cpp"):
    #include <stdio.h>

    int main(int argc, char *argv[]) {
        printf("Command line arguments:\n");
        for (int i = 0; i < argc; ++i) {
            printf("%s\n", argv[i]);
        }
        return 0;
    }
Если эта программа (которая получила имя args после компиляции) запускается с помощью командной строки
    ./args e2e4 e7e5
то она печатает:
    Command line arguments:
    ./args
    e2e4
    e7e5

В ряде задач предполагается, что файл содержит латинские и русские буквы в кодировке UTF-8. В других задачах файл содержит только ASCII-символы.

Кодировка UTF-8

Unicode — это нумерация символов всех языков и алфавитов на планете Земля. Unicode неправильно называть кодировкой, это лишь присвоение символам их численных номеров, при этом не определяется, как именно эти номера хранятся в файле или в памяти компьютера. Номер символа называется кодовой точкой (code point) в терминологии Unicode. В настоящее время в Unicode более миллиона символов и кодовых точек.

Кодировка UTF-8 представляет кодовые точки символов в виде последовательностей байтов. Она обладает неоспоримыми преимуществами по сравнению со всеми существовавшими ранее кодировками и парой несущественных недостатков. Преимущества — совместимость с кодировкой ASCII, которая используется, например, компиляторами большинства языков программирования, независимость от компьютерной архитектуры (Little Endian или Big Endian), возможность включать любые символы (русские и английские буквы, умлауты, иероглифы, математические значки и пр.) в один текст, однозначная интерпретация в любых контекстах. Недостатков два: 1) количество байтов, используемых для записи одного символа, переменное (от 1 до 6); 2) работа с символами и строками в кодировке UTF-8 не поддерживается стандартными библиотеками языков C и C++. Но преимущества намного перекрывают эти недостатки. Уже в настоящее время большая часть страниц в Internet использует кодировку UTF-8. (Придумана она была в 1993 году, но повсеместно использоваться стала лишь недавно, примерно с середины десятилетия 2000-2010.)

Символы в UTF-8 кодируются следующим образом.

Для примера рассмотрим русскую букву 'А'. Ее кодовая точка — это число 1040 в десятичной записи, или 10000010000 в двоичной. В кодировке UTF-8 код буквы А представляется двумя байтами, которые в шестнацатеричном виде записываются как

    d0 90,
в двоичной записи
    11010000 10010000.
Здесь префиксы начального байта и байта продолжения выделены красным цветом. Оставшиеся биты, составляющие двоичную запись кодовой точки символа А, показаны синим цветом и подчеркнуты.

Пример программы, которая печатает Unicode-номера руссских букв (кодовые точки): "ruscodes.cpp" (ее текст набран в кодировке UTF-8). Программа просит ввести номер русской буквы (от 0 до 65) и в ответ печатает саму букву и ее кодовую точку. Исходный текст программы включает строку, набранную в UTF-8, содержащую 66 русских букв в алфавитном порядке, сначала прописные (большие) буквы, затем строчные. В алфавит включена буква Ё. Программа по номеру n выбирает из этой строки 2 байта, составляющих UTF-8 кодировку n-й буквы русского алфавита, и по описанному выше алгоритму выделяет биты кодовой точки и печатает ответ. Вот пример работы программы:

    ./ruscodes
    Number of Russian letter (0-65): 0
    Unicode point of letter А = 1040
    Number of Russian letter (0-65): 1
    Unicode point of letter Б = 1041
    Number of Russian letter (0-65): 5
    Unicode point of letter Е = 1045
    Number of Russian letter (0-65): 6
    Unicode point of letter Ё = 1025
    Number of Russian letter (0-65): 7
    Unicode point of letter Ж = 1046
    Number of Russian letter (0-65): 65
    Unicode point of letter я = 1103
Отсюда видно, что кодовые точки всех русских букв упрядочены по алфавиту, за исключением несчастной буквы Ё.

Список задач

  1. Подсчитать число русских букв, русских слов и общее число строк в файле.
  2. Напечатать все строки файла, содержащие заданный фрагмент текста. Рассматриваются только ASCII-символы. Фрагмент текста может быть просто словом (составленным из любых символов, кроме пробелов и *) или включать в себя символ * (звездочка) в середине фрагмента. Звездочка означает, что вместо нее может содержаться любая подпоследовательность символов. Фрагмент задается как второй аргумент командной строки; если используется звездочка, то его надо обязательно заключить в двойные апострофы. Например, команда
         ./search "M_*PI" /usr/include/math.h
    
    должна распечатать все строки файла "/usr/include/math.h", которые содержат фрагмент, начинающийся с префикса "M_" и заканчивающийся суффиксом "PI". (Предполагается, что search — название программы.) Вот результат выполнения этой команды:
    # define M_PI   3.14159265358979323846  /* pi */
    # define M_PI_2 1.57079632679489661923  /* pi/2 */
    # define M_PI_4 0.78539816339744830962  /* pi/4 */
    # define M_1_PI 0.31830988618379067154  /* 1/pi */
    # define M_2_PI 0.63661977236758134308  /* 2/pi */
    # define M_2_SQRTPI 1.12837916709551257390  /* 2/sqrt(pi) */
    # define M_PIl  3.141592653589793238462643383279502884L /* pi */
    # define M_PI_2l 1.570796326794896619231321691639751442L /* pi/2 */
    # define M_PI_2l 1.570796326794896619231321691639751442L /* pi/2 */
    # define M_PI_4l 0.785398163397448309615660845819875721L /* pi/4 */
    # define M_1_PIl 0.318309886183790671537767526745028724L /* 1/pi */
    # define M_2_PIl 0.636619772367581343075535053490057448L /* 2/pi */
    # define M_2_SQRTPIl 1.128379167095512573896158903121545172L /* 2/sqrt(pi) */
    
  3. В тексте, содержащем русские буквы, заменить их транслитерацией. Например, текст "Русское слово" заменяется на "Rousskoe slovo". Исходный текст считывается из файла, имя которого задано в командной строке, результат выводится в выходной поток.
  4. Текстовый файл содержит какое-то количество целых констант (запись константы может начинаться со знака "+" или "-" и далее содержать только десятичные цифры). Найти и напечатать их сумму.
  5. Упорядочить строки файла лексикографически по возрастанию. Предполагается, что текст содержит только ASCII-символы.