Ввод-вывод в консольном приложении

При создании консольного приложения весь «пользовательский диалог» реализуется в текстовом режиме (пользователь получает от приложения информацию в текстовом виде и либо осуществляет выбор действия, либо вводит, необходимы для работы приложения, данные). Такое взаимодействие с пользователем заключается в реализации консольного ввода-вывода информации.

В системе базовых типов .NET Framework предусмотрен класс Console, который содержит набор статических методов и свойств, необходимых для осуществления консольного ввода-вывода, и получения служебной информации о консоли.

В классе Console определены следующие свойства:

• BackgroundColor – возвращает или устанавливает фоновый цвет выводимого в консоль текста (возвращает объект перечисления ConsoleColor).

• CapsLock – возвращает true, если нажата клавиша CapsLock.

• CursorLeft – возвращает или устанавливает номер колонки буферной зоны, в которой находиться курсор.

• CursorSize – возвращает или устанавливает высоту курсора относительно высоты ячейки символа.

• CursorTop – возвращает или устанавливает номер ряда буферной зоны, в которой находиться курсор.

• CursorVisible – возвращает или устанавливает значение индикатора видимости курсораю.

• Error – возвращает стандартный поток для вывода информации о возникающих ошибках (релевантен cerr в С++).

• ForegroundColor – возвращает или устанавливает цвет выводимого в консоль текста (возвращает объект перечисления ConsoleColor).

• In – возвращает стандартный поток ввода.

• LargestWindowHeight – возвращает наибольшее количество рядов в буферной зоне консоли, базируясь на значениях текущего шрифта и разрешения экрана.

• LargestWindowWidth – возвращает наибольшее количество колонок в буферной зоне консоли, базируясь на значениях текущего шрифта и разрешения экрана.

• NumberLock – возвращает true, если нажат NUM LOCK/

• Out – возвращает стандартный поток вывода.

• Title – возвращает или устанавливает текст заголовка окна консоли.

• WindowHeight – возвращает или устанавливает ширину окна консоли.

• WindowLeft – возвращает или устанавливает отступ окна консоли слева, относительно экрана.

• WindowTop – возвращает или устанавливает отступ окна консоли сверху, относительно экрана.

• WindowWidth – возвращает или устанавливает высоту окна консоли.

Далее будут перечислены статические методы класса Console, специфичные для консольного ввода-вывода:

• Beep – проигрывает звук указанной частоты на протяжении указанного времени.

• Clear – очищает буфер консоли и окно консоли от теста.

• OpenStandardError – открывает стандартный поток вывода ошибок с указанным размером буфера.

• OpenStandardInput – открывает стандартный поток ввода с указанным размером буфера.

• OpenStandardOutput – открывает стандартный поток вывода с указанным размером буфера.

• Read – читает следующий символ из стандартного потока ввода.

• ReadKey – получает информацию о нажатой пользователем клавише (объект класса ConsoleKeyInfo).

• ReadLine – возвращает следующую строку текста из стандартного потока ввода.

• ResetColor – устанавливает значение цвета текста и фона в значение по умолчанию.

• SetBufferSize – устанавливает высоту и ширину буфера консоли.

• SetCursorPosition – устанавливает позицию курсора.

• SetError – передаёт объект класса System.IO.TextWriter, указанный в качестве параметра, в качестве значения свойства Error.

• SetIn – передаёт объект класса System.IO.TextReader, указанный в качестве параметра, в качестве значения свойства In.

• SetOut – передаёт объект класса System.IO.TextWriter, указанный в качестве параметра, в качестве значения свойства Out.

• SetWindowPosition – устанавливает позицию окна консоли относительно экрана.

• SetWindowSize – устанавливает размер окна консоли.

• Write – осуществляет вывод информации в стандартный поток вывода.

• WriteLine – аналогично методу Write, за исключением того, что данный метод дополняет выводимую строку служебным символом «\n» (переводит текст на следующую строку).

Console.Title = «Пример использования инструментов класса Console»;

Console.BackgroundColor = ConsoleColor.White;

Console.ForegroundColor = ConsoleColor.DarkGreen;

Пример:

Console.Title = "Пример использования инструментов класса Console";

Console.BackgroundColor = ConsoleColor.White;

Console.ForegroundColor = ConsoleColor.DarkGreen;

Console.SetWindowSize(15, 8);

Console.SetBufferSize(15, 8);

Console.WriteLine("Some otuput data(green color)")

Console.ResetColor();

Console.write("Enter your name: ");

string name = Console.ReadLine();

Console.WriteLine("Hello, " + name + "!");

 

Math

