Примеры консольных приложений на C#
сейчас в списке: 39 приложений Здесь размещены небольшие учебные примеры приложений на C#, проверенные в актуальной на момент создания статьи сборке Visual Studio 2019 (или позднее в 2022).
Все программы в ветке - консольные, то есть, созданы командой Файл -> Создать -> Проект -> C# -> Консольное приложение (.NET Framework), после чего текст примера можно скопировать и вставить вместо сгенерированного содержимого файла Program.cs
.
Логического порядка в примерах нет, будут добавляться по мере написания. Для поиска на странице нужного слова используйте комбинацию клавиш Ctrl+F в своём браузере.
1. Табулирование функций
С помощью делегата Function
строим таблицы значений двух различных функций одним и тем же кодом.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class Program { delegate double Function (double x); static void Table (double x1, double dx, double x2, Function f) { double y; string OutputFormat = "{0:F4} {1:F4}"; Console.WriteLine (OutputFormat, "x", "y"); for (double x = x1; x <= x2; x += dx) { y = f (x); Console.WriteLine (OutputFormat,x,y); } } static void Main (string[] args) { double sin_ (double x) { return Math.Sin (x); } double cos_ (double x) { return Math.Cos (x); } Console.Clear (); Function function = sin_; Table (0, Math.PI / 10, Math.PI, function); function = cos_; Table (0, Math.PI / 10, Math.PI, function); Console.ReadKey(); } } }
2. Второй класс в проекте, генерирующий случайное число
Для добавления нового класса достаточно обратиться к меню Проект -> Добавить класс и назначить новому классу имя.
Если классы располагаются в одном пространстве имён, для ссылки на метод объекта второго класса главной программе достаточно выполнить что-то вроде
var obj = new ProjectNamespace.SecondClass(); obj.Method();
//---------------------- файл Program.cs //C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class Program { static void Main (string[] args) { var gen = new ConsoleApp1.RandomNumber(); Console.WriteLine ("{0:F6}",gen.get()); Console.ReadKey(); } } } //---------------------- файл RandomNumber.cs //меню Проект - Добавить класс using System; namespace ConsoleApp1 { class RandomNumber { private double min, max; private Random rand; public RandomNumber (double min = 0, double max = 1) { this.rand = new Random (); this.min = min; this.max = max; } public double get () { return rand.NextDouble() * ( this.max - this.min ) + this.min; } } }
3. Два класса в одном файле
Достаточно, чтобы каждый класс располагался в своих операторных скобках внутри общего namespace
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class A { private string str; private double val; public A(string str="", double val = 0) { this.str = str; this.val = val; } public string view() { return str + ", " + val.ToString(); } } class Program { static void Main (string[] args) { string name = "Паоло Гудини"; A a = new A(name,1); Console.WriteLine (a.view()); Console.ReadKey(); } } }
4. Матрицы обычная и ступенчатая
По сути, в C# есть "паскалеподобная" матрица с квадратными скобками вида [i,j]
, для которой количество элементов в каждой строке одинаково,
и "си-подобная" ("ступенчатая") со скобками вида [i][j]
и возможностью рассматривать матрицу как вектор векторов, соответственно, имея в различных строках различное количество элементов.
Показаны выделение памяти, заполнение и построчный вывод элементов.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class Program { static void PrintMatrix(int [,] a) { for (int i = 0; i < a.GetLength(0); i++) { for (int j = 0; j < a.GetLength(1); j++) { Console.Write ("{0:F4} ",a[i,j]); } Console.WriteLine (); } } static void PrintJaggedMatrix (int [][] a) { for (int i = 0; i < a.Length; i++) { for (int j = 0; j < a[i].Length; j++) { Console.Write ("{0:F4} ", a[i][j]); } Console.WriteLine (); } } static void Main (string[] args) { //"Паскалеподобная" матрица, количество элементов в каждой строке одинаково int [,] matrix = { {1,2,3 }, {4,5,6 } }; PrintMatrix (matrix); //"Си-подобная" матрица, количество элементов в каждой строке может быть различным const int jaggedMarixRows = 3; int [] [] jaggedMarix = new int [jaggedMarixRows] []; for (int i = 0, k = 1; i < jaggedMarix.Length; i++) { try { jaggedMarix [i] = new int [i + 1]; } catch (OutOfMemoryException e) { Console.WriteLine ("Memory allocation failed \"{0:C}\" in string {0:D}\n",e,i); break; } for (int j = 0; j < jaggedMarix[i].Length; j++) jaggedMarix [i] [j] = k++; } PrintJaggedMatrix (jaggedMarix); Console.ReadKey (); } } }
5. Шаблон класса стека
Показаны стек целых и стек вещественных чисел, использующие один и тот же шаблон класса.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { public class Stack <T> where T : new() { //у типа данных стека должен быть конструктор по умолчанию T [] stck; // массив, содержащий стек int tos; // индекс вершины стека public Stack (int size) { stck = new T [size]; // распределить память для стека tos = 0; } public int Push (T ch) { // Поместить элементы в стек if (tos == stck.Length) { //стек заполнен return -1; } stck [tos] = ch; tos++; return tos; } public T Pop () { // Извлечь элемент из стека if (tos == 0) { //стек пуст return default (T); } tos--; return stck [tos]; } public bool IsFull () { // Возвратить значение true, если стек заполнен return tos == stck.Length; } public bool IsEmpty () { // Возвратить значение true, если стек пуст return tos == 0; } public int Capacity () { // Возвратить общую емкость стека return stck.Length; } public int GetNum () { // Возвратить количество объектов, находящихся в данный момент в стеке return tos; } } class Program { static void Main (string[] args) { const int Stack1Size = 10; Stack <int> stk1 = new Stack <int> (Stack1Size); for (int i = 0; !stk1.IsFull (); i++) stk1.Push (i+1); if (stk1.IsFull ()) Console.WriteLine ("Стек stk1 заполнен."); // Вывести содержимое стека stk1. Console.Write ("Содержимое стека stk1: "); while (!stk1.IsEmpty ()) { int i = stk1.Pop (); Console.Write ("{0} ", i); } Console.WriteLine (); if (stk1.IsEmpty ()) Console.WriteLine ("Стек stk1 пуст.\n"); // Поместить дополнительные символы в стек stk1. Console.WriteLine ("Вновь поместить элементы в стек stk1."); for (int i = 0; !stk1.IsFull (); i++) stk1.Push (Stack1Size - i ); Console.WriteLine ("А теперь извлечь символы из стека stk1 " + "и поместить их в стек stk2, добавив дробную часть"); Stack <double> stk2 = new Stack <double> (Stack1Size); while (!stk1.IsEmpty ()) { int i = stk1.Pop (); stk2.Push ((double)i + 0.5); } Console.Write ("Содержимое стека stk2: "); while (!stk2.IsEmpty ()) { double d = stk2.Pop (); Console.Write ("{0:F1} ", d); } Console.WriteLine (); Console.WriteLine ("Емкость стека stk2: " + stk2.Capacity ()); Console.WriteLine ("Количество объектов в стеке stk2: " + stk2.GetNum ()); Console.ReadKey (); } } }
6. Шаблон функции и аргументы по ссылке
Передача и возврат аргументов по ссылке, функция с переменным числом аргументов.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class Program { static void Swap <T> (ref T lhs, ref T rhs) { //Аргументы получены по ссылке T temp = lhs; lhs = rhs; rhs = temp; } static void GetNumberParts (double n, out double whole, out double frac) { //Второй и третий аргументы будут возвращены по ссылке whole = Math.Floor(n); frac = n - whole; } static double Summa (params double [] nums) { //Функция может иметь переменное число аргументов double sum = 0; for (int i = 0; i < nums.Length; i++) sum += nums [i]; return sum; } static void Main (string[] args) { double a = 1.5; double b = 2.7; Swap (ref a, ref b); //Аргументы переданы по ссылке Console.WriteLine (a + " " + b); double d, f; GetNumberParts (a, out d, out f); //Второй и третий аргументы будут возвращены по ссылке Console.WriteLine ("Целая часть числа равна " + d); Console.WriteLine ("Дробная часть числа равна " + f); double s = Summa (1, 2, 3, 4); Console.WriteLine ("1+2+3+4= " + s); Console.ReadKey (); } } }
7. Фабрика объектов
Так называют статический метод в классе, возвращающий новый объект этого же класса. Имеет смысл, если по каким-то причинам не хотим делать конструктор класса публичным методом.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class Factory { private int val; private Factory (int val = 0) { //Конструктор класса приватен this.val = ++val; } public static Factory makeFactory (int val = 0) { //Но есть фабрика объектов Factory factory = new Factory (val); return factory; } public override string ToString () { //переписываем встроенный метод return val.ToString (); } } class Program { static void Main (string[] args) { const int Size = 10; Factory [] Objects = new Factory [Size]; //Объекты массива Factory пока пустые ссылки (null), то есть, конструктор по умолчанию //всё равно доступен. А вот new Factory (0) [Size] не сработало бы for (int i = 0; i < Size; i++) { Objects [i] = Factory.makeFactory (i); Console.WriteLine ("Объект номер {0}: {1}",i, Objects [i]); } Console.ReadKey (); } } }
8. Статические члены класса и оценивание арифметических выражений
Описываем в классе статический счётчик созданных объектов и оцениваем арифметические выражения одной строчкой кода с проверкой корректности (метод Exec
).
//C# -> Консольное приложение (.NET Framework) using System; using System.Data; namespace ConsoleApp1 { class Program { class Compute { private static int Count = 0; //Счётчик созданных объектов класса public Compute () { Count++; //Увеличить счётчик на 1 при создании объекта } public double Exec (string expr) { return Convert.ToDouble (new DataTable ().Compute (expr, "")); } public static int GetCount () { return Count; } //Узнать значение счётчика } static void Main () { string [] Expressions = { "(5-2)%2 + 5./4", "-1+2/3", "1*2*3*error" }; for (int i = 0; i < Expressions.Length; i++) { Compute Expr = new Compute (); //На самом деле, хватило бы статического метода в Compute try { double d = Expr.Exec (Expressions [i]); Console.WriteLine ("{0} = {1:F4}", Expressions [i], d); } catch (Exception e) { Console.WriteLine ("Ошибочное выражение {0}: \"{1}\"", Expressions [i], e.Message); } } Console.WriteLine ("Всего обработано выражений: {0}", Compute.GetCount ()); Console.ReadKey (); } } }
9. Работаем с объектом "Таблица данных"
Программно создаём таблицу с заданными характеристиками, добавляем туда данные строк и вычисляемый столбец, считаем по формулам с помощью агрегатных выражений.
//C# -> Консольное приложение (.NET Framework) using System; using System.Data; namespace ConsoleApp1 { class MyDataTable { DataTable dt; DataRow dr; DataColumn dc; public MyDataTable (string TableName, string [] ColumnNames, Type [] ColumnTypes) { dt = new DataTable (); dt.TableName = TableName; for (int i = 0; i < ColumnNames.Length; i++) dt.Columns.Add (ColumnNames [i], ColumnTypes [i]); } public DataRow CreateRow () { //Создать строку dr = dt.NewRow (); return dr; } public void Add (DataRow dr) { //Добавить строку в базу dt.Rows.Add (dr); } public DataColumn CreateColumn (string Name, Type type, string expr) { //Создать вычисляемый столбец dc = new DataColumn (); dc.ColumnName = Name; dc.DataType = type; dc.Expression = expr; dt.Columns.Add (dc); return dc; } public void AddRow (DataRow dr) { //Добавить строку в базу dt.Rows.Add (dr); } public string Exec (string expr) { return Convert.ToString (dt.Compute (expr, "")); } } class Program { static void Main () { string [] ColumnNames = { "Имя", "Оплата", "Комиссия"}; Type [] ColumnTypes = { typeof (string), typeof (int), typeof (double) }; MyDataTable Formula = new MyDataTable ("Таблица", ColumnNames, ColumnTypes); //Создали таблицу DataRow dr = Formula.CreateRow (); dr ["Имя"] = "Вася"; dr ["Оплата"] = 30000; dr ["Комиссия"] = 0.15; Formula.AddRow (dr); dr = Formula.CreateRow (); dr ["Имя"] = "Петя"; dr ["Оплата"] = 40000; dr ["Комиссия"] = 0.20; Formula.AddRow (dr); //Добавили данные в строки Console.WriteLine ("Avg(Оплата) = " + Formula.Exec ("Avg(Оплата)")); //Посчитали по формуле DataColumn dc = Formula.CreateColumn ("Итого", typeof (double), "Оплата - Оплата * Комиссия"); Console.WriteLine ("Sum(Итого) = " + Formula.Exec ("Sum(Итого)")); //Посчитали выражение над добавленным столбцом //(30000-30000*0.15)+(40000-40000*0.20) Console.ReadKey (); } } }
10. Запускаем десять потоков и выводим их состояние
Управляем временем выполнения потоков, а также позицией курсора в консоли (для вывода сообщений о состоянии потоков).
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace ConsoleApp1 { internal class Program { private static void Main (string [] args) { var Tasks = new List<Task> (); for (var i = 0; i < 10; i++) Tasks.Add (new Task (Method, TaskCreationOptions.LongRunning)); Tasks.ForEach (t => t.Start ()); var startY = Console.CursorTop; do { PrintStatus (Tasks); Console.CursorTop = startY; } while (!Task.WaitAll (Tasks.ToArray (), TimeSpan.FromSeconds (1))); PrintStatus (Tasks); Console.Write ("Выполнено"); Console.ReadKey (); } private static void PrintStatus (IEnumerable <Task> Tasks) { foreach (var task in Tasks) Console.WriteLine ($"Состояние задачи #{task.Id}: {task.Status}"); } private static void Method () { Thread.Sleep (TimeSpan.FromSeconds (2 * Task.CurrentId ?? 1)); } } }
11. Перегрузка операторов
Показаны все основные виды переопределения операторов в классе C# (кроме некоторых редко используемых нюансов).
using System; namespace ConsoleApp1 { class Point3D { private double x, y, z; //Трёхмерные координаты public Point3D () { x = y = z = 0; } public Point3D (double x, double y, double z = 0) { this.x = x; this.y = y; this.z = z; } public static Point3D operator + (Point3D op1, Point3D op2) { //Перегрузить бинарный оператор + Point3D result = new Point3D (); result.x = op1.x + op2.x; result.y = op1.y + op2.y; result.z = op1.z + op2.z; return result; } public static Point3D operator + (Point3D op1, double op2) { //Перегрузить бинарный + для сложения с числом (вторым операндом) Point3D result = new Point3D (); result.x = op1.x + op2; result.y = op1.y + op2; result.z = op1.z + op2; return result; } public static Point3D operator + (double op1, Point3D op2) { //Перегрузить бинарный + для сложения с числом, если число является первым операндом Point3D result = new Point3D (); result.x = op2.x + op1; result.y = op2.y + op1; result.z = op2.z + op1; return result; } public static Point3D operator - (Point3D op) { // Перегрузить унарный оператор - Point3D result = new Point3D (); result.x = -op.x; result.y = -op.y; result.z = -op.z; return result; } public static bool operator < (Point3D op1, Point3D op2) { // Оператор сравнения return ( Math.Sqrt (op1.x * op1.x + op1.y * op1.y + op1.z * op1.z) < Math.Sqrt (op2.x * op2.x + op2.y * op2.y + op2.z * op2.z) ? true : false ); } public static bool operator > (Point3D op1, Point3D op2) { // "Меньше" и "больше" работают только вместе return ( Math.Sqrt (op1.x * op1.x + op1.y * op1.y + op1.z * op1.z) > Math.Sqrt (op2.x * op2.x + op2.y * op2.y + op2.z * op2.z) ? true : false ); } public static bool operator true (Point3D op) { // Перегрузка true, истинна, если хотя бы 1 координата не равна 0 return op.x != 0 || op.y != 0 || op.z != 0 ? true : false; } public static bool operator false (Point3D op) { // true и false работают только вместе return op.x == 0 && op.y == 0 && op.z == 0 ? true : false; //все координаты равны 0 } public static bool operator | (Point3D op1, Point3D op2) { // Перегрузка логического "или", истинна, если хотя бы одна коррдината ненулевая return op1.x * op2.x != 0 || op1.y * op2.y != 0 || op1.z * op2.z != 0 ? true : false; } public static bool operator & (Point3D op1, Point3D op2) { // Перегрузка логического "и", истинна, если все коррдинаты ненулевые return op1.x * op2.x != 0 & op1.y * op2.y != 0 & op1.z * op2.z != 0 ? true : false; } public static bool operator ! (Point3D op) { // Перегрузка true, ложна, если хотя бы 1 координата не равна 0 return op.x != 0 || op.y != 0 || op.z != 0 ? false : true; } public static implicit operator double (Point3D op1) { //Неявное преобразование типа, выполняется автоматически, когда объект используется //в выражении вместе со значением целевого типа return op1.x * op1.y * op1.z; } public static explicit operator float (Point3D op1) { //Явное преобразование типа return (float)op1.x * (float) op1.y * (float) op1.z; } public override string ToString () { // Вернуть координаты в виде строки, перегрузив метод ToString по умолчанию return this.x + ", " + this.y + ", " + this.z; } } internal class Program { private static void Main () { Point3D a = new Point3D (1, 2, 3); Point3D b = new Point3D (1, 1, 1); Point3D c = new Point3D (); Console.WriteLine ("Координаты точки a: {0}", a.ToString ()); Console.WriteLine ("Координаты точки b: {0}", b.ToString ()); Console.WriteLine ("Координаты точки c: {0}", c.ToString ()); Point3D d = a + b + c; Console.WriteLine ("A + B + C = {0}", d.ToString ()); d = -a; Console.WriteLine ("D = -A = {0}", d.ToString ()); d += 1; //не нужно отдельно перегружать оператор "+="! Console.WriteLine ("D = -A + 1 = {0}", d.ToString ()); d = 1 + d; //а здесь вызовется второй оператор для сложения с числом Console.WriteLine ("D = 1 + D = {0}", d.ToString ()); bool cond = a < b; Console.WriteLine ("A < B = {0}", cond); cond = a > b; Console.WriteLine ("A > B = {0}", cond); if (c) Console.WriteLine ("Точка C истинна"); else Console.WriteLine ("Точка C ложна"); if (d) Console.WriteLine ("Точка D истинна"); else Console.WriteLine ("Точка D ложна"); if (a & d) Console.WriteLine ("a & d истинно"); else Console.WriteLine ("a & d ложно"); if (a | d) Console.WriteLine ("a | d истинно"); else Console.WriteLine ("a | d ложно"); //с "укороченными" формами &&, || такие перегрузки работать не будут if (!c) Console.WriteLine ("Точка !C истинна"); else Console.WriteLine ("Точка !C ложна"); double val = d * 2 + b; // преобразовать в тип double неявно Console.WriteLine ("d * 2 + b = " + val); float fval = (float)b * (float)Math.PI; // преобразовать в тип float явно Console.WriteLine ("b * PI = " + fval); Console.ReadKey (); } } }
12. Три способа преобразовать строку в число
Основные способы и простейшая обработка исключений при преобразовании.
using System; using System.Globalization; namespace ConsoleApp1 { internal class Program { private static void Main () { //Способ 1: Parse Int32 val1 = Int32.Parse ("100"); //100 //простое преобразование Int32 val2 = Int32.Parse ("(200)", NumberStyles.AllowParentheses); //-200 //перегрузка с указанием стиля int val3 = int.Parse ("30,000", NumberStyles.AllowThousands, new CultureInfo ("en-au")); //30000 //перегрузка с указанием стиля и культуры Console.WriteLine ($"{val1} {val2} {val3}"); //Способ 2: Convert val1 = Convert.ToInt32 ("123456"); //123456 val2 = Convert.ToInt32 ("10000",2); //16 val3 = Convert.ToInt32 ("100", 16); //256 Console.WriteLine ($"{val1} {val2} {val3}"); //Способ 3: TryParse string numberStr = "123456"; int number; bool isParsable = Int32.TryParse (numberStr, out number); //123456 if (isParsable) Console.WriteLine (number); else Console.WriteLine ("Неверный формат: "+ numberStr); numberStr = "123,45"; double val; isParsable = double.TryParse (numberStr, NumberStyles.Float, NumberFormatInfo.CurrentInfo, out val); //val будет равно 123,45 , если локаль русская if (isParsable) Console.WriteLine (val); else Console.WriteLine ("Неверный формат: "+ numberStr); //Обработка исключений при преобразованиях 1 и 2: string str1 = "100f"; try { val1 = Int32.Parse (str1); Console.WriteLine (val1); } catch (Exception e) { Console.WriteLine ("Неверный формат Parse: " + str1); } try { val1 = Convert.ToInt32 (str1); Console.WriteLine (val1); } catch (Exception e) { Console.WriteLine ("Неверный формат ToInt32: " + str1); } Console.ReadKey (); } } }
13. Шесть способов преобразовать число в строку
Способы как с добавлением, так и без добавления дополнительного текстового содержимого к полученной строке.
using System; using System.Text; namespace ConsoleApp1 { class Program { private static void Main () { //Способ 1: ToString int val = 0xff; Console.WriteLine (val.ToString ()); //255 val = (int) 1e5; Console.WriteLine (val.ToString ()); //100000 //Способ 2: "+" со строкой val = (int) Math.Floor(Math.PI); string str = "" + val; Console.WriteLine (str); //3 //Способ 3: StringBuilder var builder = new StringBuilder (); //System.Text builder.Append ("There are "); builder.Append (val).ToString (); builder.Append (" wolfs in our forest"); Console.WriteLine (builder); //There are 3 wolfs in our forest //Способ 4: Convert string msg = "There are " + Convert.ToString (val) + " wolfs in our forest"; Console.WriteLine (msg); //There are 3 wolfs in our forest //Способ 5: Format string msg2 = string.Format ("There are {0} wolfs in our forest", val); Console.WriteLine (msg2); //There are 3 wolfs in our forest //Способ 6: $ string msg3 = $"There are {val} wolfs in our forest"; Console.WriteLine (msg3); //There are 3 wolfs in our forest Console.ReadKey (); } } }
14. Индексаторы, свойства и автоматически реализуемые свойства
Пример на индексаторы и свойства C#.
Ограничения индексаторов таковы: значение, отдаваемое индексатором,
нельзя передавать методу в качестве параметра ref
или out
, поскольку в индексаторе
не определено место в памяти для его хранения. Индексатор должен быть членом своего класса и поэтому не может быть объявлен как static
.
Cвойство сочетает в себе поле с методами доступа к нему и состоит из имени и
аксессоров get
и set
. Аксессоры служат для получения и установки значения переменной. Имя свойства может быть использовано в выражениях и операторах присваивания аналогично имени обычной переменной, но в действительности при обращении к свойству по имени автоматически
вызываются его аксессоры get
и set
.
Свойства не определяют место в памяти для хранения полей, а лишь управляют доступом к полям. Это означает, что само свойство не предоставляет поле, поэтому поле должно быть определено независимо от свойства. Свойство также не должно изменять состояние базовой переменной при вызове аксессора get
.
Исключение из этого правила составляет автоматически реализуемое свойство.
Автоматически реализуемое свойство не может быть доступно только для чтения или только для записи. При его объявлении нужно указывать оба аксессора — get
и set
, хотя любой из них можно сделать приватным, доступным только методам своего класса.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { public class Array <T> where T : new() { //"Отказоустойчивый" вектор с индексаторами T [] a; //Ссылка на базовый массив public int Length { get; set; } //Длина массива сделана автосвойством public bool ErrFlag { get; private set; } //Результат последней операции - автосвойство "только для чтения" извне public Array (int size) { //Конструктор if (size > 0) { a = new T [size]; Length = size; } else Length = 0; } public T this [int index] { //Индексатор get { //Аксессор get if (IndexIsValid (index)) { ErrFlag = false; return a [index]; } else { ErrFlag = true; return default (T); } } set { //Аксессор set; получает неявный параметр value! if (IndexIsValid (index)) { a [index] = value; ErrFlag = false; } else ErrFlag = true; } } public T this [double index] { //Ещё один индексатор, для индекса типа double get { int intIndex = (int) Math.Round (index); return this [intIndex]; } set { int intIndex = (int) Math.Round (index); this [intIndex] = value; } } public bool this [bool index] { //Индексатор без set, только для чтения, возвращает состояние this.ErrFlag get { return ErrFlag; } //Аксессор set отсутствует } private bool IndexIsValid (int index) { if (Length < 1) return false; return (index >= 0 & index < Length ? true : false); } } public class Array2D <T> where T : new() { //"Отказоустойчивая" матрица с индексаторами T [,] a; //Ссылка на базовый массив int rows, cols; //Количество строк и столбцов, приватные int len; //Длина массива, на этот раз приватная public int Length { //Получим её свойством "только для чтения" get { return len; } } bool err; //Результат последней операции, приватный public bool ErrFlag { //Получим его свойством "только для чтения" get { return err; } } public Array2D (int r, int c) { if (r > 0 & c > 0) { rows = r; cols = c; a = new T [rows, cols]; len = rows * cols; } else len = 0; } public void Print (String hdr = "") { //Вывод в консоль Console.WriteLine (); Console.WriteLine (hdr); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) Console.Write ("{0} ", a [i, j]); Console.WriteLine (); } } public T this [int row, int col] { //Двумерный индексатор get { if (IndexIsValid (row, col)) { err = false; return a [row, col]; } else { err = true; return default (T); } } set { if (IndexIsValid (row, col)) { a [row, col] = value; err = false; } else err = true; } } public int Rows { //свойство Rows для изменения приватного количества строк get { return rows; } set { if (value > -1 & value < rows) rows = value; } } public int Cols { //свойство Rows для изменения приватного количества столбцов get { return cols; } set { if (value > -1 & value < cols) cols = value; } } private bool IndexIsValid (int r, int c) { if (len < 1) return false; return ( r > -1 & r < rows & c > -1 & c < cols ? true : false ); } } class Program { static void Main (string [] args) { Array <int> arr = new Array <int> (5); for (int i = 0; i < arr.Length + 1; i++) { //Берём 1 лишний элемент! arr [i] = i + 1; Console.WriteLine (arr [i] + " (ErrFlag = "+ arr.ErrFlag + ")"); } Array <double> darr = new Array <double> (5); for (double x = 0.1; x < arr.Length ; x += 0.9) { //Берём 1 лишний элемент, 2-й индексатор darr [x] = x + 1; Console.WriteLine (arr [x] + " (ErrFlag = " + arr.ErrFlag + ")"); } Console.WriteLine ("arr [true] = " + arr [true]); //Третий индексатор Array2D <int> matr = new Array2D<int> (2, 2); for (int i = 0; i < 3; i++) { //Лишняя строка Console.WriteLine(); for (int j = 0; j < 2; j++) { matr [i, j] = i + j; Console.Write ("{0:D} ({1}) ", matr [i, j], matr.ErrFlag); } } matr.Rows = 2; //Меняем количество строк с помощью свойства matr.Print ("Матрица после изменения количества строк"); Console.ReadKey (); } } }
15. Наследование
Довольно развёрнутый пример, показывающий особенности реализации наследования в классах C#.
Абстрактный, базовый и производный классы, приватные данные и публичные свойства-"обёртки" над ними, абстрактные и виртуальные методы, приватные, публичные и защищённые члены класса, неуниверсальный статический класс с расширениями, класс, запрещённый к наследованию, массив из объектов базового класса.
//C# -> Консольное приложение (.NET Framework) using System; using System.Collections.Generic; namespace ConsoleApp1 { abstract class Shape { //Абстрактный класс "фигура" public abstract string GetTypeString (); //Абстрактный метод, должен быть определён у потомков } class Shape2D : Shape { //Базовый класс - рамка фигуры double width, height; //Приватные ширина и высота int frac = 2; //Количество знаков в дробной части при выводе public Shape2D (double width = 0, double height = 0, int frac = 2) { //Конструктор базового класса-"рамки" Width = width; Height = height; this.frac = frac; } public double Width { // Публичные свойства ширины и высоты двумерного объекта get { return width; } set { width = value < 0 ? -value : value; } } public double Height { get { return height; } set { height = value < 0 ? -value : value; } } public int Frac { //Публичное свойство "количество знаков в дробной части" get { return frac; } set { width = frac < 1 ? 0 : ( frac > 14 ? 14 : value ); } } public double RoundTo (double d) { //Округлить до нужного количества знаков return Math.Round (d, Frac); } public string GetSizeString () { //Строка с габаритами return RoundTo (Width) + " x " + RoundTo (Height); } public override string GetTypeString () { //Реализация абстрактного метода return "рамка фигуры"; } public virtual double Area () { //Виртуальный метод "площадь рамки" return RoundTo (Width * Height); } } class Triangle : Shape2D { //Производный класс - треугольник protected string Style; //Тип треугольника, защищённое свойство protected double A, B, C; //Стороны треугольника, защищённые свойства public Triangle (double A = 3, double B = 4, double C = 5) { if (A + B > C & A + C > B & B + C > A) { double [] Temp = new double [] { A , B , C }; Array.Sort (Temp); this.C = Width = Temp [2]; this.B = Temp [1]; this.A = Temp [0]; Height = GetHeight (Temp [1]); var Temp2 = new List <double> () { Temp [0] * Temp [0], Temp [1] * Temp [1], Temp [2] * Temp [2] }; this.Style = Temp2 [2] < Temp2 [0] + Temp2 [1] ? (Temp2 [0] == Temp2 [1] ? "равносторонний" : "остроугольный") : (Temp2 [2] > Temp2 [0] + Temp2 [1] ? "тупоугольный" : "прямоугольный"); } else { this.A = this.B = this.C = Width = Height = 0; this.Style = "не существует"; } } private double GetHeight (double a) {//высота, опущенная на сторону a (большую из всех) double P = ( A + B + C ) / 2; return RoundTo (2 * Math.Sqrt (P * ( P - A ) * ( P - B ) * ( P - C )) / A); } public override double Area () { //Площадь треугольника double P = ( A + B + C ) / 2; return RoundTo (Math.Sqrt(P * (P - A) * (P - B) * (P - C))); } public override string GetTypeString () { //Переопределение виртуального метода return Style; } } public static class Measures { //Неуниверсальный статический класс с расширениями public static double ToRadians (this double angleInDegree) { //Угол в градусах - в радианы return ( angleInDegree * Math.PI ) / 180.0; } } sealed class Triangle2С : Triangle { //Производный от треугольника - треугольник, заданный двумя сторонами и углом между ними //Этот класс наследовать уже нельзя (sealed) public Triangle2С (double A, double B, double angleC) : base (A, B, Math.Sqrt(Math.Pow(A,2) + Math.Pow(B,2) - 2*A*B*Math.Cos(Measures.ToRadians(angleC)))) { //использует конструктор базового класса, вычислив по теореме косинусов третью сторону } } class Program { //Главный класс, демонстрация static void Main (string [] args) { Triangle t1 = new Triangle (); Console.WriteLine ("Сведения об объекте t1: тип {0}, габариты {1}", t1.GetTypeString (), t1.GetSizeString ()); Console.WriteLine ("Площадь равна " + t1.Area () + System.Environment.NewLine); Triangle t2 = new Triangle (4,4,4); Console.WriteLine ("Сведения об объекте t2: тип {0}", t2.GetTypeString ()); Console.WriteLine ("Габариты равны {0}x{1}", t2.Width, t2.Height); Console.WriteLine ("Площадь равна " + t2.Area ()); Console.WriteLine (); Triangle2С t3 = new Triangle2С (4,4,60); //Совпадёт со 2-м, но задан по-другому Console.WriteLine ("Сведения об объекте t3: тип {0}, габариты {1}", t3.GetTypeString (), t3.GetSizeString ()); //тип изменился с "равносторонний" на "остроугольный" из-за погрешностей при расчёте C! Console.WriteLine ("Площадь равна " + t3.Area ()); Console.WriteLine (); Shape2D [] Shapes = new Shape2D [4]; //Массив объектов базового класса Shapes [0] = t1; Shapes [1] = t2; Shapes [2] = t3; Shapes [3] = new Shape2D(3,-4,1); //Свойство Height превратит "-4" в "4" foreach (var Shape in Shapes) Console.WriteLine ($"Тип объекта = {Shape.GetTypeString()}"); Console.WriteLine (); Console.ReadKey (); } } } //namespace
Далее показан вывод этого приложения:
Сведения об объекте t1: тип прямоугольный, габариты 5 x 4 Площадь равна 6 Сведения об объекте t2: тип равносторонний Габариты равны 4x3,46 Площадь равна 6,93 Сведения об объекте t3: тип остроугольный, габариты 4 x 3,46 Площадь равна 6,93 Тип объекта = прямоугольный Тип объекта = равносторонний Тип объекта = остроугольный Тип объекта = рамка фигуры
16. Упаковка и распаковка
Все типы в С#, включая простые типы значений, являются производными от класса object. Следовательно, ссылкой типа object можно воспользоваться для обращения к любому другому типу, в том числе и к типам значений.
Когда ссылка на объект класса object используется для обращения к типу значения, такой процесс называется упаковкой. Упаковка приводит к тому, что значение простого типа сохраняется в экземпляре объекта, т.е. "упаковывается" в объекте, который затем используется как и любой другой объект. Но в любом случае упаковка происходит автоматически. Для этого достаточно присвоить значение переменной ссылочного типа object, а об остальном позаботится компилятор.
Распаковка представляет собой процесс извлечения упакованного значения из объекта. Это делается с помощью явного приведения типа ссылки на объект класса object к соответствующему типу значения. Попытка распаковать объект в другой тип может привести к ошибке времени выполнения.
В примере значение типа int передаётся в качестве аргумента методу Sqr(), который, в свою очередь, принимает параметр типа object.
Также показана работа с массивом из разнотипных элементов, точнее, из элементов базового класса object.
//C# -> Консольное приложение (.NET Framework) using System; namespace ConsoleApp1 { class BoxingDemo { int x; BoxingDemo (int x = 0) { this.x = x; } //Конструктор public override string ToString () { return x.ToString (); } //Переопределение метода ToString класса object public static int Sqr (object о) { //Метод для возведения в квадрат с параметром типа object return (int) о * (int) о; } } class Program { //Главный класс Program static int Main () { int x = 10; Console.WriteLine ("x = " + x); x = BoxingDemo.Sqr (x); // значение x автоматически упаковывается, когда оно передается методу Sqr() Console.WriteLine ("x^2 = " + x); object obj = x; // упаковать значение переменной х в объект int y = (int) obj; // распаковать значение из объекта, доступного по ссылке obj, в переменную типа int Console.WriteLine ("y = " + y); object [] arr = new object [3]; //массив из разнотипных значений arr [0] = x; arr [1] = (double) x + 0.5; arr [2] = "Привет"; for (int i = 0; i < arr.Length; i++) { var item = arr [i]; var type = item.GetType (); Console.WriteLine ($"arr[{i}] = {item} ({type})"); } Console.ReadKey (); return 0; } } //Program } //namespace
17. Табулирование функции
В отличие от примера 1, не применяем делегатов, но проверяем допустимость входных данных и не выпускаем пользователя из цикла ввода, пока он не введёт корректные данные. Функция для примера такая:
функция для табулирования на C#
using System; namespace ConsoleApp1 { class Program { static void Main() { double x1, dx, x2, x, y; //Хитрый ввод Console.Clear(); while (true) { Console.Write("Хотите ввести данные (y/n)? "); ConsoleKeyInfo key = Console.ReadKey(); if (key.Key == ConsoleKey.N) { x1 = -5.0; dx = 0.05; x2 = 5.0; //поставить значения по умолчанию break; } else if (key.Key == ConsoleKey.Y) { try { Console.WriteLine(); Console.Write("X1="); x1 = Convert.ToDouble(Console.ReadLine()); Console.Write("dX="); dx = Convert.ToDouble(Console.ReadLine()); Console.Write("X2="); x2 = Convert.ToDouble(Console.ReadLine()); } catch (System.FormatException ex) { Console.WriteLine(ex.Message); continue; } if (!(Math.Abs(dx) > 1e-4 & (dx > 0 & x1 + dx <= x2 || dx < 0 & x1 + dx >= x2))) { //Дополнительные проверки корректности данных - //шаг не слишком мал и есть хотя бы 2 строки таблицы. //Сверху количество шагов не ограничиваем Console.WriteLine ("Введите допустимые начальное значение, шаг, конечное значение"); continue; } break; } } //Обработка и форматный вывод string OutputFormat = "{0,10:F4}\t{1,10:F4}"; Console.Clear(); Console.WriteLine(OutputFormat, "x", "y"); if (x1 > x2) { double t = x1; x1 = x2; x2 = t; dx = -dx; } for (x = x1; x <= x2; x += dx) { if (Math.Abs(x) < 1.0E-12) y = Double.NaN; //учёл разрыв - на самом деле, формально его в моём варианте нет else if (x < 0) y = Math.Log(Math.Abs(x)); else y = Math.Sqrt(x); Console.WriteLine(OutputFormat, x, y); } Console.ReadKey(); } } }
18. Простейший консольный калькулятор на C#
На 4 арифметических действия, с базовыми проверками корректности и возможностью вычислить несколько выражений.
using System; namespace Calculator { class Calculator { public static double DoOperation(double num1, double num2, string op) { double result = double.NaN; //Ставим результату по умолчанию значение "не число" //Смотрим действие и выполняем switch (op) { case "+": result = num1 + num2; break; case "-": result = num1 - num2; break; case "*": result = num1 * num2; break; case "/": if (num2 != 0) { result = num1 / num2; } break; default: break; } return result; } } class Program { static void Main(string[] args) { bool endApp = false; //Выводим слова Console.WriteLine("Консольный калькулятор на C#\n"); do { string numInput1 = ""; string numInput2 = ""; double result = 0; Console.Write("Введите первый аргумент и нажмите Enter: "); numInput1 = Console.ReadLine(); double cleanNum1 = 0; while (!double.TryParse(numInput1, out cleanNum1)) { Console.Write("Это неверный ввод. Пожалуйста, введите допустимое число: "); numInput1 = Console.ReadLine(); } Console.WriteLine("Введите действие из списка:"); Console.WriteLine("\t+ - Сложить"); Console.WriteLine("\t- - Вычесть"); Console.WriteLine("\t* - Умножить"); Console.WriteLine("\t/ - Разделить"); Console.Write("Ваш выбор? "); string op = Console.ReadLine().Substring(0, 1); string ops = "+-*/"; while (ops.IndexOf(op) == -1) { Console.Write("Это неверный ввод. Пожалуйста, выберите допустимое действие: "); op = Console.ReadLine(); if (op.Length > 1) op = op.Substring(0, 1); } Console.Write("Введите второй аргумент и нажмите Enter: "); numInput2 = Console.ReadLine(); double cleanNum2 = 0; while (!double.TryParse(numInput2, out cleanNum2)) { Console.Write("Это неверный ввод. Пожалуйста, введите допустимое число: "); numInput2 = Console.ReadLine(); } try { result = Calculator.DoOperation(cleanNum1, cleanNum2, op); if (double.IsNaN(result) | double.IsInfinity(result)) { Console.WriteLine("Операция не может быть выполнена\n"); } else Console.WriteLine("Ваш результат: {0:0.##}\n", result); } catch (Exception e) { Console.WriteLine("Ошибка при использовании Math.\nДетали: " + e.Message); } Console.Write("Нажмите 'n' и Enter для выхода или любую клавишу и Enter для продолжения: "); op = Console.ReadLine(); if (op.Length > 1) op = op.Substring(0, 1); if (op == "n") endApp = true; Console.WriteLine("\n"); } while (!endApp); return; } } }
19. Простой класс таймера на C#
using System; namespace ConsoleApp1 { class Timer { private System.Diagnostics.Stopwatch t; private bool started; public void Start() { t = new System.Diagnostics.Stopwatch(); t.Start(); started = true; } public double Stop() { if (started) { t.Stop(); started = false; return t.ElapsedMilliseconds; } return Double.NaN; } } class Program { static void Main(string[] args) { Timer t = new Timer(); Console.WriteLine("Сейчас запустим таймер и паузу в 1 сек."); t.Start(); System.Threading.Thread.Sleep(1000); double time = t.Stop(); Console.WriteLine("Измеренное время = "+time+" мс"); } } }
Простым тестом для приложения служит останов выполнения потока на 1 секунду с выводом результата измерения времени останова. Вместо останова, разумеется, можно вызывать любой вычислительный процесс, продолжительность которого нужно измерить.
20. Вызываем функцию через заданный интервал времени
using System; namespace ConsoleApp1 { class Program { private static void Callback(Object o) { //Функция, которая будет вызываться с периодом Console.SetCursorPosition(0, 0); Console.WriteLine("{0:T}\n", DateTime.Now); } static void Main(string[] args) { System.Threading.Timer t = new System.Threading.Timer(Callback,null,0,1000); //вызывать Callback каждую секунду (1000 мс) Console.Clear(); Console.CursorVisible = false; do { //остаёмся в цикле до нажатия Esc ConsoleKeyInfo key = Console.ReadKey(true); if (key.Key == ConsoleKey.Escape) break; } while (true); t.Dispose(); } } }
Callback-функцию мы написали такой, чтобы она выводила в верхней строке консоли идущие часы. Для завершения работы достаточно нажать клавишу Esc
.
21. Простой класс "Прямоугольник"
Стороны параллельны осям координат, но положение левого верхнего угла корректируется в зависимости от положительности или отрицательности переданных ширины и высоты.
using System; namespace ConsoleApplication1 { class Rectangle { //Простейший класс "Прямоугольник" со сторонами, параллельными осям //координат, заданный координатами левого верхнего угла, шириной и высотой private double left = 0, top = 0, width = 0, height = 0; public Rectangle (double left, double top, double width, double height) { //корректируем данные и пишем из аргументов конструктора в объект this if (width < 0) { left += width; width = -width; } if (height < 0) { top += height; height = -height; } this.left = left; this.top = top; this.width = width; this.height = height; } public void Print (string header = null) { //Вывод заголовка header и координат 2 углов в консоль if (!String.IsNullOrEmpty (header)) { if (header.Trim ().Length > 0) Console.Write (header + " "); } Console.WriteLine ($"({left:F2},{top:F2}) - " + $"({(left+width):F2},{(top+height):F2})"); } public double Area () { //Площадь return width * height; } public double Perimeter () { //Периметр return 2 * (width + height); } } class Program { static void Main (string [] args) { Rectangle r = new Rectangle (-3,-5,-10,6); //Создали прямоугольник r.Print (); //Вывели координаты 2 углов Console.WriteLine ($"Area={r.Area():F2}, Perimeter={r.Perimeter ():F2}"); //Вывели площадь и периметр Console.ReadKey (); } } }
22. Перекодируем строку в UTF-8 и обратно
Проверить, какая у вас кодовая страница в запущенном окне консоли можно, нажав комбинацию клавиш Alt
+Пробел
, выбираем команду меню Свойства, далее в окне свойств выбираем вкладку Настройки, см. "Текущая кодовая страница".
В современных версиях Studio по умолчанию везде UTF-8, никаких телодвижений совершать не нужно.
Шрифт, установленный в это же окне консоли на одноимённой вкладке должен быть типа True Type и таким, что он поддерживает кодовую страницу Юникода UTF-8 (например, Consolas).
using System; using System.Text; using System.Text.RegularExpressions; namespace ConsoleApp1 { class Program { static void Main () { Console.OutputEncoding = Encoding.GetEncoding (1251); //Кодовая страница "русская однобайтовая Windows" в консоль String Utf8String = ToUnicodeString ("Привет, это тест"); Console.WriteLine (Utf8String); Console.WriteLine (FromUnicodeString (Utf8String)); Console.ReadKey (); } static string FromUnicodeString (string str) { string toReturn = ""; toReturn = Regex.Unescape (str); return toReturn; } static string ToUnicodeString (string str) { StringBuilder sb = new StringBuilder (); foreach (var c in str) { sb.Append ("\\u" + ( (int) c ).ToString ("X4")); } return sb.ToString (); } } }
23. Получаем список папок и файлов рекурсивно
Метод GetList
класса DirectoryList
позволяет рекурсивно обойти вложенные папки, начиная с указанного пути. Для вложенных папок слева делается отступ в 2 пробела, чтобы список был древовидным.
Сохраняя данные в ArrayList
или StringBuilder
можно собрать информацию о нужных файлах и папках.
using System; using System.IO; namespace ConsoleApp1 { class DirectoryList { private static void Print1 (string s, uint level) { //Делаем отступ слева для вложенных папок for (int i = 0; i < level; i++) Console.Write (" "); Console.WriteLine (s); } public static void GetList (string Folder, uint level) { try { Print1 (Folder, level); foreach (string f in Directory.GetFiles (Folder)) { Print1 (f, level); } foreach (string d in Directory.GetDirectories (Folder)) { GetList (d, level + 1); } } catch (System.Exception e) { Console.WriteLine (e.Message); } } } class Program { static void Main () { DirectoryList.GetList ("D:\\Temp",0); //укажите свой путь к нужной папке Console.ReadLine (); } } }
Заметим, что встроенной функцией возможен поиск по маске и во вложенных папках, по принципу
try { Directory.GetFiles ("D:\\", "*.*", SearchOption.AllDirectories); } catch (System.Exception e) { Console.WriteLine (e.Message); }
24. Построчное чтение текстового файла
Продемонстрируем чтение строк из текстового файла test.txt
в кодировке Юникода UTF-8, располагающегося в той же папке, что и файл Program.cs
.
В реальном приложении можно получить имя файла от пользователя или из стандартного системного диалога открытия файла.
using System; using System.IO; class Example { static void Main () { StreamReader file = null; try { file = new StreamReader ("test.txt"); } catch (Exception e) { Msg ("Ошибка: " + e.Message); } string line; do { line = file.ReadLine (); Console.WriteLine (line); } while (!file.EndOfStream); file.Close (); Msg ("Файл прочитан"); } static void Msg (string msg) { Console.WriteLine (msg + "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } }
25. Построчная запись текстового файла
С помощью объекта класса StreamWriter
и метода WriteLine
, имеющего множество перегрузок, можно записать файл, содержащий необходимые данные. В качестве примера запишем в файл таблицу значений функции f(x) = x2
для значений x = 1, 1.5, 2, ..., 10
.
using System; using System.IO; class Example { static void Main () { StreamWriter file = null; try { file = new StreamWriter ("test.txt"); } catch (Exception e) { Msg ("Ошибка: " + e.Message); } string line; for (double x = 1.0; x <= 10.0; x+= 0.5) { file.WriteLine ($"{x,10:F2}{f(x),10:F2}"); } file.Close (); Msg ("Файл записан"); } static double f (double x) { return x * x; } static void Msg (string msg) { Console.WriteLine (msg + "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } }
26. Чтение массива чисел из текстового файла
Достаточно часто требуется получать из файла наборы числовых данных. Продемонстрируем чтение массива вещественных чисел отдельным примером.
Чтобы код получился компактным, мы прочитаем в строку всё содержимое файла методом ReadAllText
, затем строковым методом Split
получим набор лексем (слов) words
, разделённых допустимыми разделителями (пробел, табуляция, перевод строки), наконец, с учётом российской локали методом TryParse
"вытащим" все допустимые числа из words
и добавим их в ArrayList
, который затем легко преобразовать в "обычный" массив a
. При таком подходе не произойдёт сбоя, если в файле есть не только допустимые лексемы и даже если нет ни одной допустимой.
using System; using System.IO; using System.Globalization; using System.Collections; class Example { static void Main () { string file = null; try { file = File.ReadAllText ("test.txt"); } catch (Exception e) { Msg ("Ошибка: " + e.Message); } char [] separators = new char [] { ' ', '\t', '\r' }; var words = file.Split (separators, StringSplitOptions.RemoveEmptyEntries); ArrayList arr = new ArrayList(); double d; CultureInfo culture = CultureInfo.CreateSpecificCulture ("ru-RU"); NumberStyles style = NumberStyles.Number | NumberStyles.AllowCurrencySymbol; foreach (var word in words) { bool b = double.TryParse (word, style, culture, out d); if (b) arr.Add (d); } var a = arr.ToArray (); foreach (double f in a) Console.Write ($"{f} "); Msg ($"\nФайл прочитан, всего чисел: {a.Length}"); } static void Msg (string msg) { Console.WriteLine (msg + "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } }
27. Файл с произвольным доступом по байтам
В приведенном ниже коде демонстрируется ввод-вывод в файл с произвольным доступом. Сначала в файл записываются прописные буквы английского алфавита, а затем несколько случайных букв считывается из файла.
using System; using System.IO; class Example { static void Main () { FileStream f = null; char ch; try { f = new FileStream ("test.dat", FileMode.Create); //Записать английский алфавит в файл: for (int i = 0; i < 26; i++) f.WriteByte ((byte) ( 'A' + i )); //Cчитать отдельные буквы английского алфавита из случайных позиций: Random r = new Random (); for (int i = 0; i < 5; i++) { int Pos = r.Next (26); f.Seek (Pos, SeekOrigin.Begin); //найти байт ch = (char) f.ReadByte (); Console.WriteLine ($"Символ номер {i}={ch}"); } } catch (IOException e) { Msg ("Ошибка ввода-вывода: " + e.Message); } finally { if (f != null) f.Close (); } Msg ("Успешное завершение"); } static void Msg (string msg) { Console.WriteLine (msg + "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } }
28. Чтение и запись кириллицы
Если требуется чтение и запись не отдельных байт, а символов Юникода (например, кириллицы), достаточно указать требуемую кодировку из перечисления System.Text.Encoding
при создании потока StreamWriter
или StreamReader
.
В следующем примере мы применим оператор using
для контроля объектов StreamWriter
и StreamReader
.
Создав поток записи FileStream
, "отдадим" его объекту StreamWriter
с указанием нужной кодировки. Затем запишем в файл 10 слов из случайных букв кириллицы.
Случайное слово генерирует метод RandomWord
, которому аргументами передаётся допустимый алфавит letters
(набор символов) и генератор случайных чисел rnd
(если создавать генератор внутри метода, "случайность" получаемых строк может нарушиться).
После этого, аналогичным образом создав поток чтения и передав его в StreamReader
, последовательно прочитаем все символы файла. Чтение файла продолжается до тех пор, пока метод Peek
может проверить наличие в потоке чтения следующего символа. Получив методом Read
целочисленный код символа, мы проверяем стандартным методом IndexOf
, есть ли такой символ в алфавите, и если значение index
оказывается допустимым, учитываем вхождение символа в частотной таблице freq
.
По завершении чтении файла в консоль выводится частотная таблица символов алфавита letters
.
using System; using System.IO; using System.Text; class Example { static void Main () { char [] letters = "абвгдеёжзийклмнопрстуфхцчшщъыьэюя".ToCharArray (); FileStream f = null; StreamWriter writer = null; Encoding Encoding = Encoding.UTF8; try { f = new FileStream ("test.txt", FileMode.Create, FileAccess.Write); writer = new StreamWriter (f, Encoding); Random rnd = new Random (); using (writer) { for (int i = 0; i < 10; i++) writer.Write (RandomWord (letters, rnd) +"\n"); } writer.Close (); f.Close (); } catch (IOException e) { Msg ("Ошибка записи файла: " + e.Message); } finally { if (writer != null) writer.Close (); if (f != null) f.Close (); } StreamReader reader = null; int [] freq = new int [letters.Length]; char first = letters [0]; try { f = new FileStream ("test.txt", FileMode.Open, FileAccess.Read); reader = new StreamReader (f, Encoding); string symbols = new string (letters); using (reader) while (reader.Peek() > -1) { int c = reader.Read (); int index = symbols.IndexOf ((char) c); if (index > -1 & index < letters.Length) freq [index]++; } reader.Close (); f.Close (); } catch (IOException e) { Msg ("Ошибка чтения файла: " + e.Message); } finally { if (reader != null) reader.Close (); if (f != null) f.Close (); } for (int i = 0; i < letters.Length; i++) { Console.Write ($"{letters[i],1}: {freq[i],-3}"); } Msg ("\nУспешное завершение"); } static void Msg (string msg) { Console.WriteLine (msg + "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } static string RandomWord (char [] letters, Random rnd) { int len = rnd.Next (3, 10); string word = ""; for (int i = 0; i < len; i++) { int n = rnd.Next (0, letters.Length); word += letters [n]; } return word; } }
29. В заданной строке найти количество вхождений символа
Предполагается, что ищем букву или цифру, регистр букв при поиске игнорируется.
using System; class Example { static void Main () { Console.WriteLine ("Введите строку: "); string str = Console.ReadLine ().Trim(); if (str.Length < 1) Msg ("Строка пуста"); Console.WriteLine ("Введите символ: "); string chars = Console.ReadLine ().Trim (); if (chars.Length < 1) Msg ("Требуется хотя бы 1 символ"); char c = char.Parse(chars.Substring (0, 1)); if (Char.IsLetterOrDigit(c) == false) Msg ("Требуется буква или цифра"); Console.WriteLine (); int cnt = CountNumberOfChars (str, c.ToString ()); Msg ($"Найдено символов: {cnt}"); } static void Msg (string msg) { Console.WriteLine (msg+ "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } static int CountNumberOfChars (string str, string search) { //найдёт количество вождений подстроки search в строку str, //регистр символов игнорируется string s0 = str.ToUpper().Replace (search.ToUpper(), ""); int count = (str.Length - s0.Length) / search.Length; return count; } }
30. Самое длинное слово
В заданной строке найти самое длинное слово. Слова могут быть разделены пробелом, табуляцией или переводом строки, при определении длины слова учитываются только алфавитно-цифровые символы и единственный дефис, стоящий в любой позиции, кроме первой и последней.
using System; using System.Text; class Example { static void Main () { Console.WriteLine ("Введите строку: "); string str = Console.ReadLine ().Trim(); if (str.Length < 1) Msg ("Строка пуста"); char [] separators = new char [] { ' ', '\t', '\r' }; string [] words = str.Split (separators, StringSplitOptions.RemoveEmptyEntries); //удалить пустые слова string maxw = ""; foreach (var word in words) { string w = Filter (word); if (w.Length > maxw.Length) maxw = w; } if (maxw.Length < 1) maxw = "Не найдено допустимых слов"; Msg ($"Самое длинное слово: {maxw}"); } static void Msg (string msg) { Console.WriteLine (msg+ "\nEnter для выхода"); Console.ReadLine (); Environment.Exit (0); //выйти из приложения } static string Filter (string str) { StringBuilder res = new StringBuilder (); bool hyphen = false; //наличие дефиса int pos = 0; //счётчик позиций foreach (char c in str) { if (Char.IsLetterOrDigit (c) == true) res.Append (c); else if (c == '-' & !hyphen) { //дефис кроме первой и последней позиций if (pos > 0 && pos < str.Length - 1) { res.Append (c); hyphen = true; } } pos++; } string resstr = res.ToString (); return resstr == "-" ? "" : resstr; } }
Использование StringBuilder
в данном случае экономит ресурсы, так как при операциях со String
, например, при сложении строки с новым символом, каждый раз возвращается новая строка.
Заметим, что эта и подобные ей задачи на фильтрацию строк текста быстрее (но не всегда экономичнее) решаются с помощью регулярных выражений.
31. Преобразуем список списков строк в зубчатый массив
Преобразование выполняем с помощью System.Linq
.
using System; using System.Linq; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main (string[] args) { List <List <string>> list = new List <List <string>> () { new List <string> () { "C++", "C#" }, new List <string> () { "Java", "Javascript", "Python" } }; string [][] arr = list.Select(row => row.ToArray()).ToArray(); foreach (var row in arr) Console.WriteLine (String.Join("\t",row)); Console.ReadLine (); } } }
32. Преобразование строк в массивы и обратно
using System; namespace ConsoleApplication1 { class Program { static void Main (string[] args) { string str = "Привет, я строка текста!"; //1. Преобразовать строку в массив символов char [] chars = str.ToCharArray(); foreach (char c in chars) Console.Write ($"{c} "); Console.WriteLine (); //2. Преобразовать массив обратно в строку string control = string.Join("",chars); Console.WriteLine (control); //... или для других типов данных и разделителей: string [] names = {"Билл","Том","Василий"}; string namesStr = string.Join(",",names); Console.WriteLine (namesStr); double [] nums = {1.5,2.5,-3.5}; string numsStr = string.Join(";",nums); Console.WriteLine (numsStr); //3. Получить из строки массив слов, разделённых //допустимыми разделителями separators char [] separators = {' ',',','!',':',';','?'}; string [] words = str.Split(separators,StringSplitOptions.RemoveEmptyEntries); foreach (string word in words) Console.WriteLine ($"{word}"); Console.ReadLine (); } } }
33. Рекурсивно сократить дробь x/y
Применяем рекурсивный алгоритм поиска НОД, числитель и знаменатель могут быть положительными или отрицательными.
//Сократить дробь вида x/y с помощью рекурсивного поиска //наибольшего общего делителя для x и y (CGD) using System; namespace ConsoleApplication1 { class Program { ///Чтение целого числа из консоли с приглашением к вводу ///msg. Вернёт true или false (введено ли допустимое число), ///само число запишет в val public static bool ReadInt (string msg, ref int val) { Console.Write (msg); string input = Console.ReadLine (); bool result = int.TryParse (input, out int v); if (result) val = v; return result; } ///Рекурсивное сокращение дроби x/y. Новая дробь запишется ///как a/b public static void GCD (int x, int y, ref int a, ref int b) { if (y == 0) { a /= x; b /= x; return; } GCD (y, x % y, ref a, ref b); } static void Main (string[] args) { int x = 0,y = 0,a,b; Console.WriteLine ("Вводите дроби; не число - выход"); do { if (ReadInt ("Числитель = ", ref x) == false || ReadInt ("Знаменатель = ", ref y) == false) break; a = x; b = y; GCD (x, y, ref a, ref b); Console.WriteLine ($"{x}/{y}={a}/{b}"); } while (true); } } }
34. Максимум и второй максимум
В массиве целых чисел найти максимум и второй максимум, учитывая случай, когда все элементы массива одинаковы.
using System; namespace ConsoleApp1 { class Program { static void Main () { const int n = 5; double [] arr = new double [n] { 4,5,5,5,5 }; double max = Double.MinValue, max2 = Double.MinValue; foreach (var i in arr) { if (i > max) { max2 = max; max = i; } else if (i > max2 && i < max) max2 = i; } Console.WriteLine ($"max={max}"); if (max2 != Double.MinValue) Console.WriteLine ($"max2={max2}"); else Console.WriteLine ("max2 not found"); Console.ReadLine (); } } }
35. Поменять местами 2 цифры целого числа
Поменять местами 2 произвольных цифры целого числа (положительного или отрицательного). Нумерация цифр выполняется слева направо, начиная с 1.
Проще всего применить преобразование числа в строку и обратно.
using System; namespace ConsoleApp1 { class Program { static int Change2Digits (int n, int d1, int d2) { int d = Math.Abs(n), sign = Math.Sign(n); int l = d.ToString().Length-1; int pos1 = Math.Min(d1,d2)-1, pos2 = Math.Max(d1,d2)-1; if (pos1 < 0 || pos2 < 0 || pos1 > l || pos2 > l || pos1 == pos2) return n; string s = d.ToString (); string res = String.Empty; if (pos1 > 0) res += s.Substring (0, pos1); res += s.Substring (pos2, 1); if (pos2 > pos1 + 1) res += s.Substring (pos1+1, pos2-pos1-1); res += s.Substring (pos1, 1); if (pos2 < s.Length - 1) res += s.Substring (pos2+1); return sign*Convert.ToInt32(res); } static void Main () { int n = -12345; Console.WriteLine (Change2Digits (n, 1, 2)); Console.WriteLine (Change2Digits (n, 1, 3)); Console.WriteLine (Change2Digits (n, 2, 3)); Console.WriteLine (Change2Digits (n, 4, 2)); Console.ReadLine (); } } }
36. Организовать XOR-шифрование введённой строки с использованием строгой дизъюнкции и её дешифровку
using System; public class XORCipher { private string GetRepeatKey (string s, int n) { //генератор повторений пароля var r = s; while (r.Length < n) r += r; return r.Substring (0, n); } private string Cipher (string text, string secretKey) { //метод шифрования/дешифровки var currentKey = GetRepeatKey (secretKey, text.Length); var res = string.Empty; for (var i = 0; i < text.Length; i++) { res += ( (char) ( text [i] ^ currentKey [i] ) ).ToString (); } return res; } //шифрование текста public string Encrypt (string plainText, string password) => Cipher (plainText, password); //расшифровка текста public string Decrypt (string encryptedText, string password) => Cipher (encryptedText, password); } class Program { static void Main (string [] args) { var x = new XORCipher (); Console.Write ("Введите текст сообщения: "); var message = Console.ReadLine (); Console.Write ("Введите пароль: "); var pass = Console.ReadLine (); var encryptedMessageByPass = x.Encrypt (message, pass); Console.WriteLine ("Зашифрованное сообщение {0}", encryptedMessageByPass); Console.WriteLine ("Расшифрованное сообщение {0}", x.Decrypt (encryptedMessageByPass, pass)); Console.ReadLine (); } }
37. Организовать перевод введённого двоичного числа в десятичное и обратно
В этой задаче не разрешается использование стандартных функций, а только "ручная" реализация алгоритма.
using System; using System.Text; class Program { static uint BinaryToDecimal (string binaryNumber) { var exp = 1u; var result = 0u; for (var i = binaryNumber.Length - 1; i >= 0; i--) { if (binaryNumber [i] == '1') result += exp; //Всё, что не 1, будет нулями exp *= 2; } return result; } static string DecimalToBinary (uint decimalNumber) { var binaryNumber = string.Empty; while (decimalNumber > 0) { binaryNumber = ( decimalNumber % 2 ) + binaryNumber; decimalNumber /= 2; } return binaryNumber; } static void Main (string [] args) { Console.OutputEncoding = Encoding.UTF8; Console.Write ("Введите число в двоичной системе: "); var binNum = Console.ReadLine (); var decNum = BinaryToDecimal (binNum); Console.WriteLine ("{0} => {1}", binNum, decNum); binNum = DecimalToBinary (decNum); Console.WriteLine ("{0} => {1}", decNum, binNum); Console.ReadLine (); } }
38. Организовать перевод введённого троичного числа в десятичное и обратно
С тем же ограничением, что в предыдущей задаче.
using System; using System.Text; class Program { static uint TernaryToDecimal (string ternaryNumber) { var exponent = 0; var result = 0u; for (var i = ternaryNumber.Length - 1; i >= 0; i--) { var ternaryDigit = Convert.ToUInt32 (ternaryNumber [i].ToString ()); if (ternaryDigit > 2) return 0; //только 0, 1 или 2 result += ternaryDigit * Convert.ToUInt32 (Math.Pow (3, exponent)); exponent++; } return result; } static string DecimalToTernary (uint decimalNumber) { var ternaryNumber = string.Empty; while (decimalNumber > 0) { ternaryNumber = ( decimalNumber % 3 ) + ternaryNumber; decimalNumber /= 3; } return ternaryNumber; } static void Main (string [] args) { Console.OutputEncoding = Encoding.UTF8; Console.Write ("Введите число в троичной системе: "); var ternaryNum = Console.ReadLine (); var decimalNum = TernaryToDecimal (ternaryNum); Console.WriteLine ("{0} => {1}", ternaryNum, decimalNum); ternaryNum = DecimalToTernary (decimalNum); Console.WriteLine ("{0} => {1}", decimalNum, ternaryNum); Console.ReadLine (); } }
39. Реализовать решето Эратосфена для поиска простых чисел, не превышающих заданного N
Алгоритм в "Вики".
Так как мы будем удалять элементы списка, а массивы не позволяют этого, для хранения данных используем класс List
из библиотеки System.Collections.Generic
.
На .NET это довольно медленно, как и всё на нём.
using System; using System.Collections.Generic; class Program { static List<uint> SieveEratosthenes (uint n) { var numbers = new List<uint> (); //заполнение списка числами от 2 до n-1 for (var i = 2u; i < n; i++) numbers.Add (i); for (var i = 0; i < numbers.Count; i++) { for (var j = 2u; j < n; j++) { //удаляем кратные числа из списка numbers.Remove (numbers [i] * j); } } return numbers; } static void Main (string [] args) { Console.Write ("N = "); var n = Convert.ToUInt32 (Console.ReadLine ()); var primeNumbers = SieveEratosthenes (n); Console.WriteLine ("Простые числа до заданного {0}:", n); Console.WriteLine (string.Join (", ", primeNumbers)); Console.ReadLine (); } }
09.01.2021, 18:29 [39814 просмотров]