Необходимо реализовать шаблон 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);
Научитесь возводить комплексное число в действительную степень
степень x при помощи формулы Муавра.
Для этого реализуйте статический метод класса.
static complex<T> pow(complex<T> z, T x);
Для данного комплексного \(z\) вычислите \(e^z\). Вам поможет формула Эйлера.
Для этого реализуйте статический метод класса.
static complex<T> exp(complex<T> z);
Теперь посчитайте синус.
И косинус.
На очереди — натуральный логарифм. Он называется log.
Комплексный логарифм — многозначная функция (для каждого числа определено счётное число возможных значений логарифма). Функция должна возвращать главное значение логарифма, мнимая часть которого принадлежит интервалу \((-\pi; \pi]\).
Комплексная степень сводится к логарифму, поэтому тоже является многозначной функций. Необходимо использовать главное значение логарифма.
Необходимо реализовать считывание комплексных чисел, определив
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);
Дано кубическое уравнение с комплексными коэффициентами: \(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.