3. ООП Енкапсулация. Основни компоненти на класовете. Дефиниране на базов клас. Компоненти на класовете - полета (член-данни, атрибути) константи методи (член-функции ) конструктори (constructors) деструктори (destructors) свойства - C# вложени типове (класове, структури, изброени типове и др.) предефинирани оператори (operators) индексатори (indexers) събития (events) В тази тема ще разгледаме най-основните компоненти на класовете, а именно: полета, методи, конструктори, деструктори, свойства и константи. В рамките на класа текущата инстанция (обект) на класа е достъпна чрез специална референция с твърдо зададено име. В езиците C++, C# и Java за означаване на тази референция се използва ключовата дума this. В езика Python за означаване на тази референция се използва ключовата дума self. Полета (член-данни, атрибути) Полетата, които можете да срещнете и с името атрибути или член-данни представляват променливи, които описват определени свойства на класа. Имат смисъл само в контекста на класа, в който са дефинирани. В езиците C++, C# и Java полетата се дефинират в рамките на класа, но извън неговите методи. Дефинициите, които са направени в рамките на някой метод са с локален за метода характер и не са полета на класа. В езика Python, за разлика от C++, C# и Java се разглеждат два типа полета: променливи на класа (дефинират се в рамките на класа, но извън методите на класа) и променливи на инстанцията (дефинират се в рамките на някой метод на класа посредством референцията self). Променливите на класа могат да се достъпват както чрез името на класа, така и чрез инстанция на класа. Променливите на инстанцията се достъпват само чрез инстанция на класа. Нека разгледаме клас, който описва един автомобил. Негови атрибути могат да бъдат: марка, модел, година на производство, мощност на двигателя. Те се дефинират като променливи, както е показано в следния пример. Доц. Павлинка Радойска ВУТП 1 В езиците C++, C# и Java полетата могат да бъдат инициализирани (brand, productionYear) или не (model, power). Java public class Car{ String brand="non"; String model; int productionYear=2000; int power; } Дефиницията в C# и С++ е идентична. C# class Car{ String brand="non"; String model; int productionYear=2000; int power; } C++ class Car{ string brand="non"; string model; int productionYear=2000; int power; }; В езика Python полетата (променливите) винаги се инициализират. В следващия пример полето brand е променлива на класа. Полетата model, productionYear, power са променливи на инстанцията. Те са дефинирани в метода __init__ посредством референцията self. Променливата x, която също е дефинирана в метода __init__ не е поле на класа, понеже при нейната дефиниция не е използвана референцията self. class Car: brand = "non" #променлива def __init__(self): self.model="nomodel" self.productionYear=2000 self.power = 0 x=5 Доц. Павлинка Радойска ВУТП на класа #променлива на инстанцията #променлива на инстанцията #променлива на инстанцията #не е поле на класа 2 Полетата могат да бъдат и сложни обекти. Пример. Дефинираме базов клас Address, който описва един пощенски адрес чрез пощенски код, град, улица и номер. Полетата са от тип стринг, само номера е цяло число. Дефинираме втори базов клас Person, който описва един човек чрез име, тип стринг и възраст – цяло число. Дефинираме клас House, който описва една къща чрез пощенски адрес и собственик. Пощенският адрес е обект от тип Address, а собственикът – обект от тип Person. C++, C# class Address { string ZIPcode = "0000"; string city = "non"; string street = "non"; int streetNb = 0; … } class Person { String name = "Noname"; int age = 0; … } class House { Address houseAddr; Person Ouner; … } Java class Address{ String ZIPcode="0000"; String city="non"; String street="non"; int streetNb=0; … } class Person { String name="Noname"; int age=0; … } static class House{ Address houseAddr; Person Ouner; … } Python Доц. Павлинка Радойска ВУТП 3 class Address: def __init__(self): self.ZIPcode = "0000" self.city = "non" self.street = "non" self.streetNb = 0 class Person: def __init__(self): self.name = "Noname" self.age = 0 class House: def __init__(self): self.houseAddr=Address() self.Ouner=Person() Константи Константите са данни, които не могат да променят своето място в паметта, своя размер и своята стойност през цялото си време на живот. Java В Java се декларират само Runtime константи. Тези константи се инициализират на етапа на изпълнение и не могат да променят своята стойност по време на изпълнение на програмата. Това се постига с ключовата дума final. public class ConstExample{ static public class Circle{ final String name = "Circle"; final double Pi=3.14; double radius=0.0; double perimeter=0.0; double place=0.0; public Circle() { radius=0.0; Calculations(); } public Circle(double r) { radius=r; Calculations(); } private void Calculations() { perimeter= 2*Pi*radius; place=Pi*radius*radius; } Доц. Павлинка Радойска ВУТП 4 public void printData() { System.out.println("radius=" +radius+"\nperimeter="+perimeter+"\nplace="+place); } } public static void main(String []args){ Circle c1=new Circle(); c1.printData(); Circle c2=new Circle(2); c2.printData(); } } C# Константите в С# биват два вида – константи, които приемат стойността си по време на компилация (compile-time константи) и такива, които получават стойност по време на изпълнение на програмата (runtime константи). Compile-time константи Compile-time константите се декларират със запазената дума const. Те задължително се инициализират в момента на декларирането им и не могат да се променят след това. Те реално не съществуват като променливи в програмата. По време на компилация се заместват със стойността им. Например: public const double PI = 3.1415926535897932; const string COMPANY_NAME = "Менте Софт"; Runtime константи Runtime константите се декларират като полета с модификатора readonly. Представляват полета, които са само за четене. Инициализират се по време на изпълнение (в момента на деклариране или в конструктора на типа) и не могат да се променят след като веднъж са инициализирани. Например: public readonly DateTime NOW = DateTime.Now; class Circle { const string name = "Circle"; readonly double Pi = 3.14; double radius; double perimeter; double place; public Circle() { radius = 0.0; Доц. Павлинка Радойска ВУТП 5 Calculations(); } public Circle(double r) { radius = r; Calculations(); } private void Calculations() { perimeter = 2 * Pi * radius; place = Pi * radius * radius; } public void printData() { Console.WriteLine(name + "\nradius=" + radius + "\nperimeter=" + perimeter + "\nplace=" + place); } } C++ В С++ с помощта на ключовата дума const се дефинират Runtime константи. Compile-time константите се дефинират като макроси и не могат да бъдат компоненти на класовете. #include <iostream> #include<string> using namespace std; class Circle { const string name = "Circle"; const double Pi = 3.14; double radius; double perimeter; double place; public: Circle() { radius = 0.0; Calculations(); } Circle(double r) { radius = r; Calculations(); } private: void Calculations() { perimeter = 2 * Pi * radius; place = Pi * radius * radius; } public: void printData() { Доц. Павлинка Радойска ВУТП 6 cout << name << "\nradius=" << radius << "\nperimeter=" << perimeter << "\nplace=" << place; } }; void main() { Circle c1; c1.printData(); Circle c2(2); c2.printData(); system("Pause"); } В С++ могат да се дефинират и константни методи. Това ще бъде разгледано в следващата подточка. Python В Python на практика няма константи. Променливите, които не трябва да променят стойността си се дефинират като името им се изписва с главни букви. Това обаче са си нормални променливи и тяхната стойност може да се променя по време на изпълнение на кода. Ако трябва да се работи с програмно непроменяеми стойности се използват изброими типове. class Circle: NAME="Окръжност" PI=3.14 def __init__(self, radius=0): self.radius=radius self.perimeter=0 self.area=0 self.calculation() def calculation(self): self.perimeter=2*Circle.PI*self.radius self.area=Circle.PI*self.radius*self.radius def printData(self): print (Circle.NAME) print("perimeter=",self.perimeter) print("area=",self.area) def __main__(): c=Circle(2) c.printData() if __name__ =="__main__": Доц. Павлинка Радойска ВУТП 7 __main__() Внимание! Константите е добре да бъдат статични. Как се дефинират статични данни и защо, ще разискваме в следващата тема. Методи Методите са член-функции на класа. Те определят поведението на класа. Имат смисъл само в контекста на класа, в който са дефинирани. Имат пълен достъп до всички компоненти на класа. C++, C# и Java public class Triangle{ double a=0, b=0, c=0, perimeter=0, square=0; void makePerimetre() { perimeter=a+b+c;} void makeSquare() { double p=(a+b+c)/2; square=p*(p-a)*(p-b)*(p-c); } } В езиците C++, C# и Java методите могат да се предефинират, стига да се различават по тип и/или брой на аргументите). public class Triangle{ double a=1, b=2, c=3, perimeter=0,square=0; void setValue(double x){a=b=c=x;} //задаване на стойности са страните при равностранен триъгълник void setValue(double xa, double xc){ //задаване на стойности са страните при равнобедрен триъгълник a=b=xa; c=xc;} void setValue(double xa, double xb, double xc){ //задаване на стойности са страните при разностранен триъгълник a=xa; b=xb; c=xc;} void makePerimetre() { perimeter=a+b+c;} void makeSquare() { double p=(a+b+c)/2; square=p*(p-a)*(p-b)*(p-c); } Доц. Павлинка Радойска ВУТП 8 } Python В Python методите не могат да се предефинират, но се допуска дефиниране на методи с подразбиращи се стойности. Методите в Python задължително получават параметър self. Във всеки един метод на класа могат да се дефинират променливи на инстанциите. import math class Triangle: def __init__(self, c=0.0,b=0.0,a=0.0): if c>0 and b==0.0 and a==0.0: #равностранен триъгълник b=c a=c elif c>0 and b>0 and a==0.0: #равнобедрен триъгълник a=b else: a=b=c=0.0 # некоректни стойности self.a=a self.b=b self.c=c self.perimeter=0.0 self.area=0.0 self.calculate() def calculate(self): self.perimeter=self.a+self.b+self.c p=self.perimeter/2 if p>0.0: self.area=math.sqrt(p*(p-self.a)*(p-self.b)*(p-self.c)) def print(self): print("a=",self.a) print("b=",self.b) print("c=",self.c) print("perimeter=",self.perimeter) print("area=",self.area) Константни методи В езикът С++ могат да се дефинират константни методи. Това е метод, за който се гарантира, че няма да промени обекта или да извика друг метод, които не е const (тъй като той може да промени обекта). Константните методи се дефинират чрез ключовата дума const, която се записва след списъка с аргументите и преди тялото на метода. class Something { public: Доц. Павлинка Радойска ВУТП 9 int m_value; Something() : m_value(0) { } void resetValue() { m_value = 0; } void setValue(int value) { m_value = value; } int getValue() const { return m_value; } }; Конструктори Конструкторите са методи на класа със специално предназначение. Те се извикват автоматично при създаване на обект. В езиците C++, C# и Java конструкторите имат следните характеристики: 1) Конструкторът се извиква автоматично при създаване на обект от типа на класа. 2) Конструкторът задължително носи името на класа. 3) При дефиниране на конструкторът не се пише тип на върнатата стойност. Той неявно връща референция/указател към създадения обект. Тази референция може да се използва в рамките на класа посредством ключовата дума this. 4) Конструкторите могат да се предефинират. 5) Ако не се дефинира конструктор, се изпълнява подразбиращ се конструктор, който изпълнява операциите по заделяне на памет за създавания обект. При създаване на обект, първо се изпълнява подразбиращия се конструктор, който заделя необходимата памет за създавания обект, създава таблица с локалния адрес и типовите характеристики на член-данните. При езиците Java и C# се изпълнява инициализиране на член-данните или със зададената при дефинирането им стойност или подразбираща се стойност (съобразно правилата на съответния език), ако те не са инициализирани явно. При С++, ако член-данните не са инициализирани, те имат произволна стойност. В езика Python конструктора има следните характеристики: 1) Конструкторът се извиква автоматично при създаване на обект от типа на класа. 2) Конструкторът задължително носи името __init__. 3) Конструкторът е само един. 4) Конструкторът задължително получава като аргумент референцията към създадения обект (инстанция) self. 5) Конструкторът може да има допълнителни аргументи, които могат да притежават подразбиращи се стойности. Пример за конструктори в езиците C++, C# и Java В следващия пример, написан на езика Java, е създаден клас Triangle, който описва един триъгълник. В класа са дефинирани 4 конструктора: без параметри, с един параметър, с 2 параметъра и с 3 параметъра. Java public static class Triangle{ double a, b, c, perimeter, square; Доц. Павлинка Радойска ВУТП 10 … public Triangle(){} //Първи конструктор public Triangle(double x){a=b=c=x;} //Втори конструктор public Triangle(double xa, double xc){ //Трети конструктор a=b=xa; c=xc;} public Triangle(double xa, double xb, double xc){ //Четвърти конструктор a=xa; b=xb; c=xc;} } С помощта на всеки един от четирите конструктора е създаден по един обект (инстанция на класа). Triangle tr1=new Triangle(); Triangle tr2=new Triangle(5); Triangle tr3=new Triangle(2,3); Triangle tr4=new Triangle(2,3,4); //Изпълнява се първи конструктор //Изпълнява се втори конструктор //Изпълнява се трети конструктор //Изпълнява се четвърти конструктор След стартиране на кода Ако не е дефиниран първия конструктор, този без параметри, ще се изпълни само подразбиращия се. public static class Triangle{ double a, b, c, perimeter, square; … //public Triangle(){} //Първи конструктор public Triangle(double x){a=b=c=x;} //Втори конструктор public Triangle(double xa, double xc){ //Трети конструктор a=b=xa; c=xc;} public Triangle(double xa, double xb, double xc){ //Четвърти конструктор a=xa; b=xb; c=xc;} } … Triangle tr1=new Triangle(); //Изпълнява се само подразбиращия се конструктор Triangle tr2=new Triangle(5); //Изпълнява се втори конструктор Triangle tr3=new Triangle(2,3); //Изпълнява се трети конструктор Triangle tr4=new Triangle(2,3,4); //Изпълнява се четвърти конструктор Доц. Павлинка Радойска ВУТП 11 C# class Program { public class Triangle { double a , b , c, perimeter = 0, square = 0; public Triangle() { a = 1; b = 1; c = 1; makePerimetre(); makeSquare(); } public Triangle(double x) //Втори конструктор { a = b = c = x; makePerimetre(); makeSquare(); } public Triangle(double xa, double xc)//Трети конструктор { a = b = xa; c = xc; makePerimetre(); makeSquare(); } public Triangle(double xa, double xb, double xc)//Четвърти конструктор { a = xa; b = xb; c = xc; makePerimetre(); makeSquare(); } public void print() { Console.WriteLine(" a= " + a + ", b=" + b + ", c=" + c); Console.WriteLine(" perimeter= " + perimeter); Console.WriteLine(" square=" + square); } void makePerimetre() { perimeter = a + b + c; } void makeSquare() { double p = (a + b + c) / 2; square = p * (p - a) * (p - b) * (p - c); } } static void Main(string[] args) { Triangle tr1 = new Triangle(); Console.WriteLine(" tr1" ); tr1.print(); Triangle tr2 = new Triangle(2); Console.WriteLine(" tr2"); tr2.print(); Triangle tr3 = new Triangle(2,3); Console.WriteLine(" tr3"); tr3.print(); Triangle tr4 = new Triangle(2,3,4); Console.WriteLine(" tr4"); tr4.print(); } } Доц. Павлинка Радойска ВУТП 12 Референция this - начин на употреба. В рамките на класа може да се използва this за достъп до членовете на класа. Това е полезно, когато има дублиране на имена на формални параметри на метод или конструктор с имена на член-данни на класа. Освен това при писане на код средата дава подсказка за полетата на класа, което облекчава работата на програмиста. В следващия пример, на член-данните a,b,c ще се присвоят подадените стойности при създаването на обекта tr4: a=2, b=3, c=4. double a, b, c, perimeter, square; … public Triangle(double a, double b, double c){ this.a=a; this.b=b; this.c=c;} … Triangle tr4=new Triangle(2,3,4); Но в следващия пример, на член-данните a,b,c няма да се присвоят подадените стойности при създаването на обекта tr4, т.к. се получава нееднозначност. Те ще получат стойностите по подразбиране: a=0, b=0, c=0. double a, b, c, perimeter, square; … public Triangle(double a, double b, double c){ a=a; b=b; c=c;} … Triangle tr4=new Triangle(2,3,4); Цялостен код на задачата на Java. public class OOP1{ public static class Triangle{ double a, b, c, perimeter=0,square=0; void setValue(double x){a=b=c=x;} //задаване на стойности са страните при равностранен триъгълник void setValue(double xa, double xc){ //задаване на стойности са страните при равнобедрен триъгълник a=b=xa; c=xc;} при void setValue(double xa, double xb, double xc){ //задаване на стойности са страните разностранен триъгълник a=xa; b=xb; c=xc;} Доц. Павлинка Радойска ВУТП 13 void makePerimetre() { perimeter=a+b+c;} void makeSquare() { double p=(a+b+c)/2; square=p*(p-a)*(p-b)*(p-c); } public Triangle(){} public Triangle(double x){a=b=c=x;} public Triangle(double xa, double xc){ a=b=xa; c=xc;} public Triangle(double a, double b, double c){ this.a=a; this.b=b; this.c=c;} public void print() { //System.out.println(name +" - "+a + ", "+b+", "+c); System.out.println("a="+a + ", b="+b+", c="+c); } } public static void main(String []args){ Triangle tr1=new Triangle(); tr1.print(); Triangle tr2=new Triangle(5); tr2.print(); Triangle tr3=new Triangle(2,3); tr3.print(); Triangle tr4=new Triangle(2,3,4); tr4.print(); } } Резултат от изпълнението. Цялостен код на същата задача на C#. Кодът е почти същия, както и на Java. using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading.Tasks; Доц. Павлинка Радойска ВУТП 14 namespace ConsoleApp17 { class Program { public class Triangle { double a, b, c, perimeter = 0, square = 0; void setValue(double x) { a = b = c = x; } //задаване на стойности са страните при равностранен триъгълник void setValue(double xa, double xc) { //задаване на стойности са страните при равнобедрен триъгълник a = b = xa; c = xc; } void setValue(double xa, double xb, double xc) { //задаване на стойности са страните при разностранен триъгълник a = xa; b = xb; c = xc; } void makePerimetre() { perimeter = a + b + c; } void makeSquare() { double p = (a + b + c) / 2; square = p * (p - a) * (p - b) * (p - c); } public Triangle() { } public Triangle(double x) { a = b = c = x; } public Triangle(double xa, double xc) { a = b = xa; c = xc; } public Triangle(double a, double b, double c) { this.a = a; this.b = b; this.c = c; } public void print() { Console.WriteLine( " a= " + a + ", b=" + b + ", c=" + c); } } static void Main(string[] args) { Triangle tr1 = new Triangle(); tr1.print(); Triangle tr2 = new Triangle(5); tr2.print(); Triangle tr3 = new Triangle(2, 3); tr3.print(); Triangle tr4 = new Triangle(2, 3, 4); Доц. Павлинка Радойска ВУТП 15 tr4.print(); } } } Резултат от изпълнението. C++ #include <iostream> using namespace std; class Triangle { double a=0, b, c, perimeter = 0, square = 0; public: void setValue(double x) { a = b = c = x; } страните при равностранен триъгълник void setValue(double xa, double xc) { //задаване на стойности са страните при равнобедрен триъгълник a = b = xa; c = xc; } void setValue(double xa, double xb, double xc) { //задаване на стойности са страните при a = xa; b = xb; c = xc; } //задаване на стойности са разностранен триъгълник void makePerimetre() { perimeter = a + b + c; } void makeSquare() { double p = (a + b + c) / 2; square = p * (p - a) * (p - b) * (p - c); } Triangle() { } Triangle(double x) { a = b = c = x; } Triangle(double xa, double xc) { a = b = xa; c = xc; } Triangle(double a, double b, double c) { this->a = a; this->b = b; this->c = c; } Доц. Павлинка Радойска ВУТП 16 void print() { cout<<" a= " << a << ", b=" << b << ", c=" << c<<endl; } }; void main() { Triangle tr1; tr1.print(); Triangle *tr2 = new Triangle(5); tr2->print(); Triangle tr3(2, 3); tr3.print(); Triangle *tr4 = new Triangle(2, 3, 4); tr4->print(); system("Pause"); } Решение на същата задача на Python import math from pickle import TRUE class Triangle: def __init__(self, c=0.0,b=0.0,a=0.0): if c>0 and b==0.0 and a==0.0: #равностранен триъгълник b=c a=c elif c>0 and b>0 and a==0.0: #равнобедрен триъгълник a=b elif c>0 and b>0 and a>0: #разностранен триъгълник pass else: a=b=c=0.0 # некоректни стойности self.a=a self.b=b self.c=c self.perimeter=0.0 self.area=0.0 self.__calculate() #Проверка дали подадените стойности def __isTriangle(self): if self.a>self.b+self.c: return if self.b>self.a+self.c: return if self.c>self.a+self.b: return return True #Изчисляване на обиколката и лицето def __calculate(self): if self.__isTriangle(): Доц. Павлинка Радойска ВУТП за страните образуват триъгълник False False False 17 self.perimeter=self.a+self.b+self.c p=self.perimeter/2 self.area=math.sqrt(p*(p-self.a)*(p-self.b)*(p-self.c)) else: self.perimeter=0 self.area=0 #Метод за извеждане на екрана def print(self): print("a=",self.a) print("b=",self.b) print("c=",self.c) print("perimeter=",self.perimeter) print("area=",self.area) def __main__(): tr1=Triangle() tr2=Triangle(3) #равностранен триъгълник tr3=Triangle(3,4) #равнобедрен триъгълник tr4=Triangle(3,4,5) #разностранен триъгълник tr1.print() tr2.print() tr3.print() tr4.print() if __name__=="__main__": __main__() В този случай конструкторът е само един. За да се даде възможност да се създава обект от тип равностранен или равнобедрен триъгълник като се подава само една, респективно само две стойности, се дава възможност конструкторът да има подразбиращи се стойности и в тялото му се прави проверка на броят на подадените стойности. Свойства В езикът Python член-данните (и променливите на класа и променливите на инстанциите) по подразбиране са с публичен достъп и могат да се достъпват посредством името на класа или инстанцията. Това не винаги е добър подход, защото може да доведе до грешки в работата на кода. В горния пример с класа Triangle директната промяна на стойността на някоя от страните на триъгълника няма да доведе до съответната промяна на периметъра и площта, което ще доведе до грешни резултати. Поради тази причина е добре променливите на инстанциите да се направят с частен достъп и да се създадат методи за достъп до тях – за запис на страните на триъгълника и за четене на периметъра и площта. Python Доц. Павлинка Радойска ВУТП 18 import math from pickle import TRUE class Triangle: def __init__(self, c=0.0,b=0.0,a=0.0): if c>0 and b==0.0 and a==0.0: #равностранен триъгълник b=c a=c elif c>0 and b>0 and a==0.0: #равнобедрен триъгълник a=b elif c>0 and b>0 and a>0: #разностранен триъгълник pass else: a=b=c=0.0 # некоректни стойности self.__a=a self.__b=b self.__c=c self.__perimeter=0.0 self.__area=0.0 self.__calculate() #Проверка дали подадените стойности за страните образуват триъгълник def __isTriangle(self): if self.__a>self.__b+self.__c: return False if self.__b>self.__a+self.__c: return False if self.__c>self.__a+self.__b: return False return True #Изчисляване на обиколката и лицето def __calculate(self): if self.__isTriangle(): self.__perimeter=self.__a+self.__b+self.__c p=self.__perimeter/2 self.__area=math.sqrt(p*(p-self.__a)*(p-self.__b)*(p-self.__c)) else: self.__perimeter=0 self.__area=0 #Метод за промяна на стойността на страната a def setA(self, a): if a>0: self.__a=a self.__calculate() #Метод за промяна на стойността на страната b def setB(self, b): if b>0: self.__b=b self.__calculate() #Метод за промяна на стойността на страната c Доц. Павлинка Радойска ВУТП 19 def setC(self, c): if c>0: self.__c=c self.__calculate() #Метод за извеждане на екрана def print(self): print("a=",self.__a) print("b=",self.__b) print("c=",self.__c) print("perimeter=",self.__perimeter) print("area=",self.__area) def __main__(): tr1=Triangle() tr2=Triangle(3) #равностранен триъгълник tr3=Triangle(3,4) #равнобедрен триъгълник tr4=Triangle(3,4,5) #разностранен триъгълник tr1.print() tr2.print() tr3.print() tr4.print() tr=Triangle(2,6,7) tr.print() tr.setA(4) tr.print() tr.setB(4) tr.print() tr.setC(5) tr.print() #Промяна на стойността на страната a #Промяна на стойността на страната b #Промяна на стойността на страната c if __name__=="__main__": __main__() По подразбиране в езиците С++, C# и Java член-данните са с частен достъп (private) и не са видими извън рамките на класа. Практиката е да се създава код за контролиран достъп до тези данни. В езиците С++ и Java се пишат методи за достъп до член-данните на класа. Отделен метод за четене и отделен за запис. Примери public class OOP1{ public static class Triangle{ double a, b, c, perimeter=0,square=0; public double getSideA() {return a;} //четене на атрибута a Доц. Павлинка Радойска ВУТП 20 public void setSideA(double a){ if (a > 0) { this.a = a; makePerimetre(); makeSquare(); } } //запис на стойност в атрибута a … } public static void main(String []args){ Triangle tr4=new Triangle(2,3,4); tr4.print(); tr4.setSideA(2.5); double x=tr4.getSideA(); } } В езикът C# е създаден специален член на класа – свойство (property) посредством което може да се осъществи достъп до личните данни на класа. Свойството е специален вид метод, който има тип на върнатата стойност, същия, като типа на член-данната до която предоставя достъп и няма аргументи. Нещо повече, при дефинирането му не се записват кръглите скоби, в които би трябвало да се опишат аргументите на всеки един метод. В тялото на този специален метод може да има само 2 секции: get и set. В секцията get се кодира логиката за връщане на стойността на член-данната, а в секцията set - логиката за подаване на стойност на член-данната на класа. Свойството неявно получава един подразбиращ се параметър, достъпен посредством ключовата дума value. Пример public class Triangle { double a, b, c, perimeter = 0, square = 0; public double sideA //свойство за достъп до страната a { set { //запис if (value > 0){ a = value; makePerimetre(); makeSquare(); } } get { return a; } //четене } public double Perimeter //свойство за достъп до периметъра { get { return perimeter; } //има рубрика само за четене } … } static void Main(string[] args) { Triangle tr4 = new Triangle(2, 3, 4); Доц. Павлинка Радойска ВУТП 21 tr4.print(); tr4.sideA = 5; double x = tr4.sideA; double p = tr4.Perimeter; } Вижда се, че свойството sideA може да стои както от лявата страна, така и от дясната страна на знака за присвояване. Свойството Perimeter може да стои само от дясно, т.к. при него е дефинирана само секцията за четене get. Автоматично-имплементирани свойства (Auto-implemented properties) Ако не се предвижда дефиниране на никаква специална логика при запис и четене в някое свойство, то може да се използва със статут на поле, като секциите get и set се оставят без тяло - Автоматично-имплементирани. Пример using System; public class SaleItem { public string Name { get; set; } public decimal Price { get; set; } } class Program { static void Main(string[] args) { var item = new SaleItem{ Name = "Shoes", Price = 19.95m }; Console.WriteLine($"{item.Name}: sells for {item.Price:C2}"); } } Деструктори Основната задача на деструктора е да изпълни завършващи действия, свързани с освобождаване на заетата от обекта памет, затваряне на конекции и др. В езика C++ деструкторът се извиква при явно унищожаване на обект (оператор delete) или при приключване на работата на блока, в който е дефиниран обекта. В езиците Java, C# и Python е създаден механизъм за освобождаване на неизползваната памет – garbage collector, който освобождава заетата от обекта памет по специален и по-икономичен по отношение на машинно време алгоритъм. Въпреки това и при тях има механизми, които са аналогични на деструкторите в С++. Характеристики на деструкторите в С++ и C#: 1) Деструкторът се извиква автоматично при унищожаване на обект. 2) Деструкторът задължително носи името на класа, предшестван от знака тилда (~). 3) Деструкторът не връща стойност. 4) Деструкторът няма аргументи. Доц. Павлинка Радойска ВУТП 22 5) Деструкторът е само един. 6) Ако не се дефинира деструктор, се изпълнява подразбиращ се такъв, който изпълнява операциите по освобождаване на заделената за обекта памет, но не и паметта, заделяна в методите и конструкторите на класа. class Triangle { double a=0, b, c, perimeter = 0, square = 0; public: … Triangle() { } Triangle(double x) { a = b = c = x; } Triangle(double xa, double xc) { a = b = xa; c = xc; } Triangle(double a, double b, double c) { this->a = a; this->b = b; this->c = c; } ~Triangle() {} }; Явното дефиниране на деструктор има смисъл, ако в тялото на някой конструктор или метод е заделена динамична памет. В рамките на обекта се разполага само указателят. Допълнително заделената памет се разполага извън обекта, някъде из купа на паметта. Тя трябва да се освободи явно преди да се освободи паметта за обекта. В противен случай остава блокирана, но неизползваема (указателят, който осигурява достъп до нея е унищожен). Пример #include <iostream> using namespace std; class IntegerArray { int *arr=NULL; //Указател към динамичния масив int size=0; //Размер на масива public: IntegerArray() { //Конструктор arr = new int[size]; } IntegerArray(int size) { //Конструктор this->size = size; arr = new int[size]; } ~IntegerArray() //Деструктор { if (arr != NULL) delete[] arr; } void print() { if (arr != NULL) for (int i = 0; i < size; i++) cout << arr[i] << ", "; Доц. Павлинка Радойска ВУТП 23 cout << endl; } }; void main() { IntegerArray myArr1; cout << "myArr1\n"; myArr1.print(); IntegerArray *myArr2=new IntegerArray(5); cout << "myArr2\n"; myArr2->print(); cout << "\n\n"; system("Pause"); } Деструкторът се извиква при явно унищожаване на обект (с оператора delete) или при приключване на работата на функцията или метода, в който е дефиниран обекта (когато времето на живот на обекта приключи). За да се разбере по-добре написаното, ще добавим извеждане на текст, показващ кога се извиква конструктор и кога деструктор. За нуждите на демонстрацията е създадена нова функция - demoFunction(), в която се дефинират обектите. #include <iostream> using namespace std; class IntegerArray { int *arr=NULL; //Указател към динамичния масив int size=0; //Размер на масива public: IntegerArray() { //Конструктор cout << "First constructor (size=" << size << ")\n"; arr = new int[size]; } IntegerArray(int size) { //Конструктор cout << "Second constructor (size=" << size << ")\n"; this->size = size; arr = new int[size]; } ~IntegerArray() //Деструктор { cout << "Destructor for array with size=" << size << ")\n"; if (arr != NULL) delete[] arr; } void print() { if (arr != NULL) for (int i = 0; i < size; i++) cout << arr[i] << ", "; Доц. Павлинка Радойска ВУТП 24 cout << endl; } }; void demoFunction() { IntegerArray myArr1; cout << "myArr1\n"; myArr1.print(); IntegerArray *myArr2 = new IntegerArray(5); cout << "myArr2\n"; myArr2->print(); delete myArr2; } void main() { demoFunction(); cout << "\n\n"; system("Pause"); } Забележете! Първо се извиква дектруктора на обекта myArr2, защото е зададено унищожаване на обекта чрез оператора delete, а след това, при приключване на работата на функцията demoFunction(), се извиква и дектруктора на обекта myArr1. В C# деструкторите се дефинират по същия начин. Извикват се от събирача на боклук и изпълняват завършващи действия. Пример // C# Program to illustrate how using System; a destructor works namespace GeeksforGeeks { class Complex { // Class members, private // by default int real, img; // Defining the constructor public Complex() { Доц. Павлинка Радойска ВУТП 25 real = 0; img = 0; } // SetValue method sets // value of real and img public void SetValue(int r, int i) { real = r; img = i; } // DisplayValue displays // values of real and img public void DisplayValue() { Console.WriteLine("Real = " + real); Console.WriteLine("Imaginary = " + img); } // Defining the destructor // for class Complex ~Complex() { Console.WriteLine("Destructor was called"); } } // End class Complex // Driver Class class Program { // Main Method static void Main(string[] args) { // Creating an instance of class // Complex C invokes constructor Complex C = new Complex(); // Calling SetValue method using // instance C Setting values of // real to 2 and img to 3 C.SetValue(2, 3); // Displaying values of real // and imaginary parts C.DisplayValue(); // Instance is no longer needed Доц. Павлинка Радойска ВУТП 26 // Destructor will be called } // End Main } // End class Program } Java В езикът Java е създаден метод finalize(), който е еквивалентен на деструктор на C ++. Когато работата на даден обект приключи, или обектът вече не се използва в програмата, обектът е известен като боклук(garbadge). Процесът на премахване на обекта от работеща програма е известен като събиране на боклука (garbage collection). Събирането на боклук освобождава паметта и тази памет може да се използва от други програми или същата програма по-нататък при изпълнението му. Преди даден обект да бъде събран като боклук, JRE (Java Runtime Environment) извиква метода finalize (). Методът finalize () може да бъде използван най-добре от програмиста за затваряне на I/O потоците, JDBC (Java Database Connectivity) връзките или сокетите и т.н. Методът finalize() е дефиниран в класа Object и се предефинира в потребителските класове. Профилът му е следния: protected void finalize( ) throws Throwable Пример public class Demo { static Demo d1, d2 ; public void show( ) { System.out.println("Hello 1"); } protected void finalize( ) throws Throwable { if(d1 != null) { System.out.println("d1 object is not eligible for garbage collection and is still active"); d1 = null; if (d1 == null) System.out.println("d1 is not referenced and getting removed from memory"); } if(d2 != null) { System.out.println("d2 object is not eligible for garbage collection and is still active"); d2 = null; if(d2 == null) System.out.println("d2 is not referenced and getting removed from memory"); Доц. Павлинка Радойска ВУТП 27 } super.finalize( ); } public static void main( String args[]) { d1 = new Demo(); d2 = new Demo(); d1.show(); d2.show( ); System.runFinalizersOnExit(true); } } public class Demo { public static void main(String[] args) { Demo dm = new Demo(); dm = null; System.gc(); System.out.println("In the Main Method"); } protected void finalize() { System.out.println("object is garbage collected "); } } Python В езика Python също е създаден специален метод, който изпълнява ролята на дектруктор. Методът носи името ___del__ и получава аргумента self, както всички методи в Python. Подобно на езика С++ и при Python деструкторите могат да се извикват явно чрез оператора del. В следващия пример е създаден деструктор, в който се извежда информация за триъгълника, който се унищожава. import math from pickle import TRUE class Triangle: def __init__(self, c=0.0,b=0.0,a=0.0): if c>0 and b==0.0 and a==0.0: #равностранен триъгълник b=c a=c elif c>0 and b>0 and a==0.0: #равнобедрен триъгълник a=b elif c>0 and b>0 and a>0: #разностранен триъгълник pass else: Доц. Павлинка Радойска ВУТП 28 a=b=c=0.0 # некоректни стойности self.a=a self.b=b self.c=c self.perimeter=0.0 self.area=0.0 self.__calculate() #Проверка дали подадените стойности за страните образуват триъгълник def __isTriangle(self): if self.a>self.b+self.c: return False if self.b>self.a+self.c: return False if self.c>self.a+self.b: return False return True #Изчисляване на обиколката и лицето def __calculate(self): if self.__isTriangle(): self.perimeter=self.a+self.b+self.c p=self.perimeter/2 self.area=math.sqrt(p*(p-self.a)*(p-self.b)*(p-self.c)) else: self.perimeter=0 self.area=0 #Метод за извеждане на екрана def print(self): print("a=",self.a) print("b=",self.b) print("c=",self.c) print("perimeter=",self.perimeter) print("area=",self.area) #деструктор def __del__(self): print("Унищожава се триъгълник със страни ", self.a,",", self.b, ",",self.c) def __main__(): tr1=Triangle() tr2=Triangle(3) #равностранен триъгълник tr3=Triangle(3,4) #равнобедрен триъгълник tr4=Triangle(3,4,5) #разностранен триъгълник tr1.print() tr2.print() tr3.print() tr4.print() del tr4 Доц. Павлинка Радойска ВУТП 29 if __name__=="__main__": __main__() Доц. Павлинка Радойска ВУТП 30