Класс System.Math представляет собой набор методов реализующих найболее востребованные арифметические операции, некоторые их них:

Abs() - модуль числа;

Cos() - косинус числа;

Sin() - синус числа;

Floor() - возвращает целое число, меньшее или равное исходному числу;

Max() - возвращает большее число из двух;

Min() - возвращает меньшее из двух чисел;

Pow() - возведение в степень;

Round() - округлене числа до целого.

Пример использования Math:

double result = Math.Max(1, Math.Min(100, number)); // ensure number in range 1..100

double afterDot = number - Math.Floor(number); // получение дробной части числа

ЛЕКЦИЯ 6

Классы

Классы C#, как и в С++ – это некие шаблоны, по которым вы можете создавать объекты. Каждый объект содержит данные и методы, манипулирующие этими данными. Класс определяет, какие данные и какую функциональность может иметь каждый конкретный объект (иногда называемый экземплярам) этого класса.

Например, если у вас есть класс, представляющий студента, он может определять такие поля, как studentID, firstName, lastName, group, и т.д. которые нужны ему для хранения информации о конкретном студенте.

Класс также может определять функциональность, которая работает с данными, хранящимися в этих полях. Вы создаете экземпляр этого класса для представления конкретного студента, устанавливаете значения полей экземпляра и используете его функциональность. При создании классов, как и всех ссылочных типов – используется ключевое слово new для выделения памяти под экземпляр. В результате объект создастся и инициализируется (помним, что числовые поля инициализируются нулями, логические – false, ссылочные – в null).

Синтаксис объявления и инициализации класса:

[спецификатор][модификатор] Class <имя класса>

{

[спецификатор][модификатор] тип <имя поля1>;

[спецификатор][модификатор] тип <имя поля2>;

 

[спецификатор][модификатор] тип <Метод1()>

{

}

[спецификатор][модификатор] тип <Метод2()>

{

}

}

Доступ к полям и методам класса, как и в «С++», осуществляется через «.» из-

под объекта класса. Также следует напомнить, что доступ к содержимому класса вне его границ, мы имеем только к общедоступным данным. Остальные остаются

инкапсулированными.

Пример:

public class NoteBook {

int counter = 0;

string[] notes = new string[10];

public void addNote(string note) {

if (counter - 1 < notes.Length) {

notes[counter++] = note;

}

}

}

Спецификаторы доступа

С помощью спецификаторов доступа мы можем регулировать доступность некоторого типа или данных внутри типа. При определении класса с видимостью в рамках файла, а не другого класса, его можно сделать открытым (public) или внутренним (internal). Открытый тип доступен любому коду любой сборки. Внутренний класс доступен только из сборки, где он определен. По умолчанию компилятор С# делает класс внутренним. При определении члена класса (в том числе вложенного) можно указать спецификатор доступа к члену. Спецификаторы определяют, на какие члены можно ссылаться из кода. В CLR определен свой набор возможных спецификаторов доступа, но в каждом языке программирования существует свой синтаксис и термины.

Рассмотрим спецификаторы определяющие уровень ограничения – от максимального (private) до минимального (public):

private – данные доступны только методам внутри класса вложенных в него классам

protected – данные доступны только методам внутри класса (и вложенным в него классам ) или одном из его производных классов

internal – данные доступны только методам в сборке

protected internal – данные доступны только методам вложенного или производного типа класса и любым методам сборки

public – данные доступны всем методам во всех сборках

Вы также должны понимать, что доступ к члену класса можно получить, только если он определен в видимом классе. То есть, если в сборке А определен внутренний класс, имеющий открытый метод, то код сборки Б не сможет вызвать открытый метод, поскольку внутренний класс сборки А не доступен из Б.

В процессе компиляции кода компилятор проверяет корректность обращения кода к классам и членам. Обнаружив некорректную ссылку на какие-либо классы или члены, выдается ошибка компиляции.

Если не указать явно спецификатор доступа, компилятор С# выберет по умолчанию закрытый – наиболее строгий из всех.

Если в производном классе переопределяется член базового – компилятор С# потребует, чтобы у членов базового и производного классов был одинаковый спецификатор доступа. При наследовании базовому классу CLR позволяет понижать, но не повышать уровень доступа к члену.

Поля класса

Любой класс может включать в себя множество данных. К таким данным относятся: константы, поля, конструкторы экземпляров класса, конструкторы класса, методы, перегруженные операторы, свойства, события, а также вложенные классы.

Остановимся для начала на полях. Поле – это переменные, которые хранит значение любого стандартного типа или ссылку на ссылочный тип. При объявлении полей могут указываться следующие модификаторы:

