Форматирование строк. Метод String.Format

Потребность в форматировании строк возникает довольно часто (вывод определенного количества знаков после запятой для числа или дата в нужном формате и. т.д.).

В C# возможностью задать форматирование обладает метод Format класса String. Он возвращает отформатированную строку.

Общая структура форматирования строк имеет следующий вид:

String.Format("строка формата", arg0, arg1, …, argn);

arg0 и arg1 здесь – аргументы форматирования (числа, строки, даты, и т. д.), из которых в результате будет создана новая отформатированная строка.

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

{[номер аргумента], [ширина]:[формат]}

По [номеру аргумента] указывается, к какому аргументу будет применена данная команда (отсчет аргументов начинается с нуля). [ширина] задает минимальный размер поля. [формат] – спецификатор формата.

Параметры [ширина] и [формат] не являются обязательными.

Пример простого форматирования:

string formString = string.Format("Result is {0}", 5);

// "Result is 5"

Здесь на место команды {0} подставляется 0-й аргумент.

int num1 = 5, num2 = 3;

string formattedString =

string.Format("{0}+{1}={2}", num1, num2, num1+num2);

// "5+3=8"
Console.WriteLine(formattedString);

Console.ReadLine();

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

Console.WriteLine("Result is {0, 6}", 1.2789);

Console.WriteLine("Result is {0, 6}", 7.54);

Console.WriteLine("Result is {0, -6}", 1.2789);

Console.WriteLine("Result is {0, -6}", 7.54);

В результате получится, что в первых двух строках числа выровнены по правому краю, в двух других по левому:

Result is 1,2789

Result is 7,54

Result is 1,2789

Result is 7,54

Встроенные форматы числовых данных. Рассмотрим параметр команды форматирования после двоеточия – формат. Для числовых аргументов определен целый ряд форматов (табл.3.6). Почти для всех форматов можно также задать точность. Например, формат для чисел с фиксированной точкой «f» по умолчанию выводит не больше двух знаков после точки. Чтобы задать 4 знака, нужно указать после обозначения формата необходимое число, «f4».


Таблица 3.6. Символы форматирования строк

Специальный символ Формат Описание
c/C Денежная единица Указывает количество десятичных знаков
d/D Целые числа Указывает минимальное количество цифр. При необходимости добавляются нули
e/E Экспоненциальные числа Указывает количество десятичных знаков
f/F Числа с фиксированной точкой Указывает количество десятичных знаков
g/G Форматы e/E и f/F Выбирается наиболее компактная запись из двух вариантов: экспоненциального и с фиксированной запятой
n/N Числа с фиксированной точкой с отделением групп разрядов Указывает количество десятичных знаков
p/P Проценты Умножает число на 100 и выводит со знаком процентов. Указывает количество десятичных знаков

Окончание табл.3.6

r/R Формат кругового преобразования. Только фиксированная точка Форматирует число в строку таким образом, что его можно обратно преобразовать без потерь точности
x/X Шестнадцатеричные числа Указывает минимальное количество цифр. При необходимости добавляются нули

 

Пример использования некоторых из встроенных форматов:

Console.WriteLine("{0:c}", 5.50); // "5,50 руб."

Console.WriteLine("{0:c1}", 5.50); // "5,5 руб."

Console.WriteLine("{0:e}", 5.50); // "5,500000е+000"

Console.WriteLine("{0:d}", 32); // "32"

Console.WriteLine("{0:d4}", 32); // "0032"

Console.WriteLine("{0:p}", 0.55); // "55,00%"

 

Контрольные вопросы

1. Из каких базовых элементов состоит программа на C#?

2. Из чего состоит класс в C#?

3. Поясните роль метода Main в программе.

4. В чем преимущества строгой типизации языка?

5. Могут ли примитивные типы являться также типами-значениями и типами-ссылками? Приведите примеры.

6. Приведите пример использования операторов ветвления в C#.

7. Приведите пример использования операторов циклов в C#.

8. Приведите пример объявления и использования массивов в C#.

9. Приведите примеры операций со строками в C#.


10.

4. Основы объектно-ориентированного программирования в C#

4.1. Структура класса в С#

Представим, что в предметной области существует некоторый объект, который характеризуется рядом свойств. Например, работник на некой фирме. У него есть такие свойства, как фамилия, возраст, стаж и т. п. Удобно каждого работника описывать не рядом независимых переменных (строкового типа для фамилии, целого типа для возраста и стажа), а одной переменной типа Worker, внутри которой и содержатся переменные для фамилии, возраста и стажа. Конечно, типа Worker среди встроенных типов данных нет, однако его можно создать самостоятельно. Созданный таким образом новый тип данных, скомпонованный из примитивных типов или типов, созданных разработчиками ранее, в объектно-ориентированных языках принято называть классом.

