Blog
Обектно-ориентирано програмиране – Класове и обекти
1. Класове и обекти
Класовете са типове, които се дефинират от потребителите.
Променлива от тип клас се нарича обект.
1.1. Пример 1 – клас за човек и работа с обект от класа
#include <iostream>
#include <string>
using namespace std;
class Person // описание на клас за човек
{
public:
string name; // име на човека – низ – атрибут
int age; // атрибут за възраст
void info() // метод, който извежда в конзолата информация за човек – използва атрибутите
{
cout << name << ” e na ” << age << ” godini” << endl;
}
};
void main()
{
Person p; // създаване на обект, заделя се памет за всички атрибути
p.name = “Ivan”; // задава се стойност на атрибута за име на обекта p
p.age = 28;
p.info(); // извиква се метода за извеждане на информация за обекта p
// резултата е Ivan e na 28 godini
}
МНОГО ВАЖНО: Тялото на класа се описва(загражда) във фигурни скоби и завършва с точка и запетая (;).
В примера, спецификатора за достъп public указва, че всички атрибути и методи, описани след него, са видими и могат да се извикват чрез обекти или в методи на класа
В класовете се декларират:
- член-данни(атрибути) – както при структурите;
- член-методи – методите имат достъп до атрибутите на класа, но може да получават и параметри (както обикновените методи).
Достъп да атрибутите и методите на обект става с точка (.). Например, p.name, p.info()
Атрибути може да са:
- променливи от прости типове;
- обекти от клас (например string е клас).
1.2. Спецификатори за достъп
- public – само public атрибути и методи са видими чрез обект;
- protected – видими са в методи на класа и методи на класове-наследници;
- private – видими са само в методи на класа. Това е спецификатора, който се използва по подразбиране (ако не сме задали).
2. Конструктор на клас
- Конструкторът е специален метод на класа;
- Името на конструктор съвпада с името на класа;
- За конструктор не се указва тип на връщана стойност;
- В един клас може да има няколко конструктора – с различни по тип и брой параметри (имената на конструкторите са еднакви);
- Ако не сме описали конструктор (както в горния пример), в класа съществува конструктор по подразбиране без параметри (той се създава автоматично при компилиране). Ако сме описали конструктор с параметри не се създава автоматично конструктор по подразбиране (и трябва да го създадем ако желаем да се ползва – в тялото му може нищо да не описваме);
- Конструктор се извиква винаги при създаване на обект от клас;
- При извикване на конструктор се заделя памет за обект и за всички негови атрибути;
- В конструктора обикновено се задават първоначални стойности за атрибутите (и се заделя памет за атрибутите-указатели). Ако не се зададат стойности атрибутите се инициализират автоматично със стойности по подразбиране (0 за числа, null за обекти)или случайни стойности, намиращи се в паметта, използвана от атрибутите – зависи от компилатора.
В горния пример при декларацията на обект (Person p;) се извиква автоматично конструктора по подразбиране. Можехме да извикаме конструктора явно: Person p = Person();
2.1. Пример 2 – конструктор на клас за човек
#include <iostream>
#include <string>
using namespace std;
class Person
{
private:
string name;
int age;
public:
void info()
{
cout << name << ” e na ” << age << ” godini” << endl;
}
// конструктор с 2 параметъра
// задава първоначални стойности на атрибутите (по време на изпълнение на програмата)
// чрез формалните параметри
Person(string n, int a)
{
name = n;
age = a;
}
};
void main()
{
Person p1 = Person(“Ivan”, 28);
Person p2 = Person(“Petar”, 20);
p1.info(); // Ivan e na 28 godini
p2.info(); // Petar e na 20 godini
}
3. Капсулиране
Добра техника на програмиране е атрибутите да се капсулират, като се декларират като private.
- атрибутите се декларират като private;
- атрибутите са видими само в/чрез методи на класа – достъп до атрибут извършваме чрез т.нар. set и get методи (техника/стил на програмиране е имената на тези методи да започват със думите set или get, последвани от името – с главна буква – на атрибута, за който се отнасят);
- при капсулирането се предпазваме от неправилен/неоторизиран достъп до атрибутите.
3.1. Пример 3 – достъп до private атрибути – set и get методи. Два конструктора
#include <iostream>
#include <string>
using namespace std;
class Person
{
private:
string name;
int age;
public:
void info()
{
cout << name << ” e na ” << age << ” godini” << endl;
}
void setAge(int a) // защита от грешно зададена година – по-малка от нула
{
if(a>0)
age = a;
}
string getName()
{
return name;
}
Person(string n, int a)
{
name = n;
setAge(a); // тук може да използваме метода за задаване на години
// – вместо да дублираме алгоритъма му
}
Person(string n)
{
name = n;
}
};
void main()
{
Person p1 = Person(“Ivan”); // конструктор с 1 параметър
Person p2 = Person(“Petar”, 20); // конструктор с 2 параметъра
p1.info(); // Ivan e na xxxxxxxxxx godini
p1.setAge(28);
p2.setAge(-6); // След извикване на метода, годините на p2 не се променят
p1.info(); // Ivan e na 28 godini
p2.info(); // Petar e na 20 godini
cout << p1.getName() << endl; // Ivan
}
4. Наследяване – пример
При наследяването класа-наследник притежава всички характеристики(атрибути и методи) на класа-родител и добавя собствени.
#include <iostream>
#include <string>
using namespace std;
class Person
{
protected:
string name;
int age;
};
class Student: public Person // Студент наследява човек
{
protected:
string fn;
public:
Student(string n, int a, string f)
{
name = n; // за да виждаме родителския атрибут той трябва да е protected или public
age = a;
fn = f;
}
void info()
{
cout << name << “, s fn ” << fn << ” e na ” << age << ” godini” << endl;
}
};
void main()
{
Student s1 = Student(“Ivan”, 20, “2222222”);
s1.info(); // Ivan, s fn 2222222 e na 20 godini
}
5. Обръщение към атрибути и методи на класа с this, обръщение към атрибути и методи на родителския клас
Ще покажем някои малки промени към последния пример. Не създаваме цяла програма. Предефинираме конструктора на Student като задаваме имената на параметрите да съвпадат с имената на съответните атрибути. Това е добра техника на писане, защото по-лесно може да се види кой параметър на кой атрибут съответства. Това обаче създава конфликти на имената – имаме променливи с еднакви имена, които искаме да използваме в един и същи метод (конструктора) – в този случай най-видима в блока на метода е дефинираната в метода променлива.
Проблемите възникнали при това разрешаваме като използваме специални начини за обръщение към атрибутите:
- С ключовата дума this и -> се обръщаме към атрибут (и метод) на текущия клас – например this->fn
- С име на клас и :: се обръщаме към атрибут (и метод) на родителския клас – например Person::name
- Към родителски атрибут (и методи) може да се обръщаме и с this->, но само ако няма атрибут (или метод) със същото име, който го припокрива.
class Person
{
protected:
string name;
int age;
};
class Student: public Person
{
protected:
string fn;
public:
Student(string name, int age, string fn)
{
Person::name = name; // Person::name – обръщение към атрибут дефиниран в Person
this->age = age; // this->age – обръщение към атрибут на текущия клас –
// въпреки, че е дефиниран в родителския клас, той е част и от
// текущия клас
this->fn = fn;
}
};
- this е ключова дума;
- използва се само в описанието на метод на класза обръщение към атрибути и методи на класа (най-често);
- this е указател към обект, който се създава по време на създаване на обекта – инициализира се (задава му се стойност) автоматично при създаване на обекта (по време на изпълнение на програмата).
6. Предимства на ООП
- Компактност – всичко необходимо (данни и методи) за работа с даден обект (от реалния свят) се обединява в един клас;
- Избягват се конфликти на имената – за различни класове може да създаваме методи и атрибути с еднакви имена, без да се получават конфликти на имената;
- Капсулиране – предпазване от нежелан достъп до атрибути;
- Наследяване – класът наследник използва всички атрибути и методи на класа родител;
- Полиморфизъм – дава възможност чрез обекти на клас родител да се работи с данни на създаден като негов наследник обект – ще разгледаме това по-нататък.
7. Стил на програмиране
Няколко (незадължителни) правила за работа с класове и обекти:
- Имената на класовете започват с главна буква;
- Имената на обектите – с малка;
- Имената на методите са описателни, започват с малка буква и всяка следваща дума с главна;
- Член-данните се задават като private;
- Методи, които няма да се използват извън класа се задават като private;
- Добре е работната логика, реализирана в даден клас да е разделена от входно-изходната логика (но за целите на обучението няма да се придържаме към това правило);
- … други правила за подредба на кода.
8. Нови библиотеки и области на имената (namespaces)
C++ се променя във времето. Поради множеството отделни производители (добавящи самостоятелни функции) и повтарящи се имена на функции в отделни библиотеки се налага създаването на стандартна версия. Новите библиотеки са с имена близки до имената на старите библиотеки. При включването им не се записва „.h”:
#include <iostream>
#include <cmath>
Функциите, променливите, обектите, класовете и др., които използваме от новите библиотеки са дефинирани в т. нар. области от имена namespaces. Стандартния namespace е с име std.
За да използваме в нашите програми тези стандартни неща(функции, класове…), към тях може да се обръщаме с пълните им имена: Например std::cout, std::cin, std::pow.
Но за да избегнем записването на пълните имена, сме указали в нашата програма „using namespace std;” след директивите за включване на библиотеките. Така автоматично се търсят обектите в тази област на имената.
В новите библиотеки са включени, нови функции, класове, променливи и обекти. Съответно, те имат повече възможности от старите библиотеки. По-нататък няма да конкретизираме какви точно неща използваме от новите библиотеки (но в елементарните примери, които показваме новите неща са малко).
Можем сами да създаваме области на имената и да дефинираме в тях обекти. Но няма да се занимаваме с това подробно. Пример за създаване на namespace с 2 променливи декларирани в него:
namespace test {
int i;
int j;
}
9. Задачи
- Да се създаде клас за кръг – атрибути x, y, r – координати на центъра и дължина на радиуса. Методи – за въвеждане на атрибутите, изчисляване на дължина на окръжност и лице на кръг, в кой квадрант се намира центъра на кръга. В програмата – да се инициализира кръг и да се изведе информация за него. (Трябва да се дефинира константа double const PI=3.14159265)
- Триъгълник – задават се координатите на върховете. Намират се дължините на страните sqrt((x1-x2)^2 + (y1-y2)^2), периметъра и лицето sqrt(p*(p-a)*(p-b)*(p-c)), p – полупериметър.
#include <iostream>
#include <cmath>
using namespace std;
double const PI=3.14159265;
class Circle
{
double x,y,r;
public:
void initCircle(); // може само да декларираме метод при
// описанието на класа, а го дефинираме след това
double perimeter();
double surface();
};
void Circle::initCircle() // Дефинираме метод като указваме, че принадлежи на класа Circle
{
cout << “x=”; cin >> x;
cout << “y=”; cin >> y;
cout << “r=”; cin >> r;
}
double Circle::perimeter()
{
return 2*PI*r;
}
double Circle::surface()
{
return PI*pow(r, 2);
}
void main()
{
Circle circle;
circle.initCircle();
cout << “perimeter=” << circle.perimeter() << “\n”;
cout << “surface=” << circle.surface() << “\n”;
}
10. inline методи
Методи, които са описани в тялото на класа са вградени (inline). Вградените методи се изпълняват по-бързо, защото кода на методите се копира навсякъде, където има обръщение към тях. Inline метод може да се задава и извън декларацията на клас чрез модификатор inline. Напр.,
inline double Circle::surface()