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 сумата, како замена за продуктот, од 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 шаблон структурата за да складираме променливи од било кои два типа на податоци што ги сакаме.

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)