Одна из важнейших особенностей классов - это то, что в них помимо переменных разных типов содержатся функции (или, что то же самое, методы) для работы с этими переменными. Скажем, в нашем примере с классом Worker логично ввести специальные функции для записи возраста и стажа. Функции могут, в частности, проверять правильность вводимой информации. Например, ясно, что возраст у работника не может быть отрицательным или большим 150.

Простейший класс С# может быть определен следующим образом:

class Worker

{

public int age=0;

 public string I; // Имя

public string O; // Отчество

public string F; // Фамилия

  

public string Fio()

{

  return I + " " + O + " " + F;

}

}

Внутри этого класса существуют четыре переменные - целая age для возраста и F, I и O строкового типа для фамилии имени и отчества. При этом для переменной age задано некоторое начальное значение прямо сразу после объявления переменной.

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

// Создается объект. Ссылка на него помещается в wrk1

Worker wrk1 = new Worker();

// Полям класса присваиваются некоторые значения

wrk1.age=34;

wrk1.I = "Иван";

wrk1.O = "Иванович";

wrk1.F = "Иванов";

// Присвоенные значения выводятся на экран

// консольного приложения

Доступ к полям с фамилией, именем и отчеством при выводе на экран осуществляется через метод Fio(). При выполнении метода Fio() выполняются  соответствующие действия. В данном случае это «склеивание» соответствующих полей в одну строку.


Конструкторы классов

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

Добавим в класс Worker, определенный выше, метод-конструктор:

 

class Worker

{

...

//Конструктор 1

public Worker(int Aage,

 string AF,

 string AI,

 string AO)

{

  this.age=Aage;

  this.I=AI;

  this.F=AF;

  this.O=AO;

}

}

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

Вызов конструктора может быть выполнен следующим образом:

Worker wrk1 = new Worker(34,

   "Иванов", "Иван", "Иванович");

4.3. Инкапсуляция и области видимости в C#

Принцип инкапсуляции является одним из трех «столпов» объектно-ориентированного программирования и позволяет разработчикам программ эффективно регулировать доступ к полям и методам класса. Сам термин «инкапсуляция» можно перевести как «упрятывание». Упрятывание очень удобно для того, чтобы по возможности уменьшить несанкционированное обращение к элементам класса, с одной стороны, и не затруднять работу программиста несущественными подробностями выполнения тех или иных действий - с другой.

Существуют три ключевых слова, которые регулируют доступ к элементам класса.

1. Элементы, которые могут быть использованы только в пределах данного класса. Эти элементы помечаются ключевым словом private (приватная). Сюда относятся элементы, которые выполняют в объекте слишком специфичные функции и которые поэтому целесообразно скрыть от других частей программы. Либо такие элементы, для которых по ряду причин не следует разрешать доступ извне объекта.

2. Элементы, которые могут быть использованы только семейством класса (то есть самим классом и всеми его потомками). Они помечаются ключевым словом protected (защищенная). Здесь размещаются элементы, которые важны лишь для функционирования объектов конкретного класса и его потомков, но которые могут трансформироваться в потомках.

3. Элементы, которые могут быть использованы всюду в программе. Такие элементы помечаются как public (общедоступная) и становятся доступны извне конкретного объекта.

Элементы, перед которыми не указывается ни одно из приведенных ключевых слов, считаются относящимися к элементам private.

Приведенные ключевые слова относятся к элементам класса. Следует понимать, что любая переменная, объявленная в теле метода, может быть доступна только из определенного места программы. Говорят, что переменные существуют только в своей области видимости, при выходе из которой переменная «умирает». Область видимости переменной в первом приближении начинается в строке, где переменная объявлена, и кончается на закрывающей фигурной скобке (переменная должна быть объявлена между этой скобкой и парной к ней открывающей). Например:

k=3;

// переменая k видна

Console.WriteLine(k);

В следующем примере вывести на экран значение переменной не получится.

...

{

int k=3;

}

Console.WriteLine(k); // Ошибка! Переменая k не видна

Во вложенных областях видимости, как правило, нельзя объявлять переменные с одинаковыми именами:

int k=4;

{

int k=3; //Ошибка!

}

4.4. Наследование в C#

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

Учитывая, что у каждого класса только один предок, можно сказать, что в любой программной системе на C# все классы организованы иерархически. На верхних уровнях иерархии находятся классы «предки», ниже - «потомки». На самой вершине иерархии находится класс System.Object.

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

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

В C#, как и в любом полноценном объектно-ориенти­рованном языке, возможно и обратное преобразование – от предка к потомку (движение по иерархии сверху вниз). Такое преобразование возможно только тогда, когда под ссылкой на класс-предок реально «скрывается» объект класса потомка. Т.е. объект, созданный конструктором потомка и никак иначе.

В следующем примере объявляется класс Boss, производный от класса Worker. Класс Boss будет отличаться от класса Worker, во-первых, наличием переменной numOfWorkers (для количества подчиненных) и, во-вторых, в классе будет присутствовать метод для задания возраста (setAge).

using System;

namespace test

{

//Класс Boss

class Boss: Worker

{

  public int numOfWorkers; //Количество подчиненных