• Если модификатор не указывать, то это означает, что поле связано с экземпляром класса, а не самим классом.

• Модификатор static – означает, что поле является частью класса, а не объекта.

• Модификатор readonly – означает, что поле будет использоваться только для чтения и запись в поле разрешается только из кода метода конструктора либо сразу при объявлении (не путать с константами).

CLR поддерживает изменяемые (read/write) и неизменяемые (readonly) поля. Большинство полей – изменяемые. Это означает, что значение таких полей может многократно меняться во время исполнения кода. Неизменяемые поля вроде бы сродни константам, но являются более гибкими, так как значение константам можно задать только при объявлении, а у неизменяемых полей это еще можно сделать и в конструкторах. Важно понимать, что неизменность поля ссылочного типа означает неизменность ссылки, которую оно содержит, но только не объекта, на которую эта ссылка указывает. То есть перенаправить ссылку на другое место в памяти мы не можем, но изменить значение объекта, на который указывает ссылка – можем. Значения неизменяемых полей значимых типов – изменять не можем.

Пример:

public class Librarian {

readonly NoteBook noteBook = new NoteBook();

}

Методы класса

В языках С и С++ можно было определить глобальные функции, которые были не связаны с определенным классом. В С# этого сделать не получится. То есть все функции обязательно должны определяться внутри классов или структур.

Синтаксис объявления методов похож на объявления методов в С-подобных языках, и практически идентичен синтаксису С++. Основное отличие от С++ заключается в том, что в С# каждый метод отдельно объявлен как общедоступный или приватный. То есть блоки public:, private: для группировки нескольких методов использовать нельзя.

Также все методы С# объявляются и определяются внутри определения класса, таким образом нельзя отделить реализацию метода от объявления, как это делается в С++.

В С# определение метода состоит из спецификаторов и модификаторов, типа возвращаемого значения, за которым следует имя метода, затем – список аргументов (если они есть), заключенный в скобки, и далее – тело метода, заключенное в фигурные скобки:

[спецификаторы][модификаторы] тип_возврата <Имя Метода>([параметры])

{

// Тело метода

}

Специально для работы со статическими полями – были введены статические методы. Эти методы, как и статические поля, принадлежат классу, а не объекту. Они исключают возможность вызова из-под объекта и соответственно не работают с нестатическими полями.

В качестве примера, создадим два класса NoteBook, Librarian. Класс Notebook хранит записи, а Librarian отвечает за добавление и получение их.

public class NoteBook {

int counter = 0; //кол-во записей

string[] notes = new string[10]; // массив записей

 

public int getNoteCount() {

return counter;

}

 

public string getNote(int index) {

return notes[index];

}

 

public string getLastNote() {

if (counter == 0) {

return "";

}

return notes[counter - 1];

}

 

public void addNote(string note) {

if (counter - 1 < notes.Length) {

notes[counter++] = note;

}

}

}

 

public class Librarian { // класс, выполняет операции с NoteBook

const string ADD_COMMAND = "add "; // ключевое слово для добавления записи в NoteBook

const string GET_COMMAND = "get "; // ключевое слово для получения записи по номеру

const string SIZE_COMMAND = "size"; // выводит в консоль кол-во записей

 

readonly NoteBook noteBook = new NoteBook();

 

public void performOperation(string command, string argument) {

switch(command) {

case ADD_COMMAND:

addNote(argument);

break;

case GET_COMMAND:

getNote(argument);

break;

case SIZE_COMMAND:

size();

break;

}

}

 

void addNote(string note) {

noteBook.addNote(note);

Console.WriteLine("Note \"" + noteBook.getLastNote() + "\" was added!");

}

 

void getNote(string argument) {

int index = Int32.Parse(argument);

Console.WriteLine("Note " + index + ": " + noteBook.getNote(index));

}

 

void size() {

Console.WriteLine("Current notebook size = " + noteBook.getNoteCount());

}

}

public static void Main(string[] args) {

Librarian librarian = new Librarian();

string inputCommand;

do {

inputCommand = Console.ReadLine(); //считывает строку - первые 4 символа команда, остальные аргументы, например "get 2" или "add new note"

string command = inputCommand.Substring(0, 4);

string commandArg = inputCommand.Substring(4);

librarian.performOperation(command, commandArg);

} while (inputCommand != "quit");

}

Результат работы(содержимое консольного окна):

size

Current notebook size = 0

add my new note

Note "my new note" was added!

add and one more

Note "and one more" was added!

add third note

Note "third note" was added!

add and last one

Note "and last one" was added!

size

Current notebook size = 4

get 1

