Необходимо реализовать шаблон complex
для
работы с комплексными числами. Комплексное число имеет вид
\(z = a + bi\), где \(a\) — действительная часть
числа, \(b\) — мнимая часть числа. При операциях
с комплексными числами предполагается, что \(i^2=-1\).
Тем самым комплексное число будет структурой, содержащей
два поля: действительную и мнимую часть. Сам класс complex
должен быть шаблоном.
template<typename T> complex
T
может быть одним из действительных типов данных:
float
, double
, long double
.
Кроме того, в задачах на конструкторы и ввод-вывод допускается
использование целочисленых типов в качестве базового типа,
но в большинстве задач будут рассматриваться только действительные типы.
Вам необходимо будет реализовать операции сложения, вычитания, умножения, деление. Все эти операции могут работать как с комплексными, так и с действительными числами. Между тем при вычислениях с комплексными числами не бывает такой операции, как изменение отдельно действительной или мнимой части числа, все вычисления в программе должны работать с комплексным числом, как с единым целым (не должно быть возможности значению действительной части комплексного числа присвоить новое значение). Поэтому поля структуры, в которых хранятся значения действительной и мнимой части должны быть недоступны для прямого изменения. Это делается при помощи модификаторов доступа к полям и методам.
Модификаторы доступа бывают следующими:
Модификаторы доступа пишутся внутри определения класса, после модификатора доступа ставится двоеточие. Действие модификатора доступа распространяется на все поля и методы до конца объявления класса или до следующего модификатора. Пример:
class complex { private: double m_re, m_im; public: complex(); complex(double x); complex(double x, double y); double re() {return m_re;} double im() {return m_im;} };
Если не указывать модификатор доступа, то для struct
по умолчанию доступ public
,
а для class
по умолчанию private
.
Между тем бывает полезно получить доступ к private
-полям не из методов класса.
В этом случае пишутся специальные функции-обёртки, возвращающие значение закрытого поля,
как методы re()
и im()
в примере выше. Такая технология
называется инкапсуляцией. Можно также реализовать методы для изменения
private
-полей класса, если в этом есть необходимость.
По правилам оформления программ в тестирующей системе перед модификаторами доступа ставится отступ в один пробел.
Иногда в программах необходимо использовать значение числовой константы
определенного типа. Например, если требуется производить вычисления c типом
long long
, то может возникнуть желание использовать
целочисленные константы (например, 0, 1, 2,...) данного типа.
Это можно сделать при помощи явного преобразования типов
(long long)1
, но есть и более компактная запись
с использованием суффиксов-литералов, записываемых сразу
после числа, например, 1LL.
Для целых типов данных возможны следующие суффиксы:
Их можно комбинировать, например, 1ULL — это число типа unsigned long long.
Начиная со стандарта C++11 пользователи могут создавать собственные литералы. Например, тригонометрические функции математической библиотеки работают с углами в радианах. Но, возможно, захочется задавать углы в градусах, например, так:
long double alpha = 45.0_deg; cout << sin(alpha) << endl;
В данном примере _deg
— это определённый пользователем
литерал, который применяется к предшествующей константе и может быть использован
для перевода значения константы из градусов в радианы. Для этого нужно предварительно
определить литерал следующим образом:
long double operator"" _deg(long double deg) { return deg * M_PI / 180; }
Параметр этой функции — константа, записанная перед литералом. При использовании в коде константы с литералом вместо значения константы будет использоваться значение, которое возвращает данная функция. Название функции является идентификатором литерала и должно начинаться с символа подчеркивания.
Пользователь может объявлять литералы, которые можно использовать для суффиксов типа unsigned long long int и long long double, а также для символьных и строковых констант.
У класса могут быть статические методы (а также поля) — методы, не привязанные ни к одному экземпляру класса, общие для всех экземпляров класса. Можно рассматривать статические методы, как функции, которые имеют какое-то отношение к классу, но мы не хотим вызывать их для конкретного экземпляра класса.
Пример статического поля в библиотеке STL: std::string::npos.
Статический метод объявляется так же, как и обычный метод,
со словом static
перед ним. Например, в одном из заданий
необходимо построить комплексное число по двум действительным
числам: модулю и аргументу числа. Для этого нельзя создать конструктор,
так как конструктор от двух действительных чисел уже объявлен, поэтому
создадим статический метод, возвращающий комплексное число:
static complex<T> polar(T r, T angle);
Статический метод вызывается так: complex<double>::polar(r, phi)
.
На проверку в этих задачах сдаётся реализация шаблона структуры complex, если не оговорено иное.
Для отладки задач вам может помочь интерпретатор python или WolframAlpha.
Реализуйте шаблон структуры complex и конструкторы со следующими параметрами:
Структура должна содержать инкапсулирующие методы re()
и im()
, возвращающие
действительную и мнимую части числа.
Хочется писать в программах:
complex<double> z = 1 + 2_i;
Для этого сначала определим литерал _i
, возвращающий значение типа complex<double>
.
Для этого необходимо реализовать два оператора: complex<double> operator"" _i(long double)
и complex<double> operator"" _i(unsigned long long)
.
Правда, ещё нужно будет определить оператор “+”, но это будет сделано позже.
Помимо этого определите литералы _if
, возвращающий значение типа complex<float>
и
_il
, возвращающий значение типа complex<long double>
.
Реализуйте оператор вывода
template<typename T> ostream & operator<<(ostream&, complex<T>);
Правила вывода такие:
Реализуйте метод conj()
:
complex<T> conj() const;
возвращающий сопряжённое к данному числу. Сопряжённое к числу \(z=a+bi\) это число \(\overline{z}=a-bi\).
Примеры использования метода:
complex<double> w = z.conj(); cout << z.conj() << endl;
Реализуйте операцию “+” так, чтобы можно было складывать два комплексных числа, комплексное число и действительное число, действительное число и комплексное число. Тип действительного числа (float, double, long double) совпадает с базовым типом комплексного числа.
Аналогично определите операцию “-” для вычитания действительных и комплексных чисел.
Аналогично определите операцию “*” для умножения действительных и комплексных чисел.
Аналогично определите операцию “/” для деления действительных и комплексных чисел.
Определите унарные операторы “+” и “-”. Для этого нужно определить
функции (или методы) operator+
и operator-
, работающие только с одним операндом.
Реализуйте методы abs()
и arg()
, возвращающие модуль и аргумент (полярный угол)
комплексного числа. Метод arg()
возвращает значение из интервала \((-\pi, \pi]\).
Для нулевого комплексного числа метод arg()
должен возвращать 0.
Необходимо уметь создавать число по тригонометрической форме. Для этого реализуйте статический метод класса.
static complex<T> polar(T r, T angle);
Необходимо реализовать статический метод класса sqrt, возвращающий квадратный корень из комплексного числа. Комплексное число (кроме нуля) имеет два различных комплексных корня, метод может вернуть любое из них.
Начиная с какого-то момента вы столкнётесь с тем, что определёнными вами функции
имеют одинаковые названия со стандартными функциями из библиотеки cmath
,
что приводит компилятор в затрудение. Чтобы указать компилятору, что необходимо
использовать функцию стандартной библиотеки, её нужно вызывать так: std::sqrt
.
Дано квадратное уравнение с комплексными коэффициентами: \(az^2+bz+c=0\), где \(a\ne 0\). Найдите его корни.
Решение оформите в виде метода
static void complex<T>::solve_quadratic_equation(complex<T> a, complex<T> b, complex<T> c, complex<T> & z1, complex<T> & z2);
Два найденных корня необходимо записать в переменные z1 и z2 в любом порядке.
Реализуйте операторы ==
и !=
, возвращающие значение типа
bool
. Сравнение действительных и мнимых частей осуществляется на точное равенство. Необходимо реализовать
сравнения двух комплексных чисел, комплексного и действительного числа,
действительного и комплексного числа.
Хоть это и неправильно, но давайте определим для комплексных чисел
оператор сравнения <
. Он должен сравнивать числа по
модулю, при равном модуле — по аргументу, при этом
аргумент должен браться на интервале \([0; 2\pi)\), то есть
\(1 \lt i \lt -1 \lt -i\). При помощи этого оператора
будет выполняться сортировка комплексных чисел
функцией sort
. Сравнивать можно между собой
только два комплексных числа.
Научитесь возводить комплексное число в целочисленную
степень n
при помощи алгоритма быстрого возведения
в степень.
Для этого реализуйте статический метод класса.
static complex<T> pow(complex<T> z, int n);
Необходимо реализовать считывание комплексных чисел, определив
template<typename T> istream & operator>>(istream&, complex<T> &);
Число подаётся на вход в том виде, в котором оно записано в примерах вывода. Если число записано со скобками, то внутри скобок допускаются пробелы, но знак “+” или “-” всегда пишется слитно с последующим символом.
Немного более формально. Запись комплексного числа на входе может иметь одну из следующих форм:
RE
IM
(RE)
(IM)
(RE IM)
где RE := x
, IM := xi | i | +i | -i
, где x
— действительное число
(запись действительного числа может начинаться со знаков “+”, “-”, “.”
или цифры). Внутри скобок и между RE
и IM
могут идти пробелы. В записи (RE IM)
литерал IM
обязательно начинается со знака “+” или “-”.
Во всех тестах запись комплексного числа будет корректной, то есть удовлетворяющей данному определению. Тестирование будет производиться при помощи цикла:
while (cin >> z) cout << z << endl;
Полезные идеи, которые могут пригодится при решении этой задачи.
У объекта istream
есть метод unget()
,
который возвращает назад в поток последний считанный символ.
Удобно извлечь из потока интересующие символы, сформировать
из них строку, затем из строки уже считывать числа и символы.
Для этого можно использовать объекты типа istringstream
:
#include<sstream> ... string str; ... istringstream is(str); ... is >> a > > b >> c;
Успешность считывания можно проверять при помощи преобразования
к типу bool
:
if (is >> a > > b >> c;)
Для повторного считывания из объекта istringstream
с самого начала нужно вызвать два метода:
is.clear(); is.seekg(0);
Научитесь возводить комплексное число в действительную степень
степень x
при помощи формулы Муавра.
Для этого реализуйте статический метод класса.
static complex<T> pow(complex<T> z, T x);
Для данного комплексного \(z\) вычислите \(e^z\). Вам поможет формула Эйлера.
Для этого реализуйте статический метод класса.
static complex<T> exp(complex<T> z);
Теперь посчитайте синус.
И косинус.
На очереди — натуральный логарифм. Он называется log
.
Комплексный логарифм — многозначная функция (для каждого числа определено счётное число возможных значений логарифма). Функция должна возвращать главное значение логарифма, мнимая часть которого принадлежит интервалу \((-\pi; \pi]\).
Комплексная степень сводится к логарифму, поэтому тоже является многозначной функций. Необходимо использовать главное значение логарифма.
Дано кубическое уравнение с комплексными коэффициентами: \(az^3+bz^2+cz+d=0\), где \(a\ne 0\). Найдите его корни.
Решение оформите в виде метода
static void complex<T>::solve_cubic_equation(complex<T> a, complex<T> b, complex<T> c, complex<T> d, complex<T> & z1, complex<T> & z2, complex<T> & z3);
Три найденных корня необходимо записать в переменные z1, z2, z3.
Вам поможет статья в википедии формула Кардано и WolframAlpha, которая умеет решать всё, для отладки.
Для вычисления кубического корня в библиотеке cmath
есть функция cbrt
.