static: общото за всички
Проблем
Всеки продукт е №1
Магазинът иска всеки нов продукт да получава пореден номер: 1, 2, 3… Слагаш брояч nextId в класа. Конструкторът дава на продукта текущия номер и увеличава брояча:
class Product{ private int nextId = 1; private int id; private string name;
public Product(string name) { this.name = name; id = nextId; nextId++; }
public override string ToString() { return "№" + id + " " + name; }}
Product bread = new Product("Хляб");Product milk = new Product("Мляко");Product coffee = new Product("Кафе");Console.WriteLine(bread);Console.WriteLine(milk);Console.WriteLine(coffee);№1 Хляб№1 Мляко№1 КафеВсички са №1. Причината е, че nextId е поле на обекта, както името. Всеки нов продукт получава свой брояч, който започва от 1. Броячът на хляба никога не стига до млякото.
Идеята
Поле на класа, не на обекта
Броячът не е част от един продукт. Той е общ за всички продукти. Добави static:
private static int nextId = 1;static поле е поле на класа. То е едно, колкото и обекти да създадеш, и всички обекти го делят:
class Product{ private static int nextId = 1; private int id; private string name;
public Product(string name) { this.name = name; id = nextId; nextId++; }
public override string ToString() { return "№" + id + " " + name; }}
class Program{ static void Main() { Product bread = new Product("Хляб"); Product milk = new Product("Мляко"); Product coffee = new Product("Кафе"); Console.WriteLine(bread); Console.WriteLine(milk); Console.WriteLine(coffee); }}№1 Хляб№2 Мляко№3 КафеХлябът взима 1 и прави брояча 2. Млякото вижда същия брояч, взима 2 и го прави 3.
private static int nextId = 1;
public Product(string name){ this.name = name; id = nextId; nextId++;}
Product bread = new Product("Хляб");Product milk = new Product("Мляко");Product coffee = new Product("Кафе");Стъпка по стъпка, с думи
- nextId е static: едно поле в класа, а не във всеки обект.
- new Product("Хляб"): нов обект, конструкторът записва името.
- id = nextId: обектът взима текущия номер, 1.
- nextId++ сменя общия брояч: вече е 2.
- Мляко: същият брояч. Взима 2 и го прави 3.
- Кафе: взима 3 и го прави 4.
- Броячът е един за всички, затова номерата са поредни.
Ако полето е public static, отвън до него се стига през името на класа (Product.nextId), а не през обект.
Пробвай
Общо и свое
Какво ще отпечата програмата?
class Order{ public static int Count = 0; public int Items = 0;
public Order() { Count++; Items++; }}
Order a = new Order();Order b = new Order();Order c = new Order();Console.WriteLine(Order.Count + " " + c.Items);Count е static: един брояч за всички поръчки, увеличен три пъти. Items е поле на обекта: всяка поръчка има свое, увеличено веднъж.Идеята
static метод: работа без обект
Същото важи за методите. static метод е метод на класа. Вика се през името на класа, без new:
class Calculator{ public static int Add(int a, int b) { return a + b; }
public static double Average(int[] values) { int sum = 0; foreach (int v in values) { sum += v; } return (double)sum / values.Length; }}
Console.WriteLine(Calculator.Add(10, 20));int[] grades = { 5, 6, 4, 6 };Console.WriteLine(Calculator.Average(grades));Console.WriteLine(Math.Max(3, 8));305.258На Calculator.Add не му трябва обект: всичко, от което има нужда, идва като параметри. Такива са и методите на Math: Math.Max, Math.Sqrt. Никой не пише new Math().
Сега е ясно защо Main е static. Когато програмата тръгва, обекти още няма. Средата вика Main направо през класа. Затова и методите от модул „Методи“, които викахме от Main, бяха static.
По-точно, за любопитните
Обратното също работи: в static метод можеш да създадеш обект и да викаш неговите методи. Точно това правим в Main от началото на модула.
Пробвай
Намери грешката
Избери реда с грешката.
Label() чете name, а name е на конкретен продукт. static метод няма обект, затова не знае чие име да вземе:error CS0120: An object reference is required for the non-static field, method, or property 'Product.name'static: етикетът е на продукта, затова е метод на обекта.Идеята
Константи
ДДС е 20% за всички продукти и не се променя, докато програмата работи. Такава стойност е константа:
class Prices{ public const double VatRate = 0.20;
public static double WithVat(double price) { return price * (1 + VatRate); }}
Console.WriteLine(Prices.WithVat(50));Console.WriteLine("ДДС: " + Prices.VatRate * 100 + "%");60ДДС: 20%const значи, че стойността е известна още при компилиране и не може да се промени. const е винаги на класа, затова static не се пише. Имената са като на свойствата: VatRate.
Ако някой опита да я смени,
Prices.VatRate = 0.25;програмата не се компилира:
error CS0131: The left-hand side of an assignment must be a variable, property or indexerПолзата е двойна: числото 0.20 е написано на едно място с ясно име, и никой не може да го промени по погрешка.
По-точно, за любопитните
Понякога стойността е различна за всеки обект, но след създаването не трябва да се мени. Такъв е номерът на продукта:
private static int nextId = 1;private readonly int id;
public Product(string name){ this.name = name; id = nextId; nextId++;}readonly поле се задава веднъж, в конструктора, и после не се сменя. Има и static readonly: като const, но стойността се изчислява при пускане, а не при компилиране. Ползва се за стойности, които не са просто число или текст.
Пробвай
Кое липсва?
class Calendar
{
public int Months = 12;
}const: стойността е константа на класа. static не се пише пред const.Идеята
Кога static и кога не
Питай се: „Това на конкретен обект ли е?“
- Да (балансът на сметката, името на продукта, тегленето от сметка): поле или метод на обекта, без
static. - Не (броят на всички сметки, ДДС, сбор на две числа):
static.
Честа грешка е всичко да стане static, защото Main е static и компилаторът спира да се оплаква. Тогава всички „обекти“ делят едни и същи данни и се връщаш при проблема с успоредните масиви от първия урок.
Упражнение
Общото за всички
Реши задачите в редактора си.
1. Поръчки. Клас Order с клиент и сума. Всяка нова поръчка получава пореден номер от static брояч. Static свойство Count само за четене казва колко поръчки са създадени.
Order[] orders ={ new Order("Мария", 45.5), new Order("Петър", 120), new Order("Елена", 18.9)};foreach (Order o in orders){ Console.WriteLine(o);}Console.WriteLine("Поръчки: " + Order.Count);Поръчка №1: Мария, 45.5 €Поръчка №2: Петър, 120 €Поръчка №3: Елена, 18.9 €Поръчки: 32. Калкулатор. Клас Calculator със static методи за събиране, изваждане и умножение на цели числа и за деление на дробни.
Console.WriteLine(Calculator.Add(10, 20));Console.WriteLine(Calculator.Subtract(10, 15));Console.WriteLine(Calculator.Multiply(6, 7));Console.WriteLine(Calculator.Divide(5, 2));30-5422.53. Обмяна. Клас Converter с константи за курса: 1 € = 1,08 щатски долара и 1 € = 0,85 британски лири (курсовете са примерни). Static методи превръщат сума в евро в долари и в лири. За 20 €:
20 EUR = 21.6 USD20 EUR = 17 GBPОще задачи
- Сметки с номера. Към
Accountот урока „Капсулация“ добави пореден номер на сметката от static брояч иreadonlyполе за номера. - Обмяна в двете посоки. Добави към
Converterметоди от долари и от лири към евро. Превърни 20 € в долари и обратно в евро. Получава ли се точно 20? - Ограничение. Магазинът приема най-много 3 продукта в количка. Сложи константа за това и накарай метода за добавяне да отказва четвъртия.
Накратко
Модулът накратко
- Клас е твой тип, обект е конкретен екземпляр по него, създаден с
new. Променливата сочи към обекта. Свой тип - Конструкторът задължава обектът да се създаде цял.
thisе текущият обект. Конструктори и this - Методите на обекта работят с неговите полета.
ToString()казва как изглежда като текст. Поведение privateкрие полетата. Отвън се стига до тях само през методи и свойства, които проверяват. Капсулацияstaticе общото за класа: броячи, помощни методи и константи.
Данни и правила на едно място, които никой не може да заобиколи: това е обектно-ориентираното програмиране. В следващия модул класовете ще започнат да споделят код помежду си.
Тест