C++ класи
Програмскиот јазик C++ им дозволува на програмерите да дефинираат специфични типови на податоци преку употребата на класи. Овие типови на податоци се познати како објекти и можат да содржат променливи, константи, функции и преоптоварени оператори дефинирани од самиот програмер. Синтаксички, класите се проширување од C структурата, коишто не можат да содржат функции или преоптоварени оператори.
Разлики помеѓу структури во C и класи во C++
уредиВо C++, структура е класа дефинирана со резервираниот збор struct. Нејзините членови и основни класи се дефинирани под дифолт. Класа дефинирана со резервираниот збор class има сопствени членови и основни класи под дифолт.
Групни класи
уредиГрупна класа е класа без декларирани конструктори од страна на програмерот, не приватни или заштитени податоци, без основни класи и без виртуални функции. Таква класа може да биде иницијализирана со заштитен, со запирка одделена, список на иницијализирани параграфи. Следниот код има иста синтакса и во C и во C++:
struct C
{
int a;
double b;
};
struct D
{
int a;
double b;
C c;
};
// initialize an object of type C with an initializer-list
C c = { 1, 2 };
// D has a sub-aggregate of type C. In such cases initializer-clauses can be nested
D d = { 10, 20, { 1, 2 } };
|
POD структури
уредиPOD структура (алгл. Plain Old Data Structure)е групна класа која што нема нестатички податоци од типот не-POD-структура (или низа од такви типови)или референца, и нема оператор за задавање и деструктор дефиниран од самиот програмер. POD структурата може да се речи дека е C++ еквивалент на C структурата. Во најмногу случаи, POD структурата ќе има ист распоред на меморијата со соодветната структура декларирана во C. Поради оваа причина, POD структурите се понекогаш вообичаени како C-стил структурите.
Декларација и употреба
уредиC++ структурите и класите имаат свои сопствени членови. Овие членови содржат променливи (вклучувајќи други структури и класи), функции (определени идентификатори или преоптоварени оператори) познати како методи, конструктори и деструктори. Членовите се декларирани да бидат приватни или јавно достапни употребувајќи јавен и приватен пристап. Секој член покажуван од покажувач ќе има поврзан пристап сè додека не наиде друг покажувач. Исто така има наследување помеѓу класите кои можат да користат “заштитени:” покажувачи.
Основна декларација и променливи членови
уредиКласите и структурите се декларирани со резервираните зборови class и struct соодветно. Декларацијата на членовите се наоѓа внатре во оваа декларација. Парчето код кое следува покажува пример на двете struct и class декларации:
struct person
{
string name;
int age;
};
|
class person
{
public:
string name;
int age;
};
|
Двете декларации се функционално еквивалентни. Во горенаведените примери name и age се наречени променливи членови на личноста datatype. Забележете дека знакот точка запирка е обавезен. Треба да се знае дека во една од овие декларации (но не и во двете), person може да се употреби како што следува за создавање нови дефинирани променливи на person типот на податоци:
#include <iostream>
#include <string>
using namespace std;
class person
{
public:
string name;
int age;
};
int main ()
{
person a, b;
a.name = "Calvin";
b.name = "Hobbes";
a.age = 30;
b.age = 20;
cout << a.name << ": " << a.age << endl;
cout << b.name << ": " << b.age << endl;
return 0;
}
|
При извршувањето на горенаведениот код како излез ќе се добие:
Calvin: 30
Hobbes: 20 |
Функции на членовите
уредиВажна одлика на C++ класи и структури се функциите на членовите (member function). Секој тип на податок може да има своја сопствена функција (наречени методи) кои имаат пристап до сите (приватни и јавни) членови на типот на податоци. Во телото на овие нестатички функции на членовите, резервираниот збор this се користи за однос на објектот со функции кои ги повикува. Тоа обично се спроведува со носење на адресата на објектот како имплициден прв аргумент на функцијата. Види го претходниот person тип во нов пример:
class person
{
std::string name;
int age;
public:
person() : age(5) { }
void print() const;
};
void person::print() const
{
cout << name << ";" << this->age << endl;
/* we don't have to mention what "name" and "age" are,
because it automatically refers back to the member variables.
The "this" keyword is an expression whose value is the address
of the object for which the member was invoked. Its type is
const person*, because the function is declared const.
*/
}
|
Во погорниот пример print() функцијата е декларирана во телото на класата и се дефинира со името на класата проследена со знакот ::. И name и person се приватни (вообичаено за класи) и print() е деклариран како јавен како што е потребно ако се користи надвор од класата. Со функцијата на членот print(), печатењето може да биде упростено во:
a.print();
b.print();
|
каде што a и b погоре се нарекуваат испраќачи, и секој од нив ќе се однесува на нивните променливи членови кога print() функцијата е извршена. Тоа е вообичаено да се раздели декларацијата на класата или структурата (наречена нејзин интерфејс) и нивното дефинирање (наречено нивна имплементација) во разделени единки. Интерфејсот, потребен за корисникот, се чува во хедерот (header) и имплементацијата се чува засебно во секој извор или компајлирана форма.
Наследување
уредиНаследувањето е имплементирано во конкатеинг (concatenating) класите во меморијата. Ова ја прави is-a врската наследување многу природна. Земете го предвид примерот:
class P
{
int x;
};
|
class C : public P
{
int y;
};
|
Наслeдувањето на P со P* p покажувањето на него, во меморијата ќе изгледа вака:
+----+ |P::x| +----+ ↑ p
Наследувањето на С со С* с покажувањето на него, во меморијата ќе изгледа вака:
+----+----+ |P::x|C::y| +----+----+ ↑ c
На овој начин, во многу вистинска смисла, *с е Р.
Повеќекратните наследувања не се толку едноставни. Ако класата D ги наследува Р1 и Р2, во меморијата Р1 ќе биде следбеник на Р2 и следбеник на телото од телото на D. Кога D е потребно да се претвори во Р2, компајлерот автоматски го донесува покажувачот на Р2.
Преоптоварени оператори
уредиВо С++, оператори, како + - * /, можат да се преоптоварат за да се прилагодат на потребите на програмерите. Овие оператори се нарекуваат преоптоварени оператори. По договор, преоптоварените оператори треба да се однесуваат слично како што се однесуваат во вградените бази на податоци. (int, float, etc.), но ова е е барано. Еден може да декларира структура наречена „цел број“ во која променливите вистина содржат целобројни податоци, но со повикување integer * integer сумата, како замена за производот, од integer може да се добие:
struct integer
{
int i;
integer (int j = 0) : i (j) {}
integer operator* (const integer &k) const
{
return integer (i + k.i);
}
};
|
Кодот погоре користи конструктор за да ја “конструира” излезната вредност. За појасна претстава (иако ова ќе ја намалува ефикасноста на програмата), погорниот код може да биде препишан:
integer operator* (const integer &k) const
{
integer m;
m.i = i + k.i;
return m;
}
|
Програмерите можат исто така што ставаат прототипи на оператори во struct декларацијата и да дефинираат функција на операторот во глобалниот простор:
struct integer
{
int i;
integer (int j = 0) : i (j) {}
integer operator* (const integer &k) const;
};
integer integer::operator* (const integer &k) const
{
return integer (i + k.i);
}
|
i погоре претставува сопствена променлива на испраќачот, каде што k.i претставува променлив член од променливиот аргумент k. Резервираниот збор cosnt се појавува два пати во кодот погоре. При првото појавување, аргументот const integer &k, покажува дека променливиот аргумент нема да биде променет со функцијата. Второто појавување на крајот на декларацијата му укажува на компајлерот дека испраќачот нема да се смени со стартување на функцијата. Во const integer &k знакот (&) осначува “премин со повикување”. Кога функцијата е повикана, покажувачот на променливата ќе премине на функцијата, подобро од вредноста на променливата. Истите својстава за преоптоварување од погоре се применети и во класите. Треба да се каже дека arity, associativity и precedence на операторите не може да се промени.
Бинарни преоптоварени оператори
уредиБинарните оператори (оператори со два аргументи) се преоптоваруваат со декларирање функција со “идентификаторот” operator(something) кој повикува само еден аргумент. Променливите лево од операторот се испраќачите, а пак тоа од десно е аргументот.
integer i = 1;
/* we can initialize a structure variable this way as
if calling a constructor with only the first
argument specified. */
integer j = 3;
/* variable names are independent of the names of the
member variables of the structure. */
integer k = i * j;
cout << k.i << endl;
|
Ќе се испечати “4”.
Следно е список на бинарните преоптоварени оператори:
Оператор | Општа употреба |
---|---|
+ - * / % | Аритметички операции |
<< >> | Битова пресметка |
< > == != <= >= | Логичка споредба |
&& | Логичка конјункција |
= <<= >>= | Сложена задача |
, | (нема општа употреба) |
Операторот “=” помеѓу две променливи од ист тип е преоптоварен автоматски да го копира целата содржина променливи од една на друга. Тоа може да биде препишано со нешто друго, ако е потребно. Операторите мораат да се преоптоваруваат еден по еден, со други зборови, ниедно преоптоварување не е поврзано едно со друго.
Унарни (единечни) преоптоварени оператори
уредиДодека некои оператори, како што е наведено погоре, заземаат два услови, испраќачот од лево и аргументот од десно, некои оператори имаат само еден аргумент – испраќачот, и тие се наречени унарни (еденинечни). Примери се знакот за негација (кога ништо не е внесено лево од него) и логичкото НЕ (запишано со знак !). Испраќачот на унарните оператори може да биде во лево или во десно од операторот. Следно е список на унарните преотоварени оператори:
Оператор | Општа употреба | Позиција на испраќачот |
---|---|---|
+ - | Знак за позитивно/негативно | Десно |
* & | Дереференцирање | Десно |
! ~ | Логичко / Битово НЕ | Десно |
++ -- | Зголемување/намалување на претходници | Десно |
++ -- | Зголемување/намалување на следбеници | Лево |
Синтаксата на за преоптоварување на унарен оператор, каде што испраќачот е во десно, како што следува:
return_type operator@ ()
Кога испраќачот е во лево, декларацијата е:
return_type operator@ (int)
@ стои погоре за да операторот биди преоптоварен. Заменете го return_type со типот на излезната величина (int, bool, structures etc.)
Int параметарот во суштина не значи ништо освен показател за тоа дали испраќачот е во лево од операторот. Const аргументите можат да бидат додадени на крајот од декларацијата ако е тоа возможно.
Преоптоварени загради
уредиСредните загради [] и малите загради () можат да бидат преоптоварени во C++ структурите. Средните загради мораат да зодржат точно еден аргумент, додека малите загради можат да содржат повеќе аргументи или пак да не содржат аргументи.
Следната декларација ја преоптоварува средната заграда:
return_type operator[] (argument)
Делот кој се наоѓа во заградата е наведен во делот за аргументи.
Малите загради се преоптоваруваат на сличен начин:
return_type operator() (arg1, arg2, ...)
Членовите во заградата при повик на операторот се наведени во втората заграда.
Според операторите претставени погоре, операторот стрелка (->), и стрелката со ѕвезда (->*), резервираните зборови key и delete исто така можат да бидат преоптоварени. Овие мемориски-или-покажувачки-поврзани оператори мораат да ги извршат функциите за момориско доделување после преоптоварувањето. Како и операторот за додавање (=), тие се исто така преоптоварени иако не е направена некоја посебна декларација.
Конструктори
уредиПонекогаш софтверските ижинери сакаат нивните променливи да имаат стандардни или специфични вредности преку декларација. Ова може да се направи преку декларирање конструктори.
person (string N, int A)
{
name = N;
age = A;
}
|
Променливите можат да се иницијализираат во листа за иницијализирање, со употреба на две точки, како во примерот подолу. Ова се разликува од ова погоре по тоа што тоа иницијализира (со употреба на конструктор), подобро од користење на операторот за доделување. Ова е поефикасно за класите, пред тоа потребно било да бидат конструирани директно; бидејќи е така со доделување, тие прво мораат да бидат иницијализирани со употреба на стандарден конструктор и тогаш да се додели друга вредност. Исто така некои типови (како референците и const типовите) неможат да бидат доделени и како резултат на тоа мораат да бидат иницијализирани во листата за иницијализација.
person (std::string N, int A) : name (N), age (A) {}
|
Забележете дека големите загради (на крајот) не можат да бидат изоставени, па дури и да се празни. Стандардните вредности можат да им се доделат на последните аргументи за да помогнат при иницијализирање на стандардните вредности.
person (std::string N = "", int A = 0) : name (N), age (A) {}
|
Кога не се доделени аргументи на конструкторот во примерот погоре, тоа е еднакво на повикување на следечкиот конструктор без аргументи (default constructor).
person () : name (""), age (0) {}
|
Декларацијата на конструктор изгледа како функција со име исто како типот на податокот. Фактички, ние навистина можеме да го повикуваме конструкторот во форма на функција. Во овој случај излезната вечичина од person типот би била:
int main ()
{
person r = person ("Wales", 40);
r.print ();
}
|
Погорниот код создава привремен person објект, и го означува тоа на r употребувајќи копиран конструктор. Подобара начин за создавање на конструктор ( без непотребното копирање) е:
int main ()
{
person r ("Wales", 40);
r.print ();
}
|
Специфичните програмски операции кои можат или пак не можат да се поврзат со променливи, можат да се додатат како дел од конструкторот.
person ()
{
std::cout << "Hello!" << endl;
}
|
Со погорниот конструктор, “Hello” ќе се испечати во случај кога person променливата без специфична вредност е иницијализирана.
Деструктори
уредиДеструкторот е обратно од конструкторот. Тој се повикува кога пример од класа е уништен, пример кога објект на класа создаден во блок (во големи загради {}) е избришан после затворањето на заградите, тогаш деструкторот е повикан автоматски. Тој ќе биде повикан при празнење на мемориската локација во која е сместена променливата. Деструкторите можат да се употребуваат за ослободување ресурси, како heap-доделената меморија и отворените податотеки кога примерот на класа е уништен. Синтаксата за декларирање деструктор е слична на онаа од конструкторот. Тука нема излезна величина и името на методот е исто со името на класата со знакот тилда (~) пред него.
~person ()
{
cout << "I'm deleting " << name << "with age" << age << endl;
}
|
Структури за предлошки
уредиC++ дозволува структурите да бидат декларирани како предлошки. Со други зборови, конструктивните членови не е неопходно да дојдат од специфичниот тип. На пример, следната структура спамти две променливи од самосоздадениот тип и има плус преоптоварен оператор.
template <class A, class B> struct twothings
{
A one;
B two;
twothings operator+ (const twothings &arg) const
{
twothings <A, B> temp;
temp.one = one + arg.one;
temp.two = two + arg.two;
return temp;
}
};
|
После ова, во главната функција, ние можеме да ја употербуваме двостварната прдложна структура за да складираме променливи од било кои два типа на податоци што ги сакаме.
twothings<int, float> twonumber;
twothings<string, int> bigmap;
twonumber.one = 16;
twonumber.two = 1.667;
bigmap.one = "Hallo";
bigmap.two = 19;
|
Сите од погорните соопштенија се точни, бидејќи типовите на податоци одговараат точно. Освен тоа, како што е погоре, структурата или класата на типот на податоци string (класа во библиотека) e искористена исто како типот на конструктивните променливи. Ова покажува дека структурите самите можат да се заменат како предлошки. Строго речено, структурите за предлошки се различни од структурите. Ова е примерливо (толкување со примери) со фактот дека twothings foo; нема да се компајлира, но twothings<int, float> foo; will—only the latter is a fully-defined type. Слично, twothings<int, float> и twothings<int, double> се различни типови. Ова може да биде збунувачко за оние нови предлошки На пример, сè додека float може индиректно да се претвора во double, std::vector<float> нема да се претвора индиректно во std::vector<double>. Како и да е, бидејќи конструкторот на std::vector зазема итератор, следново функционира:
std::vector<float> foo(10, 1.0); // Ten 1.0s.
// std::vector<double> bar = foo; // Won't work.
std::vector<double>bar(foo.begin(), foo.end());
// Works; constructs bar by copying the range [<code>foo.begin()</code>, <code>foo.end()</code>).
|
Примената на предлошките во постарите верзии на C++ придонесе за “раѓање” на Стандардната библиотека за предлошки (Standard Template Library).
Својства
уредиСинтаксата во C++ пробува да го направи секој аспект на структурите да изгледа како да е од основните типови на податоци. Поради тоа, преоптоварените оператори дозволуваат структурите да се “злоупотербуваат” исто како integer-от и float-покажувачи броеви, низите на структурите можат да бидат декларирани со помош на средните загради (some_structure variable_name[size]), и покажувачите на структурите можат да бидат дереференцирани на истиот начин како покажувачите во основните типови на податоци.
Потрошувачка на меморија
уредиПотрошувачата на меморија на структурите е помалку од сумата од големината на меморијата од нејзините составни променливи. Да го видиме на пример twonums структурата подолу како пример.
struct twonums
{
int a;
int b;
};
|
Структурата се состои од два integer-и. Во многу сегашни компајлери, integer-ите се 32 битни стандардно, па секоја променлива зазема четири бајти од меморијата. Целата структура, поради тоа, зазема најмалку (или поточно) осум бајти од меморијата, како што следи:
+----+----+ | a | b | +----+----+
Како и да е, компајлерот ќе додаде полнење помеѓу променливите или на крајот од структурата за да го осигура сопственото податочно подредување за дадена сметачка архитектура, често променливите за полнење да бидат 32 битни подредени. Пример, структурата:
struct bytes_and_such
{
char c;
char C;
short int s;
int i;
double d;
};
|
може да изгледа:
+-+-+--+--+--+----+--------+ |c|C|XX|s |XX| i | d | +-+-+--+--+--+----+--------+
во меморијата, каде што XX се два неупотребени бајти. Како што структурите можат да употербуваат покажувачи и низи за да декларираат и иницијализираат променливи членови, потрошувачката на меморијата на структурите не е неопхоно константна. Друг пример за неконстантна големина на меморијата се структурите за предлошки.
Премин преку обраќање
уредиМногу програмери претпочитаат да го употребуваат знакот & за да ги декларираат аргументите на функцијата вклучуваќи структури. Тоа е бидејќи со употребата на дефернецирањето на само еден збор (вообичаено 4 бајти на 32 битна машина, 8 бајти на 64 битна машина) потребно е да премине во функција, а тоа е мемориската локација на променливата. Спротивно, ако премин-преку-вредност е употребен, аргументот треба да биде копиран секој пат кога функцијата е повикана, што е “скапо” со големи структури.
Резервираниот збор this
уредиЗа овозможување ефикасна употреба на структури, во С++ е имплементиран резервираниот збор this за повикување функции, конструктори и деструктори, а се однесува на сопствената положба на структурата. Тип на податок од this е покажувач на структура. Резервираниот збор this е посебно важен за функциите на членовите со структурата сама себеси како излезна величина.
complex operator+= (const complex & c)
{
realPart += c.realPart;
imagPart += c.imagPart;
return *this;
}
|
Како што е прикажано погоре, this е покажувач, па ние треба да го употребиме ѕвездичка (*) или pointer-to-member (->) ја врати излезната вредност на структурата за пресметки. Забележете дека резервираниот збор const се појавува само еднаш во првата линија. Ова е бидејќи испраќачот е сменет од функцијата, додека пак параметарот не е. Кога испраќачката променлива е променета и вратена со повик од функцијата, знакот & може исто така што се употреби и во декларацијата за повратниот тип.
complex& operator += (const complex & c)
|