Процесс структурирования кода (классы, объекты, структуры) На примере решения квадратного уравнения рассмотрим процесс создания приложения и реструктуризации кода. Версия 0 Создадим решение solQuadrEq и в нем нулевую версию консольного приложения QuadrEq0 со следующим содержанием // Так определяются величины, регулирующие условную компиляцию // #define означает "определи", #undef означает "отмени определение" // При использовании, например, версии version0_0 следует поставить // перед #undef version0_0 // В этом случае будут компилироваться те операторы программы, которые окружены // декларациями типа #if version0_0 #endif. #define version0_0 // Это модификация версии 0, в которой коэффициенты уравнения вводятся с консоли //#undef version0_0 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace nsQuadrEq0 { /// <summary> /// Определяет версию 0 решения квадратного уравнения x^2 + a[1]*x + a[0] = 0. /// В этой версии весь код находится внутри единственного метода класса main /// </summary> class QuadrEq0Class { /// <summary> /// Точка входа в приложение. /// Вводит коэффициенты квадратного уравнения, решает, тестирует и выводит результат /// </summary> /// <param name="args"> /// Аргументы командной строки /// </param> static void Main(string[] args) { #region Объявления и инициализации // Объявление массива a, состоящего из переменных типа double double[] a; // К этому моменту в переменной a содержится "ссылка в никуда" (значение null). // Инициализация ссылки на массив a, состоящего из двух переменных типа double a = new double[2]; // Теперь ссылка на массив a получила конкретное значение // и элементы массива a[0],a[1] можно использовать. #if !version0_0 // Объявление объекта rnd класса Random; rnd - ссылка на объект 1 // сразу в объявлении происходит инициализация ссылки на этот объект оператором new // и вызовом конструктора Random() для инициализации полей объекта rnd Random rnd = new Random(); #endif #endregion // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения #if version0_0 // Ввод коэффициентов уравнения с консоли (версия 0_0) Console.Write("Введите свободный член уравнения a[0]: "); a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью a[1]: "); a[1] = Double.Parse(Console.ReadLine()); #else // Инцииализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта rnd) a[0] = rnd.NextDouble(); a[1] = rnd.NextDouble(); #endif #endregion // Комментарий к задаче Console.WriteLine("Решаем квадратное уравнение x^2 " + (a[1] > 0 ? "+" : "-") + " {0}*x " + (a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(a[1]),Math.Abs(a[0])); #region Решение квадратного уравнения // Объявление и вычисление дискриминанта квадратного уравнения discr double discr = a[1] * a[1] - 4 * a[0]; // Объявление и определение знака дискриминанта. // Указание на знак в форме значения true (если знак не отрицательный) // или false (при отрицательном знаке) // сохраняется в логической переменной discrSign bool discrSign = discr >= 0; // Вычисление корня из модуля дискриминанта // величины, которая используется в формулах определения корней уравнения discr = Math.Sqrt(Math.Abs(discr)); // Объявление и инцииализация ссылок на массивы для харанения // вещественных, мнимых частей корней и левых частей уравнения double[] xRe = new double[2], xIm = new double[2], zeroRe = new double[2], zeroIm = new double[2]; // Ветвление, разделяющее вид корней - вещественные или комплексные if (discrSign) { // Вещественные корни xRe[0] = -.5 * (a[1] + discr); xRe[1] = -.5 * (a[1] - discr); xIm[0] = xIm[1] = 0; 2 for (int i = 0; i < 2; i++) { zeroRe[i] = xRe[i] * xRe[i] + a[1] * xRe[i] + a[0]; zeroIm[i] = 0; } } else { // Комплексные корни xRe[0] = xRe[1] = -.5 * a[1]; xIm[0] = -(xIm[1] = .5 * discr); for (int i = 0; i < 2; i++) { zeroRe[i] = xRe[i] * xRe[i] - xIm[i] * xIm[i] + a[1] * xRe[i] + a[0]; zeroIm[i] = 2 * xRe[i] * xIm[i] + a[1] * xIm[i]; } } #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования for (int i = 0; i < 2; i++) { Console.WriteLine("{0} - й корень: {1} " + (i == 0 ? "-" : "+") + " {2}i", i + 1, xRe[i], Math.Abs(xIm[i])); Console.WriteLine("Левая часть уравнения для {0} - ого корня: {1} " + (zeroIm[i] >= 0 ? "+" : "-") + " {2}i", i + 1, zeroRe[i], Math.Abs(zeroIm[i])); } #endregion Console.WriteLine("Press any key to restart or esc to exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } } Версия 1 Добавим к решению новый проект с именем QuadrEq1. В новой версии выделим методы, управляющие инициализацией коэффициентов уравнения, самим решением и выводом результатов. Это может выглядеть следующим образом using System; using System.Collections.Generic; using System.Linq; using System.Text; 3 using System.IO; using System.Data; using System.Xml; namespace nsQuadrEq1 { /// <summary> /// Определяет версию 1 решения квадратного уравнения x^2 + _a[1]*x + _a[0] = 0. /// В этой версии код распределен по статическим методам /// и локальные переменные метода main заменены статическими полями класса. /// Статические члены класса (поля, методы) могут быть вызваны только именем самого класса /// (в данном случае именем QuadEq1Class) или внутри статических методов этого класса. /// В последнем случае имя класса указывать не обязательно. /// </summary> class QuadrEq1Class { #region Members (члены класса) #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> static Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> static double[] _a; /// <summary> /// Хранит ссылку на массив вещественных частей корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> static double[] _xRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей корней уравнения /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> static double[] _xIm = new double[2]; /// <summary> /// Хранит ссылку на массив вещественных частей левой части уравнения /// после подстановки туда соответствующих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> static double[] _zeroRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей левой части уравнения /// после подстановки туда соответствующих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. 4 /// </summary> static double[] _zeroIm = new double[2]; #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> static void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); } /// <summary> /// Задает коэффициенты, как случайные числа в интервале [0;1) /// </summary> static void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) . // (метод NextDouble() объекта _rnd) _a[0] = _rnd.NextDouble(); _a[1] = _rnd.NextDouble(); } /// <summary> /// Находит корни квадратного уравнения, и значения левой части уравнения в этих точках /// </summary> static void Solve() { // Объявление и вычисление дискриминанта квадратного уравнения discr double discr = _a[1] * _a[1] - 4 * _a[0]; // Объявление и определение знака дискриминанта. // Указание на знак в форме значения true (если знак не отрицательный) // или false (при отрицательном знаке) // сохраняется в логической переменной discrSign bool discrSign = discr >= 0; // Вычисление корня из модуля дискриминанта // величины, которая используется в формулах определения корней уравнения discr = Math.Sqrt(Math.Abs(discr)); // Ветвление, разделяющее вид корней - вещественные или комплексные if (discrSign) { // Вещественные корни _xRe[0] = -.5 * (_a[1] + discr); _xRe[1] = -.5 * (_a[1] - discr); _xIm[0] = _xIm[1] = 0; for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 0; } 5 } else { // Комплексные корни _xRe[0] = _xRe[1] = -.5 * _a[1]; _xIm[0] = -(_xIm[1] = .5 * discr); for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] - _xIm[i] * _xIm[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 2 * _xRe[i] * _xIm[i] + _a[1] * _xIm[i]; } } } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экрану /// </param> static void WriteResult(TextWriter tw) { // Вывод результатов на носитель, // который сопоставлен объекту tw (экран или текстовой файл) for (int i = 0; i < 2; i++) { tw.WriteLine("{0} - й корень: {1} " + (i == 0 ? "-" : "+") + " {2}i", i + 1, _xRe[i], Math.Abs(_xIm[i])); tw.WriteLine( "Левая часть уравнения для {0} - ого корня: {1} " + (_zeroIm[i] >= 0 ? "+" : "-") + " {2}i", i + 1, _zeroRe[i], Math.Abs(_zeroIm[i])); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> static void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка ds.Tables[0].Rows.Add(ds.Tables[0].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 2; i++) ds.Tables[0].Rows[ds.Tables[0].Rows.Count - 1][i] = _a[i]; // К таблице XTable добавляются две строки, // в которые помещаются соответствующие корни for (int i = 0; i < 2; i++) { 6 ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][0] = _xRe[i]; ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][1] = _xIm[i]; } // К таблице ZeroTable добавляются две строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 2; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][0] = _zeroRe[i]; ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][1] = _zeroIm[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> static void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { BinaryWriter bw = new BinaryWriter(s); for (int i = 0; i < 2; i++) { bw.Write(_a[i]); bw.Write(_xRe[i]); bw.Write(_xIm[i]); bw.Write(_zeroRe[i]); bw.Write(_zeroIm[i]); } } catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } /// <summary> /// Точка входа в приложение 7 /// Организует ввод коэффициентов, решение и тестирование квадратного уравнения, // и вывод результата. /// </summary> /// <param name="args"> /// Аргументы командной строки /// </param> static void Main(string[] args) { #region Инициализация // Инициализация ссылки на массив коэффициентов _a = new double[2]; #endregion // Создание базы таблиц с данными о коэффициентах уравнения, //корнях и значениях левых частей using (DataSet ds = new DataSet("Quadratic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); // К таблице XTable добавляется колонка с именем Re(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Re(x)")); // К таблице XTable добавляется колонка с именем Im(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Im(x)")); // К таблице ZeroTable добавляется колонка с именем Re(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Re(Zero)")); // К таблице ZeroTable добавляется колонка с именем Im(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Im(Zero)")); // Создание потока в виде бинарного файла using (Stream s = File.Create("QuadrEq1")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения //ReadCoeff(); SetRandomCoeff(); #endregion Console.WriteLine("Решаем квадратное уравнение x^2 " + (_a[1] > 0 ? "+" : "-") + " {0}*x " + (_a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(_a[1]), Math.Abs(_a[0])); #region Решение квадратного уравнения Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования 8 // Вывод на экран WriteResult(Console.Out); // Вывод в текстовой файл WriteResult(File.AppendText("QuadrEq1_result.txt")); // Запись в поток WriteResult(s); // Добавка строк в таблицы базы ds WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("QuadrEq1.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } #endregion #endregion } } 9 Версия 2 Новый проект QuadrEq2 содержит версию 2, в которой поля и методы описаны как нестатические. Это требует создания экземпляра объекта класса внутри метода main. Так выглядит новая версия кода using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Data; using System.Xml; namespace nsQuadrEq2 { /// <summary> /// Определяет версию 2 решения квадратного уравнения x^2 + _a[1]*x + _a[0] = 0. /// В отличие от версии 1 этой версии код распределен по обычным (не статическим) методам /// Не статическими являются также поля класса QuadrEq2Class. /// Для использования не статических полей и методов внутри статического метода main /// создается экземпляр (объект) класса QuadrEq2Class. /// Не статические члены класса относятся только /// к экземпляру (объекту) класса, но не самому классу. /// </summary> class QuadrEq2Class { #region Members (члены класса) #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a; /// <summary> /// Хранит ссылку на массив вещественных частей корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей корней уравнения /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xIm = new double[2]; /// <summary> 10 /// Хранит ссылку на массив вещественных частей левой части уравнения /// после подстановки туда соответствующих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей левой части уравнения /// после подстановки туда соответствующих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroIm = new double[2]; #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); } /// <summary> /// Задает коэффициенты как случайные числа в интервале [0;1) /// </summary> void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта _rnd) _a[0] = _rnd.NextDouble(); _a[1] = _rnd.NextDouble(); } /// <summary> /// Находит корни квадратного уравнения, и значения левой части уравнения в этих точках /// </summary> void Solve() { // Объявление и вычисление дискриминанта квадратного уравнения discr double discr = _a[1] * _a[1] - 4 * _a[0]; // Объявление и определение знака дискриминанта. // Указание на знак в форме значения true (если знак не отрицательный) // или false (при отрицательном знаке) // сохраняется в логической переменной discrSign bool discrSign = discr >= 0; // Вычисление корня из модуля дискриминанта // величины, которая используется в формулах определения корней уравнения discr = Math.Sqrt(Math.Abs(discr)); // Ветвление, разделяющее вид корней - вещественные или комплексные 11 if (discrSign) { // Вещественные корни _xRe[0] = -.5 * (_a[1] + discr); _xRe[1] = -.5 * (_a[1] - discr); _xIm[0] = _xIm[1] = 0; for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 0; } } else { // Комплексные корни _xRe[0] = _xRe[1] = -.5 * _a[1]; _xIm[0] = -(_xIm[1] = .5 * discr); for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] - _xIm[i] * _xIm[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 2 * _xRe[i] * _xIm[i] + _a[1] * _xIm[i]; } } } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экрану /// </param> void WriteResult(TextWriter tw) { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < 2; i++) { tw.WriteLine("{0} - й корень: {1} " + (i == 0 ? "-" : "+") + " {2}i", i + 1, _xRe[i], Math.Abs(_xIm[i])); tw.WriteLine( "Левая часть уравнения для {0} - ого корня: {1} " + (_zeroIm[i] >= 0 ? "+" : "-") + " {2}i", i + 1, _zeroRe[i], Math.Abs(_zeroIm[i])); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> void WriteResult(DataSet ds) { 12 // К таблице CoeffTable добавляется строка ds.Tables[0].Rows.Add(ds.Tables[0].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 2; i++) ds.Tables[0].Rows[ds.Tables[0].Rows.Count - 1][i] = _a[i]; // К таблице XTable добавляются две строки, // в которые помещаются соответствующие корни for (int i = 0; i < 2; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][0] = _xRe[i]; ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][1] = _xIm[i]; } // К таблице ZeroTable добавляются две строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 2; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][0] = _zeroRe[i]; ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][1] = _zeroIm[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine("Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { BinaryWriter bw = new BinaryWriter(s); for (int i = 0; i < 2; i++) { bw.Write(_a[i]); bw.Write(_xRe[i]); bw.Write(_xIm[i]); bw.Write(_zeroRe[i]); bw.Write(_zeroIm[i]); } } 13 catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } /// <summary> /// Точка входа в приложение /// Организует создание экземпляра класса QuadrEq2Class, ввод коэффициентов, /// решение и тестирование квадратного уравнения, и вывод результата. /// </summary> /// <param name="args"> /// Аргументы командной строки /// </param> static void Main(string[] args) { #region Инициализация // Объявление и инициализация объекта класса QuadrEq2Class QuadrEq2Class qe = new QuadrEq2Class(); // Везде далее обращение к полям и методам класса QuadrEq2Class // должно содержать ссылку на объект qe! // Инициализация ссылки на массив коэффициентов qe._a = new double[2]; #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet("Quadratic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); // К таблице XTable добавляется колонка с именем Re(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Re(x)")); // К таблице XTable добавляется колонка с именем Im(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Im(x)")); // К таблице ZeroTable добавляется колонка с именем Re(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Re(Zero)")); // К таблице ZeroTable добавляется колонка с именем Im(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Im(Zero)")); // Создание потока в виде бинарного файла using (Stream s = File.Create("QuadrEq1")) // Организация цикла с постусловием (типа do ... while) do 14 { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения //ReadCoeff(); qe.SetRandomCoeff(); #endregion Console.WriteLine("Решаем квадратное уравнение x^2 " + (qe._a[1] > 0 ? "+" : "-") + " {0}*x " + (qe._a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(qe._a[1]), Math.Abs(qe._a[0])); #region Решение квадратного уравнения qe.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран qe.WriteResult(Console.Out); // Вывод в текстовой файл qe.WriteResult(File.AppendText("QuadrEq1_result.txt")); // Запись в поток qe.WriteResult(s); // Добавка строк в таблицы базы ds qe.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("QuadrEq1.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } #endregion #endregion } } 15 Версия 3 В новой версии 3 класс решения квадратного уравнения отделен от класса, содержащего метод main, хотя и описан в том же пространстве имен и в том же физическом файле кода. Это отделение, однако, приводит к необходимости изменить права доступа к членам класса. В следующем коде изменены права доступа к методам введено свойство a для доступа к полю _a введен конструктор, в котором инициализируется поле _a using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Data; using System.Xml; namespace nsQuadrEq3 { /// <summary> /// Организует решение квадратного уравнения. /// В отличие от предыдущей версии 2 во вновь созданном классе QuadrEq /// необходимо изменить права доступа к членам класса, /// используемым в методе main. В данном случае это относится к полю, /// содержащему ссылку на масcив коэффициентов _a, и всем методам класса /// </summary> class QuadrEq { #region Members (члены класса) #region Ctr /// <summary> /// Конструктор инициализирует поля объекта /// </summary> public QuadrEq() { // Инициализация ссылки на массив коэффициентов _a = new double[2]; } #endregion // Поля обычно имеют доступ private (доступны только внутри класса). // При отсутствии модификатора доступа у членов класса - доступ private #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> 16 /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a; /// <summary> /// Хранит ссылку на массив вещественных частей корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей корней уравнения /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xIm = new double[2]; /// <summary> /// Хранит ссылку на массив вещественных частей левой части уравнения /// после подстановки туда соответтсвуюих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей левой части уравнения /// после подстановки туда соответтсвуюих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroIm = new double[2]; #endregion // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> /// Возвращает ссылку на массив коэффициентов /// </summary> public double[] a { get { return _a; } } #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> public void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); } 17 /// <summary> /// Задает коэффициенты как случайные числа в интервале [0;1) /// </summary> public void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта _rnd) _a[0] = _rnd.NextDouble(); _a[1] = _rnd.NextDouble(); } /// <summary> /// Находит корни квадратного уравнения /// и значения левой части уравнения в этих точках /// </summary> public void Solve() { // Объявление и вычисление дискриминанта квадратного уравнения discr double discr = _a[1] * _a[1] - 4 * _a[0]; // Объявление и определение знака дискриминанта. // Указание на знак в форме значения true (если знак не отрицательный) // или false (при отрицательном знаке) // сохраняется в логической переменной discrSign bool discrSign = discr >= 0; // Вычисление корня из модуля дискриминанта // величины, которая используется в формулах определения корней уравнения discr = Math.Sqrt(Math.Abs(discr)); // Ветвление, разделяющее вид корней - вещественные или комплексные if (discrSign) { // Вещественные корни _xRe[0] = -.5 * (_a[1] + discr); _xRe[1] = -.5 * (_a[1] - discr); _xIm[0] = _xIm[1] = 0; for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 0; } } else { // Комплексные корни _xRe[0] = _xRe[1] = -.5 * _a[1]; _xIm[0] = -(_xIm[1] = .5 * discr); for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] - _xIm[i] * _xIm[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 2 * _xRe[i] * _xIm[i] + _a[1] * _xIm[i]; } } } 18 /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экране /// </param> public void WriteResult(TextWriter tw) { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < 2; i++) { tw.WriteLine("{0} - й корень: {1} " + (i == 0 ? "-" : "+") + " {2}i", i + 1, _xRe[i], Math.Abs(_xIm[i])); tw.WriteLine("Левая часть уравнения для {0} - ого корня: {1} " + (_zeroIm[i] >= 0 ? "+" : "-") + " {2}i", i + 1, _zeroRe[i], Math.Abs(_zeroIm[i])); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> public void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка ds.Tables[0].Rows.Add(ds.Tables[0].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 2; i++) ds.Tables[0].Rows[ds.Tables[0].Rows.Count - 1][i] = _a[i]; // К таблице XTable добавляются две строки, // в которые помещаются соответствующие корни for (int i = 0; i < 2; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][0] = _xRe[i]; ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1][1] = _xIm[i]; } // К таблице ZeroTable добавляются две строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 2; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][0] = _zeroRe[i]; ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1][1] = _zeroIm[i]; } 19 } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> public void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { BinaryWriter bw = new BinaryWriter(s); for (int i = 0; i < 2; i++) { bw.Write(_a[i]); bw.Write(_xRe[i]); bw.Write(_xIm[i]); bw.Write(_zeroRe[i]); bw.Write(_zeroIm[i]); } } catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } #endregion #endregion } /// <summary> /// В версии 3 класс, содержащий метод main отделен от класса, /// решающего квадратное уравнение. /// </summary> class QuadrEq3Class { /// <summary> /// Точка входа в приложение /// Организует создание экземпляра класса QuadrEq, ввод коэффициентов, /// решение и тестирование квадратного уравнения и вывод результата. /// </summary> 20 /// <param name="args"> /// Аргументы командной строки /// </param> static void Main(string[] args) { #region Инициализация // Объявление и инициализация объекта класса QuadrEq QuadrEq qe = new QuadrEq(); // Везде далее обращение к полям и методам класса QuadrEq // должно содержать ссылку на объект qe! #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet("Quadratic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); // К таблице XTable добавляется колонка с именем Re(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Re(x)")); // К таблице XTable добавляется колонка с именем Im(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Im(x)")); // К таблице ZeroTable добавляется колонка с именем Re(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Re(Zero)")); // К таблице ZeroTable добавляется колонка с именем Im(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Im(Zero)")); // Создание потока в виде бинарного файла using (Stream s = File.Create("QuadrEq3")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения //ReadCoeff(); qe.SetRandomCoeff(); #endregion Console.WriteLine("Решаем квадратное уравнение x^2 " + (qe.a[1] > 0 ? "+" : "-") + " {0}*x " + (qe.a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(qe.a[1]), Math.Abs(qe.a[0])); #region Решение квадратного уравнения qe.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран 21 qe.WriteResult(Console.Out); // Вывод в текстовой файл qe.WriteResult(File.AppendText("QuadrEq3_result.txt")); // Запись в поток qe.WriteResult(s); // Добавка строк в таблицы базы ds qe.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("QuadrEq3.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } } 22 Версия 4 В версии 4 структурирования процесса решения квадратного уравнения класс QuadEq помещается в отдельное приложение, компилируемое в исполняющий файл библиотеки классов (с расширением .dll). С этой целью к проекту добавляется шаблон Class Library и в него помещается описание класса QuadEq. Добавляется также консольное приложение с именем QuadEq4, которое, уже не содержит класс QuadEq, а лишь ссылку ( в узле References) на библиотеку QuadEq и дополнительную декларацию using nsQuadEq, ссылающуюся на пространство имен этой библиотеки. Следует отметить, что класс QuadEq в своем объявлении не имел модификатор доступа. По умолчанию отсутствие модификатора доступа в объявлении класса означает, что доступ к классу ограничен приложением (сборкой), в котором класс описан. Модификатор этого ограничения доступа internal можно не указывать. Библиотека, в которую помещен класс в этой версии, является отдельным приложением (сборкой). Поэтому уровень доступа internal оказывается недостаточным для доступа к классу из другого приложения. Поэтому следует к заголовку класса QuadEq добавить модификатор public. В новой версии имеем, т.о., два приложения – библиотеку (Class Library) QuadEq с описанием класса и приложение QuadEq4 с функцией main, использующей класс QuadEq. Библиотека и класс QuadEq using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Data; using System.Xml; namespace nsQuadrEq { /// <summary> /// Организует решение квадратного уравнения. /// В отличие от предыдущей версии 3 класс QuadrEq /// помещен в отдельный проект QuadrEq - динамически загружаемая библиотека. /// Для обеспечения доступа класс должен быть описан с модификатором public /// (по умолчанию модификатор доступа класса internal - доступен только внутри проекта). /// </summary> public class QuadrEq { #region Members (члены класса) #region Ctr /// <summary> /// Конструктор инициализирует поля объекта /// </summary> public QuadrEq() { // Инициализация ссылки на массив коэффициентов _a = new double[2]; } #endregion 23 // Поля обычно имеют доступ private (доступны только внутри класса). // При отсутствии модификатора доступа у членов класса - доступ private #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a; /// <summary> /// Хранит ссылку на массив вещественных частей корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей корней уравнения /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _xIm = new double[2]; /// <summary> /// Хранит ссылку на массив вещественных частей левой части уравнения /// после подстановки туда соответтсвуюих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroRe = new double[2]; /// <summary> /// Хранит ссылку на массив мнимых частей левой части уравнения /// после подстановки туда соответтсвуюих корней /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> double[] _zeroIm = new double[2]; #endregion // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> /// Возвращает ссылку на массив коэффициентов /// </summary> public double[] a { get { return _a; } } #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли 24 /// </summary> public void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); } /// <summary> /// Задает коэффициенты как случайные числа в интервале [0;1) /// </summary> public void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта _rnd) _a[0] = _rnd.NextDouble(); _a[1] = _rnd.NextDouble(); } /// <summary> /// Находит корни квадратного уравнения /// и значения левой части уравнения в этих точках /// </summary> public void Solve() { // Объявление и вычисление дискриминанта квадратного уравнения discr double discr = _a[1] * _a[1] - 4 * _a[0]; // Объявление и определение знака дискриминанта. // Указание на знак в форме значения true (если знак не отрицательный) // или false (при отрицательном знаке) // сохраняется в логической переменной discrSign bool discrSign = discr >= 0; // Вычисление корня из модуля дискриминанта // величины, которая используется в формулах определения корней уравнения discr = Math.Sqrt(Math.Abs(discr)); // Ветвление, разделяющее вид корней - вещественные или комплексные if (discrSign) { // Вещественные корни _xRe[0] = -.5 * (_a[1] + discr); _xRe[1] = -.5 * (_a[1] - discr); _xIm[0] = _xIm[1] = 0; for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 0; } } else { // Комплексные корни _xRe[0] = _xRe[1] = -.5 * _a[1]; 25 _xIm[0] = -(_xIm[1] = .5 * discr); for (int i = 0; i < 2; i++) { _zeroRe[i] = _xRe[i] * _xRe[i] - _xIm[i] * _xIm[i] + _a[1] * _xRe[i] + _a[0]; _zeroIm[i] = 2 * _xRe[i] * _xIm[i] + _a[1] * _xIm[i]; } } } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экране /// </param> public void WriteResult(TextWriter tw) { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < 2; i++) { tw.WriteLine("{0} - й корень: {1} " + (i == 0 ? "-" : "+") + " {2}i", i + 1, _xRe[i], Math.Abs(_xIm[i])); tw.WriteLine("Левая часть уравнения для {0} - ого корня: {1} " + (_zeroIm[i] >= 0 ? "+" : "-") + " {2}i", i + 1, _zeroRe[i], Math.Abs(_zeroIm[i])); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> public void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка ds.Tables["CoeffTable"].Rows.Add(ds.Tables["CoeffTable"].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 2; i++) ds.Tables["CoeffTable"].Rows[ds.Tables["CoeffTable"].Rows.Count-1][String.Format("a[{0}]", i)] = _a[i]; // К таблице XTable добавляются две строки, // в которые помещаются соответствующие корни for (int i = 0; i < 2; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1]["Re(x)"] = _xRe[i]; ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1]["Im(x)"] = _xIm[i]; 26 } // К таблице ZeroTable добавляются две строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 2; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count-1]["Re(Zero)"] = _zeroRe[i]; ds.Tables[2].Rows[ds.Tables[2].Rows.Count-1]["Im(Zero)"] = _zeroIm[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> public void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { BinaryWriter bw = new BinaryWriter(s); for (int i = 0; i < 2; i++) { bw.Write(_a[i]); bw.Write(_xRe[i]); bw.Write(_xIm[i]); bw.Write(_zeroRe[i]); bw.Write(_zeroIm[i]); } } catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } #endregion #endregion } } 27 Консольное приложение QuadEq4 using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsQuadrEq; using System.Data; using System.IO; namespace nsQuadrEq4 { /// <summary> /// В версии 4 структуризации задачи о решении квадратного уравнения /// класс решения уравнения помещается в отдельный проект QuadrEq, компилируемый в dll. /// В раздел references настоящего проекта добавляется ссылка на библиотеку QuadrEq /// В using добавляется ссылка на пространство имен библиотеки nsQuadrEq /// </summary> class QuadrEq4Class { /// <summary> /// Точка входа в приложение /// Организует создание экземпляра класса QuadrEq, ввод коэффициентов, /// решение и тестирование квадратного уравнения и вывод результата. /// </summary> /// <param name="args"> /// Аргументы командной строки /// </param> static void Main(string[] args) { #region Инициализация // Объявление и инициализация объекта класса QuadrEq QuadrEq qe = new QuadrEq(); // Везде далее обращение к полям и методам класса QuadrEq // должно содержать ссылку на объект qe! #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet("Quadratic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); 28 // К таблице XTable добавляется колонка с именем Re(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Re(x)")); // К таблице XTable добавляется колонка с именем Im(x) ds.Tables["XTable"].Columns.Add(new DataColumn("Im(x)")); // К таблице ZeroTable добавляется колонка с именем Re(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Re(Zero)")); // К таблице ZeroTable добавляется колонка с именем Im(Zero) ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Im(Zero)")); // Создание потока в виде бинарного файла using (Stream s = File.Create("QuadrEq4")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения //qe.ReadCoeff(); qe.SetRandomCoeff(); #endregion Console.WriteLine("Решаем квадратное уравнение x^2 " + (qe.a[1] > 0 ? "+" : "-") + " {0}*x " + (qe.a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(qe.a[1]), Math.Abs(qe.a[0])); #region Решение квадратного уравнения qe.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран qe.WriteResult(Console.Out); // Вывод в текстовой файл qe.WriteResult(File.AppendText("QuadrEq4_result.txt")); // Запись в поток qe.WriteResult(s); // Добавка строк в таблицы базы ds qe.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("QuadrEq4.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } 29 } Версия 5 В этой версии добавляется библиотека классов (Class Library), содержащая описание структуры комплексных чисел Complex. В дальнейшем структура Complex будет использована в новой версии класса решения квадратного уравнения. Самостоятельно написать консольное приложение, тестирующее структуру Complex, приведенную ниже. using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace nsComplex { /// <summary> /// Представляет комплексное число как совокупность /// вещественной и мнимой частей в виде чисел с двойной точностью типа double /// </summary> public struct Complex { #region Fields /// <summary> /// Хранит мнимую единицу /// </summary> public static readonly Complex i = new Complex(0, 1); /// <summary> /// Хранит комплексный нуль /// </summary> public static readonly Complex Zero; /// <summary> /// Хранит текущее значение вещественной части /// </summary> private double _re; /// <summary> /// Хранит текущее значение мнимой части /// </summary> double _im; #endregion #region Конструкторы /// <summary> /// Инициализирует поля структуры /// вещественную и мнимую часть комплексного числа /// </summary> /// <param name="re"> /// Вещественная часть /// </param> /// <param name="im"> /// Мнимая часть 30 /// </param> public Complex(double re, double im) { _re = re; _im = im; } /// <summary> /// Создает комплексное число с заданным модулем и аргументом /// </summary> /// <param name="mod"> /// Модуль /// </param> /// <param name="arg"> /// Аргумент /// </param> /// <returns> /// Комплексное число /// </returns> /// <remarks> /// Вычисляет вещественную и мнимую части часть и вызывает конструктор. /// </remarks> static public Complex Create(double mod, double arg) { if (mod < 0) throw new ArgumentOutOfRangeException( "Модуль комплексного числа не может быть отрицательным!"); if (mod == 0) return Complex.Zero; return new Complex(mod * Math.Cos(arg), mod * Math.Sin(arg)); } #endregion #region Properties /// <summary> /// Возвращает вещественную часть /// </summary> public double re { get { return _re; } } /// <summary> /// Возвращает мнимую часть /// </summary> public double im { get { return _im; } } /// <summary> /// Возвращает модуль /// </summary> public double mod { get { return Math.Sqrt(_re * _re + _im * _im); } 31 } /// <summary> /// Возвращает аргумент /// </summary> public double arg { get { return Math.Atan2(_im, _re); } } #endregion #region Комплексная арифметика. Переопределенные операторы /// <summary> /// Складывает два комплексных числа (бинарный плюс) /// </summary> static public Complex operator +(Complex c1, Complex c2) { return new Complex(c1.re + c2.re, c1.im + c2.im); } /// <summary> /// Возвращает комплексное число с обратным знаком (унарный минус) /// </summary> static public Complex operator -(Complex c) { return new Complex(-c.re, -c.im); } /// <summary> /// Вычитает два комплексных числа (бинарный минус) /// </summary> static public Complex operator -(Complex c1, Complex c2) { return c1 + (-c2); } /// <summary> /// Умножает два комплексных числа /// </summary> static public Complex operator *(Complex c1, Complex c2) { return new Complex(c1.re * c2.re - c1.im * c2.im, c1.re * c2.im + c1.im * c2.re); } /// <summary> /// Делит два комплексных числа /// </summary> static public Complex operator /(Complex c1, Complex c2) { double _mod = c2.mod; return c1 * ~c2 / _mod / _mod; } /// <summary> /// Преобразует комплексное число в сопряженное /// </summary> static public Complex operator ~(Complex c) { 32 return new Complex(c.re, -c.im); } /// <summary> /// Неявно преобразует действительное число в комплексное. /// </summary> static public implicit operator Complex(double a) { return new Complex(a, 0); } // Операторы равенства/неравенства /// <summary> /// Определяет равенство двух комплексных чисел /// </summary> /// <returns> /// true, если числа равны, false в противном случае /// </returns> static public bool operator ==(Complex c1, Complex c2) { return c1.re == c2.re && c1.im == c2.im; } /// <summary> /// Определяет неравенство двух комплексных чисел /// </summary> /// <returns> /// true, если числа не равны, false в противном случае /// </returns> static public bool operator !=(Complex c1, Complex c2) { return !(c1 == c2); } #endregion #region Methods #region Переопределенные версии унаследованных виртуальных методов /// <summary> /// Проверяет равенство комплексных чисел /// </summary> /// <param name="obj"> /// Сравниваемое комплексное число /// </param> /// <returns> /// true, если текущее число совпадает с аргументом, false в противном случае /// </returns> public override bool Equals(object obj) { return obj is Complex && this == (Complex)obj; } /// <summary> /// Сопоставляет каждому комплексному числу хэш-код - целое число. /// </summary> /// <returns> /// Хэш-код текущего числа /// </returns> 33 public override int GetHashCode() { return re.GetHashCode() ^ im.GetHashCode(); } /// <summary> /// Формирует текстовое представление комплексного числа /// </summary> /// <returns> /// Строка, отвечающая текстовому представлению /// </returns> public override string ToString() { return _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString() : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString()) + "i")); } #endregion #region Собственные методы /// <summary> /// Извлекает квадратный корень из комплексного числа, получая результат с положительной /// вещественной частью /// </summary> /// <param name="c"> /// Комплексное число - аргумент /// </param> /// <returns> /// Комплексное число - результат /// </returns> public static Complex Sqrt(Complex c) { return Complex.Create(Math.Sqrt(c.mod), .5 * c.arg); } /// <summary> /// Формирует текстовое представление комплексного числа в заданном формате /// </summary> /// <param name="format"> /// Строка формата /// </param> /// <returns> /// Строка, представляющая комплексное число в выбранном формате /// </returns> public string ToString(string format) { return _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString(format) : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString(format)) + "i")); } #endregion #endregion } } 34 Версия 6 Во-первых, в структуре Complex после тестирования обнаруживается ошибка в перегрузке оператора деления: Внутри оператора / имеется обращение к делению комплексного числа на число типа double double _mod = c2.mod; return c1 * ~c2 / _mod / _mod; С другой стороны, в определении структуры Complex присутствует оператор неявного (implicit) преобразования объекта типа double в объект типа Complex static public implicit operator Complex(double a) { return new Complex(a, 0); } Поэтому в операторе ~c2/_mod (из кода оператора деления) объект _mod типа double вначале преобразуется в объект типа Complex, а затем выполняется операция деления. Но указанный участок кода сам находится внутри оператора деления! Обращение происходит к этому же оператору. Такое обращение называется рекурсивным. В данном случае это рекурсивное обращение ничем не ограничено, поэтому приводит к бесконечной последовательности вызовов оператора деления. В конечном итоге, память, выделяемая под стек, используемый оператором деления, оказывается исчерпанной, и приложение порождает объект исключительной ситуации класса StackOverflowException. Ошибка легко исправляется новым кодом оператора деления: double _mod = c2.mod, mod_2 = 1.0 / _mod / _mod; return c1 * ~c2 * mod_2; В этой версии отсутствует деление комплексного числа на вещественное, а лишь деление вещественных чисел. Во-вторых, если возникает необходимость в пересылке бинарного значения комплексного числа в поток, следует написать наследника класса BinaryWriter, добавив в него метод Write с аргументом – объектом типа Complex. Это можно сделать непосредственно внутри структуры Complex. Еще одной важной особенностью следующей версии структуры Complex будет использование нового наследования – наследования от интерфейса. При обычном выводе комплексного числа C оператором Console.WriteLine("C = {0}", C) вещественная и мнимая части комплексного числа выводятся в замещающем формате g (15 знаков) и в том виде, который диктуется описанным в структуре методом ToString() (без параметров), перекрывающим виртуальный метод класса Object. Выполнение оператора Console.WriteLine(C.ToString("f5")) распечатает число C в требуемом формате f5 (5 знаков после запятой). Здесь явно используется другой метод ToString(string format), также описанный в структуре Complex. Но если попытаться вывести число на экран оператором Console.WriteLine("C = {0:f5}", C), указав форматирующую строку внутри оператора WriteLine, то будет использован тотже метод ToString() (без параметра), и число будет выведено по-прежнему в замещающем формате g (испытайте сами!). Существует, однако, механизм, позволяющий снабдить структуру Complex способностью реагировать на форматирующую строку в вышеприведенном примере кода Console.WriteLine("C = {0:f5}", C). Этот механизм называется наследование интерфейсов. В библиотеке .NET имеется множество классов, которые несут в себе только описания и полностью лишены кодов реализации. Это так называемые интерфейсы. Любой класс, структура 35 могут наследовать любое количество этих интерфейсов. Смысл в том, что при наследовании класс берет на себя обязательство реализовать объявленные интерфейсом методы. Сделав это, класс становится типом, совместимым с унаследованным интерфейсом. Поэтому везде, где используется тип унаследованного интерфейса, можно подставлять ссылку на объект классанаследника-реализатора. Воспользуемся этим механизмом на примере структуры Complex. Поставим ее предком интерфейс IFormattable и реализуем его единственный метод ToString с двумя параметрами, переписав код структуры в виде public struct Complex : IFormattable { #region Fields /// <summary> /// Хранит мнимую единицу /// </summary> public static readonly Complex i = new Complex(0, 1); /// <summary> /// Хранит комплексный нуль /// </summary> public static readonly Complex Zero; /// <summary> /// Хранит текущее значение вещественной части /// </summary> private double _re; /// <summary> /// Хранит текущее значение мнимой части /// </summary> double _im; #endregion #region Конструкторы /// <summary> /// Инициализирует поля структуры /// вещественную и мнимую часть комплексного числа /// </summary> /// <param name="re"> /// Вещественная часть /// </param> /// <param name="im"> /// Мнимая часть /// </param> public Complex(double re, double im) { _re = re; _im = im; } /// <summary> /// Создает комплексное число с заданным модулем и аргументом /// </summary> /// <param name="mod"> /// Модуль /// </param> /// <param name="arg"> /// Аргумент 36 /// </param> /// <returns> /// Комплексное число /// </returns> /// <remarks> /// Вычисляет вещественную и мнимую части часть и вызывает конструктор. /// </remarks> static public Complex Create(double mod, double arg) { if (mod < 0) throw new ArgumentOutOfRangeException( "Модуль комплексного числа не может быть отрицательным!"); if (mod == 0) return Complex.Zero; return new Complex(mod * Math.Cos(arg), mod * Math.Sin(arg)); } #endregion #region Properties /// <summary> /// Возвращает вещественную часть /// </summary> public double re { get { return _re; } } /// <summary> /// Возвращает мнимую часть /// </summary> public double im { get { return _im; } } /// <summary> /// Возвращает модуль /// </summary> public double mod { get { return Math.Sqrt(_re * _re + _im * _im); } } /// <summary> /// Возвращает аргумент в интервале (-pi; pi] /// </summary> public double arg { get { return Math.Atan2(_im, _re); } } #endregion #region Комплексная арифметика. Переопределенные операторы /// <summary> /// Складывает два комплексных числа (бинарный плюс) /// </summary> 37 static public Complex operator +(Complex c1, Complex c2) { return new Complex(c1.re + c2.re, c1.im + c2.im); } /// <summary> /// Возвращает комплексное число с обратным знаком (унарный минус) /// </summary> static public Complex operator -(Complex c) { return new Complex(-c.re, -c.im); } /// <summary> /// Вычитает два комплексных числа (бинарный минус) /// </summary> static public Complex operator -(Complex c1, Complex c2) { return c1 + (-c2); } /// <summary> /// Умножает два комплексных числа /// </summary> static public Complex operator *(Complex c1, Complex c2) { return new Complex(c1.re * c2.re - c1.im * c2.im, c1.re * c2.im + c1.im * c2.re); } /// <summary> /// Делит два комплексных числа /// </summary> static public Complex operator /(Complex c1, Complex c2) { double _mod = c2.mod, mod_2 = 1 / _mod / _mod; return c1 * ~c2 * mod_2; } /// <summary> /// Преобразует комплексное число в сопряженное /// </summary> static public Complex operator ~(Complex c) { return new Complex(c.re, -c.im); } /// <summary> /// Неявно преобразует действительное число в комплексное. /// </summary> static public implicit operator Complex(double a) { return new Complex(a, 0); } // Операторы равенства/неравенства /// <summary> /// Определяет равенство двух комплексных чисел /// </summary> /// <returns> 38 /// true, если числа равны, false в противном случае /// </returns> static public bool operator ==(Complex c1, Complex c2) { return c1.re == c2.re && c1.im == c2.im; } /// <summary> /// Определяет неравенство двух комплексных чисел /// </summary> /// <returns> /// true, если числа не равны, false в противном случае /// </returns> static public bool operator !=(Complex c1, Complex c2) { return !(c1 == c2); } #endregion #region Methods #region Переопределенные версии унаследованных виртуальных методов /// <summary> /// Проверяет равенство комплексных чисел /// </summary> /// <param name="obj"> /// Сравниваемое комплексное число /// </param> /// <returns> /// true, если текущее число совпадает с аргументом, false в противном случае /// </returns> public override bool Equals(object obj) { return obj is Complex && this == (Complex)obj; } /// <summary> /// Сопоставляет каждому комплексному числу хэш-код - целое число. /// </summary> /// <returns> /// Хэш-код текущего числа /// </returns> public override int GetHashCode() { return re.GetHashCode() ^ im.GetHashCode(); } /// <summary> /// Формирует текстовое представление комплексного числа /// </summary> /// <returns> /// Строка, отвечающая текстовому представлению /// </returns> public override string ToString() { return _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString() : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + 39 (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString()) + "i")); } #endregion #region Собственные методы /// <summary> /// Формирует текстовое представление комплексного числа в заданном формате /// </summary> /// <param name="format"> /// Строка формата /// </param> /// <returns> /// Строка, представляющая комплексное число в выбранном формате /// </returns> public string ToString(string format) { return ToString(format, null); // Вызов реализации ToString интерфейса IFormattable /* _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString(format) : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString(format)) + "i")); */ } #endregion #region Реализация метода ToString интерфейса IFormattable /// <summary> /// Форматирует значение текущего экземпляра с использованием заданного формата /// </summary> /// <param name="format"> /// Объект, задающий используемый формат /// </param> /// <param name="provider"> /// Объект, используемый для форматирования значения /// </param> /// <returns> /// Значение текущего экзмепляра в заданном формате /// </returns> public string ToString(string format, IFormatProvider formatProvider) { return _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString(format, formatProvider) : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString(format, formatProvider)) + "i")); } #endregion #endregion /// <summary> /// Доопределяет метод Write для структуры Complex /// </summary> public class BinaryWriter : System.IO.BinaryWriter { 40 /// <summary> /// Вызывает конструктор предка, не выполняя иных действий /// </summary> /// <param name="s"></param> public BinaryWriter(Stream s) : base(s) { } /// <summary> /// Посылает объект типа Complex в бинарном виде в поток /// </summary> /// <param name="c"> /// Значение комплексного числа, посылаемого в поток /// </param> public void Write(Complex c) { base.Write(c.re); base.Write(c.im); } } } Проверьте работу новой версии на том же примере кода Console.WriteLine("C = {0:f5}", C). В следующей версии необходимо изменить класс QuadrEq, используя в нем структуру Complex. Сделайте это самостоятельно и измените приложение, использующее класс QuadrEq. 41 Версия 7 Новая версия класса QuadrEq, которую назовем QuadrEqx поместим в новую библиотеку с тем же именем, сославшись на библиотеку с структурой Complex и написав следующий код класса using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Data; using System.Xml; using nsComplex; namespace nsQuadrEqX { /// <summary> /// Организует решение квадратного уравнения. /// В отличие от предыдущей версии класс QuadrEqX /// использует структуру комплексных чисел, что позволяет упростить код /// </summary> public class QuadrEqX { #region Members (члены класса) // Поля обычно имеют доступ private (доступны только внутри класса). // При отсутствии модификатора доступа у членов класса - доступ private #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a=new double[2]; /// <summary> /// Хранит ссылку на массив корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> Complex[] _x = new Complex[2]; /// <summary> /// Хранит ссылку на массив значений левой части уравнения /// после подстановки туда соответствуюих корней /// </summary> Complex[] _zero = new Complex[2]; #endregion // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> 42 /// Возвращает ссылку на массив коэффициентов /// </summary> public double[] a { get { return _a; } } #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> public void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); } /// <summary> /// Задает коэффициенты как случайные числа в интервале [0;1) /// </summary> public void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта _rnd) _a[0] = _rnd.NextDouble(); _a[1] = _rnd.NextDouble(); } /// <summary> /// Находит корни квадратного уравнения /// и значения левой части уравнения в этих точках /// </summary> public void Solve() { // Вычисляется корень из дискриминанта (не зависимо от его знака, т.к. корень комплексный) Complex sqrt = Complex.Sqrt(_a[1] * _a[1] - 4 * _a[0]); // Вычисляются корни _x[0] = -.5 * (_a[1] + sqrt); _x[1] = -.5 * (_a[1] - sqrt); // Вычисляются значения левой части уравнения for (int i = 0; i < 2; i++) _zero[i] = _x[i] * _x[i] + _a[1] * _x[i] + _a[0]; } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экране /// </param> public void WriteResult(TextWriter tw) 43 { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < 2; i++) { tw.WriteLine("{0} - й корень: {1} " , i + 1, _x[i]); tw.WriteLine("Левая часть уравнения для {0} - ого корня:\n {1} ", } tw.Close(); i + 1, _zero[i]); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> public void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка ds.Tables["CoeffTable"].Rows.Add(ds.Tables["CoeffTable"].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 2; i++) ds.Tables["CoeffTable"].Rows[ds.Tables["CoeffTable"].Rows.Count-1][String.Format("a[{0}]", i)] = _a[i]; // К таблице XTable добавляются две строки, // в которые помещаются соответствующие корни for (int i = 0; i < 2; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count-1]["X"] = _x[i]; } // К таблице ZeroTable добавляются две строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 2; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count-1]["Zero"] = _zero[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> public void WriteResult(Stream s) { if (!s.CanWrite) 44 { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { Complex.BinaryWriter bw = new Complex.BinaryWriter(s); for (int i = 0; i < 2; i++) { bw.Write(_a[i]); bw.Write(_x[i]); bw.Write(_zero[i]); } } catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } #endregion #endregion } } Добавьте консольное приложение QuadrEq7 с тестированием новой версии класса. При этом в ссылки надо добавить библиотеки QuadrEqX и dllComplex. Тестирующее приложение будет лишь немного отличаться от версии 4 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Data; using System.Xml; using nsComplex; using nsQuadrEqX; namespace nsQuadrEq7 { class QuadrEq7Class { static void Main(string[] args) { #region Инициализация // Объявление и инициализация объекта класса QuadrEq QuadrEqX qe = new QuadrEqX(); // Везде далее обращение к полям и методам класса QuadrEq 45 // должно содержать ссылку на объект qe! #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet("Quadratic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); // К таблице XTable добавляется колонка с именем X ds.Tables["XTable"].Columns.Add(new DataColumn("X")); // К таблице ZeroTable добавляется колонка с именем Zero ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Zero")); // Создание потока в виде бинарного файла using (Stream s = File.Create("QuadrEq7")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты квадратного уравнения"); #region Задание коэффициентов уравнения //qe.ReadCoeff(); qe.SetRandomCoeff(); #endregion Console.WriteLine("Решаем квадратное уравнение x^2 " + (qe.a[1] > 0 ? "+" : "-") + " {0}*x " + (qe.a[0] > 0 ? "+" : "-") + " {1} = 0", Math.Abs(qe.a[1]), Math.Abs(qe.a[0])); #region Решение квадратного уравнения qe.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран qe.WriteResult(Console.Out); // Вывод в текстовой файл qe.WriteResult(File.AppendText("QuadrEq7_result.txt")); // Запись в поток qe.WriteResult(s); // Добавка строк в таблицы базы ds qe.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); 46 } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("QuadrEq7.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } } Проверьте работу класса QuadrEqX с помощью этого приложения. Подготовьте новый класс CubeEq, который решает кубическое уравнение x3 + a2x2 + a1x + a0 = 0 по аналогии с квадратным уравнением, используя в методе Solve две версии формул, определяющих корни уравнения. Замена x = y – a2/3 приводит исходное уравнение к виду: y3 + py + q = 0, где p = - a22/3 + a1; q = (a2/3)*[2(a2/3)2 – a1] + a0. Дискриминант уравнения Q = (p/3)3 + (q/2)2. 1-ая версия A = [-q/2 + Q1/2]1/3; B = [-q/2 – Q1/2]1/3. Выбираются значения кубических корней так, чтобы произведение AB = -p/3. Другими словами, B = -p/3/A. Корни равны y1 = A + B; y2 = -(A + B)/2 + √3*(A - B)/2*i; y3 = -(A + B)/2 - √3*(A - B)/2*i; 2-ая версия Для вещественных корней (условие Q < 0) применяются формулы y1 = 2(-p/3)1/2*cos(α/3) y2 = -2(-p/3)1/2*cos(α/3 + π/3) y3 = -2(-p/3)1/2*cos(α/3 - π/3), где cos(α) = -q/2/(-p/3)3/2. В случае Q >=0 – те же формулы, что в предыдущей версии. 47 Версия 8 Первая версия класса решения кубического уравнения может выглядеть следующим образом #define AB // используется первый метод расчета корней //#undef AB using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsComplex; using System.IO; using System.Data; namespace nsCubicEq { public class CubicEq { #region Members (члены класса) // Поля обычно имеют доступ private (доступны только внутри класса). // При отсутствии модификатора доступа у членов класса - доступ private #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a = new double[3]; /// <summary> /// Хранит ссылку на массив корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> Complex[] _x = new Complex[3]; /// <summary> /// Хранит ссылку на массив значений левой части уравнения /// после подстановки туда соответствуюих корней /// </summary> Complex[] _zero = new Complex[3]; #endregion // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> /// Возвращает ссылку на массив коэффициентов /// </summary> public double[] a { get { return _a; } } 48 #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> public void ReadCoeff() { // Ввод коэффициентов уравнения с консоли Console.Write("Введите свободный член уравнения: "); _a[0] = double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед первой степенью: "); _a[1] = Double.Parse(Console.ReadLine()); Console.Write("Введите коэффициент перед второй степенью: "); _a[2] = Double.Parse(Console.ReadLine()); } /// <summary> /// Задает коэффициенты, как случайные числа в интервале [0;1) /// </summary> public void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [0;1) // (метод NextDouble() объекта _rnd) _a[0] = 20 * _rnd.NextDouble() - 10; _a[1] = 20 * _rnd.NextDouble() - 10; _a[2] = 20 * _rnd.NextDouble() - 10; } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экрану /// </param> public void WriteResult(TextWriter tw) { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < 3; i++) { tw.WriteLine("{0} - й корень: {1} ", i + 1, _x[i]); tw.WriteLine("Левая часть уравнения для {0} - ого корня\n {1} ", i + 1, _zero[i]); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> public void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка 49 ds.Tables["CoeffTable"].Rows.Add(ds.Tables["CoeffTable"].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < 3; i++) ds.Tables["CoeffTable"].Rows[ds.Tables["CoeffTable"].Rows.Count - 1][String.Format("a[{0}]", i)] = _a[i]; // К таблице XTable добавляются три строки, // в которые помещаются соответствующие корни for (int i = 0; i < 3; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1]["X"] = _x[i]; } // К таблице ZeroTable добавляются три строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < 3; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1]["Zero"] = _zero[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> public void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { Complex.BinaryWriter bw = new Complex.BinaryWriter(s); for (int i = 0; i < 3; i++) { bw.Write(_a[i]); bw.Write(_x[i]); bw.Write(_zero[i]); } } catch (Exception ex) { 50 // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } /// <summary> /// Решает кубическое уравнение x^3 + a[2]*x^2 + a[1]*x + a[0] = 0 /// </summary> public void Solve() { #if AB double a3 = a[2] / 3.0, p = -a[2] * a3 + a[1], q = a3 * (2 * a3 * a3 - a[1]) + a[0], p3 = p / 3.0, q2 = .5 * q, Q = p3 * p3 * p3 + q2 * q2; // Замена x = y - a/3 приводит исходное уравнение к виду // y^3 + py +q = 0, где p = - a^2/3 + b; q = (a/3)*[2(a/3)^2 - b] + c. // Характер решения зависит от знака дискриминанта Q = (p/3)^3 + (q/2)^2 Complex sqrtQ = Complex.Sqrt(Q), d1 = -q2 + sqrtQ, A = Complex.Create(Math.Pow(d1.mod, 1 / 3.0), d1.arg / 3),// + (Q > 0 ? 0 : 2 * Math.PI / 3)), B = -p / 3 / A; // A = [-q/2 + Q^(1/2)]^(1/3); B = [-q/2 - Q^(1/2)]^(1/3) // y1 = A + B; // y2 = -(A + B)/2 + 3^(1/2)*(A - B)/2*i; // y3 = -(A + B)/2 - 3^(1/2)*(A - B)/2*i; _x[0] = A + B - a3; _x[1] = -.5 * (A + B) + Complex.i * .5 * (A - B) * Math.Sqrt(3) - a3; _x[2] = -.5 * (A + B) - Complex.i * .5 * (A - B) * Math.Sqrt(3) - a3; #else double a3 = a[2] / 3.0, p = -a[2] * a3 + a[1], q = a3 * (2 * a3 * a3 - a[1]) + a[0], p3 = p / 3.0, q2 = .5 * q, Q = p3 * p3 * p3 + q2 * q2; // Замена x = y - a/3 приводит исходное уравнение к виду // y^3 + py +q = 0, где p = - a^2/3 + b; q = (a/3)*[2(a/3)^2 - b] + c. // Характер решения зависит от знака дискриминанта Q = (p/3)^3 + (q/2)^2 if (Q < 0) { // Вещественные корни double sqrtp3 = Math.Sqrt(-p3), cosalpa = -q2 / sqrtp3 / sqrtp3 / sqrtp3, alpha = Math.Acos(cosalpa); 51 // y1 = 2(-p/3)^(1/2)*cos(alpha/3); // y2 = -2(-p/3)^(1/2)*cos(alpha/3 + Pi/3); // y3 = -2(-p/3)^(1/2)*cos(alpha/3 - Pi/3); _x[0] = 2 * sqrtp3 * Math.Cos(alpha / 3.0) - a3; _x[1] = -2 * sqrtp3 * Math.Cos((alpha + Math.PI) / 3.0) - a3; _x[2] = -2 * sqrtp3 * Math.Cos((alpha - Math.PI) / 3.0) - a3; } else { // Один корень вещественный; два комплексно сопряженные double sqrtQ = Math.Sqrt(Q), d1 = -q2 + sqrtQ, A = Math.Pow(Math.Abs(d1), 1 / 3.0) * Math.Sign(d1), d2 = -q2 - sqrtQ, B = Math.Pow(Math.Abs(d2), 1 / 3.0) * Math.Sign(d2); // A = [-q/2 + Q^(1/2)]^(1/3); B = [-q/2 - Q^(1/2)]^(1/3) // y1 = A + B; // y2 = -(A + B)/2 + 3^(1/2)*(A - B)/2*i; // y2 = -(A + B)/2 - 3^(1/2)*(A - B)/2*i; _x[0] = A + B - a3; _x[1] = new Complex(-.5 * (A + B) - a3, .5 * (A - B) * Math.Sqrt(3)); _x[2] = ~_x[1]; } #endif for (int i = 0; i < 3; i++) _zero[i] = _x[i] * (_x[i] * (_x[i] + _a[2]) + _a[1]) + _a[0]; } #endregion #endregion } } Тестирующее приложение можно написать по образу приложения, использованного для тестирования класса решения квадратного уравнения using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsCubicEq; using nsComplex; using System.Data; using System.IO; namespace nsCubicEqTest { class CubicEqTestClass { static void Main(string[] args) { #region Инициализация // Объявление и инициализация объекта класса CubeEq CubicEq ce = new CubicEq(); 52 // Везде далее обращение к полям и методам класса CubeEq // должно содержать ссылку на объект ce! #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet("Cubic Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляется колонка с именем a[0] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[0]")); // К таблице CoeffTable добавляется колонка с именем a[1] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[1]")); // К таблице CoeffTable добавляется колонка с именем a[2] ds.Tables["CoeffTable"].Columns.Add(new DataColumn("a[2]")); // К таблице XTable добавляется колонка с именем X ds.Tables["XTable"].Columns.Add(new DataColumn("X")); // К таблице ZeroTable добавляется колонка с именем Zero ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Zero")); // Создание потока в виде бинарного файла using (Stream s = File.Create("CubicEq")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты кубического уравнения"); #region Задание коэффициентов уравнения //ce.ReadCoeff(); ce.SetRandomCoeff(); #endregion Console.WriteLine("Решаем кубическое уравнение\n x^3 " + (ce.a[2] > 0 ? "+" : "-") + " {0}*x^2 " + (ce.a[1] > 0 ? "+" : "-") + " {1}*x " + (ce.a[0] > 0 ? "+" : "-") + " {2} = 0", Math.Abs(ce.a[2]), Math.Abs(ce.a[1]), Math.Abs(ce.a[0])); #region Решение кубического уравнения ce.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран ce.WriteResult(Console.Out); // Вывод в текстовой файл ce.WriteResult(File.AppendText("CubicEq_result.txt")); // Запись в поток 53 ce.WriteResult(s); // Добавка строк в таблицы базы ds ce.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("CubicEq.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } } В следующей версии будет изменена структура Complex. К ней, кроме уже имеющейся реализации интерфейса IFormattable, добавятся реализации интерфейсов IConvertible, IComparable, IComparable<Complex>, IEquatable<Complex>. Познакомьтесь с содержанием этих интерфейсов и подумайте над тем, как добавить их реализации. Версия 9 В этой версии будет изменена структура Complex так, что она будет наследником не только интерфейса IFormattable, но и интерфейсов, указанных в следующем ниже коде. Изучите новую версию и интерфейсы, которые она реализует. Напишите тестирующее приложение, в котором проверьте работу новой версии структуры. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace nsComplex { /// <summary> /// Представляет комплексное число как совокупность /// вещественной и мнимой частей в виде чисел с двойной точностью типа double /// </summary> public struct Complex : IFormattable, IConvertible, IComparable, IComparable<Complex>, IEquatable<Complex> { #region Fields /// <summary> /// Хранит мнимую единицу /// </summary> 54 public static readonly Complex i = new Complex(0, 1); /// <summary> /// Хранит комплексный нуль /// </summary> public static readonly Complex Zero; /// <summary> /// Хранит текущее значение вещественной части /// </summary> private double _re; /// <summary> /// Хранит текущее значение мнимой части /// </summary> double _im; #endregion #region Конструкторы /// <summary> /// Инициализирует поля структуры /// вещественную и мнимую часть комплексного числа /// </summary> /// <param name="re"> /// Вещественная часть /// </param> /// <param name="im"> /// Мнимая часть /// </param> public Complex(double re, double im) { _re = re; _im = im; } /// <summary> /// Создает комплексное число с заданным модулем и аргументом /// </summary> /// <param name="mod"> /// Модуль /// </param> /// <param name="arg"> /// Аргумент /// </param> /// <returns> /// Комплексное число /// </returns> /// <remarks> /// Вычисляет вещественную и мнимую части часть и вызывает конструктор. /// </remarks> static public Complex Create(double mod, double arg) { if (mod < 0) throw new ArgumentOutOfRangeException( "Модуль комплексного числа не может быть отрицательным!"); if (mod == 0) return Complex.Zero; 55 return new Complex(mod * Math.Cos(arg), mod * Math.Sin(arg)); } #endregion #region Properties /// <summary> /// Возвращает вещественную часть /// </summary> public double re { get { return _re; } } /// <summary> /// Возвращает мнимую часть /// </summary> public double im { get { return _im; } } /// <summary> /// Возвращает модуль /// </summary> public double mod { get { return Math.Sqrt(_re * _re + _im * _im); } } /// <summary> /// Возвращает аргумент в интервале (-pi; pi] /// </summary> public double arg { get { return Math.Atan2(_im, _re); } } #endregion #region Комплексная арифметика. Переопределенные операторы /// <summary> /// Складывает два комплексных числа (бинарный плюс) /// </summary> static public Complex operator +(Complex c1, Complex c2) { return new Complex(c1.re + c2.re, c1.im + c2.im); } /// <summary> /// Возвращает комплексное число с обратным знаком (унарный минус) /// </summary> static public Complex operator -(Complex c) { return new Complex(-c.re, -c.im); } /// <summary> /// Вычитает два комплексных числа (бинарный минус) /// </summary> 56 static public Complex operator -(Complex c1, Complex c2) { return c1 + (-c2); } /// <summary> /// Умножает два комплексных числа /// </summary> static public Complex operator *(Complex c1, Complex c2) { return new Complex(c1.re * c2.re - c1.im * c2.im, c1.re * c2.im + c1.im * c2.re); } /// <summary> /// Делит два комплексных числа /// </summary> static public Complex operator /(Complex c1, Complex c2) { double _mod = c2.mod, mod_2 = 1 / _mod / _mod; return c1 * ~c2 * mod_2; } /// <summary> /// Преобразует комплексное число в сопряженное /// </summary> static public Complex operator ~(Complex c) { return new Complex(c.re, -c.im); } /// <summary> /// Неявно преобразует действительное число в комплексное. /// </summary> static public implicit operator Complex(double a) { return new Complex(a, 0); } // Операторы равенства/неравенства /// <summary> /// Определяет равенство двух комплексных чисел /// </summary> /// <returns> /// true, если числа равны, false в противном случае /// </returns> static public bool operator ==(Complex c1, Complex c2) { return c1.re == c2.re && c1.im == c2.im; } /// <summary> /// Определяет неравенство двух комплексных чисел /// </summary> /// <returns> /// true, если числа не равны, false в противном случае /// </returns> static public bool operator !=(Complex c1, Complex c2) { 57 return !(c1 == c2); } #endregion #region Methods #region Переопределенные версии унаследованных виртуальных методов /// <summary> /// Проверяет равенство комплексных чисел /// </summary> /// <param name="obj"> /// Сравниваемое комплексное число /// </param> /// <returns> /// true, если текущее число совпадает с аргументом, false в противном случае /// </returns> public override bool Equals(object obj) { return obj != null && obj is Complex && Equals((Complex)obj); } /// <summary> /// Сопоставляет каждому комплексному числу хэш-код - целое число. /// </summary> /// <returns> /// Хэш-код текущего числа /// </returns> public override int GetHashCode() { return re.GetHashCode() ^ im.GetHashCode(); } /// <summary> /// Формирует текстовое представление комплексного числа /// </summary> /// <returns> /// Строка, отвечающая текстовому представлению /// </returns> public override string ToString() { // Вызов реализации метода ToString с параметром-строкой форматирования return ToString(null); } #endregion #region Собственные методы /// <summary> /// Определяет квадратный корень из комплексного числа /// с положительной вещественной частью /// </summary> /// <param name="c"> /// Аргумент /// </param> /// <returns> /// Квадратный корень из аргумента, имеющий положительную вещественную часть /// </returns> 58 public static Complex Sqrt(Complex c) { return Complex.Create(Math.Sqrt(c.mod), .5 * c.arg); } /// <summary> /// Формирует текстовое представление комплексного числа в заданном формате /// </summary> /// <param name="format"> /// Строка формата /// </param> /// <returns> /// Строка, представляющая комплексное число в выбранном формате /// </returns> public string ToString(string format) { return ToString(format, null); // Вызов реализации ToString интерфейса IFormattable } #endregion #region Реализации методов унаследованных интерфейсов /// <summary> /// Форматирует значение текущего экземпляра с использованием заданного формата /// </summary> /// <param name="format"> /// Объект, задающий используемый формат /// </param> /// <param name="provider"> /// Объект, используемый для форматирования значения /// </param> /// <returns> /// Значение текущего экзмепляра в заданном формате /// </returns> /// <remarks> /// Реализация метода интерфейса IFormattable /// </remarks> public string ToString(string format, IFormatProvider formatProvider) { return _re == 0 && _im == 0 ? "0" : ((_re != 0 ? _re.ToString(format, formatProvider) : "") + (_im == 0 ? "" : (_im < 0 ? "-" : _re == 0 ? "" : "+") + (Math.Abs(_im) == 1 ? "" : Math.Abs(_im).ToString(format, formatProvider)) + "i")); } /// <summary> /// Определяет равенство двух комплексных чисел /// </summary> /// <param name="c"> /// Аргумент, с которым сравнивается вызывающий объект /// </param> /// <returns> /// true, если вызывающий обхект равен аргументу, и false в противном случае /// </returns> /// <remarks> 59 /// Реализация метода интерфейса IEuatable(Complex) /// </remarks> public bool Equals(Complex c) { return this == c; } /// <summary> /// Сравнивает два комплексных числа, сравнивая значения их модулей /// </summary> /// <param name="c"> /// Аргмент. с которым сравнивается вызывающее число /// </param> /// <returns> /// -1, если модуль вызывающего числа меньше модуля аргумента, /// 0, если модули равны, 1, если модуль вызывающего числа больше модуля аргумента. /// </returns> public int CompareTo(Complex c) { // Сравниваются модули mod вызывающего объекта и аргумента return mod.CompareTo(c.mod); } /// <summary> /// Сравнивает два комплексных числа, сравнивая значения их модулей /// </summary> /// <param name="obj"> /// Комплексное число, с которым сравнивается экземпляр, вызывающий метод /// </param> /// <returns> /// -1, если вызывающий экземпляр имеет модуль меньший модуля аргумента, /// 0, если оба модуля равны, и 1, /// если модуль вызывающего объекта больше модуля аргумента. /// </returns> /// <exception cref="ArgumentException"> /// Объект порождается, если тип аргумента obj не является Complex. /// </exception> public int CompareTo(object obj) { if (!(obj is Complex)) // Создается объект исключительной ситуации типа Argument Exception // если аргумент не относится к типу Complex throw new ArgumentException( "obj is not the same type as this instance: Тип obj не совпадает с Complex!"); // Вызываем метод с параметром типа Complex return CompareTo((Complex)obj); } #region IConvertible methods implementation public TypeCode GetTypeCode() { return TypeCode.Object; } bool IConvertible.ToBoolean(IFormatProvider provider) { 60 return this != Complex.Zero; } byte IConvertible.ToByte(IFormatProvider provider) { return ((IConvertible)mod).ToByte(provider); } sbyte IConvertible.ToSByte(IFormatProvider provider) { return ((IConvertible)mod).ToSByte(provider); } char IConvertible.ToChar(IFormatProvider provider) { return ((IConvertible)mod).ToChar(provider); } short IConvertible.ToInt16(IFormatProvider provider) { return ((IConvertible)mod).ToInt16(provider); } ushort IConvertible.ToUInt16(IFormatProvider provider) { return ((IConvertible)mod).ToUInt16(provider); } int IConvertible.ToInt32(IFormatProvider provider) { return ((IConvertible)mod).ToInt32(provider); } uint IConvertible.ToUInt32(IFormatProvider provider) { return ((IConvertible)mod).ToUInt32(provider); } long IConvertible.ToInt64(IFormatProvider provider) { return ((IConvertible)mod).ToInt64(provider); } ulong IConvertible.ToUInt64(IFormatProvider provider) { return ((IConvertible)mod).ToUInt64(provider); } float IConvertible.ToSingle(IFormatProvider provider) { return ((IConvertible)mod).ToSingle(provider); } double IConvertible.ToDouble(IFormatProvider provider) { return mod; } decimal IConvertible.ToDecimal(IFormatProvider provider) { return ((IConvertible)mod).ToDecimal(provider); } DateTime IConvertible.ToDateTime(IFormatProvider provider) { 61 return ((IConvertible)mod).ToDateTime(provider); } string IConvertible.ToString(IFormatProvider provider) { return ToString(null, provider); } object IConvertible.ToType(Type conversionType, IFormatProvider provider) { return Convert.ChangeType(mod, conversionType); } #endregion #endregion #endregion /// <summary> /// Доопределяет метод Write для структуры Complex /// </summary> public class BinaryWriter : System.IO.BinaryWriter { /// <summary> /// Вызывает конструктор предка, не выполняя иных действий /// </summary> /// <param name="s"></param> public BinaryWriter(Stream s) : base(s) { } /// <summary> /// Посылает объект типа Complex в бинарном виде в поток /// </summary> /// <param name="c"> /// Значение комплексного числа, посылаемого в поток /// </param> public void Write(Complex c) { base.Write(c.re); base.Write(c.im); } } } } Следующим шагом является объединение кодов классов решения квадратного уравнения в общий код класса PolynomEq, в котором будут реализованы все методы кроме Solve. Последний будет объявлен виртуальным и реализован в наследниках – новых версия классов QuadrEq и CubicEq. Подумайте над этим. 62 Версия 10 Из содержания классов QuadrEq и CubicEq следует, что ряд методов этих классов очень похожи и могут быть объединены и записаны в общем виде как методв нового класса, создаваемого для решения уравнения произвольного порядка. Класс PolynomEq Назовем новый класс PolynomEq, и, добавив в решение ClassLibrary, запишем его код в виде using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsComplex; using System.IO; using System.Data; using nsIPolynomEq; namespace nsPolynomEq { public abstract class PolynomEq : IPolynomEq { #region Members (члены класса) // Поля обычно имеют доступ private (доступны только внутри класса). // При отсутствии модификатора доступа у членов класса - доступ private #region Fields (поля класса) /// <summary> /// Хранит ссылку на объект класса _rnd, позволяющий генерировать случайные числа /// Поле инициализируется оператором new и вызовом конструктора класса Random() /// </summary> Random _rnd = new Random(); /// <summary> /// Хранит ссылку на массив коэффициентов уравнения. /// </summary> double[] _a; /// <summary> /// Хранит ссылку на массив корней уравнения. /// Поле инициализируется оператором new /// с созданием ссылки на массив из двух чисел типа double. /// </summary> Complex[] _x; /// <summary> /// Хранит ссылку на массив значений левой части уравнения /// после подстановки туда соответствуюих корней /// </summary> Complex[] _zero; #endregion // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> /// Возвращает порядок полинома 63 /// </summary> public int N { private set; get; } /// <summary> /// Возвращает ссылку на массив коэффициентов /// </summary> public double[] a { get { return _a; } } #endregion #region Indexer /// <summary> /// Возвращает решение с заданным индексом /// </summary> /// <param name="i"> /// Значение индекса /// </param> /// <returns> /// Решение или NaN, если решение не найдено /// </returns> public Complex this[int i] { get { return (_x == null ? Double.NaN : _x[i]); } } #endregion #region Ctr /// <summary> /// Инициализирует поля экземпляра /// </summary> /// <param name="n"> /// Порядок полинома уравнения /// </param> protected PolynomEq(int n) { if (n <= 1) throw new ArgumentException("Порядок полинома должен быть больше единицы!"); N = n; _a = new double[n]; _zero = new Complex[n]; } #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> public void ReadCoeff() { // Ввод коэффициентов уравнения с консоли 64 for (int i = 0; i < N; i++) { Console.Write( (i == 0 ? "Введите свободный член уравнения:{0}" : "Введите коэффициент перед {0}-ой степенью"), (i == 0 ? "" : i.ToString())); _a[i] = double.Parse(Console.ReadLine()); } } /// <summary> /// Задает коэффициенты случайными числами в интервале [-10;10) /// </summary> public void SetRandomCoeff() { // Инициализация коэффициентов случайными числами в интервале [-10;10) // (метод NextDouble() объекта _rnd) for (int i = 0; i < N; i++) _a[i] = 20 * _rnd.NextDouble() - 10; } /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экране /// </param> public void WriteResult(TextWriter tw) { // Вывод результатов на носитель, который сопоставлен объекту tw // (экран или текстовой файл) for (int i = 0; i < N; i++) { tw.WriteLine("{0} - й корень: {1} ", i + 1, _x[i]); tw.WriteLine("Левая часть уравнения для {0} - ого корня\n {1} ", i + 1, _zero[i]); } tw.Close(); } /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> public void WriteResult(DataSet ds) { // К таблице CoeffTable добавляется строка ds.Tables["CoeffTable"].Rows.Add(ds.Tables["CoeffTable"].NewRow()); // В элементы этой строки таблицы CoeffTable заносятся значения коэффициентов for (int i = 0; i < N; i++) ds.Tables["CoeffTable"].Rows[ds.Tables["CoeffTable"].Rows.Count - 1][String.Format("a[{0}]", i)] = _a[i]; // К таблице XTable добавляются три строки, 65 // в которые помещаются соответствующие корни for (int i = 0; i < N; i++) { ds.Tables[1].Rows.Add(ds.Tables[1].NewRow()); ds.Tables[1].Rows[ds.Tables[1].Rows.Count - 1]["X"] = _x[i]; } // К таблице ZeroTable добавляются три строки, // в которые помещаются соответствующие значения левой части уравнения for (int i = 0; i < N; i++) { ds.Tables[2].Rows.Add(ds.Tables[2].NewRow()); ds.Tables[2].Rows[ds.Tables[2].Rows.Count - 1]["Zero"] = _zero[i]; } } /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> /// <param name="s"> /// Поток вывода результатов /// </param> public void WriteResult(Stream s) { if (!s.CanWrite) { // Если в поток нельзя писать, то метод не выполняется Console.WriteLine( "Поток {0} не позволяет производить в него запись данных", s.ToString()); return; } try // Попытка записи в поток s { Complex.BinaryWriter bw = new Complex.BinaryWriter(s); for (int i = 0; i < N; i++) { bw.Write(_a[i]); bw.Write(_x[i]); bw.Write(_zero[i]); } } catch (Exception ex) { // Если при записи в поток s возникла исключительная ситуация, // управление будет передано в эту область Console.WriteLine(ex.Message); return; } } /// <summary> /// Метод поиска корней уравнения, реализуемый в наследниках 66 /// </summary> /// <returns> /// Ссылку на массив корней уравнения порядка N /// </returns> protected abstract Complex[] getRoots(); /// <summary> /// Решает уравнение x^N + a[N-1]*x^(N-1) +...+ a[1]*x + a[0] = 0 /// и определяет значения левых частей уравнения после подстановки в них значений найденных корней. /// </summary> public void Solve() { // Определение корней уравнения _x = getRoots(); // Подсчет левых частей уравнения для найденных корней for (int i = 0; i < N; i++) { _zero[i] = _a[0]; Complex xPow = 1; for (int j = 1; j < N; j++) _zero[i] += _a[j] * (xPow *= _x[i]); _zero[i] += xPow * _x[i]; } } #endregion #endregion } } Интерфейс IPolynomEq В дополнение к этому абстрактному классу напишем интерфейс IPolynomEq, который этим классом реализуется using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsComplex; using System.IO; using System.Data; namespace nsIPolynomEq { public interface IPolynomEq { // Свойства создаются для доступа к полям. #region Properties (свойства) /// <summary> /// Возвращает порядок полинома /// </summary> int N { 67 get; } /// <summary> /// Возвращает ссылку на массив коэффициентов /// </summary> double[] a { get; } #endregion #region Indexer /// <summary> /// Возвращает решение с заданным индексом /// </summary> /// <param name="i"> /// Значение индекса /// </param> /// <returns> /// Решение или NaN, если решение не найдено /// </returns> Complex this[int i] { get; } #endregion #region Methods (методы класса) /// <summary> /// Читает коэффициенты с консоли /// </summary> void ReadCoeff(); /// <summary> /// Задает коэффициенты случайными числами /// </summary> void SetRandomCoeff(); /// <summary> /// Пишет результат в текстовой файл или на экран /// </summary> /// <param name="tw"> /// Объект, отвечающий текстовому файлу или экране /// </param> void WriteResult(TextWriter tw); /// <summary> /// Заполняет таблицы базы данных /// </summary> /// <param name="ds"> /// База данных /// </param> void WriteResult(DataSet ds); /// <summary> /// Отдает результаты в поток /// (сохраняет в бинарном файле или передает в сеть или в локальную память) /// </summary> 68 /// <param name="s"> /// Поток вывода результатов /// </param> void WriteResult(Stream s); /// <summary> /// Решает уравнение x^N + a[N-1]*x^(N-1) +...+ a[1]*x + a[0] = 0 /// и определяет значения левых частей уравнения /// после подстановки в них значений найденных корней. /// </summary> void Solve(); #endregion } } Классы QuadraticEquation и CubicEquation Изменим классы решения квадратного и кубического уравнений, сделав их наследниками класса PolynomEq и, следовательно, Наследниками IPolynomEq using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsComplex; using nsPolynomEq; namespace nsQuadraticEquation { public class QuadraticEquation:PolynomEq { /// <summary> /// Конструктор. Инициализирует поля, вызывая конструктор базового класса. /// </summary> public QuadraticEquation() : base(2) { } /// <summary> /// Вычисляет корни квадратного уравнения /// </summary> /// <returns> /// Массив корней /// </returns> protected override Complex[] getRoots() { Complex[] x = new Complex[2]; // Вычисляется корень из дискриминанта //(не зависимо от его знака, т.к. корень комплексный) Complex sqrt = Complex.Sqrt(a[1] * a[1] - 4 * a[0]); // Вычисляются корни x[0] = -.5 * (a[1] + sqrt); x[1] = -.5 * (a[1] - sqrt); 69 return x; } } } #define AB //#undef AB using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsComplex; using nsPolynomEq; namespace nsCubicEquation { public class CubicEquation:PolynomEq { /// <summary> /// Конструктор. Инициализирует поля, вызывая конструктор базового класса. /// </summary> public CubicEquation() : base(3) { } /// <summary> /// Вычисляет корни кубического уравнения /// </summary> /// <returns> /// Массив трех комплексных корней /// </returns> protected override Complex[] getRoots() { Complex[] x = new Complex[3]; #if AB double a3 = a[2] / 3.0, p = -a[2] * a3 + a[1], q = a3 * (2 * a3 * a3 - a[1]) + a[0], p3 = p / 3.0, q2 = .5 * q, Q = p3 * p3 * p3 + q2 * q2; // Замена x = y - a[2]/3 приводит исходное уравнение к виду // y^3 + py +q = 0, где p = - a[2]^2/3 + a[1]; q = (a[2]/3)*[2(a[2]/3)^2 - a[1]] + a[0]. // Характер решения зависит от знака дискриминанта Q = (p/3)^3 + (q/2)^2 Complex sqrtQ = Complex.Sqrt(Q), d1 = -q2 + sqrtQ, A = Complex.Create(Math.Pow(d1.mod, 1 / 3.0), d1.arg / 3), B = -p3 / A; // A = [-q/2 + Q^(1/2)]^(1/3); B = [-q/2 - Q^(1/2)]^(1/3) 70 // y1 = A + B; // y2 = -(A + B)/2 + 3^(1/2)*(A - B)/2*i; // y3 = -(A + B)/2 - 3^(1/2)*(A - B)/2*i; x[0] = A + B - a3; x[1] = -.5 * (A + B) + Complex.i * .5 * (A - B) * Math.Sqrt(3) - a3; x[2] = -.5 * (A + B) - Complex.i * .5 * (A - B) * Math.Sqrt(3) - a3; #else double a3 = a[2] / 3.0, p = -a[2] * a3 + a[1], q = a3 * (2 * a3 * a3 - a[1]) + a[0], p3 = p / 3.0, q2 = .5 * q, Q = p3 * p3 * p3 + q2 * q2; // Замена x = y - a/3 приводит исходное уравнение к виду // y^3 + py +q = 0, где p = - a^2/3 + b; q = (a/3)*[2(a/3)^2 - b] + c. // Характер решения зависит от знака дискриминанта Q = (p/3)^3 + (q/2)^2 if (Q < 0) { // Вещественные корни double sqrtp3 = Math.Sqrt(-p3), cosalpa = -q2 / sqrtp3 / sqrtp3 / sqrtp3, alpha = Math.Acos(cosalpa); // y1 = 2(-p/3)^(1/2)*cos(alpha/3); // y2 = -2(-p/3)^(1/2)*cos(alpha/3 + Pi/3); // y3 = -2(-p/3)^(1/2)*cos(alpha/3 - Pi/3); x[0] = 2 * sqrtp3 * Math.Cos(alpha / 3.0) - a3; x[1] = -2 * sqrtp3 * Math.Cos((alpha + Math.PI) / 3.0) - a3; x[2] = -2 * sqrtp3 * Math.Cos((alpha - Math.PI) / 3.0) - a3; } else { // Один корень вещественный; два комплексно сопряженные double sqrtQ = Math.Sqrt(Q), d1 = -q2 + sqrtQ, A = Math.Pow(Math.Abs(d1), 1 / 3.0) * Math.Sign(d1), d2 = -q2 - sqrtQ, B = Math.Pow(Math.Abs(d2), 1 / 3.0) * Math.Sign(d2); // A = [-q/2 + Q^(1/2)]^(1/3); B = [-q/2 - Q^(1/2)]^(1/3) // y1 = A + B; // y2 = -(A + B)/2 + 3^(1/2)*(A - B)/2*i; // y2 = -(A + B)/2 - 3^(1/2)*(A - B)/2*i; x[0] = A + B - a3; x[1] = new Complex(-.5 * (A + B) - a3, .5 * (A - B) * Math.Sqrt(3)); x[2] = ~_x[1]; } #endif return x; } } } 71 Тестирующее консольное приложение Напишем тестирующее консольное приложение using System; using System.Collections.Generic; using System.Linq; using System.Text; using nsIPolynomEq; using nsQuadraticEquation; using nsCubicEquation; using System.Data; using System.IO; namespace nsEqTest { class EqTest { static String sign(double d) { return d > 0 ? "+" : "-"; } static void Main(string[] args) { #region Инициализация Console.Write("Порядок полинома уравнения = "); int n = int.Parse(Console.ReadLine()); // Объявление и инициализация объекта eq класса IPolynomEq IPolynomEq eq = n == 2 ? (IPolynomEq)new QuadraticEquation() : (IPolynomEq)new CubicEquation(); // Везде далее обращение к полям и методам класса IPolynomEq // должно содержать ссылку на объект eq! #endregion // Создание базы таблиц с данными о коэффициентах уравнения, // корнях и значениях левых частей using (DataSet ds = new DataSet((n == 3 ? "Cubic" : "Quadratic") + " Equation")) { // К объекту ds добавляется таблица с именем CoeffTable ds.Tables.Add(new DataTable("CoeffTable")); // К объекту ds добавляется таблица с именем XTable ds.Tables.Add(new DataTable("XTable")); // К объекту ds добавляется таблица с именем ZeroTable ds.Tables.Add(new DataTable("ZeroTable")); // К таблице CoeffTable добавляются столбцы для коэффициентов уравнения for (int i = 0; i < n; i++) ds.Tables["CoeffTable"].Columns.Add(new DataColumn(String.Format("a[{0}]", i))); // К таблице XTable добавляется колонка с именем X ds.Tables["XTable"].Columns.Add(new DataColumn("X")); // К таблице ZeroTable добавляется колонка с именем Zero ds.Tables["ZeroTable"].Columns.Add(new DataColumn("Zero")); // Создание потока в виде бинарного файла 72 using (Stream s = File.Create("Eq")) // Организация цикла с постусловием (типа do ... while) do { Console.WriteLine("Задаем коэффициенты " + (n == 3 ? "кубического" : "квадратного") + " уравнения"); #region Задание коэффициентов уравнения //eq.ReadCoeff(); eq.SetRandomCoeff(); #endregion if (n == 3) Console.WriteLine("Решаем кубическое уравнение\n x^3 " + sign(eq.a[2]) + " {0}*x^2 " + sign(eq.a[1]) + " {1}*x " + sign(eq.a[0]) + " {2} = 0", Math.Abs(eq.a[2]), Math.Abs(eq.a[1]), Math.Abs(eq.a[0])); else Console.WriteLine("Решаем квадратное уравнение x^2 " + sign(eq.a[1]) + " {0}*x " + sign(eq.a[0]) + " {1} = 0", Math.Abs(eq.a[1]), Math.Abs(eq.a[0])); #region Решение уравнения eq.Solve(); #endregion Console.WriteLine("Выводим результаты решения и тестирования"); #region Вывод результатов решения и тестирования // Вывод на экран eq.WriteResult(Console.Out); // Вывод в текстовой файл eq.WriteResult(File.AppendText("Eq_result.txt")); // Запись в поток eq.WriteResult(s); // Добавка строк в таблицы базы ds eq.WriteResult(ds); #endregion Console.WriteLine("Press any key to restart or esc to save dataset and exit"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); // Условие возвращает true, если не нажата клавиша esc // Данные базы ds записываются во вновь создаваемый xml-файл ds.WriteXml("Eq.xml"); } // Метод ReadKey класса Console срабатывает при нажатии клавиши клавиатуры; // Параметр true означает, что клавиша не должна отображаться на экране; // Метод ReadKey возвращает объект структуры ConsoleKeyInfo; // У этой структуры есть свойство Key. // Свойство Key возвращает объект перечислимого типа ConsoleKey, // содержащий все возможные клавиши. } } } С помощью этого приложения простестируйте работу классов решения квадратного и кубического уравнений. Далее подумайте над тестирующим windows-приложением. 73