В общем случае понятие ПОТОК — это абстрактное понятие, которое обозначает динамическую изменяющуюся во времени последовательность чего-либо.
Применительно к обсуждаемым проблемам ввода/вывода в программах на C#, поток – это последовательность байтов, связанная с конкретными устройствами компьютера (дисками, дисплеями, принтерами, клавиатурами) посредством системы ввода/вывода.
Система ввода/вывода обеспечивает для программиста стандартные и не зависящие от физического устройства средства представления информации и управления потоками ввода/вывода. Действия по организации ввода/вывода обеспечиваются стандартными наборами, как правило, одноименных функций ввода/вывода со стандартным интерфейсом.
Функции, обеспечивающие взаимодействие с различными устройствами ввода/вывода, объявляются в различных классах. Вопрос "ОТКУДА ВЫЗВАТЬ функцию" часто становится более важным, чем вопрос "КАК ПОСТРОИТЬ выражение вызова функции".
Большинство устройств, предназначенных для выполнения операций ввода/вывода, являются байт-ориентированными. Этим и объясняется тот факт, что на самом низком уровне все операции ввода/вывода манипулируют с байтами в рамках байтовых потоков.
С другой стороны, значительный объем работ, для которых, собственно, и используется вычислительная техника, предполагает работу с символами, а не с байтами (заполнение экранной формы, вывод информации в наглядном и легко читаемом виде, текстовые редакторы).
Символьно-ориентированные потоки, предназначенные для манипулирования с символами, а не с байтами, являются потоками ввода/вывода более высокого уровня. В рамках Framework .NET определены соответствующие классы, которые при реализации операций ввода/вывода обеспечивают автоматическое преобразование данных типа byte в данные типа char и обратно.
В дополнение к байтовым и символьным потокам в C# определены два класса, реализующих механизмы считывания и записи информации непосредственно в двоичном представлении (потоки BinaryReader и BinaryWriter ).
Основные особенности и правила работы с устройствами ввода/вывода в современных языках высокого уровня описываются в рамках классов потоков. Для языков платформы .NET местом описания самых общих свойств потоков является класс System.IO.Stream.
Назначение этого класса заключается в объявлении общего стандартного набора операций (стандартного интерфейса), обеспечивающих работу с устройствами ввода/вывода, независимо от их конкретной реализации, от источников и получателей информации.
Процедуры чтения и записи информации определяется следующим (список неполон!) набором свойств и АБСТРАКТНЫХ методов, объявляемых в этом классе.
В рамках Framework .NET, независимо от характеристик того или иного устройства ввода/вывода, программист ВСЕГДА может узнать:
bool CanRead (если можно – значение должно быть установлено в true );bool CanWrite (если можно – значение должно быть установлено в true );bool CanSeek (если последовательность, в которой производится чтение/запись, не является жестко детерминированной и возможно позиционирование в потоке – значение должно быть установлено в true );long Position (возможность позиционирования в потоке предполагает возможность программного изменения значения этого свойства);long Length.В соответствии с общими принципами реализации операций ввода/вывода, для потока предусмотрен набор методов, позволяющих реализовать:
int ReadByte();count) количества байтов из потока и размещение их в массиве buff, начиная с элемента buff[index], с возвращением количества успешно прочитанных байтов – int Read(byte[] buff, int index, int count);void WriteByte(byte b);count ) количества байтов из массива buff, начиная с элемента buff[index], с возвращением количества успешно записанных байтов – int Write(byte[] buff, int index, int count) ;long Seek (long index, SeekOrigin origin) (позиция текущего байта в потоке задается значением смещения index относительно позиции origin );void Flush();void Close().Множество классов потоков ввода/вывода в Framework .NET основывается (наследует свойства и интерфейсы) на абстрактном классе Stream. При этом классы конкретных потоков обеспечивают собственную реализацию интерфейсов этого абстрактного класса.
Наследниками класса Stream являются, в частности, три класса байтовых потоков:
BufferedStream – обеспечивает буферизацию байтового потока. Как правило, буферизованные потоки являются более производительными по сравнению с небуферизованными;FileStream – байтовый поток, обеспечивающий файловые операции ввода/вывода;MemoryStream – байтовый поток, использующий в качестве источника и хранилища информации оперативную память.Манипуляции с потоками предполагают НАПРАВЛЕННОСТЬ производимых действий. Информацию из потока можно ПРОЧИТАТЬ, а можно ее в поток ЗАПИСАТЬ. Как чтение, так и запись предполагают реализацию определенных механизмов байтового обмена с устройствами ввода/вывода.
Свойства и методы, объявляемые в соответствующих классах, определяют специфику потоков, используемых для чтения и записи:
TextReader,TextWriter.Эти классы являются абстрактными. Это означает, что они не "привязаны" ни к какому конкретному потоку. Они лишь определяют интерфейс (набор методов), который позволяет организовать чтение и запись информации для любого потока.
В частности, в этих классах определены следующие методы, определяющие базовые механизмы символьного ввода/вывода. Для класса TextReader:
int Peek () – считывает следующий знак, не изменяя состояние средства чтения или источника знака. Возвращает следующий доступный знак, не считывая его в действительности из потока входных данных;int Read(...) – несколько одноименных int Read() предполагает чтение из потока одного символа с возвращением его целочисленного эквивалента или –1 при достижении конца потока. Вариант int Read (char[] buff, int index, int count) и его полный аналог int ReadBlock(char[] buff, int index, int count) обеспечивает прочтение максимально возможного количества символов из текущего потока и записывает данные в буфер, начиная с некоторого значения индекса;string ReadLine() – читает строку символов из текущего потока. Возвращается ссылка на объект типа string ;string ReadToEnd() – читает все символы, начиная с текущей позиции символьного потока, определяемого объектом класса TextReader, и возвращает результат как ссылка на объект типа string ;void Close() – закрывает поток ввода.Для класса TextWriter, в свою очередь, определяется:
void Write(...) со значениями параметров, позволяющих записывать символьное представление значений базовых типов (смотреть список базовых типов) и массивов значений (в том числе и массивов строк);void Flush() – обеспечивает очистку буферов вывода. Содержимое буферов выводится в выходной поток;void Close() – закрывает поток вывода.Эти классы являются базовыми для классов:
StreamReader – содержит свойства и методы, обеспечивающие считывание СИМВОЛОВ из байтового потока,StreamWriter – содержит свойства и методы, обеспечивающие запись СИМВОЛОВ в байтовый поток.Вышеуказанные классы включают методы своих "предков" и позволяют осуществлять процессы чтения-записи непосредственно из конкретных байтовых потоков. Работа по организации ввода/вывода с использованием этих классов предполагает определение соответствующих объектов, "ответственных" за реализацию операций ввода/вывода, с явным указанием потоков, которые предполагается при этом использовать.
Еще одна пара классов потоков обеспечивает механизмы символьного ввода– вывода для строк:
StringReader,StringWriter.В этом случае источником и хранилищем множества символов является символьная строка.
Интересно заметить, что у всех ранее перечисленных классов имеются методы, обеспечивающие закрытие потоков, и не определены методы, обеспечивающие открытие соответствующего потока. Потоки открываются в момент создания объекта-представителя соответствующего класса. Наличие функции, обеспечивающей явное закрытие потока, принципиально. Оно связано с особенностями выполнения
Предопределенные потоки ввода/вывода используются для реализации ввода/вывода в рамках консольных приложений. Это еще одна категория потоков.
В классах предопределенных потоков, в отличие от ранее рассмотренных потоков, явным образом задаются источник (откуда) и место размещения (куда) информации.
Основные свойства и методы этих потоков определены в классе System.Console. Класс Console является достаточно сложной конструкцией.
В рамках этого класса определены свойства, обеспечивающие доступ к предопределенным потокам. В классе System.Console таких потоков три ( In, Out, Error ).
Ниже описываются свойства этих потоков:
Standard input stream – является объектом – представителем класса TextReader. По умолчанию поток In ориентирован на получение информации с клавиатуры.Standard output stream – является объектом – представителем класса TextWriter. По умолчанию обеспечивает вывод информации на дисплей.Standard error stream – также является объектом – представителем класса TextWriter. И также по умолчанию выводит информацию на дисплей.В классе System.Console также определены следующие СТАТИЧЕСКИЕ (!) функции – члены (вернее, множества СТАТИЧЕСКИХ
Write и WriteLine ;Read и ReadLine.При этом функции-члены класса Console ПО УМОЛЧАНИЮ обеспечивают связь с конкретным потоком. Например, при вызове метода
Console.WriteLine(...); // Список параметров опущен
информация направляется в поток, который представляется определенным в классе статическим свойством Console.Out. Это свойство предоставляет доступ к объекту – представителю класса TextWriter. Таким образом, класс Console также обеспечивает вызов соответствующей функции-члена от имени объекта – представителя класса TextWriter. Непосредственное обращение к члену класса Console (то есть к потоку), отвечающего за текстовый вывод, имеет следующую форму вызова:
Console.Out.WriteLine(...);
Вызов определенного в классе Console метода ReadLine
Console.ReadLine(...);
обеспечивает получение информации через поток, доступ к которому предоставляется статическим свойством Console.In. Непосредственный вызов аналогичной функции от имени члена класса TextReader выглядит следующим образом:
Console.In.ReadLine(...);
Функция – представитель класса Console, отвечающая за непосредственное направление информации в поток вывода сообщений об ошибках, не предусмотрена.
Вывод информации об ошибках может быть реализован путем непосредственного обращения к standard error :
Console.Error.WriteLine(...);
Если вспомнить, что свойство Error возвращает ссылку на объект – представитель класса TextWriter и, так же как и свойство Out, обеспечивает вывод в окно консольного приложения, разделение сообщений, связанных с нормальным ходом выполнения приложения и сообщений об ошибках, становится более чем странным.
Информация, выводимая приложением в "штатном" режиме, и сообщения об ошибках одинаково направляются в окно приложения на экран консоли. Однако в классе Console реализована возможность перенаправления соответствующих потоков. Например, стандартный поток вывода можно перенаправить в файл, а поток сообщений об ошибках оставить без изменений — направленным в окно приложения. Результат перенаправления очевиден.
Процедуры void SetIn(TextReader in), void SetOut(TextWriter out), void SetErr(TextWriter err) и будут рассмотрены ниже.
C# является языком, строго и в полном объеме реализующим принципы ООП. В этом языке все построено на классах и нет ничего, что бы не соответствовало принципам объектно-ориентированного программирования. Любой Object, реализующего, в частности, метод String ToString(), формирующий в виде Object (а это ВСЕ типы!) имеет либо унаследованную, либо собственную переопределенную версию метода ToString(). Применительно к объектам предопределенного типа из CTS, соответствующие версии методов ToString() обеспечивают преобразование значения данного типа к строковому виду.
Все сделано для программиста. Реализация метода преобразования значения в строку в конечном счете оказывается скрытой от программиста. Именно поэтому вывод значений предопределенных типов с использованием функций Write и WriteLine в программах на языке C# осуществляется так легко и просто. В справочнике по поводу этих функций так и сказано:
The text representation of value is produced by calling Type.ToString.
Эта функция имеет перегруженный вариант, использующий параметр типа string для указания желаемого формата представления. Множество значений этого параметра ограничено предопределенным списком символов форматирования (представлены ниже), возможно, в сочетании с целочисленными значениями.
| Символ форматирования | Описание |
|---|---|
C |
Отображение значения как валюты с использованием принятого по соглашению символа |
D |
Отображение значения как десятичное целое |
E |
Отображение значения в соответствии с научной нотацией |
F |
Отображение значения как fixed Point |
G |
Отображение значения в формате с фиксированной точкой или как десятичное целое. Общий формат |
N |
Применение запятой для разделения порядков |
X |
Отображение значения в шестнадцатеричной нотации |
Непосредственно за символом форматирования может быть расположена целочисленная ограничительная константа, которая, в зависимости от типа выводимого значения, может определять количество выводимых знаков после точки либо общее количество выводимых символов. При этом дробная часть действительных значений округляется или дополняется нулями справа. При выводе целочисленных значений ограничительная константа игнорируется, если количество преобразуемых символов превышает ее значение. В противном случае преобразуемое значение слева дополняется нулями.
Форматирование используется для преобразования значений "обычных" .NET Framework типов данных в строковое представление, да еще в соответствии с каким-либо общепринятым форматом. Например, целочисленное значение 100 можно представить в общепринятом формате currency для отображения валюты. Для этого можно использовать метод ToString() с использованием символа (строки?) форматирования (" C "). В результате может быть получена строка вида " $100.00 ". И это при условии, что установки операционной системы компьютера, на котором производится выполнение данного программного кода, соответствуют U.S. English specified as the current culture (имеется в виду "Настройка языковых параметров, представление чисел, денежных единиц, времени и дат").
int MyInt = 100;
String MyString = MyInt.ToString("C");
Console.WriteLine(MyString);
При обсуждении процедур ввода/вывода следует иметь в виду одно важное обстоятельство. Независимо от типа выводимого значения, в конечном результате выводится СИМВОЛЬНОЕ ПРЕДСТАВЛЕНИЕ значения. Это становится очевидным при выводе информации в окно приложения, что и обеспечивают по умолчанию методы Console.Write и Console.WriteLine.
WriteLine отличается тем, что завершает свою работу обязательным выводом line feed/.
Console.WriteLine("The Captain is on the board!"); // Вывод строки.
Console.WriteLine(314); //Символьное представление целочисленного значения.
Console.WriteLine(3.14); //Символьное представление значения типа float.
Выводимая символьная строка может содержать
Методы с одним параметром достаточно просты. Практически все происходит само собой. Уже на стадии компиляции при выяснении типа выводимого значения подбирается соответствующий вариант
При выводе значения определяется его тип, производится соответствующее стандартное преобразование к символьному виду, которое в виде последовательности символов, соответствующей существующей в операционной системе настройке языковых параметров, представления чисел, времени и дат, и выводится в окно представления.
Для облегчения процесса программирования ввода/вывода в C# также используются варианты функций Write и WriteLine с несколькими параметрами.
Эти методы называются ПЕРЕГРУЖАЕМЫМИ (см. о перегрузке методов). Для программиста C# это означает возможность вызова этих функций с различными параметрами. Можно предположить, что различным вариантам списков параметров функции могут соответствовать различные варианты функций вывода.
Ниже представлен вариант метода WriteLine с тремя параметрами. Во всех случаях, когда количество параметров превышает 1, первый параметр обязан быть символьной строкой:
Console.WriteLine("qwerty",314,3.14);
Чтобы понять, как выполняется такой оператор, следует иметь в виду, что всякий параметр метода является выражением определенного типа, которое в процессе выполнения метода может вычисляться для определения значения соответствующего выражения. Таким образом, при выполнении метода WriteLine должны быть определены значения его параметров, после чего в окне приложения должна появиться определенная последовательность символов. Ниже представлен результат выполнения выражения вызова функции:
qwerty
Значения второго и третьего параметров не выводятся.
Дело в том, что первый строковый параметр выражений вызова функций Write и WriteLine используется как управляющий шаблон для представления выводимой информации. Значения следующих за строковым параметром выражений будут выводиться в окно представления лишь в том случае, если первый параметр-строка будет явно указывать места расположения выводимых значений, соответствующих этим параметрам. Явное указание обеспечивается маркерами выводимых значений, которые в самом простом случае представляют собой заключенные в фигурные скобки целочисленные литералы (например, {3}).
При этом способ указания места состоит в следующем:
WriteLine, идущие за первым параметром-строкой. При этом второй по порядку параметр получает индекс 0, следующий за ним – 1, и т. д. до конца списка параметров;Таким образом, оператор
Console.WriteLine("The sum of {0} and {1} is {2}",314,3.14,314+3.14);
обеспечивает вывод следующей строки:
The sum of 314 and 3.14 is 317.14
В последнем операнде выражения вызова WriteLine при вычислении значения выражения используется неявное приведение типа. При вычислении значения суммы операнд типа int без потери значения приводится к типу float. Безопасные преобразования типов проводятся автоматически.
Несмотря на явную абсурдность выводимых утверждений, операторы
Console.WriteLine("The sum of {0} and {1} is {0}",314,3.14,314+3.14);
Console.WriteLine("The sum of {2} and {1} is {0}",314,3.14,314+3.14);
также отработают вполне корректно.
Помимо индекса параметра, маркер выводимого значения может содержать дополнительные сведения относительно формата представления выводимой информации. Выводимые значения преобразуются к символьному представлению, которое, в свою очередь, при выводе в окно приложения может быть дополнительно преобразовано в соответствии с предопределенным "сценарием преобразования". Вся необходимая для дополнительного форматирования информация размещается непосредственно в маркерах и отделяется запятой от индекса маркера.
Таким образом, в операторах вывода можно определить область позиционирования выводимого значения. Например, результатом выполнения следующего оператора вывода:
Console.WriteLine("***{0,10}***",3.14);
будет следующая строка:
***3.14 ***
А выполнение такого оператора:
Console.WriteLine("***{0,–10}***",3.14);
приведет к следующему результату:
*** 3.14***
Кроме того, в маркерах вывода могут также размещаться дополнительные строки форматирования ( FormatString ). При этом маркер приобретает достаточно сложную структуру, внешний вид которой в общем случае можно представить следующим образом ( M – значение индекса, N – область позиционирования):
{M,N:FormatString},
либо
{M:FormatString},
если не указывается значение области позиционирования.
Сама же строка форматирования аналогична ранее рассмотренной строке – параметру метода ToString и является комбинацией предопределенных символов форматирования и дополнительных целочисленных значений.
Непосредственно за символом форматирования может быть расположена целочисленная ограничительная константа, которая, в зависимости от типа выводимого значения, может определять количество выводимых знаков после точки либо общее количество выводимых символов. При этом дробная часть действительных значений округляется или дополняется нулями справа. При выводе целочисленных значений ограничительная константа игнорируется, если количество выводимых символов превышает ее значение. В противном случае выводимое значение слева дополняется нулями.
Следующие примеры иллюстрируют варианты применения маркеров со строками форматирования:
Console.WriteLine("Integer fotmating – {0:D3},{1:D5}",12345, 12);
Console.WriteLine("Currency formatting – {0:C},{1:C5}", 99.9, 999.9);
Console.WriteLine("Exponential formatting – {0:E}", 1234.5);
Console.WriteLine("Fixed Point formatting – {0:F3}", 1234.56789);
Console.WriteLine("General formatting – {0:G}", 1234.56789);
Console.WriteLine("Number formatting – {0:N}", 1234567.89);
Console.WriteLine("Hexadecimal formatting – {0:X7}",12345);//Integers only!
В результате выполнения этих операторов в окно консольного приложения будут выведены следующие строки:
Integer fotmating – 12345,00012 Currency formatting – $99.90,$999.90000 Exponential formatting – 1.234500E+003 Fixed Point formatting – 1234.568 General formatting – 1234.56789 Number formatting – 1,234,567.89 Hexadecimal formatting – 0003039
В маркерах выражений вызова функций вывода могут также размещаться спецификаторы (custom
В приведенной ниже таблице представлены символы, используемые для создания настраиваемых строк числовых форматов.
Следует иметь в виду, что на выходные строки, создаваемые с помощью некоторых из этих знаков, влияют настройки компонента "Язык и региональные стандарты" панели управления объекта NumberFormatInfo, связанного с текущим потоком. Результаты будут различными на компьютерах с разными параметрами культуры.
| Знак формата | Имя | Описание |
|---|---|---|
0 |
Знак – заместитель нуля | Цифра, расположенная в соответствующей позиции форматируемого значения, будет скопирована в выходную строку, если в этой позиции в строке формата присутствует " 0 ". Позиции крайних знаков " 0 " определяют знаки, всегда включаемые в выходную строку. Строка " 00 " приводит к округлению значения до ближайшего знака, предшествующего разделителю, если используется исключение из округления нуля. Например, в результате форматирования числа 34,5 с помощью строки " 00 " будет получена строка " 35 " |
# |
Заместитель цифры | Цифра, расположенная в соответствующей позиции форматируемого значения, будет скопирована в выходную строку, если в этой позиции в строке формата присутствует знак " # ". В противном случае в эту позицию ничего не записывается. Обратите внимание, что ноль не будет отображен, если он не является значащей цифрой, даже если это единственный знак строки. Ноль отображается, только если он является значащей цифрой форматируемого значения. Строка формата " ## " приводит к округлению значения до ближайшего знака, предшествующего разделителю, если используется исключение из округления нуля. Например, в результате форматирования числа 34,5 с помощью строки " ## " будет получена строка " 35 " |
| . | Разделитель | Первый знак " ." определяет расположение разделителя целой и дробной частей, дополнительные знаки " ." игнорируются. Отображаемый разделитель целой и дробной частей определяется свойством NumberDecimalSeparator объекта NumberFormatInfo |
| , | Разделитель тысяч | Знак "," применяется в двух случаях. Во-первых, если знак " ," расположен в строке формата между знаками-заместителями ( ," сразу после разделителя целой и дробной частей, число будет разделено на ," встречается в строке формата. Например, после форматирования строки " 0,," значение 100000000 будет преобразовано в " 100 ". Применение этого знака для масштабирования не включает в строку разделитель тысяч. Таким образом, чтобы разделить число на миллион и вставить разделители тысяч, следует использовать строку формата " #,##0,,"
|
% |
Заместитель процентов | При использовании этого знака число перед форматированием будет умножено на 100. В соответствующую позицию выходной строки будет вставлен знак " % ". Знак процента определяется текущим классом NumberFormatInfo |
E0 E+0 E–0 e0 e+0 e–0 |
Научная нотация | Если в строке формата присутствует один из знаков " E ", " E+ ", " E– ", " e ", " e+ " или " e– ", за которым следует по крайней мере один знак " 0 ", число представляется в научной нотации; между числом и экспонентой вставляется знак " E " или " e ". Минимальная длина экспоненты определяется количеством нулей, расположенных за знаком формата. Знаки " E+ " и " e+ " устанавливают обязательное отображение знака "плюс" или "минус" перед экспонентой. Знаки " E ", " e ", " E– " и " e– " устанавливают отображение знака только для отрицательных чисел |
\ |
Escape-знак | В языке C# и управляемых расширениях C++ знак, следующий в строке формата за обратной косой чертой, воспринимается как \n — новая строка).Чтобы использовать обратную косую черту как знак, в некоторых языках ее необходимо удвоить. В противном случае она будет интерпретирована компилятором как Обратите внимание, что escape-знак не поддерживается в Visual Basic, однако объект |
'ABC' "ABC" |
Строка букв | Знаки, заключенные в одинарные или двойные кавычки, копируются в выходную строку без форматирования |
; |
Разделитель секций | Знак " ; " служит для разделения секций положительных, отрицательных чисел и нулей в строке формата |
| Другие | Все остальные знаки | Все остальные знаки копируются в выходную строку в соответствующие позиции |
Строки формата с E0 ", " E+0 ", " E–0 ", " e0 ", " e+0 " или " e–0 ") ОКРУГЛЯЮТ значение с точностью, заданной количеством знаков-заместителей справа от разделителя целой и дробной частей.
Если в строке формата нет разделителя (точки), число округляется до ближайшего целого значения.
Если в целой части числа больше цифр, чем знаков – заместителей цифр, лишние знаки копируются в выходную строку перед первым знаком – заместителем цифры.
К строке может быть применено различное форматирование в зависимости от того, является ли значение положительным, отрицательным или нулевым. Для этого следует создать строку формата, которая состоит из трех секций, разделенных точкой с запятой.
При генерации выходной строки в этом типе форматирования не учитывается предыдущее форматирование. Например, при использовании разделителей секций отрицательные значения всегда отображаются без знака "минус". Чтобы конечное форматированное значение содержало знак "минус", его следует явным образом включить в настраиваемый указатель формата. В примере ниже показано применение разделителей секций для форматирования.
Следующий код демонстрирует использование разделителей секций при форматировании строки:
// Объявляются переменные с различными значениями (положительное,
// отрицательное, нулевое)
double MyPos = 19.95, MyNeg = –19.95, MyZero = 0.0;
string MyString = MyPos.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: $19.95.
MyString = MyNeg.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: ($19.95).
MyString = MyZero.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: Zero.
Приведенная далее таблица иллюстрирует варианты представления выводимых значений в зависимости от используемых строк форматирования. Предполагается, что форматирование проводится в рамках ToString метода, а значения в столбце "Формат" соответствуют используемой строке форматирования.
В столбце "Data type" указывается тип форматируемого значения. В столбце "Значение" – значение, подлежащее форматированию. В столбце "Вывод" отображается результат форматирования строки для U.S. English параметров культуры.
| Формат | Data type | Значение | Вывод |
|---|---|---|---|
##### |
Double |
123 |
123 |
00000 |
Double |
123 |
00123 |
(###) ### – #### |
Double |
1234567890 |
(123) 456 – 7890 |
#.## |
Double |
1.2 |
1.2 |
0.00 |
Double |
1.2 |
1.20 |
00.00 |
Double |
1.2 |
01.20 |
#,# |
Double |
1234567890 |
1,234,567,890 |
#,, |
Double |
1234567890 |
12345 |
#,,, |
Double |
1234567890 |
1 |
#,##0,, |
Double |
1234567890 |
1,235 |
#0.##% |
Double |
0.095 |
9.5% |
0.###E+0 |
Double |
95000 |
9.5E+4 |
0.###E+000 |
Double |
95000 |
9.5E+004 |
0.###E–000 |
Double |
95000 |
9.5E004 |
[##–##–##] |
Double |
123456 |
[12–34–56] |
##;(##) |
Double |
1234 |
1234 |
##;(##) |
Double |
–1234 |
(1234) |
Демонстрация custom number formatting. Еще один пример:
Double myDouble = 1234567890; String myString = myDouble.ToString( "(# ##) ### – ####" ); // The value of myString is "(123) 456 – 7890". int MyInt = 42; MyString = MyInt.ToString( "My Number \n= #" ); // In the U.S. English culture, MyString has the value: // "My Number // = 42".
Следует иметь в виду, что чтение информации из входного потока класса Console связано с получением из буфера клавиатуры СИМВОЛЬНЫХ последовательностей и в большинстве случаев предполагает дальнейшие преобразования этих последовательностей к соответствующему типу значений.
Консольный ввод предполагает использование уже известных статических (!) функций – членов класса Console:
Read() – читает следующий знак из стандартного входного потока:public static int Read();
Возвращаемое значение:
Следующий знак из входного потока или значение " –1 ", если знаков больше нет.
Метод не будет завершен до окончания операции чтения, например при нажатии клавиши "Ввод". При наличии данных входной поток содержит ввод пользователя и зависящую от окружения последовательность знаков перехода на новую строку.
ReadLine() – считывает следующую строку символов из стандартного входного потока:public static string ReadLine();
Возвращаемое значение:
Следующая строка из входного потока или пустая ссылка, если знаков больше нет.
Строка определяется как последовательность символов, завершаемая парой escape-символов \r\n ") – ( ), ( ). При этом возвращаемая строка эти символы не включает.
В любом случае речь идет о получении символьной информации. Символьная информация достаточно просто извлекается из входного потока. Однако это не самая большая составляющая общего объема обрабатываемой информации. Как правило, содержимое входного потока приходится приводить к одному из базовых типов.
В .NET FCL реализован процесс преобразования информации в рамках Общей Системы Типов (CTS).
.NET Framework Class Library включает класс System.Convert, в котором реализовано множество функций-членов, предназначенных для выполнения ЗАДАЧИ ПРЕОБРАЗОВАНИЯ ЗНАЧЕНИЙ ОДНОГО базового ТИПА В ЗНАЧЕНИЯ ДРУГОГО базового ТИПА. В частности, в этом классе содержится множество функций (по нескольку функций на каждый базовый тип), обеспечивающих попытку преобразования символьных строк в значения базовых типов.
Кроме того, множество классов, входящих в CTS и FCL, располагают вариантами функции Parse(), основное назначение которой – ПОПЫТКА преобразования строк символов (в частности, получаемых в результате выполнения методов консольного ввода) в значения соответствующих типов.
При обсуждении функций преобразования неслучайно употребляется слово "попытка". Не каждая строка символов может быть преобразована к определенному базовому типу. Для успешного преобразования предполагается, что символьная строка содержит символьное представление значения в некотором общепринятом формате в соответствии с соответствующей существующей в операционной системе настройкой языковых параметров, представления чисел, времени и дат. С аналогичной ситуацией мы уже сталкивались при обсуждении понятия литералов. В случае успешного преобразования функции возвращают результат преобразования в виде значения соответствующего типа.
Если же значение одного типа не может быть преобразовано к значению другого типа, преобразующая функция ВЫРАБАТЫВАЕТ ИСКЛЮЧЕНИЕ, с помощью которого CLR (то есть среда выполнения!) уведомляется о неудачной попытке преобразования.
FileMode enumeration:
описывает, каким образом операционная система должна открывать файл.
| Имя члена | Описание |
|---|---|
Append |
Открывается существующий файл и выполняется поиск конца файла или же создается новый файл. FileMode.Append можно использовать только вместе с FileAccess.Write. Любая попытка чтения заканчивается неудачей и создает исключение ArgumentException |
Create |
Описывает, что операционная система должна создавать новый файл. Если файл уже существует, он будет переписан. Для этого требуется FileIOPermissionAccess.Write и FileIOPermissionAccess.Append. System.IO.FileMode.Create эквивалентно следующему запросу: если файл не существует, использовать ; в противном случае использовать Truncate |
|
Описывает, что операционная система должна создать новый файл. Для этого требуется FileIOPermissionAccess.Write. Если файл уже существует, создается исключение IOException |
Open |
Описывает, что операционная система должна открыть существующий файл. Возможность открыть данный файл зависит от значения, задаваемого FileAccess. Если данный файл не существует, создается исключение System.IO.FileNotFoundException |
OpenOrCreate |
Указывает, что операционная система должна открыть файл, если он существует, в противном случае должен быть создан новый файл. Если файл открыт с помощью FileAccess.Read, требуется FileIOPermissionAccess.Read. Если файл имеет доступ FileAccess.ReadWrite и данный файл существует, требуется FileIOPermissionAccess.Write. Если файл имеет доступ FileAccess.ReadWrite и файл не существует, в дополнение к Read и Write требуется FileIOPermissionAccess.Append |
Truncate |
Описывает, что операционная система должна открыть существующий файл. После открытия должно быть выполнено усечение файла таким образом, чтобы его размер стал равен нулю. Для этого требуется FileIOPermissionAccess.Write. Попытка чтения из файла, открытого с помощью Truncate, вызывает исключение |
FileAccess enumerations:
| Члены перечисления | Описание |
|---|---|
Read |
Доступ к файлу для чтения. Данные могут быть прочитаны из файла. Для обеспечения возможностей чтения/записи может комбинироваться со значением Write |
ReadWrite |
Доступ к файлу для чтения/записи |
Write |
Доступ к файлу для записи. Данные могут быть записаны в файл. Для обеспечения возможностей чтения/записи может комбинироваться со значением Read |
using System;
using System.IO;
namespace fstream00
{
class Class1
{
static string[] str = {
"1234567890",
"qwertyuiop",
"asdfghjkl",
"zxcvbnm", };
static void Main(string[] args)
{
int i;
// Полное имя файла.
string filename = @"D:\Users\WORK\Cs\fstream00\test.txt";
string xLine = "";
char[] buff = new char[128];
for (i = 0; i < 128; i++) buff[i] = (char)25;
// Запись в файл.
FileStream fstr = new FileStream(filename,
FileMode.Create,
FileAccess.Write);
BufferedStream buffStream = new BufferedStream(fstr);
StreamWriter streamWr = new StreamWriter(buffStream);
for (i = 0; i < str.Length; i++)
{
streamWr.WriteLine(str[i]);
}
streamWr.Flush();
streamWr.Close();
Console.WriteLine("–––––––––––––––––––––––––––––––––––––––––––");
fstr = new FileStream(filename,
FileMode.Open,
FileAccess.Read);
StreamReader streamRd = new StreamReader(fstr);
for ( ; xLine != null; )
{
xLine = streamRd.ReadLine();
Console.WriteLine(xLine);
}
Console.WriteLine("––––––––––––––––––––––––––––––––––––––––––––");
fstr.Seek(0, SeekOrigin.Begin);
streamRd.Read(buff,0,10);
Console.WriteLine(new string(buff));
Console.WriteLine("1––––––––––––––––––––––––––––––––––––––––––");
streamRd.Read(buff,0,20);
Console.WriteLine(new string(buff));
Console.WriteLine("2––––––––––––––––––––––––––––––––––––––––––");
Console.WriteLine(streamRd.Read(buff,0,15));
i = (int)fstr.Seek(–20, SeekOrigin.Current);
Console.WriteLine(streamRd.ReadLine());
Console.WriteLine("3––––––––––––––––––––––––––––––––––––––––––");
}
}
}
Пример
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace OutFile
{
class Program
{
static void Main(string[] args)
{
string buff;
FileStream outFstr, inFstr; // Ссылки на файловые потоки.
// Ссылка на выходной поток. Свойства и методы,
// которые обеспечивают запись в...
StreamWriter swr;
// Ссылка на входной поток. Свойства и методы,
// которые обеспечивают чтение из...
StreamReader sr;
// Класс Console – средство управления ПРЕДОПРЕДЕЛЕННЫМ потоком.
// Сохранили стандартный выходной поток,
// связанный с окошком консольного приложения.
TextWriter twrConsole = Console.Out;
// Сохранили стандартный входной поток, связанный с буфером клавиатуры.
TextReader trConsole = Console.In;
inFstr = new FileStream
(@"F:\Users\Work\readme.txt", FileMode.Open, FileAccess.Read);
sr = new StreamReader(inFstr); // Входной поток, связанный с файлом.
outFstr = new FileStream
(@"F:\Users\Work\txt.txt", FileMode.Create, FileAccess.Write);
swr = new StreamWriter(outFstr); // Выходной поток, связанный с файлом.
// А вот мы перенастроили предопределенный входной поток.
// Он теперь связан не с буфером клавиатуры, а с файлом, открытым для чтения.
Console.SetIn(sr);
Console.SetOut(swr);
while (true)
{
// Но поинтересоваться в предопределенном потоке отностительно
// конца файла невозможно.
// Такого для предопределенных потоков просто не предусмотрено.
if (sr.EndOfStream) break;
// А вот читать - можно.
buff = Console.ReadLine();
Console.WriteLine(buff);
}
Console.SetOut(twrConsole);
Console.WriteLine("12345");
Console.SetOut(swr);
Console.WriteLine("12345");
Console.SetOut(twrConsole);
Console.WriteLine("67890");
Console.SetOut(swr);
Console.WriteLine("67890");
sr.Close();
inFstr.Close();
swr.Close();
outFstr.Close();
}
}
}
В общем случае понятие ПОТОК — это абстрактное понятие, которое обозначает динамическую изменяющуюся во времени последовательность чего-либо.
Применительно к обсуждаемым проблемам ввода/вывода в программах на C#, поток – это последовательность байтов, связанная с конкретными устройствами компьютера (дисками, дисплеями, принтерами, клавиатурами) посредством системы ввода/вывода.
Система ввода/вывода обеспечивает для программиста стандартные и не зависящие от физического устройства средства представления информации и управления потоками ввода/вывода. Действия по организации ввода/вывода обеспечиваются стандартными наборами, как правило, одноименных функций ввода/вывода со стандартным интерфейсом.
Функции, обеспечивающие взаимодействие с различными устройствами ввода/вывода, объявляются в различных классах. Вопрос "ОТКУДА ВЫЗВАТЬ функцию" часто становится более важным, чем вопрос "КАК ПОСТРОИТЬ выражение вызова функции".
Большинство устройств, предназначенных для выполнения операций ввода/вывода, являются байт-ориентированными. Этим и объясняется тот факт, что на самом низком уровне все операции ввода/вывода манипулируют с байтами в рамках байтовых потоков.
С другой стороны, значительный объем работ, для которых, собственно, и используется вычислительная техника, предполагает работу с символами, а не с байтами (заполнение экранной формы, вывод информации в наглядном и легко читаемом виде, текстовые редакторы).
Символьно-ориентированные потоки, предназначенные для манипулирования с символами, а не с байтами, являются потоками ввода/вывода более высокого уровня. В рамках Framework .NET определены соответствующие классы, которые при реализации операций ввода/вывода обеспечивают автоматическое преобразование данных типа byte в данные типа char и обратно.
В дополнение к байтовым и символьным потокам в C# определены два класса, реализующих механизмы считывания и записи информации непосредственно в двоичном представлении (потоки BinaryReader и BinaryWriter ).
Основные особенности и правила работы с устройствами ввода/вывода в современных языках высокого уровня описываются в рамках классов потоков. Для языков платформы .NET местом описания самых общих свойств потоков является класс System.IO.Stream.
Назначение этого класса заключается в объявлении общего стандартного набора операций (стандартного интерфейса), обеспечивающих работу с устройствами ввода/вывода, независимо от их конкретной реализации, от источников и получателей информации.
Процедуры чтения и записи информации определяется следующим (список неполон!) набором свойств и АБСТРАКТНЫХ методов, объявляемых в этом классе.
В рамках Framework .NET, независимо от характеристик того или иного устройства ввода/вывода, программист ВСЕГДА может узнать:
bool CanRead (если можно – значение должно быть установлено в true );bool CanWrite (если можно – значение должно быть установлено в true );bool CanSeek (если последовательность, в которой производится чтение/запись, не является жестко детерминированной и возможно позиционирование в потоке – значение должно быть установлено в true );long Position (возможность позиционирования в потоке предполагает возможность программного изменения значения этого свойства);long Length.В соответствии с общими принципами реализации операций ввода/вывода, для потока предусмотрен набор методов, позволяющих реализовать:
int ReadByte();count) количества байтов из потока и размещение их в массиве buff, начиная с элемента buff[index], с возвращением количества успешно прочитанных байтов – int Read(byte[] buff, int index, int count);void WriteByte(byte b);count ) количества байтов из массива buff, начиная с элемента buff[index], с возвращением количества успешно записанных байтов – int Write(byte[] buff, int index, int count) ;long Seek (long index, SeekOrigin origin) (позиция текущего байта в потоке задается значением смещения index относительно позиции origin );void Flush();void Close().Множество классов потоков ввода/вывода в Framework .NET основывается (наследует свойства и интерфейсы) на абстрактном классе Stream. При этом классы конкретных потоков обеспечивают собственную реализацию интерфейсов этого абстрактного класса.
Наследниками класса Stream являются, в частности, три класса байтовых потоков:
BufferedStream – обеспечивает буферизацию байтового потока. Как правило, буферизованные потоки являются более производительными по сравнению с небуферизованными;FileStream – байтовый поток, обеспечивающий файловые операции ввода/вывода;MemoryStream – байтовый поток, использующий в качестве источника и хранилища информации оперативную память.Манипуляции с потоками предполагают НАПРАВЛЕННОСТЬ производимых действий. Информацию из потока можно ПРОЧИТАТЬ, а можно ее в поток ЗАПИСАТЬ. Как чтение, так и запись предполагают реализацию определенных механизмов байтового обмена с устройствами ввода/вывода.
Свойства и методы, объявляемые в соответствующих классах, определяют специфику потоков, используемых для чтения и записи:
TextReader,TextWriter.Эти классы являются абстрактными. Это означает, что они не "привязаны" ни к какому конкретному потоку. Они лишь определяют интерфейс (набор методов), который позволяет организовать чтение и запись информации для любого потока.
В частности, в этих классах определены следующие методы, определяющие базовые механизмы символьного ввода/вывода. Для класса TextReader:
int Peek () – считывает следующий знак, не изменяя состояние средства чтения или источника знака. Возвращает следующий доступный знак, не считывая его в действительности из потока входных данных;int Read(...) – несколько одноименных int Read() предполагает чтение из потока одного символа с возвращением его целочисленного эквивалента или –1 при достижении конца потока. Вариант int Read (char[] buff, int index, int count) и его полный аналог int ReadBlock(char[] buff, int index, int count) обеспечивает прочтение максимально возможного количества символов из текущего потока и записывает данные в буфер, начиная с некоторого значения индекса;string ReadLine() – читает строку символов из текущего потока. Возвращается ссылка на объект типа string ;string ReadToEnd() – читает все символы, начиная с текущей позиции символьного потока, определяемого объектом класса TextReader, и возвращает результат как ссылка на объект типа string ;void Close() – закрывает поток ввода.Для класса TextWriter, в свою очередь, определяется:
void Write(...) со значениями параметров, позволяющих записывать символьное представление значений базовых типов (смотреть список базовых типов) и массивов значений (в том числе и массивов строк);void Flush() – обеспечивает очистку буферов вывода. Содержимое буферов выводится в выходной поток;void Close() – закрывает поток вывода.Эти классы являются базовыми для классов:
StreamReader – содержит свойства и методы, обеспечивающие считывание СИМВОЛОВ из байтового потока,StreamWriter – содержит свойства и методы, обеспечивающие запись СИМВОЛОВ в байтовый поток.Вышеуказанные классы включают методы своих "предков" и позволяют осуществлять процессы чтения-записи непосредственно из конкретных байтовых потоков. Работа по организации ввода/вывода с использованием этих классов предполагает определение соответствующих объектов, "ответственных" за реализацию операций ввода/вывода, с явным указанием потоков, которые предполагается при этом использовать.
Еще одна пара классов потоков обеспечивает механизмы символьного ввода– вывода для строк:
StringReader,StringWriter.В этом случае источником и хранилищем множества символов является символьная строка.
Интересно заметить, что у всех ранее перечисленных классов имеются методы, обеспечивающие закрытие потоков, и не определены методы, обеспечивающие открытие соответствующего потока. Потоки открываются в момент создания объекта-представителя соответствующего класса. Наличие функции, обеспечивающей явное закрытие потока, принципиально. Оно связано с особенностями выполнения
Предопределенные потоки ввода/вывода используются для реализации ввода/вывода в рамках консольных приложений. Это еще одна категория потоков.
В классах предопределенных потоков, в отличие от ранее рассмотренных потоков, явным образом задаются источник (откуда) и место размещения (куда) информации.
Основные свойства и методы этих потоков определены в классе System.Console. Класс Console является достаточно сложной конструкцией.
В рамках этого класса определены свойства, обеспечивающие доступ к предопределенным потокам. В классе System.Console таких потоков три ( In, Out, Error ).
Ниже описываются свойства этих потоков:
Standard input stream – является объектом – представителем класса TextReader. По умолчанию поток In ориентирован на получение информации с клавиатуры.Standard output stream – является объектом – представителем класса TextWriter. По умолчанию обеспечивает вывод информации на дисплей.Standard error stream – также является объектом – представителем класса TextWriter. И также по умолчанию выводит информацию на дисплей.В классе System.Console также определены следующие СТАТИЧЕСКИЕ (!) функции – члены (вернее, множества СТАТИЧЕСКИХ
Write и WriteLine ;Read и ReadLine.При этом функции-члены класса Console ПО УМОЛЧАНИЮ обеспечивают связь с конкретным потоком. Например, при вызове метода
Console.WriteLine(...); // Список параметров опущен
информация направляется в поток, который представляется определенным в классе статическим свойством Console.Out. Это свойство предоставляет доступ к объекту – представителю класса TextWriter. Таким образом, класс Console также обеспечивает вызов соответствующей функции-члена от имени объекта – представителя класса TextWriter. Непосредственное обращение к члену класса Console (то есть к потоку), отвечающего за текстовый вывод, имеет следующую форму вызова:
Console.Out.WriteLine(...);
Вызов определенного в классе Console метода ReadLine
Console.ReadLine(...);
обеспечивает получение информации через поток, доступ к которому предоставляется статическим свойством Console.In. Непосредственный вызов аналогичной функции от имени члена класса TextReader выглядит следующим образом:
Console.In.ReadLine(...);
Функция – представитель класса Console, отвечающая за непосредственное направление информации в поток вывода сообщений об ошибках, не предусмотрена.
Вывод информации об ошибках может быть реализован путем непосредственного обращения к standard error :
Console.Error.WriteLine(...);
Если вспомнить, что свойство Error возвращает ссылку на объект – представитель класса TextWriter и, так же как и свойство Out, обеспечивает вывод в окно консольного приложения, разделение сообщений, связанных с нормальным ходом выполнения приложения и сообщений об ошибках, становится более чем странным.
Информация, выводимая приложением в "штатном" режиме, и сообщения об ошибках одинаково направляются в окно приложения на экран консоли. Однако в классе Console реализована возможность перенаправления соответствующих потоков. Например, стандартный поток вывода можно перенаправить в файл, а поток сообщений об ошибках оставить без изменений — направленным в окно приложения. Результат перенаправления очевиден.
Процедуры void SetIn(TextReader in), void SetOut(TextWriter out), void SetErr(TextWriter err) и будут рассмотрены ниже.
C# является языком, строго и в полном объеме реализующим принципы ООП. В этом языке все построено на классах и нет ничего, что бы не соответствовало принципам объектно-ориентированного программирования. Любой Object, реализующего, в частности, метод String ToString(), формирующий в виде Object (а это ВСЕ типы!) имеет либо унаследованную, либо собственную переопределенную версию метода ToString(). Применительно к объектам предопределенного типа из CTS, соответствующие версии методов ToString() обеспечивают преобразование значения данного типа к строковому виду.
Все сделано для программиста. Реализация метода преобразования значения в строку в конечном счете оказывается скрытой от программиста. Именно поэтому вывод значений предопределенных типов с использованием функций Write и WriteLine в программах на языке C# осуществляется так легко и просто. В справочнике по поводу этих функций так и сказано:
The text representation of value is produced by calling Type.ToString.
Эта функция имеет перегруженный вариант, использующий параметр типа string для указания желаемого формата представления. Множество значений этого параметра ограничено предопределенным списком символов форматирования (представлены ниже), возможно, в сочетании с целочисленными значениями.
| Символ форматирования | Описание |
|---|---|
C |
Отображение значения как валюты с использованием принятого по соглашению символа |
D |
Отображение значения как десятичное целое |
E |
Отображение значения в соответствии с научной нотацией |
F |
Отображение значения как fixed Point |
G |
Отображение значения в формате с фиксированной точкой или как десятичное целое. Общий формат |
N |
Применение запятой для разделения порядков |
X |
Отображение значения в шестнадцатеричной нотации |
Непосредственно за символом форматирования может быть расположена целочисленная ограничительная константа, которая, в зависимости от типа выводимого значения, может определять количество выводимых знаков после точки либо общее количество выводимых символов. При этом дробная часть действительных значений округляется или дополняется нулями справа. При выводе целочисленных значений ограничительная константа игнорируется, если количество преобразуемых символов превышает ее значение. В противном случае преобразуемое значение слева дополняется нулями.
Форматирование используется для преобразования значений "обычных" .NET Framework типов данных в строковое представление, да еще в соответствии с каким-либо общепринятым форматом. Например, целочисленное значение 100 можно представить в общепринятом формате currency для отображения валюты. Для этого можно использовать метод ToString() с использованием символа (строки?) форматирования (" C "). В результате может быть получена строка вида " $100.00 ". И это при условии, что установки операционной системы компьютера, на котором производится выполнение данного программного кода, соответствуют U.S. English specified as the current culture (имеется в виду "Настройка языковых параметров, представление чисел, денежных единиц, времени и дат").
int MyInt = 100;
String MyString = MyInt.ToString("C");
Console.WriteLine(MyString);
При обсуждении процедур ввода/вывода следует иметь в виду одно важное обстоятельство. Независимо от типа выводимого значения, в конечном результате выводится СИМВОЛЬНОЕ ПРЕДСТАВЛЕНИЕ значения. Это становится очевидным при выводе информации в окно приложения, что и обеспечивают по умолчанию методы Console.Write и Console.WriteLine.
WriteLine отличается тем, что завершает свою работу обязательным выводом line feed/.
Console.WriteLine("The Captain is on the board!"); // Вывод строки.
Console.WriteLine(314); //Символьное представление целочисленного значения.
Console.WriteLine(3.14); //Символьное представление значения типа float.
Выводимая символьная строка может содержать
Методы с одним параметром достаточно просты. Практически все происходит само собой. Уже на стадии компиляции при выяснении типа выводимого значения подбирается соответствующий вариант
При выводе значения определяется его тип, производится соответствующее стандартное преобразование к символьному виду, которое в виде последовательности символов, соответствующей существующей в операционной системе настройке языковых параметров, представления чисел, времени и дат, и выводится в окно представления.
Для облегчения процесса программирования ввода/вывода в C# также используются варианты функций Write и WriteLine с несколькими параметрами.
Эти методы называются ПЕРЕГРУЖАЕМЫМИ (см. о перегрузке методов). Для программиста C# это означает возможность вызова этих функций с различными параметрами. Можно предположить, что различным вариантам списков параметров функции могут соответствовать различные варианты функций вывода.
Ниже представлен вариант метода WriteLine с тремя параметрами. Во всех случаях, когда количество параметров превышает 1, первый параметр обязан быть символьной строкой:
Console.WriteLine("qwerty",314,3.14);
Чтобы понять, как выполняется такой оператор, следует иметь в виду, что всякий параметр метода является выражением определенного типа, которое в процессе выполнения метода может вычисляться для определения значения соответствующего выражения. Таким образом, при выполнении метода WriteLine должны быть определены значения его параметров, после чего в окне приложения должна появиться определенная последовательность символов. Ниже представлен результат выполнения выражения вызова функции:
qwerty
Значения второго и третьего параметров не выводятся.
Дело в том, что первый строковый параметр выражений вызова функций Write и WriteLine используется как управляющий шаблон для представления выводимой информации. Значения следующих за строковым параметром выражений будут выводиться в окно представления лишь в том случае, если первый параметр-строка будет явно указывать места расположения выводимых значений, соответствующих этим параметрам. Явное указание обеспечивается маркерами выводимых значений, которые в самом простом случае представляют собой заключенные в фигурные скобки целочисленные литералы (например, {3}).
При этом способ указания места состоит в следующем:
WriteLine, идущие за первым параметром-строкой. При этом второй по порядку параметр получает индекс 0, следующий за ним – 1, и т. д. до конца списка параметров;Таким образом, оператор
Console.WriteLine("The sum of {0} and {1} is {2}",314,3.14,314+3.14);
обеспечивает вывод следующей строки:
The sum of 314 and 3.14 is 317.14
В последнем операнде выражения вызова WriteLine при вычислении значения выражения используется неявное приведение типа. При вычислении значения суммы операнд типа int без потери значения приводится к типу float. Безопасные преобразования типов проводятся автоматически.
Несмотря на явную абсурдность выводимых утверждений, операторы
Console.WriteLine("The sum of {0} and {1} is {0}",314,3.14,314+3.14);
Console.WriteLine("The sum of {2} and {1} is {0}",314,3.14,314+3.14);
также отработают вполне корректно.
Помимо индекса параметра, маркер выводимого значения может содержать дополнительные сведения относительно формата представления выводимой информации. Выводимые значения преобразуются к символьному представлению, которое, в свою очередь, при выводе в окно приложения может быть дополнительно преобразовано в соответствии с предопределенным "сценарием преобразования". Вся необходимая для дополнительного форматирования информация размещается непосредственно в маркерах и отделяется запятой от индекса маркера.
Таким образом, в операторах вывода можно определить область позиционирования выводимого значения. Например, результатом выполнения следующего оператора вывода:
Console.WriteLine("***{0,10}***",3.14);
будет следующая строка:
***3.14 ***
А выполнение такого оператора:
Console.WriteLine("***{0,–10}***",3.14);
приведет к следующему результату:
*** 3.14***
Кроме того, в маркерах вывода могут также размещаться дополнительные строки форматирования ( FormatString ). При этом маркер приобретает достаточно сложную структуру, внешний вид которой в общем случае можно представить следующим образом ( M – значение индекса, N – область позиционирования):
{M,N:FormatString},
либо
{M:FormatString},
если не указывается значение области позиционирования.
Сама же строка форматирования аналогична ранее рассмотренной строке – параметру метода ToString и является комбинацией предопределенных символов форматирования и дополнительных целочисленных значений.
Непосредственно за символом форматирования может быть расположена целочисленная ограничительная константа, которая, в зависимости от типа выводимого значения, может определять количество выводимых знаков после точки либо общее количество выводимых символов. При этом дробная часть действительных значений округляется или дополняется нулями справа. При выводе целочисленных значений ограничительная константа игнорируется, если количество выводимых символов превышает ее значение. В противном случае выводимое значение слева дополняется нулями.
Следующие примеры иллюстрируют варианты применения маркеров со строками форматирования:
Console.WriteLine("Integer fotmating – {0:D3},{1:D5}",12345, 12);
Console.WriteLine("Currency formatting – {0:C},{1:C5}", 99.9, 999.9);
Console.WriteLine("Exponential formatting – {0:E}", 1234.5);
Console.WriteLine("Fixed Point formatting – {0:F3}", 1234.56789);
Console.WriteLine("General formatting – {0:G}", 1234.56789);
Console.WriteLine("Number formatting – {0:N}", 1234567.89);
Console.WriteLine("Hexadecimal formatting – {0:X7}",12345);//Integers only!
В результате выполнения этих операторов в окно консольного приложения будут выведены следующие строки:
Integer fotmating – 12345,00012 Currency formatting – $99.90,$999.90000 Exponential formatting – 1.234500E+003 Fixed Point formatting – 1234.568 General formatting – 1234.56789 Number formatting – 1,234,567.89 Hexadecimal formatting – 0003039
В маркерах выражений вызова функций вывода могут также размещаться спецификаторы (custom
В приведенной ниже таблице представлены символы, используемые для создания настраиваемых строк числовых форматов.
Следует иметь в виду, что на выходные строки, создаваемые с помощью некоторых из этих знаков, влияют настройки компонента "Язык и региональные стандарты" панели управления объекта NumberFormatInfo, связанного с текущим потоком. Результаты будут различными на компьютерах с разными параметрами культуры.
| Знак формата | Имя | Описание |
|---|---|---|
0 |
Знак – заместитель нуля | Цифра, расположенная в соответствующей позиции форматируемого значения, будет скопирована в выходную строку, если в этой позиции в строке формата присутствует " 0 ". Позиции крайних знаков " 0 " определяют знаки, всегда включаемые в выходную строку. Строка " 00 " приводит к округлению значения до ближайшего знака, предшествующего разделителю, если используется исключение из округления нуля. Например, в результате форматирования числа 34,5 с помощью строки " 00 " будет получена строка " 35 " |
# |
Заместитель цифры | Цифра, расположенная в соответствующей позиции форматируемого значения, будет скопирована в выходную строку, если в этой позиции в строке формата присутствует знак " # ". В противном случае в эту позицию ничего не записывается. Обратите внимание, что ноль не будет отображен, если он не является значащей цифрой, даже если это единственный знак строки. Ноль отображается, только если он является значащей цифрой форматируемого значения. Строка формата " ## " приводит к округлению значения до ближайшего знака, предшествующего разделителю, если используется исключение из округления нуля. Например, в результате форматирования числа 34,5 с помощью строки " ## " будет получена строка " 35 " |
| . | Разделитель | Первый знак " ." определяет расположение разделителя целой и дробной частей, дополнительные знаки " ." игнорируются. Отображаемый разделитель целой и дробной частей определяется свойством NumberDecimalSeparator объекта NumberFormatInfo |
| , | Разделитель тысяч | Знак "," применяется в двух случаях. Во-первых, если знак " ," расположен в строке формата между знаками-заместителями ( ," сразу после разделителя целой и дробной частей, число будет разделено на ," встречается в строке формата. Например, после форматирования строки " 0,," значение 100000000 будет преобразовано в " 100 ". Применение этого знака для масштабирования не включает в строку разделитель тысяч. Таким образом, чтобы разделить число на миллион и вставить разделители тысяч, следует использовать строку формата " #,##0,,"
|
% |
Заместитель процентов | При использовании этого знака число перед форматированием будет умножено на 100. В соответствующую позицию выходной строки будет вставлен знак " % ". Знак процента определяется текущим классом NumberFormatInfo |
E0 E+0 E–0 e0 e+0 e–0 |
Научная нотация | Если в строке формата присутствует один из знаков " E ", " E+ ", " E– ", " e ", " e+ " или " e– ", за которым следует по крайней мере один знак " 0 ", число представляется в научной нотации; между числом и экспонентой вставляется знак " E " или " e ". Минимальная длина экспоненты определяется количеством нулей, расположенных за знаком формата. Знаки " E+ " и " e+ " устанавливают обязательное отображение знака "плюс" или "минус" перед экспонентой. Знаки " E ", " e ", " E– " и " e– " устанавливают отображение знака только для отрицательных чисел |
\ |
Escape-знак | В языке C# и управляемых расширениях C++ знак, следующий в строке формата за обратной косой чертой, воспринимается как \n — новая строка).Чтобы использовать обратную косую черту как знак, в некоторых языках ее необходимо удвоить. В противном случае она будет интерпретирована компилятором как Обратите внимание, что escape-знак не поддерживается в Visual Basic, однако объект |
'ABC' "ABC" |
Строка букв | Знаки, заключенные в одинарные или двойные кавычки, копируются в выходную строку без форматирования |
; |
Разделитель секций | Знак " ; " служит для разделения секций положительных, отрицательных чисел и нулей в строке формата |
| Другие | Все остальные знаки | Все остальные знаки копируются в выходную строку в соответствующие позиции |
Строки формата с E0 ", " E+0 ", " E–0 ", " e0 ", " e+0 " или " e–0 ") ОКРУГЛЯЮТ значение с точностью, заданной количеством знаков-заместителей справа от разделителя целой и дробной частей.
Если в строке формата нет разделителя (точки), число округляется до ближайшего целого значения.
Если в целой части числа больше цифр, чем знаков – заместителей цифр, лишние знаки копируются в выходную строку перед первым знаком – заместителем цифры.
К строке может быть применено различное форматирование в зависимости от того, является ли значение положительным, отрицательным или нулевым. Для этого следует создать строку формата, которая состоит из трех секций, разделенных точкой с запятой.
При генерации выходной строки в этом типе форматирования не учитывается предыдущее форматирование. Например, при использовании разделителей секций отрицательные значения всегда отображаются без знака "минус". Чтобы конечное форматированное значение содержало знак "минус", его следует явным образом включить в настраиваемый указатель формата. В примере ниже показано применение разделителей секций для форматирования.
Следующий код демонстрирует использование разделителей секций при форматировании строки:
// Объявляются переменные с различными значениями (положительное,
// отрицательное, нулевое)
double MyPos = 19.95, MyNeg = –19.95, MyZero = 0.0;
string MyString = MyPos.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: $19.95.
MyString = MyNeg.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: ($19.95).
MyString = MyZero.ToString("$#,##0.00;($#,##0.00);Zero");
// In the U.S. English culture, MyString has the value: Zero.
Приведенная далее таблица иллюстрирует варианты представления выводимых значений в зависимости от используемых строк форматирования. Предполагается, что форматирование проводится в рамках ToString метода, а значения в столбце "Формат" соответствуют используемой строке форматирования.
В столбце "Data type" указывается тип форматируемого значения. В столбце "Значение" – значение, подлежащее форматированию. В столбце "Вывод" отображается результат форматирования строки для U.S. English параметров культуры.
| Формат | Data type | Значение | Вывод |
|---|---|---|---|
##### |
Double |
123 |
123 |
00000 |
Double |
123 |
00123 |
(###) ### – #### |
Double |
1234567890 |
(123) 456 – 7890 |
#.## |
Double |
1.2 |
1.2 |
0.00 |
Double |
1.2 |
1.20 |
00.00 |
Double |
1.2 |
01.20 |
#,# |
Double |
1234567890 |
1,234,567,890 |
#,, |
Double |
1234567890 |
12345 |
#,,, |
Double |
1234567890 |
1 |
#,##0,, |
Double |
1234567890 |
1,235 |
#0.##% |
Double |
0.095 |
9.5% |
0.###E+0 |
Double |
95000 |
9.5E+4 |
0.###E+000 |
Double |
95000 |
9.5E+004 |
0.###E–000 |
Double |
95000 |
9.5E004 |
[##–##–##] |
Double |
123456 |
[12–34–56] |
##;(##) |
Double |
1234 |
1234 |
##;(##) |
Double |
–1234 |
(1234) |
Демонстрация custom number formatting. Еще один пример:
Double myDouble = 1234567890; String myString = myDouble.ToString( "(# ##) ### – ####" ); // The value of myString is "(123) 456 – 7890". int MyInt = 42; MyString = MyInt.ToString( "My Number \n= #" ); // In the U.S. English culture, MyString has the value: // "My Number // = 42".
Следует иметь в виду, что чтение информации из входного потока класса Console связано с получением из буфера клавиатуры СИМВОЛЬНЫХ последовательностей и в большинстве случаев предполагает дальнейшие преобразования этих последовательностей к соответствующему типу значений.
Консольный ввод предполагает использование уже известных статических (!) функций – членов класса Console:
Read() – читает следующий знак из стандартного входного потока:public static int Read();
Возвращаемое значение:
Следующий знак из входного потока или значение " –1 ", если знаков больше нет.
Метод не будет завершен до окончания операции чтения, например при нажатии клавиши "Ввод". При наличии данных входной поток содержит ввод пользователя и зависящую от окружения последовательность знаков перехода на новую строку.
ReadLine() – считывает следующую строку символов из стандартного входного потока:public static string ReadLine();
Возвращаемое значение:
Следующая строка из входного потока или пустая ссылка, если знаков больше нет.
Строка определяется как последовательность символов, завершаемая парой escape-символов \r\n ") – ( ), ( ). При этом возвращаемая строка эти символы не включает.
В любом случае речь идет о получении символьной информации. Символьная информация достаточно просто извлекается из входного потока. Однако это не самая большая составляющая общего объема обрабатываемой информации. Как правило, содержимое входного потока приходится приводить к одному из базовых типов.
В .NET FCL реализован процесс преобразования информации в рамках Общей Системы Типов (CTS).
.NET Framework Class Library включает класс System.Convert, в котором реализовано множество функций-членов, предназначенных для выполнения ЗАДАЧИ ПРЕОБРАЗОВАНИЯ ЗНАЧЕНИЙ ОДНОГО базового ТИПА В ЗНАЧЕНИЯ ДРУГОГО базового ТИПА. В частности, в этом классе содержится множество функций (по нескольку функций на каждый базовый тип), обеспечивающих попытку преобразования символьных строк в значения базовых типов.
Кроме того, множество классов, входящих в CTS и FCL, располагают вариантами функции Parse(), основное назначение которой – ПОПЫТКА преобразования строк символов (в частности, получаемых в результате выполнения методов консольного ввода) в значения соответствующих типов.
При обсуждении функций преобразования неслучайно употребляется слово "попытка". Не каждая строка символов может быть преобразована к определенному базовому типу. Для успешного преобразования предполагается, что символьная строка содержит символьное представление значения в некотором общепринятом формате в соответствии с соответствующей существующей в операционной системе настройкой языковых параметров, представления чисел, времени и дат. С аналогичной ситуацией мы уже сталкивались при обсуждении понятия литералов. В случае успешного преобразования функции возвращают результат преобразования в виде значения соответствующего типа.
Если же значение одного типа не может быть преобразовано к значению другого типа, преобразующая функция ВЫРАБАТЫВАЕТ ИСКЛЮЧЕНИЕ, с помощью которого CLR (то есть среда выполнения!) уведомляется о неудачной попытке преобразования.
FileMode enumeration:
описывает, каким образом операционная система должна открывать файл.
| Имя члена | Описание |
|---|---|
Append |
Открывается существующий файл и выполняется поиск конца файла или же создается новый файл. FileMode.Append можно использовать только вместе с FileAccess.Write. Любая попытка чтения заканчивается неудачей и создает исключение ArgumentException |
Create |
Описывает, что операционная система должна создавать новый файл. Если файл уже существует, он будет переписан. Для этого требуется FileIOPermissionAccess.Write и FileIOPermissionAccess.Append. System.IO.FileMode.Create эквивалентно следующему запросу: если файл не существует, использовать ; в противном случае использовать Truncate |
|
Описывает, что операционная система должна создать новый файл. Для этого требуется FileIOPermissionAccess.Write. Если файл уже существует, создается исключение IOException |
Open |
Описывает, что операционная система должна открыть существующий файл. Возможность открыть данный файл зависит от значения, задаваемого FileAccess. Если данный файл не существует, создается исключение System.IO.FileNotFoundException |
OpenOrCreate |
Указывает, что операционная система должна открыть файл, если он существует, в противном случае должен быть создан новый файл. Если файл открыт с помощью FileAccess.Read, требуется FileIOPermissionAccess.Read. Если файл имеет доступ FileAccess.ReadWrite и данный файл существует, требуется FileIOPermissionAccess.Write. Если файл имеет доступ FileAccess.ReadWrite и файл не существует, в дополнение к Read и Write требуется FileIOPermissionAccess.Append |
Truncate |
Описывает, что операционная система должна открыть существующий файл. После открытия должно быть выполнено усечение файла таким образом, чтобы его размер стал равен нулю. Для этого требуется FileIOPermissionAccess.Write. Попытка чтения из файла, открытого с помощью Truncate, вызывает исключение |
FileAccess enumerations:
| Члены перечисления | Описание |
|---|---|
Read |
Доступ к файлу для чтения. Данные могут быть прочитаны из файла. Для обеспечения возможностей чтения/записи может комбинироваться со значением Write |
ReadWrite |
Доступ к файлу для чтения/записи |
Write |
Доступ к файлу для записи. Данные могут быть записаны в файл. Для обеспечения возможностей чтения/записи может комбинироваться со значением Read |
using System;
using System.IO;
namespace fstream00
{
class Class1
{
static string[] str = {
"1234567890",
"qwertyuiop",
"asdfghjkl",
"zxcvbnm", };
static void Main(string[] args)
{
int i;
// Полное имя файла.
string filename = @"D:\Users\WORK\Cs\fstream00\test.txt";
string xLine = "";
char[] buff = new char[128];
for (i = 0; i < 128; i++) buff[i] = (char)25;
// Запись в файл.
FileStream fstr = new FileStream(filename,
FileMode.Create,
FileAccess.Write);
BufferedStream buffStream = new BufferedStream(fstr);
StreamWriter streamWr = new StreamWriter(buffStream);
for (i = 0; i < str.Length; i++)
{
streamWr.WriteLine(str[i]);
}
streamWr.Flush();
streamWr.Close();
Console.WriteLine("–––––––––––––––––––––––––––––––––––––––––––");
fstr = new FileStream(filename,
FileMode.Open,
FileAccess.Read);
StreamReader streamRd = new StreamReader(fstr);
for ( ; xLine != null; )
{
xLine = streamRd.ReadLine();
Console.WriteLine(xLine);
}
Console.WriteLine("––––––––––––––––––––––––––––––––––––––––––––");
fstr.Seek(0, SeekOrigin.Begin);
streamRd.Read(buff,0,10);
Console.WriteLine(new string(buff));
Console.WriteLine("1––––––––––––––––––––––––––––––––––––––––––");
streamRd.Read(buff,0,20);
Console.WriteLine(new string(buff));
Console.WriteLine("2––––––––––––––––––––––––––––––––––––––––––");
Console.WriteLine(streamRd.Read(buff,0,15));
i = (int)fstr.Seek(–20, SeekOrigin.Current);
Console.WriteLine(streamRd.ReadLine());
Console.WriteLine("3––––––––––––––––––––––––––––––––––––––––––");
}
}
}
Пример
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace OutFile
{
class Program
{
static void Main(string[] args)
{
string buff;
FileStream outFstr, inFstr; // Ссылки на файловые потоки.
// Ссылка на выходной поток. Свойства и методы,
// которые обеспечивают запись в...
StreamWriter swr;
// Ссылка на входной поток. Свойства и методы,
// которые обеспечивают чтение из...
StreamReader sr;
// Класс Console – средство управления ПРЕДОПРЕДЕЛЕННЫМ потоком.
// Сохранили стандартный выходной поток,
// связанный с окошком консольного приложения.
TextWriter twrConsole = Console.Out;
// Сохранили стандартный входной поток, связанный с буфером клавиатуры.
TextReader trConsole = Console.In;
inFstr = new FileStream
(@"F:\Users\Work\readme.txt", FileMode.Open, FileAccess.Read);
sr = new StreamReader(inFstr); // Входной поток, связанный с файлом.
outFstr = new FileStream
(@"F:\Users\Work\txt.txt", FileMode.Create, FileAccess.Write);
swr = new StreamWriter(outFstr); // Выходной поток, связанный с файлом.
// А вот мы перенастроили предопределенный входной поток.
// Он теперь связан не с буфером клавиатуры, а с файлом, открытым для чтения.
Console.SetIn(sr);
Console.SetOut(swr);
while (true)
{
// Но поинтересоваться в предопределенном потоке отностительно
// конца файла невозможно.
// Такого для предопределенных потоков просто не предусмотрено.
if (sr.EndOfStream) break;
// А вот читать - можно.
buff = Console.ReadLine();
Console.WriteLine(buff);
}
Console.SetOut(twrConsole);
Console.WriteLine("12345");
Console.SetOut(swr);
Console.WriteLine("12345");
Console.SetOut(twrConsole);
Console.WriteLine("67890");
Console.SetOut(swr);
Console.WriteLine("67890");
sr.Close();
inFstr.Close();
swr.Close();
outFstr.Close();
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.