Задачи даются по темам. Номера задач для каждого студента определяются следующим образом. Пусть N — номер студента по журналу, и пусть в теме даны k задач. Тогда студенту нужно сделать задачу с номером r, где r == N (mod k), т.е. выбрать ту задачу, номер которой при делении на k дает тот же остаток, что и номер студента. Например, если в теме 4 задачи, а номер студента равен 10, то нужно сделать задачу 2; если номер студента 16, то нужно сделать задачу 4.
Во всех задачах надо вычислить стандартную функцию с точностью ε=1e-12. Для небольших значений x это делается с помощью суммирования степенного ряда. Для значений x, при которых ряд не сходится или сходится очень медленно, надо воспользоваться какой-либо формулой, позволяющей свести вычисление нужной функции к вычислению ее или других функций при "хороших" значениях x. Например, формула
В качестве образца дается файл "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 с номером студента по журналу.
Задачи можно выполнять в одном из двух вариантов:
При решении геометрических задач следует использовать классы 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.
Во всех задачах точки вводятся с консоли, результат также печатается на консоли. В реализации программы запрещено пользоваться любыми вычислениями с координатами точек и векторов, следует пользоваться только методами классов и функциями, определенными в пакете R2Graph. Используйте в качестве образца программу "inCircle.cpp", архив всех необходимых файлов: InCircle.zip.
Для оконной графики мы используем библиотеку классов Qt и среду для разработки Qt-проектов "qtcreator". К достоинствам Qt относится то, что исходный код Qt-проекта не зависит от операционной системы (если не использовать специфические функции различных ОС и оставаться только в рамках Qt, то исходный код одинаковый для Unix, MS-Windows, MAC OS-X и т.д.). Кроме того, программировать на Qt очень удобно, в Qt предусмотрено огромное множество функций, классов и других инструментов для создания самых разнообразных программ.
Недостаток Qt в том, что необходимо изучить эту систему разработки в дополнение к изучению языков C и C++. Впрочем, библиотека Qt очень естественна и, наверно, по сравнению с другими средами разработки оконых приложений наиболее проста (насколько вообще может быть проста система разработки графических оконных приложений).
Любая оконная программа занимается обработкой сообщений об оконных событиях. В центре программы — цикл обработки событий:
цикл до бесконечности выполнять | получить сообщение о событии(вых: event) | обработать событие(вход: event) конец цикла
Таким образом, классическая оконная программа большую часть времени ничего не делает, ожидая, когда произойдет событие. Когда происходит событие, операционная система добавляет сообщение о нем в очередь; программа обрабатывает события (т.е. совершает действия, логически связанные с событием) в порядке их поступления в очередь. Отметим, что цикл обработки событий может быть спрятан внутри стандартных функций или методов и явно не присутствать в программе (так, в Qt он стартует при вызове метода QApplication::exec()).
В различных операционных системах набор оконных событий может различаться, начиная от небольшого количества событий (около 15) в первых версиях Mac OS и заканчивая чрезвычайно детализированным их набором в Win32. Для программирования несложных оконных приложений на Qt достаточно обрабатывать следующие события (укажем также методы, которые обрабатывают эти события — это методы класса QWidget, который в Qt представляет базовое окно).
Технология обработки этих событий при программировании на 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, который обрабатывает нажатие на эту кнопку.
Рассмотрим проект, который можно использовать как шаблон для написания простых графических 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 программа завершает работу.
Проект состоит из следующих файлов:
Класс 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) ); }
В Qt по умолчанию используется пиксельная система координат в окне. В ней ось Y направлена вниз, а начало координат находится в левом верхнем углу окна. Единица в пиксельной системе координат соответствует размеру одного пикселя. Чтобы нарисовать линию, мы указываем две точки с пиксельными координатами — объекты типа QPoint, который в Qt представляет точки с целочисленными координатами. Например, данный фрагмент кода
painter->drawLine( QPoint(20, 50), QPoint(200, 150) );рисует отрезок с координатами концов (20, 50) и (200, 150) в пиксельной системе координат:
Пиксельная система координат не очень удобна для рисования математических объектов, поэтому мы все вычисления проводим в математической системе координат. Она использует вещественные числа вместо целых для представления координат точек и векторов. При рисовании объектов ось Y направлена вверх, как это принято в математике, а начало координат может находиться в произвольной точке окна. Также можно выбрать и произвольный масштаб, он никак не связан с размером пикселя или разрешением окна. В нашем проекте мы задаем минимальное и максимальное значения координаты x от -8.0 до 8.0, начало координат находится в центре окна, минимальное и максимальное значения координаты 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):
Наряду с методом 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());
Во всех задачах точки указываются кликами левой клавиши мыши. По клику левой клавиши мыши программа должна нарисовать крестик в отмеченной точке. После того, как пользователь отметит последнюю точку (во всех задачай — третью) и нажмет кнопку Draw в окне, программа должна нарисовать требуемую кривую, не стирая отмеченные точки.
По нажатию кнопки Clear программа должна стереть все ранее определенные точки и кривую, очистить окно и начать все заново.
По нажатию на кнопку Clear программа должна стереть все точки, очистить окно и начать все заново.
Во всех задачах отмечается множество точек кликами левой или правой клавиш мыши. Отмеченные точки изображаются крестиками синего или красного цвета. Для окончания ввода надо нажать кнопку Draw в окне. После этого программа должна вычислить и нарисовать требуемую фигуру. При нажатии на кнопку Clear в окне программа должна стереть все точки и фигуру и начать все заново.
Во всех задачах следует использовать командную строку, в которой задаются аргументы программы. Последний аргумент командной строки — это всегда имя входного файла, из которого считывается текст. Результат программы должен выводится в выходной поток (т.е. по умолчанию печататься в консольном окне). Заголовок функции 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-символы.
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 кодируются следующим образом.
Начальный байт имеет следующую структуру в двоичной записи:
11...10*...*Старший бит последовательности символа, не входящего в набор символов ASCII, всегда равен 1 (выше он отмечен красным цветом). Дальше идет последовательность единиц (отмечена синим), длина которой равна количеству байтов продолжения. Эта последовательность заканчивается нулевым битом. Оставшиеся биты начального байта содержат старшие биты кодовой точки.
Например, любая русская буква кодируется двухбайтовой последовательностью, поэтому ее начальный байт имеет вид
110*****Здесь звездочками обозначены любые биты. Итак, начальный байт двухбайтовой последовательности, кодирующей русскую букву, в двоичной записи состоит из префикса 110, за которым следуют 5 произвольных битов, составляющих старшие биты Unicode-номера (кодовой точки) русской буквы. Младшие 6 битов кодовой точки содержатся в байте продолжения. Таким образом, кодовая точка русской буквы состоит из 11 битов.
10******Биты двоичной записи кодовой точки записываются в байтах продолжения слева направо, начиная со старших битов в начальной байте последовательности и в первом байте продолжения и заканчивая младшими битами в последнем байте продолжения.
Для примера рассмотрим русскую букву 'А'. Ее кодовая точка — это число 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Отсюда видно, что кодовые точки всех русских букв упрядочены по алфавиту, за исключением несчастной буквы Ё.
./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) */