Note 1: and one more

quit

ЛЕКЦИЯ 7

Конструкторы

Конструкторы – это методы класса, которые вызываются при создании объекта. В C# конструкторов существует 3 вида. Несмотря на такое разнообразие, идеякаждого – выполнить некоторые действия при создании объектов. Рассмотрим их по порядку.

Конструктор по умолчанию – не принимает никаких параметров и предоставляется в «подарок» при создании класса. В отличие от конструктора по умолчанию в С++ (который предоставляется компилятором), этот конструктор полезен тем, что он обнуляет все числовые типы, устанавливает в false логический, и в null – ссылочный. Итого после создания объекта вы ни в одном его поле не увидите мусора. В случае необходимости вы можете переопределить конструктор по умолчанию под ваши нужды.

Конструктор с параметрами – конструктор, который может принимать необходимое количество параметров для инициализации полей класса или каких-либо других действий.

Статический конструктор – конструктор, относящийся к классу, а не к объекту. Существует для инициализации статических полей класса. Определяется без какого- либо спецификатора доступа с ключевым словом static.

При создании конструкторов нужно помнить, что все конструкторы (кроме статического) имеют спецификатор доступа public, имя, совпадающее с именем класса, и ничего не возвращают (даже void), также все, кроме конструктора по умолчанию и статического конструктора, могут иметь необходимое количество параметров. Конструктор по умолчанию может быть только один.

Еще один важный момент: если вы взялись за определение любого конструктора, то конструктор по умолчанию, который вам предоставлялся компилятором работать не будет.

This

Ключевое слово this – это неявно присутствующая в классе ссылка на объект, вызывающий метод (т.е. это ссылка на «самого себя»). Используется в нескольких случаях: если имя параметра метода совпадает с именем поля класса, то при инициализации поля указывается, что поле достается из-под ссылки this; другое применение this – это вызов конструкторов. Как правило в классе определяется главный конструктор (как правило главным выбирают конструктор с максимальным количеством параметров), который содержит код инициализации, а все остальные – вызывают главный передавая ему необходимые параметры.

Пример:

class Music {

private static string STUB_ARIST;

private static string STUB_DURATION;

private static string STUB_NAME;

 

private string artist;

private string duration;

private string name;

 

static Music() {

STUB_ARIST = "Unknown artist";

STUB_DURATION = "--:--";

STUB_NAME = "Uknwown name";

}

 

public Music() {

artist = STUB_ARIST;

duration = STUB_DURATION;

name = STUB_NAME;

 

}

 

public Music(string duration, string name):this(STUB_ARIST, duration, name) {

}

 

public Music(string artist, string duration, string name) {

this.artist = artist;

this.duration = duration;

this.name = name;

}

 

public void setArtist(string artist) {

this.artist = artist;

}

 

public void setDuration(int seconds) {

this.duration = seconds / 60 + ":" + seconds % 60;

}

 

public void setName(string name) {

this.name = name;

}

 

public void printSong() {

Console.WriteLine(artist + " - " + name + "\t" + duration);

}

}

Параметры ref и out

Как уже было сказано все, что передается в методы – передается по значению (даже, как ни удивительно, ссылочные типы). Для того, чтоб добиться передачи параметров через ссылку существуют ключевые слова ref и out. То есть по умолчанию методы могут изменять объекты но не ссылки на них:

class Storage {

private int value;

public void setValue(int value) {

this.value = value;

}

public int getValue() {

return value;

}

}

 

public static void changeVariables(int first, int[] second, int[] input3, Storage storage) {

first++;

second[0]++;

input3 = new int[] {-1};

storage.setValue(storage.getValue() + 1);

}

 

int input = 5;

int[] input2 = new int[] {10, 11}, input3 = new int[] {3, 33};

Storage storage = new Storage();

storage.setValue(-3);

changeVariables(input, input2, input3, storage);

//input = 5, input2 = [11, 11], input3 = [3, 33], storage.value = -2

Ключевым словом ref помечаются те параметры, которые должны передаваться в метод по ссылке. Таким образом, мы будем внутри метода манипулировать данными, объявленными в вызывающем методе. Аргументы, которые передаются в метод с ключевым словом ref, обязательно должны быть проинициализированы, иначе компилятор выдаст сообщение об ошибке.

Пример:

public static void changeVariables(ref int first, ref int[] second, ref int[] input3, ref Storage storage) {

first++;

second[0]++;

input3 = new int[] {-1};

storage.setValue(storage.getValue() + 1);

}

int input = 5;

int[] input2 = new int[] {10, 11}, input3 = new int[] {3, 33};

Storage storage = new Storage();

