Чланови објекта и чланови класе¶
У овој лекцији:
по чему се разликују статички методи (тј. методи класе) од нестатичких (тј. метода објекта),
како да обезбедимо удобно учитавање објеката наше класе.
У претходном примеру смо започели прављење класе Razlomak
и за сада смо омогућили да
корисник формира разломке на основу вредности бројиоца и имениоца, као и да пореди те разломке.
Логично је да претпоставимо да ће програмер који користи класу Razlomak
желети да својим
корисницима омогући да унесу вредност разломка са тастатуре. Он би то могао да уради на разне
начине у неколико линија кода. Пошто вероватно не жели да понавља тих неколико линија за сваки
разломак који треба да се унесе, могао би и да напише метод који учитава бројилац и именилац
(са или без разломачке црте), а враћа учитани разломак. Међутим, ако је већ потребно да се напише
метод за унос разломка са тастатуре, онда је боље да тај метод буде део класе. На тај начин би
метод био написан само једном (у класи), уместо да га пише сваки програмер који користи класу
Razlomak
.
Према томе, било би добро да проширимо дефиницију наше класе методом за унос разломка. Како са
тастатуре директно може да се чита једино текст, написаћемо метод који из датог стринга (који
садржи запис разломка) ишчитава вредности бројиоца и имениоца и враћа формирани разломак. То
ће омогућити крајњем кориснику (који извршава програм) да уноси разломак онако како се он и
иначе пише, на пример 2/3
.
Идеја је да се учитавање разломака обавља као и учитавање целих или реалних бројева:
int i = int.Parse(Console.ReadLine());
double x = double.Parse(Console.ReadLine());
Razlomak r = Razlomak.Parse(Console.ReadLine());
Приметимо да за метод Parse
није потребан објекат класе Razlomak
, из кога би се метод
позивао. Природније је да овај метод позивамо навођењем имена класе, тачке и имена метода,
као што то радимо и за основне типове. За разлику од овога, методе Equals
и CompareTo
смо позивали навођењем имена објекта, тачке и имена метода, на пример:
if (r1.Equals(r2)) ...
Подсетимо се: методи CompareTo
и Equals
класе Razlomak
користе вредности поља a
и
b
да би свој разломак упоредили са другим разломком и израчунали тражену повратну вредност.
Од метода Parse
не очекујемо да уопште има свој разломак (а још мање да га користи), него да
направи и врати нови разломак.
Очигледно је да постоји важна разлика између метода CompareTo
и Equals
са једне, и метода
Parse
са друге стране. Могли бисмо да кажемо да методи CompareTo
и Equals
припадају
конкретним разломцима, тј. инстанцама класе Razlomak
(имају свој разломак), док метод Parse
представља функционалност саме класе и нема свој разломак.
Метод који представља функционалност саме класе и нема свој објекат називамо статички метод.
Метод који припада инстанци класе (тј. има свој објекат) називамо нестатички метод.
Статички метод означавамо кључном речју static
испред типа повратне вредности у дефиницији
метода. Ево како би статички метод Parse
могао да буде написан:
class Razlomak
{
private int a, b;
...
public static Razlomak Parse(string s)
{
if (String.IsNullOrEmpty(s))
{
throw new Exception("Nije dobar zapis razlomka");
}
int k = s.IndexOf('/');
if (k == -1) { return new Razlomak(int.Parse(s)); }
return new Razlomak(
int.Parse(s.Substring(0, k)),
int.Parse(s.Substring(k + 1)));
}
}
Ово, наравно, није први пут да се срећемо са кључном речи static
. Напротив, ову реч смо
виђали вероватно у свим досадашњим програмима на језику C# (приметимо да је метод Main
статички, тј. да његова дефиниција почиње са static void Main
). Међутим, до сада смо реч
static
прихватали без дубљег разумевања (просто – тако се пише), а сада јасније разумемо
разлику између статичких и нестатичких метода и знамо испред којих метода треба писати ову
реч, а испред којих не.
Као што је речено, метод проглашавамо за статички када му није потребан „његов” објекат.
Мада смо ово сад разјаснили, сигурно ће се дешавати да реч static
употребимо недоследно,
тј. да урадимо једну од следеће две ствари:
да у статичком методу покушамо да употребимо неког члана класе (поље, метод, својство), или
да за објекат дате класе покушамо да позовемо неки нестатички метод те класе.
Испоставиће се да у оба случаја компајлер пријављује грешку и програм не може да се преведе и покрене. Ако већ покушамо нешто бесмислено, као што је коришћење поља свог објекта из статичког метода, или позив статичког метода из инстанце класе, за нас је најбоље да такав покушај не прође ни компајлирање. Разумевање и исправљање оваквих синтаксних грешака може у почетку да изгледа тешко, али се у ствари брзо учи и сигурно је лакше од налажења грешке настале током извршавања програма.
Да би нам касније било лакше да се снађемо и разумемо поруке о грешкама, добро је да их први пут намерно направимо и да видимо како оне тачно изгледају, јер ћемо у првом наиласку на њих лакше да их разумемо када унапред знамо у чему смо погрешили.
Проверимо зато шта би се догодило када бисмо нпр. у статичком методу Parse
класе Razlomak
поменули поље a
. Додајте на почетак метода Parse
наредбу
Console.WriteLine(a);
и покушајте да преведете програм. Требало би да добијете следећу синтаксну грешку:
Error CS0120 An object reference is required for the non-static field, method, or property 'Razlomak.a'
У преводу: потребна је референца на објекат за нестатичко поље, метод, или својство Razlomak.a
.
Ово је начин на који ће нам компајлер јављати да име поља a
у методу Parse
не може да стоји
само за себе (јер метод Parse
нема свој објекат), него се захтева да се наведе и објекат коме то
поље припада.
Можемо да покушамо и обрнуто, да ван класе Razlomak
креирамо објекат r1
те класе и
да позовемо „његов” метод Parse
.
static void Main(string[] args)
{
Razlomak r1 = new Razlomak(2,3);
Razlomak r2 = r1.Parse(Console.ReadLine());
...
У овом случају добијамо следећу синтаксну грешку:
Error CS0176 Member 'Razlomak.Parse(string)' cannot be accessed with an instance reference; qualify it with a type name instead
У преводу: члан (метод) Parse
није дохватљив из инстанце класе (зато што је метод статички).
Уместо инстанцом, позив треба квалификовати именом типа (тј. класе). Овако ће нам компајлер
пријављивати грешку, која је на неки начин обрнута од претходне.
Поменимо и да смо у досадашњем коду класе Razlomak
већ употребили два статичка метода,
које смо редом назвали NZD
и Skrati
:
private static int NZD(int a, int b)
{
// nametnut preduslov: a >= 0 i b >= 0
while (b > 0) { int r = a % b; a = b; b = r; }
return a;
}
private static void Skrati(ref int x, ref int y)
{
// preduslov: x i y nisu oba nule
int d = NZD(Math.Abs(x), Math.Abs(y));
x /= d;
y /= d;
}
Метод NZD
израчунава највећи заједнички делилац два дата ненегативна цела броја и користи
се на неколико места као помоћна функција. Метод Skrati
између осталог служи да помоћу њега
скратимо разломак који се формира у конструктору и тако успоставимо потребне услове. Методи
NZD
и Skrati
, као ни метод Parse
, не захтевају присуство неког објекта класе
Razlomak
из кога би се ти методи позивали. У ствари, у методима NZD
и Skrati
се чак
ни не помињу објекти класе Razlomak
, па је сасвим природно да их означимо као статичке.
Једна од разлика између ових статичких метода је у томе што је метод Parse
јаван и намењен
позивању из кода ван класе, а методи NZD
и Skrati
су приватни и намењени употреби само унутар
класе. О томе зашто су методи NZD
и Skrati
приватни ће бити још речи у делу о апстракцији.
Након што смо додали метод Parse
, програмеру који користи класу Razlomak
је сада лако да
омогући својим корисницима да задају вредности разломака са тастатуре. Следи пример који можете
да испробате у свом радном окружењу.
Статичке класе¶
Кључну реч static
можемо да пишемо и испред имена класе, чиме цела класа постаје статичка.
Таква класа не може да се инстанцира, тј. не могу да се креирају објекти те класе. Самим тим,
једини чланови који имају смисла за такву класу су статички чланови, најчешће само статички
методи и константе (мада она може да има и статичка поља и друге чланове).
Пример статичке класе је добро позната класа Math
из библиотеке. У досадашњем учењу
програмирања навикли смо да математичке функције позивамо пишући Math
са тачком испред
имена функције, на пример Math.Abs(x)
, Math.Min(x, y)
, Math.Sqrt(x)
итд. Овај запис
већ препознајемо као позив статичких метода класе Math
, а сад знамо и зашто никада нисмо
видели (нити ћемо видети) запис облика
Math m = new Math();
Пошто је реч о статичкој класи, објекте ове класе није могуће креирати, нити је то потребно.
Класу Math
користимо само као колекцију међусобно сродних функција (метода), а у принципу
исто важи за сваку статичку класу, па и за оне које бисмо сами написали. Када би нам била
потребна једна таква колекција функција, могли бисмо да групишемо те функције у статичку класу,
тј. да имплементирамо потребне функције као методе статичке класе. Име статичке класе обично
бирамо тако да асоцира на оно што је заједничко свим њеним методима, као што је случај и са
класом Math
.
Следећи једноставан пример илуструје како можемо да направимо статичку класу.
Програм исписује
Visina obruca u kosarci je 10 stopa, odnosno 3.05 metara.
Jedna milja ima 5280 stopa.