Рис. 3.4. Простая логика конструктора
Замечание. После определения пользовательского конструктора для типа класса конструктор, заданный по умолчанию, будет удален. Чтобы в этом случае у пользователей объекта осталась возможность создавать экземпляры типа с помощью конструктора, заданного по умолчанию такой конструктор нужно явно переопределить, как это сделано в предыдущем примере.
Утечка памяти
Если вы имеете опыт программирования на языке C++, то у вас в связи с предыдущими примерами программного кода могут возникать вопросы. В частности, следует обратить внимание на то, что метод Main() типа HelloClass не имеет явных операторов уничтожений ссылок c1 и с2.
Это не ужасное упущение, а правило .NET. Как и программистам Visual Basic и Java, программистам C# не требуется уничтожать управляемые объекты явно. Механизм сборки мусора .NET освобождает память автоматически, поэтому в C# не поддерживается ключевое слово delete. В главе 5 процесс сборки мусора будет рассмотрен подробно. До того времени вам достаточно знать лишь о том, что среда выполнения .NET автоматически уничтожит размещенные вами управляемые объекты.
Определение "объекта приложения"
В настоящее время тип HelloClass решает две задачи. Во-первых, этот класс определяет точку входа в приложение (метод Main()). Во-вторых, HelloClass поддерживает элемент данных и несколько конструкторов. Все это хорошо и синтаксически правильно, но немного странным может показаться то, что статический метод Main() создает экземпляр того же класса, в котором этот метод определен.
class HelloClass {
…
public static int Main(string[] args) {
HelloClass c1 = new HelloClass();
…
}
}
Такой подход здесь и в других примерах используется только для того, чтобы сосредоточиться на иллюстрации решения соответствующей задачи. Более естественным подходом была бы факторизация типа HelloClass с разделением его на два отдельных класса: HelloClass и HelloApp. При компоновке C#-приложения обычно один тип используется в качестве "объекта приложения" (это тип, определяющий метод Main()), в то время как остальные типы и составляют собственно приложение.
В терминах ООП это называется разграничение обязанностей. В сущности, этот принцип проектирования программ требует, чтобы класс отвечал за наименьший объем работы. Поэтому мы можем изменить нашу программу следующим образом (обратите внимание на то, что здесь в класс HelloClass добавляется новый член PrintMessage()).
class HelloClass {
public string userMessage;
public HelloClass() {Console.WriteLine("Вызван конструктор, заданный по умолчанию!");}
public HelloClass(string msg) {
Console.WriteLine("Вызван пользовательский конструктор!");
userMessage = msg;
}
public void PrintMessage() {
Console.WriteLine("Значение userMessage: {0}n", userMessage);
}
}
class HelloApp {
public static int Main(string[] args) {
HelloClass c1 = new HelloClass("Эй, вы, там…");
c1.PrintMessage();
}
}
Исходный код. Проект HelloClass размещен в подкаталоге, соответствующем главе 3.
Класс System.Console
Многие примеры приложений, созданные для первых глав этой книги, используют класс System.Console. Конечно, интерфейс CUI (Console User Interface – консольный интерфейс пользователя) не так "соблазнителен", как интерфейс Windows или WebUI, но, ограничившись в первых примерах интерфейсом CUI, мы можем сосредоточиться на иллюстрируемых базовых понятиях, не отвлекаясь на сложности построения GUI (Graphical User Interface – графический интерфейс пользователя).
Как следует из его имени, класс Console инкапсулирует элементы обработки потоков ввода, вывода и сообщений об ошибках для консольных приложений. С выходом .NET 2.0 тип Console получил новые функциональные возможности.
В табл. 3.2 представлен список некоторых наиболее интересных из них (но, конечно же, не всех).
Таблица 3.2. Подборка членов System.Console, новых для .NET 2.0
Член Описание BackgroundColor ForegroundColor Свойства, устанавливающие цвет изображения/фона для текущего потока вывода. Могут получать значения из перечня ConsoleColor BufferHeight BufferWidth Свойства, контролирующие высоту/ширину буферной области консоли Clear() Метод, выполняющий очистку буфера и области отображения консоли Title Свойство, устанавливающее заголовок текущей консоли WindowHeight WindowWidth WindowTop WindowLeft Свойства, контролирующие размеры консоли относительно заданного буфера
Ввод и вывод в классе Console
Вдобавок к членам, указанным в табл. 3.2, тип Console определяет множество методов, обрабатывающих ввод и вывод, причем все эти методы определены как статические (static), поэтому они вызываются на уровне класса. Вы уже видели, что WriteLine() вставляет текстовую строку (включая символ возврата каретки) в выходной поток. Метод Write() вставляет текст в выходной поток без возврата каретки. Метод ReadLine() позволяет получить информацию из входного потока до символа возврата каретки, a Read() используется дли захвата одного символа из входного потока.
Чтобы проиллюстрировать основные возможности ввода-вывода класса Console, рассмотрим следующий метод Main(), который запрашивает у пользователя некоторую информацию и повторяет каждый элемент в потоке стандартного вывода. На рис 3.5 показан пример выполнения такой программы.
// Использование класса Console для ввода и вывода.
static void Main(string[] args) {
// Эхо для некоторых строк.
Console.Write("Введите свое имя: ");
string s = Console.ReadLine();
Console.WriteLine("Привет {0} ", s);
Console.Write("Укажите возpаст: ");
s = Console.ReadLine();
Console.WriteLine("Вам {0} год(а)/лет", s);
}
Рис. 3.5. Ввод и вывод с помощью System.Console
Форматирование консольного вывода
В этих первых главах вы много раз видели в строковых литералах символы {0}, {1} и др. В .NET вводится новый стиль форматирования строк, немного напоминающий стиль функции printf() в C, но без загадочных флагов %d, %s и %с. Вот простой пример (соответствующий вывод показан на рис. 3.6).
static void Main(string[] args) {
...
int theInt = 90;
double theDouble = 9.99;
bool theBool = true;
// Код 'n' в строковых литералах выполняет вставку
// символа перехода на новую строку.
Console.WriteLine("Int равно {0}nDouble равно {1}nВооl равно {2}", theInt, theDouble, theBool);
}
Рис. 3.6. Множество "пустышек" в строковых литералах
Первый параметр метода WriteLine() представляет собой строковый литерал, который содержит опции-заполнители, обозначенные {0}, {1}, {2} и т.д. (нумерация в фигурных скобках всегда начинается с нуля). Остальные параметры WriteLine() являются значениями, которые должны быть вставлены на место соответствующих заполнителей (в данном случае это theInt, theDouble и theBool).
Также следует знать о том, что метод WriteLine() перегружен, чтобы можно было указывать в качестве значения заполнителя массив объектов. Так, строкой формата следующего вида можно представить любое число элементов.
// Замена заполнителей элементами массива объектов.
object[] stuff = {"Эй", 20.9, 1, "Там", "83", 99.99933);
Console.WriteLine("Мусор: {0}, {1}, {2}, {3}, {4}, {5}", stuff);
Можно также повторять заполнитель в строке. Например, если вы являетесь поклонником Beatles и хотите построить строку "9, Number 9, Number 9", то можете написать следующее.
// Джон говорит,…
Console.WriteLine ("{0}, Number {0}, Number {0}", 9);
Замечание. Если имеется несоответствие между числом различных заполнителей в фигурных скобках и числом заполняющих их аргументов, то в среде выполнения генерируется исключение FormatException.
Флаги форматирования строк .NET
Если требуется более сложное форматирование, каждый заполнитель может дополнительно содержать различные символы форматирования (в верхнем или в нижнем регистре), как показано в табл. 3.3.
Таблица 3.3. Символы форматирования строк .NET
Символы форматирования строк Описание C или с Используются для форматирования денежных значений. По умолчанию перед этим флагом будет размещаться символ локальной денежкой единицы (скажем, знак доллара [$] для U.S. English) D или d Используются для форматирования десятичных чисел. Этот флаг также указывает минимальное число знаков, используемое для представления значения Е или е Используются для представлений в экспоненциальном формате F или f Используются для представления в формате с фиксированным разделителем G или g Обозначают
general (общий [формат]). Эти символы можно использовать для представления чисел в формате с фиксированным разделителем или в экспоненциальном формате N или n Используются для базового числового форматирования (с разделением групп разрядов) X или x Используются для представления в шестнадцатиричном формате. Если используется X (в верхнем регистре), то в шестнадцатиричном представлении используются символы верхнего регистра
Символы форматирования добавляются в виде суффикса к соответствующему заполнителю через двоеточие (например, {0:C}, {1:d}, {2:X} и т.д.). Предположим, что вы добавили в Main() следующий программный код.