storage.setValue(-3);

changeVariables(ref input, ref input2, ref input3, ref storage);

//input = 6, input2 = [11, 11], input3 = [-1], storage.value = -2

Параметры, обозначенные ключевым словом out, также используются для передачи по ссылке. Отличие от ref состоит в том, что параметр считается выходным и соответственно компилятор разрешит не инициализировать его до передачи в метод и проследит, чтоб метод занес в этот параметр значение (иначе будет выдано сообщение об ошибке).

Частичные типы

Частичные типы поддерживаются только компиляторами С# и некоторых других языков, но CLR ничего о них не знает. Ключевое слово partial говорит компилятору С#, что исходный код класса может располагаться в нескольких файлах.

Ключевое слово partial применяется к типам во всех файлах с определением класса. При компиляции компилятор объединяет эти файлы, и готовый класс помещается в результирующий файл сборки с расширением .ехе или dll. Частичные типы реализуются только компилятором С#; поэтому все файлы с исходным кодом типа необходимо писать на одном языке и компилировать их в единый модуль.

Пример:

partial class Rectangle {

int width;

int height;

 

public int getWidth() {

return width;

}

 

public void setWidth(int width) {

this.width = width;

}

 

public void setHeight(int height) {

this.height = height;

}

 

public int getHeight() {

return height;

}

}

 

partial class Rectangle {

int color;

 

public void setColor(int color) {

this.color = color;

}

 

public int getColor() {

return color;

}

}

public static void Main(string[] args) {

Rectangle rectangle = new Rectangle();

rectangle.setColor(0xff0000);//RRGGBB red.

rectangle.setWidth(100);

rectangle.setHeight(30);

Console.ReadKey();

}

ЛЕКЦИЯ 8

Наследование

Наследование позволяет повторно использовать уже имеющиеся классы, но при этом расширять их возможности. Существует два вида наследования – наследование типа, при котором новый тип получает все свойства и методы родителя и наследование интерфейса, при котором новый тип получает от родителя сигнатуру методов, без их реализации. Все классы, для которых не указан базовый класс, наследуются от класса System.Object (краткая форма названия object).

C# не поддерживает множественного наследования. Это означает, что класс в C# может быть наследником только одного класса, но при этом может реализовывать несколько интерфейсов. Другими словами, класс может наследоваться не более чем от одного базового класса и нескольких интерфейсов.

Конструктор наследования в C# имеет следующий вид:

class НаследуемыйКласс : БазовыйКласс

{

// поля, свойства, события и методы класса

}

Если класс наследуется от базового класса и нескольких интерфейсов, то они перечисляются через запятую:

class НаследуемыйКласс : БазовыйКласс, Интерфейс1, Интерфейс2

{

// поля, свойства, события и методы класса

}

При создании класса – наследника на самом деле вызывается не один конструктор, а целая цепочка конструкторов. Сначала выбирается конструктор класса, экземпляр которого создается. Этот конструктор пытается обратиться к конструктору своего непосредственного базового класса. Этот конструктор в свою очередь пытается вызвать конструктор своего базового класса. Так происходит, пока не доходим до класса System.Object, который не имеет базового класса. В результате имеем последовательный вызов конструкторов всех классов иерархии, начиная с System.Object заканчивая классом, экземпляр которого хотим создать. В этом процессе каждый конструктор инициализирует поля собственного класса.

Для каждого класса можно определить несколько конструкторов. Если мы для класса-наследника хотим вызвать конструктор базового класса, то необходимо использовать ключевое слово base().

public НаследуемыйКласс() : base()

{

// поля, свойства, события и методы класса

}

Пример:

interface Printable {

void printSelf();

}

class Rectangle {

int width;

int height;

public Rectangle() {

 

}

public Rectangle(int width, int height) {

this.width = width;

this.height = height;

}

public int Width {

get{ return width;}

set{ width = value;}

}

public int Height {

get { return height; }

set { height = value; }

}

}

class ColoredRectangle : Rectangle, Printable {

string color;

public ColoredRectangle() {

 

}

public ColoredRectangle(int width, int height) : base(width, height) {

 

}

public ColoredRectangle(int width, int height, string color) : base(width, height) {

this.color = color;

}

public string Color {

get { return color;}

set { color = value;}

}

public void printSelf() {

Console.WriteLine("Colored rectangle {0}x{1} ({2})", Width, Height, color);

}

}

public static void Main(string[] args) {

ColoredRectangle rectangle = new ColoredRectangle(10, 3, "red");

rectangle.printSelf(); //Colored rectangle 10x3 (red)

Console.ReadKey();

}