  public void setAge(int age)

  {

 if(age>0 &amp;&amp; age < 45)

    this.age=age;

 else

    this.age=0;

  }

}

class Test

{

  static void Main(string[] args)

  {

 Boss boss = new Boss();

 boss.setAge(50);

 boss.numOfWorkers=4;

 Console.WriteLine("Возраст босса "+

   boss.getAge()+".\n"+

   "Количество подчиненных "+

   boss.numOfWorkers);

  }

}

}

Новый класс Boss вводится через конструкцию class Boss: Worker. Такая запись означает, что новый класс Boss будет потомком другого класса (Worker в данном случае). Можно сказать, что потомок автоматически умеет все то же самое, что и родительский класс. В частности, в нем есть поле age для хранения возраста. Далее, в класс Boss вводится переменная numOfWorkers для хранения количества подчиненных у босса. В родительском классе такой переменной не было. Кроме того, реализован метод setAge (например, по каким-то там внутренним инструкциям фирмы возраст босса не может превышать 45 лет).

Таким образом, в производном классе можно что-нибудь добавлять и что-нибудь изменять по отношению к классу родительскому. Однако убирать что-либо, полученное в наследство от родительского класса, нельзя.

4.5. Полиморфизм в C#

4.5.1. Средства реализации полиморфизма в C#

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

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

Возможность приписывать разную функциональность одному методу (функции, операции) называется перегрузкой метода (функций, операций). Такая возможность является основой для реализации принципа полиморфизма в программе.

Реализацию принципа полиморфизма можно проиллюстрировать на следующем примере. Допустим, мы идентифицировали в предметной области три класса - «Окружность» (Circle), «Прямоугольник» (Rectangle) и общий предок этих классов - «Фигура» (Figure).

Иерархия классов предметной области

У класса Figure определены два поля - координаты центра фигуры на плоскости X и Y. Помимо этого, у класса определен метод CalcArea, ответственный за вычисление площади фигуры. У классов Circle и Rectangle добавлено по одному полю - радиус R для окружности и длина стороны A и B для прямоугольника. Помимо этого, у этих двух классов определен метод CalcArea, который, на первый взгляд, наследуется от класса Figure. Однако метод CalcArea, хотя и является частью класса Figure, не может быть реализован в этом классе в принципе! Действительно, чтобы реализовать алгоритм рисования фигуры, нужно знать, площадь какой именно фигуры необходимо рассчитать. Другими, словами, уровень абстракции, которому соответствует класс Figure, не позволяет однозначно реализовать метод CalcArea. В то время, как на более низком уровне (в классах Circle и Rectangle) метод реализуется без проблем.

Приведенный на рисунке «треугольник», вершинами которого являются классы, представляет собой основу для реализации и демонстрации принципа полиморфизма на практике. Несмотря на отсутствие реализации метода CalcArea в Figure, этот класс выполняет очень важную функцию. Он является своего рода «фасадом» или «интерфейсом» классов своего семейства. Можно сказать, что независимо от того, какой класс-фигура будет впоследствии реализован, если он является наследником класса Figure, в нем обязательно должен быть реализован метод CalcArea, ответственный за расчет площади фигуры.

С учетом вышесказанного в программной системе появляется возможность единообразной работы со всеми объектами-потомками класса, при обращении к ним через ссылку на класс-предок. При этом реализация принципа полиморфизма в объектно-ориентированном языке означает, что при вызове какого-либо метода через ссылку на класс-предок фактически будет вызван метод именно того класса, к которому принадлежит объект.

Виртуальные функции

Если в родительском классе некоторый метод объявлен как виртуальный, то в производном классе его можно переопределить. При этом, если в переменную типа родительского класса будет записан экземпляр производного, то для такого экземпляра можно вызывать переопределенную функцию производного класса.

using System;

 

namespace SimpleOOP

{

class Program

{

static void Main(string[] args)

{

Figure f1 = new Circle() {X=10,Y=10,R=100};

Figure f2 = new Rectangle() {X=20,Y=20,A=50,B=60};

    Console.WriteLine("Площадь окружности - " +

f1.CalcArea());

    Console.WriteLine("Площадь прямоугольника - " +

f2.CalcArea());

    Console.ReadLine();

}

}

 

class Figure

{

// Координаты

public double X = 0;

public double Y = 0;

 

// Метод расчета площади фигуры

public virtual double CalcArea()

{

Console.WriteLine("Метод будет реализован в потомках");

return -1;

}

}

 

class Circle: Figure

{

// Радиус

public double R = 0;

 

public override double CalcArea()

{

return Math.PI*R*R;

}

}

 

class Rectangle: Figure

{

// Длина одной стороны

public double A = 0;

// Длина другой стороны

public double B = 0;

 

public override double CalcArea()

{

  return A*B;

}

}

}

Как видно, метод CalcArea в родительском классе Figure определен с ключевым словом virtual, а одноименный метод в производных классах Circle и Rectangle - с ключевым словом ovеrride.

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: