Перайсці да зместу

C++

З Вікіпедыі, свабоднай энцыклапедыі
C++
Выява лагатыпа
Клас мовы

шматпарадыгмавая: аб’ектна-арыентаванае, структурнае, працэдурнае, абагульненае праграмаванне,

метапраграмаванне
Тып выканання кампіляваная
З’явілася ў 1983
Аўтар(ы) Б’ёрн Страўструп
Пашырэнне файлаў .c++, .cpp, .cxx, .cc, .h++, .hpp, .hxx, .hh, .h
Тыпізацыя даных строгая, статычная
Асноўныя рэалізацыі
Дыялекты ISO/IEC 14882:1998 C++
ISO/IEC 14882:2003 C++
Зведала ўплыў
Паўплывала на

Java, C#, Ада 95, D,

PHP, Perl, Python
Сайт isocpp.org (англ.)

C++ (Сі++) — кампіляваная статычна тыпізаваная мова праграмавання агульнага прызначэння. Падтрымлівае розныя парадыгмы праграмавання, але, у параўнанні са сваёй папярэдніцай мовай Сі, найбольшая ўвага скіравана на падтрымку аб’ектна-арыентаванага і абагульненага праграмавання.[1]

Назва «C++» паходзіць ад назвы мовы Сі (C), у якой унарны (аднамесны) аператар ++ пазначае прырост зменнай на адзінку.

У 1990-х гадах мова стала адной з самых ўжывальных моў праграмавання агульнага прызначэння.

Пры стварэнні C++ імкнуліся захаваць сумяшчальнасць з мовай Сі. Большасць праграм на Сі можна, амаль не змяняючы, сабраць і з дапамогай кампілятара C++. Мова C++ мае сінтаксіс, заснаваны на сінтаксісе Сі.

Мова з’явілася напачатку 1980-х гадоў, калі супрацоўнік фірмы Bell Laboratories Б’ёрн Страўструп прыдумаў шэраг удасканаленняў да мовы Сі пад уласныя патрэбы. Да пачатку афіцыйнай стандартызацыі мова развівалася галоўным чынам сіламі Страўструпа ў адказ на запыты праграмісцкай супольнасці. У 1998 годзе быў прынят міжнародны стандарт мовы C++: ISO/IEC 14882:1998 «Standard for the C++ Programming Language»; пасля прыняцця тэхнічных выпраўленняў да стандарту ў 2003 годзе — цяперашняя версія гэтага стандарту — ISO/IEC 14882:2003.

Раннія версіі мовы, вядомыя пад назвай «C з класамі», пачалі з’яўляцца ў 1980-я гады.[2] Ідэя стварэння новай мовы бярэ пачатак з доследаў Страўструпа ў праграмаванні падчас працы над дысертацыяй. Ён выявіў, што мова мадэлявання Сімула (Simula) мае такія магчымасці, якія былі б вельмі карыснымі пры распрацоўцы вялікага праграмнага забеспячэння, але працуе занадта павольна. У той жа час мова BCPL даволі хуткая, але занадта блізкая да моў нізкага ўзроўню і не падыходзіць для распрацоўкі вялікага праграмнага забеспячэння. Страўструп пачаў працаваць у Bell Labs над задачамі тэорыі чэрг (у прыкладаннях да мадэлявання тэлефонных выклікаў). Спробы прымяніць існаваўшыя ў той час мовы мадэлявання засталіся безвыніковымі. Успомніўшы доследы са сваёй дысертацыі, Страўструп вырашыў дапоўніць мову Сі (пераемніцу BCPL) магчымасцямі мовы Сімула. Мова Сі, якая была асноўнай мовай сістэмы UNIX, на якой працавалі камп’ютары Bell, хуткая, шматфункцыянальная і пераносная. Страўструп дадаў да яе магчымасць працы з класамі і аб’ектамі. У выніку, практычныя задачы мадэлявання сталі даступнымі для развязання як з пункту гледжання часу распрацоўкі (дзякуючы выкарыстанню Сімула-падобных класаў) так і з пункту гледжання часу вылічэнняў (дзякуючы хуткадзеянню Сі). Напачатку ў Сі былі ўключаны класы (з інкапсуляцыяй), вытворныя класы, строгая праверка тыпаў, inline-функцыі і аргументы па змаўчанні.

Распрацоўваючы Сі з класамі (пазней C++), Страўструп таксама напісаў праграму cfront — транслятар, які перакладаў зыходны код Сі з класамі у зыходны код звычайнай Сі. Новая мова, нечакана для аўтара, набыла вялікую папулярнасць сярод калег, і неўзабаве Страўструп ужо не мог падтрымліваць яе асабіста, адказваючы на тысячы пытанняў.

У 1983 годзе адбылося перайменаванне мовы з Сі з класамі у C++. Акрамя таго, у яе былі ўключаны новыя магчымасці, такія як віртуальныя функцыі, перагрузка функцый і аператараў, спасылкі, канстанты (сталыя), карыстальніцкі кантроль над кіраваннем свабоднай памяццю, палепшаная праверка тыпаў і новы стыль каментарыяў (//). Яе першы камерцыйны выпуск адбыўся ў кастрычніку 1985 года.

Развіццё і ўпарадкаванне мовы

[правіць | правіць зыходнік]

У 1985 годзе выйшла першае выданне кнігі «Мова праграмавання C++», першае апісанне гэтай мовы, што было надзвычай важным з-за адсутнасці афіцыйнага стандарту. У 1989 годзе адбыўся выхад C++ версіі 2.0. Яго новыя магчымасці ўключалі множнае наследаванне, абстрактныя класы, статычныя функцыі-члены, функцыі-канстанты і ахаваныя члены.

У 1990 годзе выйшла «Каментаванае даведачнае кіраўніцтва па C++», якое пасля легла ў аснову стандарту. Апошнія абнаўленні ўключалі шаблоны, выключэнні, прасторы імён, новыя спосабы прывядзення тыпаў і булеўскі тып.

Стандартная бібліятэка мовы C++ таксама развівалася разам з ёю. Першым дабаўленнем к стандартнай бібліятэцы C++ сталі патокі ўводу/вываду, якія даюць сродкі для замены традыцыйных функцый Сі printf і scanf. Пазней самым значным крокам у развіцці стандартнай бібліятэкі стала ўключэнне ў яе Стандартнай бібліятэкі шаблонаў.

У 1998 годзе, пасля некалькіх гадоў працы, сумесны камітэт ANSI-ISO прыняў стандарт мовы C++ (ISO/IEC 14882:1998 — Мова праграмавання C++). На працягу некалькіх гадоў пасля афіцыйнага выхаду стандарту камітэт апрацоўваў паведамленні пра памылкі і ў выніку выпусціў выпраўленую версію стандарту C++ у 2003 годзе. У наш час працоўная група МОС (ISO) працуе над новай версіяй стандарту пад кодавай назвай C++09 (раней вядомы як C++0X), які павінен выйсці ў 2009 годзе.


Ніхто не валодае правамі на мову C++, гэта свабодная мова праграмавання. Аднак сам дакумент стандарту мовы (за выключэннем чарнавікоў) бясплатна не даступны.

Гісторыя назвы

[правіць | правіць зыходнік]

Назва «C++» была прыдумана Рыкам Масіцці (Rick Mascitti) і ўпершыню была выкарыстана ў снежні 1983 года. Раней, на ступені распрацоўкі, новая мова называлася «Сі з класамі».[2]

Імя, атрыманае ў выніку, паходзіць ад аператара мовы Сі «++» (павелічэнне значэння зменнай на адзінку). Імя «C+» не было выкарыстана таму, што з’яўляецца сінтаксічнай памылкай у Сі і, акрамя таго, гэта імя было занята іншай мовай. Мова таксама не названа «D», бо «з’яўляецца пашырэннем Сі і не спрабуе здымаць праблемы шляхам выдалення элементаў Сі».[2]

У кнізе «Дызайн і эвалюцыя C++» Б’ёрн Страуструп апісвае прынцыпы, якіх ён прытрымліваўся пры распрацоўцы мовы C++.[3] Гэтыя прынцыпы тлумачаць, чаму C++ менавіта такая, якой яна ёсць. Некаторыя з іх:

  • Атрымаць універсальную мову са статычнымі тыпамі даных, эфектыўнасцю і пераноснасцю мовы Сі.
  • Непасрэдна і ўсебакова падтрымліваць мноства стыляў праграмавання, у тым ліку працэдурнае праграмаванне, абстракцыю даных, аб’ектна-арыентаванае праграмаванне і абагульненае праграмаванне.
  • Даць праграмісту свабоду выбару, нават калі гэта дасць яму магчымасць выбіраць няправільна.
  • Максімальна захаваць сумяшчальнасць з Сі, тым самым спрашчаючы пераход з мовы Сі.
  • Пазбегнуць розначытанняў паміж Сі і C++: любая канструкцыя, якая дапушчальная ў абедзвюх мовах, павінна ў кожнай з іх абазначаць адно і тое ж і прыводзіць да адных і тых жа паводзін праграмы.
  • Пазбягаць асаблівасцей, якія залежаць ад платформы ці не з’яўляюцца ўніверсальнымі.
  • Ніякі моўны сродак не павінен прыводзіць да зніжэння прадукцыйнасці праграм, якія не выкарыстоўваюць яго.
  • Не патрабаваць занадта складанага асяроддзя праграмавання.

Стандарт C++ на 1998 год складаецца з дзвюх асноўных частак: ядра мовы і стандартнай бібліятэкі.

Стандартная бібліятэка C++ увабрала ў сябе бібліятэку шаблонаў STL, якая распрацоўвалася адначасова са стандартам. Цяпер назва STL афіцыйна не ўжываецца, аднак у кругах праграмістаў на C++ і ў навучанні гэта назва выкарыстоўваецца для пазначэння часткі стандартнай бібліятэкі, якая змяшчае азначэнні шаблонаў кантэйнераў, ітэратараў, алгарытмаў і функтараў.

Стандарт C++ утрымоўвае нарматыўную спасылку на стандарт Сі ад 1990 года і не вызначае самастойна тыя функцыі стандартнай бібліятэкі, якія пазычаны са стандартнай бібліятэкі Сі.

Акрамя таго, існуе велізарная колькасць бібліятэк C++, якія не ўваходзяць у стандарт. У праграмах на C++ можна выкарыстоўваць шматлікія бібліятэкі Сі.

Мова праграмавання C++ вызначана стандартам, аднак за гэтай назвай могуць хавацца таксама няпоўныя, абмежаваныя, дастандартныя адмены мовы. Напачатку свайго існавання мова развівалася па-за фармальнымі рамкамі, адказваючы на задачы, якія ставіліся перад ёю. Развіццю мовы спадарожнічала развіццё крос-кампілятара cfront. Змены і новыя ўласціваці ў мове адлюстроўваліся ў змене нумара версіі крос-кампілятара. Гэтыя нумары версій крос-кампілятара распаўсюджваліся і на саму мову. Аднак цяпер звычайна пра версіі мовы C++ не кажуць.

Новыя магчымасці ў параўнанні з Сі

[правіць | правіць зыходнік]

Новаўвядзеннямі C++ у параўнанні з Сі з’яўляюцца:

Мова C++ шмат у чым з’яўляецца надмноствам Сі. Новыя магчымасці C++ уключаюць аб’яўленні ў выглядзе выразаў, пераўтварэнні тыпаў у выглядзе функцый, аператары new і delete, тып bool, спасылкі, пашыранае паняцце нязменнасці аб’ектаў, падстаўляльныя (убудавальныя) функцыі, аргументы па змаўчанні, пераазначэнні, прасторы імён, класы (разам з усімі спалучанымі магчымасцямі, такімі як наследаванне, функцыі-члены, віртуальныя функцыі, абстрактныя класы і канструктары), пераазначэнні аператараў, шаблоны, аператар развязання прасторы імён ::, апрацоўку выключэнняў, дынамічнае атаясамленне і многае іншае. Таксама, мова C++ ў многіх выпадках стражэйшая за мову Сі ў дачыненні да праверкі тыпаў.

У C++ з’явіліся каментары ў выглядзе падвойнай касой рысы (//), якія былі ў папярэдніку мовы Сі — мове BCPL.

Некаторыя асаблівасці C++ пазней былі перанесены ў Сі, напрыклад ключавыя словы const і inline, аб’яўленні ў цыклах for і каментары ў стылі C++ (//). У пазнейшых рэалізацыях Сі таксама з’явіліся магчымасці, якіх няма ў C++, напрыклад макрасы vararg і палепшаная праца з масівамі-параметрамі.

Магчымасці, не звязаныя з ААП

[правіць | правіць зыходнік]

У гэтым раздзеле апісваюцца магчымасці, непасрэдна не звязаныя з аб’ектна-арыентаваным праграмаваннем (ААП). Аднак, многія з іх набываюць асаблівую важнасць іменна ў спалучэнні з ААП.

  • Апісальнік inline азначае, што функцыя з’яўляецца добрым кандыдатам на аптымізацыю, пры якой у месцах звароту да функцыі кампілятар уставіць цела гэтай функцыі, а не код выкліку. Прыклад: inline double Sqr(double x) {return x*x;}. inline з’яўляецца не дырэктывай (ўказаннем), а толькі парадай кампілятару — кампілятар не абавязан падстаўляць цела для ўбудавальных (inline) функцый, але можа, зыходзячы з даных настроек аптымізацыі, выконваць падстаноўку цела для функцый, якія не пазначаны як inline.
  • Апісальнік volatile выкарыстоўваецца ў апісанні зменных і кажа кампілятару, што значэнне пэўнай зменнай можа быць зменена спосабам, які кампілятар не ў стане адсачыць. Для зменных, пазначаных як volatile, кампілятар не павінен ужываць сродкі аптымізацыі, якія змяняюць становішча зменнай у памяці (напрыклад, перамяшчаць яе ў рэгістр) ці разлічваюць на нязменнасць значэння зменнай у прамежку паміж дзвюма прысвойваннямі ёй значэння.
  • Замест функцый malloc і free (якія пакінуты толькі для зваротнай сумяшчальнасці), уведзены новыя аператары new і delete. Калі T — адвольны тып, то
    • new T выдзяляе памяць, дастатковую для размяшчэння аднаго аб’екта тыпу Т, магчыма, задае значэнне аб’екта ў гэтай памяці, і вяртае указальнік тыпу Т* (напрыклад, Т* p = new T).
    • new T[n] выдзяляе памяць, дастатковую для размяшчэння n аб’ектаў тыпу Т, магчыма, задае кожны аб’ект у гэтай памяці, і вяртае указальнік тыпу Т* (напрыклад, Т* p = new T[n]).
    • delete p — знішчае аб’ект, на які спасылаецца указальнік p, і вызваляе вобласць памяці, выдзеленую для яго раней аператарам new T.
    • delete [] p — знішчае кожны аб’ект у масіве, на які спасылаецца указальнік p, і вызваляе вобласць памяці, выдзеленую для гэтага масіва раней аператарам new T[n].

Аператар delete правярае, што яе аргумент не NULL, у адваротным выпадку ён нічога не робіць. Пры стварэнні і выдаленні асобнікаў (аб’ектаў) класаў з дапамогай new і delete кампілятар устаўляе выклікі канструктара і дэструктара класа (гл. ніжэй).

  • Функцыі могуць прымаць аргументы па спасылцы. Напрыклад, функцыя void f(int& x) {x=3;} прысвойвае свайму аргументу значэнне 3. Таксама, функцыі могуць вяртаць вынік па спасылцы. Спасылкі можна выкарыстоўваць не толькі ў функцыях. Напрыклад, {double&b=a[3]; b=sin(b);} раўназначна коду a[3]=sin(a[3]);. Спасылкі ў пэўнай ступені падобны да ўказальнікаў, але з наступнымі асаблівасцямі: пры аб’яўленні спасылкі задаюцца ўказаннем на ўжо існуючую зменную пэўнага тыпу; на працягу свайго існавання спасылка паказвае на адзін і той жа адрас (па сутнасці, спасылка ёсць нязменным няяўным указальнікам); пры звароце па спасылцы аўтаматычна прымяняецца аператар * . Існуюць і іншыя адрозненні ва ўжыванні ўказальнікаў і спасылак.
  • Дапускаецца існаванне некалькіх функцый з аднолькавым іменем, але рознымі тыпамі ці колькасцю аргументаў (перагрузка функцый; пры гэтым тып значэння, якое функцыя вяртае, на перагрузку не ўплывае). Напрыклад, цалкам дапушчальна пісаць:
void Print(int x);
void Print(double x);
void Print(int x, int y);
  • Адзін або некалькі апошніх аргументаў функцыі могуць задавацца па змаўчанні. Напрыклад, калі функцыя апісана як void f(int x, int y=5, int z=10), выклікі f(1), f(1,5) і f(1,5,10) раўназначныя.
  • Пры аб’яўленні функцый адсутнасць аргументаў у дужках азначае (у адрозненне ад Сі), што аргументаў няма, а не тое, што невядома іх колькасць. Калі лік аргументаў невядомы, трэба карыстацца шматкроп’ем, напрыклад, int printf(const char* fmt, …).
  • Можна азначаць аперацыі (перагружаць аператары) над новымі тыпамі. Напрыклад, так:
struct Date {int day, month, year;};
void operator ++(struct Date& date);

Аператары нічым не адрозніваюцца ад звычайных функцый (па сутнасці, аператар ёсць функцыяй, і ўсе адрозненні складаюцца ў тым, як выглядае выклік аператара ў зыходным кодзе). Аднак існуе шэраг абмежаванняў на перагрузку аператараў: нельга перагружаць аператары над прадвызначанымі (убудаванымі) тыпамі (скажам, пераазначаць множанне цэлых лікаў тыпу int); нельга выдумляць новыя аператары, якіх няма ў C++ (скажам, **); нельга змяняць меснасць (колькасць аргументаў) аператараў; прыярытэты аператараў захоўваюцца (скажам, у выразе a+b*c спачатку будзе выконвацца множанне, а потым складанне, якіх бы тыпаў ні былі a, b і c). Можна пераазначаць аператары [] (з адным параметрам) і () (з любым лікам параметраў).

namespace Foo {
   const int x=5;
   typedef int** T;
   void f(y) {return y*x};
   double g(T);
  ...
}

то па-за фігурнымі дужкамі мы павінны звяртацца да T, x, f, g як Foo::T, Foo::x, Foo::f, Foo::g. Калі мы ў нейкім файле жадаем звяртацца да іх непасрэдна, мы можам напісаць

using namespace Foo;

або

using Foo::T;

Прасторы імён патрэбны, каб не ўзнікала накладак паміж пакетамі, якія маюць глабальныя зменныя, функцыі ці тыпы з аднолькавымі імёнамі. Адмысловым выпадкам з’яўляецца безыменная прастора імён

namespace {
   ...
}

Усе імёны, азначаныя ў ёй, даступныя толькі ў бягучай адзінцы трансляцыі і больш нідзе.

  • Уведзен новы тып bool, які можа прымаць значэнні true («ісціна», або «так») і false («фальш», або «не»). Аператары параўнання вяртаюць тып bool. Выразы ў дужках пасля if, while прыводзяцца да тыпу bool. (Заўвага, па сутнасці, у большасці кампілятараў тып bool уяўляе сабой цэлы тып int, пры гэтым 0 разглядаецца як false, а любое ненулявое значэнне — як true).
  • // азначае, што ўся астатняя частка радка ёсць каментаром.
  • Дабаўлены шаблоны (template). Напрыклад, template<class T> T Min(T x, T y) {return x<y?x:y;} азначае функцыю Min для любых тыпаў (для якіх вызначан аператар «<»). Шаблонамі можна задаваць не толькі функцыі, але і тыпы (класы і структуры). Напрыклад, template<class T> struct Array{int len; T* val;}; азначае масіў значэнняў любога тыпу, пасля чаго мы можам пісаць Array<float> x;
  • Уведзена стандартная бібліятэка шаблонаў (STL, Standard Template Library), у якой азначаны шаблоны і функцыі для вектараў (аднамерных масіваў адвольнай даўжыні), мностваў, слоўнікаў (асацыятыўных масіваў map), спісаў, знакавых радкоў, патокаў уводу-вываду і іншыя шаблоны і функцыі.
  • Калі апісана структура, клас, аб’яднанне (union) ці пералік (enum), яе імя з’яўляецца іменем тыпу, напрыклад:
struct Time {
    int hh, mm, ss;
};
Time t1, t2;
  • Усярэдзіне класа можна азначаць укладзеныя тыпы, як праз typedef, так і праз апісанне іншых класаў, а таксама пералікаў. Для доступу да такіх тыпаў па-за класам, да імя тыпу дадаецца імя класа і два двукроп’і:
struct S {
    typedef int** T;
    T x;
};
S::T y;

Аб’ектна-арыентаваныя асаблівасці мовы

[правіць | правіць зыходнік]

Мова C++ дадае да Сі аб’ектна-арыентаваныя магчымасці. Яна уводзіць класы, якія забяспечваюць тры самыя важныя уласцівасці ААП: інкапсуляцыю, наследаванне і полімарфізм.

Існуе два значэнні слова клас. У шырокім сэнсе клас — гэта карыстальніцкі тып, аб’яўлены з выкарыстаннем аднаго з ключавых слоў class, struct ці union. У вузкім сэнсе клас — гэта карыстальніцкі тып, аб’яўлены з выкарыстаннем ключавога слова class.

Інкапсуляцыя (абарона даных)

[правіць | правіць зыходнік]

Асноўным спосабам уладкавання інфармацыі ў мове C++ з’яўляюцца класы. У адрозненне ад тыпу структура (struct) мовы Сі, якая можа складацца толькі з палёў і укладзеных тыпаў, клас (class) C++ можа складацца з палёў, укладзеных тыпаў і функцый-членаў (member functions). Члены класа бываюць адкрытымі (public), абароненымі (protected) і закрытымі (private). У C++ тып структура падобны да тыпу клас, адрозненне ў тым, што па змаўчанні члены і наследаванне ў структуры адкрытыя, а ў класа — закрытыя.

З адкрытымі членамі класа можна рабіць ўсё, што заўгодна, як унутры так і звонку класа. Да закрытых жа членаў нельга звяртацца па-за класам; гэта зроблена адмыслова, каб не парушыць цэласнасць даных класа. Спроба такога звароту выкліча памылку кампіляцыі. Да такіх членаў могуць звяртацца толькі функцыі-члены класа (а таксама так званыя функцыі-сябры і функцыі-члены класаў-сяброў; пра паняцце сяброў у C++ гл. ніжэй). Апроч адкрытых і закрытых членаў класа, могуць быць яшчэ і абароненыя — гэта члены, даступныя свайму класу-ўладальніку, яго сябрам, а таксама вытворным ад яго класам (наследнікам). Такая ахова членаў называецца інкапсуляцыяй (або абаронай даных).

Выкарыстоўваючы інкапсуляцыю, аўтар класа можа абараніць свае даныя ад няслушнага выкарыстання. Акрамя таго, ахоўванне ўкладзеных даных задумвалася для палягчэння сумеснай распрацоўкі класаў. Мелася на ўвазе, што пры змене спосабу захоўвання даных, калі яны аб’яўлены як абароненыя ці закрытыя, не патрабуецца адпаведных змен у класах, якія выкарыстоўваюць зменены клас. Напрыклад, калі ў старой версіі класа закрытыя ці абароненыя даныя захоўваліся ў выглядзе лінейнага спісу, а ў новай версіі — у выглядзе дрэва, не трэ будзе перапісваць ніякія функцыі, акрамя функцый-членаў класа, а таксама функцый-сяброў і функцый-членаў класаў-сяброў, а ў выпадку абароненых членаў такіх жа функцый для вытворных класаў. З іншага боку, калі гэтыя даныя былі адкрытымі, трэ будзе, увогуле кажучы, перапісваць усе функцыі, якія напрамую працуюць з гэтымі палямі: як члены любых класаў, так і функцыі, якія не з’яўляюцца членамі ніякіх класаў.

Доступ private
(закрыты)
protected
(ахаваны)
public
(адкрыты)
Сам класс ёсць ёсць ёсць
Сябры ёсць ёсць ёсць
Наследнікі няма ёсць ёсць
Па-за класам няма няма ёсць

Выкарыстоўваючы ахоўванне даных, аднамерны масіў можна апісаць наступным чынам:

class Array {
public:
    void Alloc(int new_len);
    void Free();
    inline double Elem(int i);
    inline void ChangeElem(int i, double x);
protected:
    int len;
    double* val;
};

void Array::Alloc(int new_len) 
    {if (len>0) Free(); len=new_len; val=new double[new_len];}
void Array::Free() {delete [] val; len=0;}
inline double Array::Elem(int i) 
    {assert(i>=0 && i<len ); return val[i];}
inline void Array::ChangeElem(int i, double x) 
    {assert(i>=0 && i<len); val[i]=x;}

І далей

Array a;
a.Alloc(10);
a.ChangeElem(3, 2.78);
double b = a.Elem(3);
a.Free();

Тут масіў a мае 4 адкрытыя функцыі-члены і 2 ахаваныя палі. Апісальнік inline падказвае кампілятару, што замест выкліку функцыі яе код варта падставіць у кропку выкліку. Часта гэтым можна дасягнуць большай эфектыўнасці.

Азначэнне функцый у целе класа

[правіць | правіць зыходнік]

У целе класа можна апісаць толькі загаловак функцыі, а можна цалкам азначыць функцыю. У апошнім выпадку яна лічыцца ўбудавальнай (або падстаўляльнай) (inline), напрыклад:

class Array {
public:
    void Alloc(int _len) {
    	if (len == 0) Free();
    	len = _len;
    	val = new double[len];
    }

і гэтак далей.

Аднак у прыведзеным прыкладзе не развязана важная праблема: функцыі Alloc і Free па-ранейшаму трэба выклікаць уручную. Іншая праблема гэтага прыкладу — небяспечнасць аператара прысвойвання.

Каб развязаць гэтыя праблемы ў мову былі ўведзены канструктары і дэструктары. Канструктар выклікаецца кожны раз, калі ствараецца аб’ект дадзенага тыпу; дэструктар — пры знішчэнні. Пры пераўтварэннях тыпаў з удзелам асобнікаў (аб’ектаў) класаў таксама выклікаюцца канструктары і дэструктары.

З канструктарамі і дэструктарам клас выглядае так:

class Array {
public:
    Array() : len(0), val(NULL) {}
    Array(int _len) : len(_len) {val = new double[_len];}
    Array(const Array& a);
    ~Array() { Free(); }
    inline double Elem(int i);
    inline void ChangeElem(int i, double x);
protected:
    void Alloc(int _len);
    void Free();
    int len;
    double* val;
};

Array::Array(const Array& a) : len(a.len)
{
    val = new double[len];
    for (int i=0; i<len; i++)
        val[i] = a.val[i];
}

Тут Array::Array — канструктар, а Array::~Array — дэструктар. Канструктар капіравання Array::Array(const Array&) выклікаецца пры стварэнні новага аб’екта, які з’яўляецца копіяй ужо існуючага аб’екта. Зараз аб’ект класа Array нельга сапсаваць: як бы мы яго ні стваралі, што б мы ні рабілі, яго палі будуць несупярэчлівымі, таму што канструктар выклікаецца аўтаматычна. Усе небяспечныя аперацыі з указальнікамі схаваны ў закрытых функцыях-членах.

Array a(5); // выклікаецца Array::Array(int)
Array b;    // выклікаецца Array::Array()
Array c(a); // выклікаецца Array::Array(const Array&)
Array d=a;  // тое ж самае
b=c;        // адбываецца выклік аператара =
            // калі ён не азначан (як у гэтым выпадку), то выклікаецца аператар прысвойвання па змаўчанні, які
            // ажыццяўляе капіраванне ўсіх падаб’ектаў і пачасткавае капіраванне нестатычных членаў-дадзеных.
            // як правіла канструктар капіравання і аператар прысвойвання перавызначаюцца парамі

Аператар new таксама выклікае канструктары, а delete — дэструктары.

Па змаўчанні, кожны клас мае няяўна аб’яўлены канструктар без параметраў, які будуе падаб’екты класаў-продкаў і задае палі класа, выклікаючы для іх канструктары па змаўчанні. Калі ў класе няма яўна аб’яўленага дэструктара, то клас мае няяўна аб’яўлены дэструктар.

Клас можа мець колькі заўгодна канструктараў (з рознымі наборамі параметраў), але толькі адзін дэструктар (у дэструктара не можа быць параметраў).

Іншыя магчымасці функцый-членаў

[правіць | правіць зыходнік]

Функцыі-члены могуць быць і аператарамі:

class Array {
...
    inline double &operator[] (int n)
    {
         return val[n];
    }

І далей

Array a(10);
...
double b = a[5];

Функцыі-члены (і толькі яны) могуць мець апісальнік const

class Array {
...
    inline double operator[] (int n) const;

Такія функцыі не маюць права змяняць палі класа (акрамя палёў, пазначаных як mutable). Калі яны спрабуюць гэта зрабіць, кампілятар павінен выдаць паведамленне аб памылцы.

Для стварэння класаў з дадатковай функцыянальнасцю ўводзяць наследаванне. Клас-наследнік мае палі і функцыі-члены класа-продка, але не мае права звяртацца да закрытых (private) палёў і функцый бацькоўскага класа. У гэтым і складаецца розніца паміж закрытымі і ахаванымі членамі.

Клас-наследнік можа дабаўляць свае палі і функцыі або пераазначаць функцыі-члены класа-продка.

Па змаўчанні, канструктар наследніка без параметраў выклікае канструктар класа-продка, а затым канструктары тых нестатычных членаў-даных, якія з’яўляюцца асобнікамі пэўных класаў. Дэструктар працуе ў адваротным парадку. Канструктары класаў-наследнікаў даводзіцца азначаць кожны раз нанова. На шчасце, гэта можна зрабіць выклікам канструктара класа-продка.

class ArrayWithAdd : public Array {
    ArrayWithAdd(int n) : Array(n) {}
    ArrayWithAdd() : Array() {}
    ArrayWithAdd(const Array& a) : Array(a) {}
    void Add(const Array& a);
};

Наследнік змяшчае больш палёў і функцый-членаў чым продак, таму, калі наследаванне адкрытае, аб’екта класа-наследніка можна выкарыстоўваць усюды, дзе выкарыстоўваюцца асобнікі (аб’екты) класа-продка, але не наадварот.

Наследаванне бывае адкрытым, ахаваным і закрытым. Пры адкрытым наследаванні, адкрытыя і ахаваныя члены класа-продка захоўваюць свой узровень ахоўвання, а да закрытых не могуць звяртацца нават функцыі-члены наследніка. Ахаванае наследаванне адрозніваецца тым, што пры ім адкрытыя члены класа-продка з’яўляюцца ахаванымі членамі наследніка. Пры закрытым наследаванні ўсе члены класа-продка становяцца закрытымі членамі класа-наследніка; такім чынам, карыстальнік вытворнага класа не можа звяртацца да членаў бацькоўскага класа, нават калі яны аб’яўлены як адкрытыя. Клас-наследнік робіць іх закрытымі з дапамогай закрытага наследавання. Як правіла, адкрытае наследаванне ужываецца значна часцей за іншыя.

\Доступ к членам класа-продка
Узровень ахоўвання наследавання
закрыты член
(private)
ахаваны член
(protected)
адкрыты член
(public)
закрытае наследаванне
(private)
недаступны закрыты
(private)
закрыты
(private)
ахаванае наследаванне
(protected)
недаступны ахаваны
(protected)
ахаваны
(protected)
адкрытае наследаванне
(public)
недаступны ахаваны
(protected)
адкрыты
(public)

Клас можа быць наследнікам некалькіх класаў. Такое наследаванне называецца множным наследаваннем. Такі клас валодае палямі і функцыямі-членамі ўсіх сваіх продкаў. Напрыклад, клас FlyingCat (ЛятучыКот) можа быць наследнікам класаў Cat (Кот) і FlyingAnimal (ЛятучаяЖывёла)

class Cat {
   ...
    void Purr();
   ...
};
class FlyingAnimal {
   ...
    void Fly();
   ...
};
class FlyingCat : public Cat, public FlyingAnimal {
   ...
    PurrAndFly() {Purr(); Fly();}
   ...
};

Полімарфізм (разнастайнасць падкласаў)

[правіць | правіць зыходнік]

Полімарфізмам у праграмаванні завецца пераазначэнне наследнікам функцый-членаў базавага класа, напрыклад

class Figure {
   ...
    void Draw() const;
   ...
};

class Square : public Figure {
   ...
    void Draw() const;
   ...
};

class Circle : public Figure {
   ...
    void Draw() const;
   ...
};

У гэтым прыкладзе, якая з функцый будзе выклікана — Circle::Draw(), Square::Draw() ці Figure::Draw(), вызначаецца падчас кампіляцыі. Напрыклад, калі напісаць

Figure* x = new Circle(0,0,5);
x->Draw();

то будзе выклікана Figure::Draw(), бо x — аб’ект класа Figure. Такі полімарфізм называецца статычным.

Але ў C++ ёсць і дынамічны полімарфізм, калі функцыя, якую трэба выклікаць, вызначаецца падчас выканання. Для гэтага функцыі-члены павінны быць віртуальнымі.

class Figure {
   ...
    virtual void Draw() const;
   ...
};

class Square : public Figure {
   ...
    virtual void Draw() const;
   ...
};

class Circle : public Figure {
   ...
    virtual void Draw() const;
   ...
};

Figure* figures[10];
figures[0] = new Square(1, 2, 10);
figures[1] = new Circle(3, 5, 8);
...
for (int i = 0; i < 10; i++)
    figures[i]->Draw();

У гэтым выпадку для кожнага элемента будзе выклікана Square::Draw() ці Circle::Draw() у залежнасці ад фігуры.

Чыста віртуальнай функцыяй называецца віртуальная функцыя-член, якая аб’яўлена са апісальнікам = 0:

class Figure {
   ...
    virtual void Draw() const = 0;
);

Чыста віртуальная функцыя можа быць пакінута без азначэння, акрамя выпадку, калі неабходна яе выклікаць.

Абстрактным класам называецца такі, у якога ёсць хоць адна чыста віртуальная функцыя-член. Аб’екты такіх класаў ствараць забаронена. Абстрактныя класы часта выкарыстоўваюцца як інтэрфейсы.

Функцыі-сябры — гэта функцыі, якія не ёсць функцыямі-членамі, але маюць доступ да ахаваных і закрытых палёў і функцый-членаў класа. Яны павінны быць апісаны ў целе класа як friend. Напрыклад:

class Matrix {
   ...
    friend Matrix Multiply(Matrix m1, Matrix m2);
   ...
};

Matrix Multiply(Matrix m1, Matrix m2) {
   ...
}

Тут функцыя Multiply можа звяртацца да любых палёў і функцый-членаў класа Matrix.

Існуюць таксама класы-сябры. Калі клас A — сябар класа B, то ўсе яго функцыі-члены могуць звяртацца да любых палёў і функцый членаў класа B. Напрыклад:

class Matrix {
   ...
    friend class Vector;
   ...
};

Аднак у C++ не дзейнічае правіла «сябар майго сябра — мой сябар».

Па стандарце C++03 укладзены клас не мае доступу да закрытых членаў класа, які агортвае яго, і не можа быць аб’яўлен яго сябрам (апошняе вынікае з азначэння тэрміна сябар як нечлена класа). Аднак, многія пашыраныя кампілятары парушаюць абодва гэтыя правілы (відаць, з прычыны сукупнай дзіўнасці гэтых правіл).

Будучае развіццё

[правіць | правіць зыходнік]

Бягучы стандарт мовы быў прыняты ў 2003 годзе. Наступная версія стандарту носіць неафіцыйную назву C++0x.

C++ працягвае развівацца, каб адказваць сучасным патрабаванням. Адна з груп, якія займаюцца мовай C++ у яе сучасным выглядзе і накіроўваюць камітэту па стандартызацыі C++ парады па яе паляпшэнні — гэта Boost. Напрыклад, адзін з кірункаў дзейнасці гэтай групы — удасканаленне магчымасцей мовы шляхам дабаўлення ў яе асаблівасцей метапраграмавання.

Стандарт C++ не апісвае спосабы наймення аб’ектаў, некаторыя падрабязнасці апрацоўкі выключэнняў і іншыя магчымасці, спалучаныя з асаблівасцямі рэалізацыі, што прыводзіць да несумяшчальнасці аб’ектнага кода, створанага рознымі кампілятарамі. Аднак, каб зняць гэту праблему, трэцімі асобамі было створана мноства стандартаў для розных архітэктур і аперацыйных сістэм.

Тым не менш (па стане на час напісання гэтага артыкула) сярод кампілятараў C++ усё яшчэ працягваецца бітва за поўную рэалізацыю стандарту C++, асабліва ў вобласці шаблонаў — часткі мовы, зусім нядаўна распрацаванай камітэтам стандартызацыі ў поўным аб’ёме.

Ключавое слова export

[правіць | правіць зыходнік]

Адной са спрэчных пунктаў у гэтым пытанні з’яўляецца ключавое слова export, якое ўведзена з мэтай зняць патрэбу ў абавязковым папярэднім азначэнні шаблона[4].

Першым кампілятарам, які падтрымлівае export у шаблонах, стаў Comeau C++ напачатку 2003 года (праз пяць гадоў пасля выхаду стандарту). У 2004 годзе бета-версія кампілятара Borland C++ Builder X таксама ўключыла яго падтрымку.

Абодва гэтых кампілятара заснаваны на вонкавым інтэрфейсе EDG. Іншыя кампілятары, такія як Microsoft Visual C++ ці GCC (GCC 3.4.4), наогул не падтрымліваюць гэта ключавое слова. Хёрб Саттэр, сакратар камітэта па стандартызацыі C++, рэкамендаваў прыбраць export з будучых версій стандарту з прычыны сур’ёзных складанасцей у паўнавартаснай рэалізацыі, аднак у выніку яго вырашылі пакінуць.

Са спісу іншых праблем, спалучаных з шаблонамі, можна прывесці пытанні пабудовы частковай спецыялізацыі шаблонаў, якія дрэнна падтрымліваліся на працягу многіх гадоў пасля выхаду стандарту C++.

Стандартная бібліятэка

[правіць | правіць зыходнік]

У склад стандартнай бібліятэкі C++ уваходзіць стандартная бібліятэка мовы Сі з невялікімі зменамі, якія робяць яе больш прыдатнай для мовы C++. Другім істотным складнікам бібліятэкі C++ ёсць Стандартная Бібліятэка Шаблонаў (STL). Яна дае такія важныя прылады, як тыпы-скрыні (або кантэйнеры) (напрыклад, вектары і спісы) і ітэратары (абагульненыя указальнікі), якія дазваляюць працаваць са тыпамі-скрынямі (кантэйнерамі) як з масівамі. Акрамя таго, STL дазваляе падобным чынам працаваць і з іншымі скрыневымі тыпамі (тыпамі кантэйнераў), напрыклад, слоўнікамі (асацыятыўнымі спісамі), стэкамі, чэргамі.

Выкарыстоўваючы шаблоны, можна пісаць абагульненыя алгарытмы, здольныя працаваць з любымі скрыневымі тыпамі (кантэйнерамі) ці паслядоўнасцямі, вызначанымі ітэратарамі.

Гэтак жа, як і ў Сі, доступ к магчымасцям бібліятэк ажыццяўляецца з дапамогай указання #include для ўключэння стандартных файлаў. Усяго ў стандарце C++ вызначана 50 такіх файлаў.

STL да ўключэння ў стандарт C++ была незалежнай распрацоўкай, напачатку — фірмы HP, а затым SGI. Стандарт мовы не называе яе «STL», бо гэта бібліятэка стала неад’емнай часткай мовы, аднак шмат хто і дагэтуль выкарыстоўвае гэту назву, каб адрозніваць яе ад астатняй часткі стандартнай бібліятэкі (патокі ўводу/вываду (iostream), падмноства Сі і інш.).

Праект пад назвай STLport[5], заснаваны на SGI STL, ажыццяўляе сталае абнаўленне STL, IOstream і радковых класаў. Некаторыя іншыя праекты таксама займаюцца распрацоўкай асобных дастасаванняў стандартнай бібліятэкі для розных канструктарскіх задач. Кожны вытворца кампілятараў C++ абавязкова пастаўляе якую-небудзь рэалізацыю гэтай бібліятэкі, бо яна з’яўляецца вельмі важнай часткай стандарту і шырока выкарыстоўваецца.

C++ не ўключае ў сябе Сі

[правіць | правіць зыходнік]

Нягледзячы на тое, што вялікая частка кода Сі будзе слушнай і на мове C++, C++ не ёсць надмноствам мовы Сі і не ўключае яе ў сябе. Існуе і такі код, які слушны на Сі, але няслушны для C++. Гэта адрознівае яго ад Аб’ектнага Сі, яшчэ аднаго ўдасканалення Сі для ААП, які якраз і ёсць надмноствам Сі.

У прыватнасці крыніцай несумяшчальнасці з’яўляюцца новыя (адносна Сі) ключавыя словы. Так, апісанне пераменнай

    int try;

з’яўляецца цалкам слушным на Сі, але хібным на мове C++, бо слова try з’яўляецца ў C++ ключавым.

Існуюць і іншыя адрозненні. Напрыклад, C++ не дазваляе выклікаць функцыю main() усярэдзіне праграмы, у той час як у Сі гэта дзеянне правамернае. Акрамя таго, C++ стражэйшы ў некаторых пытаннях: напрыклад, ён не дапускае няяўнага прывядзення тыпаў паміж нязвязанымі тыпамі ўказальнікаў і не дазваляе выкарыстоўваць функцыі, якія яшчэ не аб’яўлены.

Да таго ж код, слушны на абедзвюх мовах, можа даваць розныя вынікі ў залежнасці ад таго, кампілятарам якой мовы ён апрацаваны. Напрыклад, на большасці платформ наступная праграма друкуе «C», калі кампілюецца кампілятарам Сі, і «C++» — калі кампілятарам C++. Так адбываецца з-за таго, што знакавыя канстанты ў Сі (напрыклад 'a') маюць тып int, а ў C++ — тып char, а памеры гэтых тыпаў звычайна адрозніваюцца.

#include <stdio.h>

int main()
{
    printf("%s\n", (sizeof('a') == sizeof(char)) ? "C++" : "C");
    return 0;
}

Прыклады праграм на C++

[правіць | правіць зыходнік]

Гэта прыклад праграмы, якая нічога не робіць. Яна пачынае выконвацца і адразу завяршаецца. Яна складаецца з асноўнага патоку: функцыі main(), якая пазначае кропку пачатку выканання праграмы на C++.

int main()
{
    return 0;
}

Стандарт C++ патрабуе, каб функцыя main() вяртала тып int. Праграма, у якой функцыя main() вяртае значэнне іншага тыпу, не адпавядае стандарту C++.

Стандарт не кажа пра тое, што насамрэч азначае вяртаймае значэнне функцыі main(). Традыцыйна яно вытлумачваецца як код вяртання праграмы. Стандарт гарантуе, што вяртанне 0 функцыяй main() паказвае, што праграма была завершана паспяхова.

Завяршэнне праграмы на C++ з памылкай традыцыйна пазначаецца шляхам вяртання ненулявога значэння.

Гэта праграма таксама нічога не робіць, але карацейшая.

int main(){}

У C++, калі выкананне праграмы даходзіць да канца функцыі main(), гэта раўназначна return 0;. Для ўсіх астатніх функцый (акрамя main()) гэта не так.

Гэта прыклад праграмы Hello World, якая выводзіць гэта знакамітае паведамленне, выкарыстоўваючы стандартную бібліятэку, і завяршаецца.

#include <iostream> // гэта неабходна для std::cout і std::endl
 
int main()
{
    std::cout << "Hello, world!" << std::endl;
}

Сучасны C++ дазваляе развязваць простым спосабам і больш складаныя задачы. Гэты прыклад паказвае акрамя ўсяго іншага выкарыстанне тыпаў-скрыняў (кантэйнераў) стандартнай бібліятэкі шаблонаў (STL).

#include <iostream>   // для выкарыстання std::cout
#include <vector>     // для std::vector<>
#include <map>        // для std::map<> і std::pair<>
#include <algorithm>  // для std::for_each()
#include <string>     // для std::string

using namespace std;  // выкарыстанне прасторы імён "std"

void display_item_count(pair< string const, vector<string> > const& person) {
   // person — гэта пары двух аб’ектаў: person.first — гэта яго імя,
   // person.second — гэта спіс яго прадметаў (вектар радкоў)
   cout << person.first << " is carrying " << person.second.size() << " items" << endl;
}

int main()
{
   // аб’яўляем слоўнік (мапу, адлюстраванне) з радковымі ключамі і дадзенымі ў выглядзе вектараў радкоў
   map< string, vector<string> > items;

   // Дабавім у гэту карту некалькі чалавек і дадзім ім некалькі рэчаў
   items["Anya"].push_back("scarf");
   items["Dimitri"].push_back("tickets");
   items["Anya"].push_back("puppy");

   // Перабяром усе аб’екты ў скрыні (кантэйнеры)
   for_each(items.begin(), items.end(), display_item_count);
}

У гэтым прыкладзе для прастаты выкарыстоўваецца ўказанне выкарыстанай прасторы імён з дапамогай ключавых слоў using namespace. Аднак у вялікіх прамысловых праграмах звычайна раяць выкарыстоўваць аб’яўленні асобных класаў і функцый, бо з-за магчымага супадзення імён у розных прасторах могуць узнікнуць шматлікія цяжка ўлоўныя памылкі. Таму лепш пісаць, напрыклад, так:

#include <vector>

int main()
{
    using std::vector;

    vector<int> my_vector;
}

Тут дырэктыва (указанне) змешчана ў вобласць функцыі, што памяншае шанцы сутыкненняў імён (гэта і стала прычынай увядзення ў мову прастор імён). Ужыванне аб’яўленняў, якія зліваюць розныя прасторы імёнаў у адну, руйнуе сам замысел прасторы імён.

Папулярныя бібліятэкі (напрыклад, boost) у спалучэнні са стандартнымі сродкамі мовы дазваляюць вельмі коратка і наглядна запісваць код. У прыведзеным ніжэй прыкладзе вылічаецца скалярны здабытак вектараў няцотных лікаў і квадратаў. У кодзе вектары значэнняў прадстаўлены STL-падобнымі паслядоўнасцямі.

#include <iostream>
#include <numeric>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/transform_iterator.hpp>

int odd(int i)
{
  return 2 * i + 1;
}

int square(int i)
{
  return i * i;
}

typedef boost::counting_iterator <int> counter;
typedef boost::transform_iterator <int (*)(int), counter> transformer;

transformer odds(int n)
{
  return transformer(counter(n), odd);
}

transformer squares(int n)
{
  return transformer(counter(n), square);
}

int main()
{
  using namespace std;

  cout << "Enter vector length: ";
  int n; cin >> n;

  cout << inner_product( odds(0), odds(n), squares(0), 0 ) << endl;
}

Гэты прыклад паказвае так званы «плоскі» стыль запісу. Гэта назва звязана з тым, што алгарытмы STL дазваляюць запісваць код без цыклаў, адпаведна шырыня водступаў у адфарматаваным кодзе не дужа змяняецца. Прыхільнікі такога падыходу лічаць, што праграмісту, знаёмаму са стандартнай бібліятэкай C++, дастаткова радка з выклікам inner_product(), каб зразумець, што робіць праграма. З гэтага пункту гледжання выклік inner_product блізкі да славеснага апісання задачы: «вылічыць скалярны здабытак вектараў няцотных лікаў і квадратаў для значэнняў ад нуля да n».

Параўнанне C++ з мовамі Java і C#

[правіць | правіць зыходнік]

Мэтай стварэння C++ было пашырэнне магчымасцей Сі, найбольш распаўсюджанай мовы сістэмнага праграмавання. Накіраваная на той жа абсяг ужытку, што і мова Сі, мова C++ пераняла ад яе ў спадчыну мноства не самых лепшых, з тэарэтычнага пункту гледжання, асаблівасцей. Пералічаныя вышэй прынцыпы, якіх прытрымліваўся аўтар мовы, прадвызначылі многія недахопы C++.

У галіне прыкладнога праграмавання альтэрнатывай C++ стала мова Java. Нягледзячы на пераемнасць у адносінах да C++, Java будавалася на прынцыпова іншай аснове, яе распрацоўшчыкі не былі абмежаваны патрабаваннямі сумяшчальнасці з мовай-продкам і забеспячэння найбольшай дасягальнай эфектыўнасці, дзякуючы гэтаму яны змаглі карэнным чынам перапрацаваць мову, адмовіцца ад мноства сінтаксічных сродкаў, каб дамагчыся ідэалагічнай цэласнасці мовы. Пазней фірма Майкрасофт прапанавала мову C#, якая ўяўляе сабой яшчэ адну перапрацоўку мовы C++ у тым жа кірунку, што і Java. Пасля з’явілася мова Nemerle, у якой да сродкаў C# далучаны сродкі функцыйнага праграмавання. Яшчэ пазней з’явілася спроба аб’яднання эфектыўнасці C++ з бяспекай і хуткасцю распрацоўкі Java і C# — была прапанавана мова D, якая пакуль не атрымала шырокага прызнання.

Java і C++ можна разглядаць як дзве мовы-пераемніцы Сі, распрацаваныя з розных меркаванняў, у выніку чаго іх шляхі разышліся. У гэтай сувязі цікава параўнаць гэтыя мовы (усё, сказанае ніжэй пра Java, можна з аднолькавым поспехам аднесці да моў C# і Nemerle, бо на такім узроўні разглядання гэтыя мовы адрозніваюцца толькі вонкава).

Сінтаксіс
C++ захоўвае сумяшчальнасць з C, наколькі гэта магчыма. Java захоўвае вонкавае падабенства да C і C++, але, у рэчаіснасці, моцна адрозніваецца ад іх — з мовы выдалена вялікая колькасць сінтаксічных сродкаў, абвешчаных неабавязковымі. У выніку праграмы на Java бываюць больш грувасткія ў параўнанні з іх аналагамі на C++. З іншага боку, Java прасцешая за C++, што палягчае як вывучэнне мовы, так і стварэнне транслятараў для яе.
Выкананне праграмы
Java-код кампілюецца ў прамежкавы код, які ў далейшым інтэрпрэтуецца ці кампілюецца, тады як мова C++ першапачаткова скіравана на кампіляцыю ў машынны код для пэўнай платформы (хоць, тэарэтычна, нішто не перашкаджае стварыць для C++ транслятар у прамежкавы код). Ужо гэта вызначае розніцу ў абласцях ужытку моў: мову Java наўрад ці можна выкарыстаць пры напісанні такіх адмысловых праграм, як драйверы прыстасаванняў ці нізкаўзроўневыя сістэмныя ўтыліты. Дзякуючы механізму выканання, напісаныя на Java праграмы, нават адкампіляваныя (у байт-код), цалкам пераносныя. Стандартнае асяроддзе і асяроддзе выканання дазваляюць выконваць праграмы на Java на любой апаратнай платформе і ў любой АС, без якіх-небудзь змен, намаганні па пераносе праграм мінімальныя (а пры выкананні парад па стварэнні пераносных праграм — і зусім нулявыя). Коштам пераноснасці становіцца страта эфектыўнасці — праца асяроддзя выканання прыводзіць да дадатковых накладных выдаткаў.
Кіраванне рэсурсамі
C++ дазваляе выкарыстоўваць прынцып «захоп рэсурсаў шляхам ініцыялізацыі» (RAII), пры якім рэсурсы спалучаюцца з аб’ектам і аўтаматычна вызваляюцца пры разбурэнні аб’екта (напрыклад, std::vector <T> і std::ifstream). Таксама магчымы падыход, калі праграміст, выдзяляючы рэсурсы (памяць пад аб’екты, адкрытыя файлы і т. п.), абавязаны яўна паклапаціцца пра своечасовае іх вызваленне. Java працуе ў асяроддзі са зборкай смецця, якая аўтаматычна адсочвае спыненне выкарыстання аб’ектаў і вызваляе занятую імі памяць, калі ў гэтым ёсць неабходнасць, у некаторы нявызначаны момант часу. Ручное кіраванне мае перавагі ў сістэмным праграмаванні, дзе патрэбен поўны кантроль над рэсурсамі, а RAII і зборка смецця зручнейшыя ў прыкладным праграмаванні, бо ў значнай ступені вызваляюць праграміста ад неабходнасці адсочваць, калі рэсурсы больш не выкарыстоўваюцца. Зборшчык смецця Java патрабуе сістэмных рэсурсаў, што змяншае эфектыўнасць выканання праграм, пазбаўляе праграмы на Java прадвызначанасці выканання, акрамя таго ён здольны сачыць толькі за памяццю. Файлы, каналы, гнёзды (сокеты), аб’екты графічнага інтэрфейсу праграміст на Java заўсёды вызваляе яўна.
Стандартызацыя асяроддзя
У Java ёсць дакладна вызначаныя стандарты на ўвод-вывад, графіку, геаметрыю, дыялог, доступ к базам даных і іншым тыпавым прыкладанням. У гэтых пытаннях у мове C++ значна больш свабоды. Стандарты на графіку, доступ к базам даных і г. д. з’яўляюцца недахопам, калі праграміст жадае вызначыць свой уласны стандарт.
Указальнікі
C++ захоўвае магчымасць працы з нізкаўзроўневымі ўказальнікі. У мове Java ўказальнікаў няма. Выкарыстанне ўказальнікаў часта ёсць прычынаю цяжка ўлоўных памылак, але праца з указальнікамі неабходна для нізкаўзроўневага праграмавання. У прынцыпе, C++ валодае наборам сродкаў (канструктары і дэструктары, стандартныя шаблоны, спасылкі), якія дазваляюць амаль цалкам выключыць ручное выдзяленне і вызваленне памяці і небяспечныя аперацыі з указальнікамі. Аднак такое выключэнне патрабуе пэўнай культуры праграмавання, у той час як у мове Java яно рэалізуецца аўтаматычна.
Парадыгма праграмавання
У адрозненне ад C++, Java з’яўляецца чыста аб’ектна-арыентаванай мовай, без магчымасці працэдурнага праграмавання. Каб аб’явіць проста функцыю ці глабальную зменную у Java неабходна ствараць фіктыўныя класы, якія змяшчаюць толькі статычныя (static) члены [6]. Для задання галоўнай функцыі нават самай простай праграмы на Java неабходна змясціць яе ў клас [7].
Дынамічная інфармацыя пра тыпы
у C++ «дынамічнае атаясамленне тыпаў даных» (RTTI) абмежавана магчымасцю параўноўваць тыпы аб’ектаў паміж сабой і з літаральнымі значэннямі тыпаў. У сістэме Java даступна больш падрабязная інфармацыя пра тыпы. Гэту магчымасць можна было б рэалізаваць у C++, маючы поўную інфармацыю пра тыпы падчас кампіляцыі («атаясамленне тыпаў даных падчас кампіляцыі» CTTI).
Папярэдняя апрацоўка (прэпрацэсар)
C++ выкарыстоўвае прэпрацэсар для ўключэння азначэнняў функцый і класаў, для падключэння бібліятэк, цалкам выкананых у зыходным кодзе, а таксама дазваляе ажыццяўляць метапраграмаванне з выкарыстаннем прэпрацэсара, якое, у прыватнасці, развязвае складаныя праблемы высокаўзроўневага паўтарэння кода[8]. Ёсць меркаванне, што гэты механізм небяспечны, бо імёны макрасаў прэпрацэсара глабальныя, а самі макрасы амаль ніяк не звязаны з пабудовамі самой мовы. Гэта можа прыводзіць да складаных супярэчнасцей імёнаў. З іншага пункту гледжання, C++ дае дастатковыя сродкі (канстанты, шаблоны, убудавальныя функцыі) для таго, каб амаль цалкам выключыць выкарыстанне прэпрацэсара. Java выключыла прэпрацэсар цалкам, пазбавіўшыся за раз ад усіх праблем з яго выкарыстаннем, але і страціўшы пры гэтым магчымасці метапраграмавання прэпрацэсара і тэкставых замен у кодзе сродкамі мовы.

Адрозненні моў прыводзяць да разлютаваных спрэчак паміж прыхільнікамі дзвюх моў пра тое, якая мова лепшая. Спрэчкі гэтыя шмат у чым беспрадметныя, бо прыхільнікі Java лічаць, што адрозненні сведчаць на карысць Java, а прыхільнікі C++ мяркуюць адваротнае. Некаторыя довады з часам састарэлі, напрыклад, папрокі ў неэфектыўнасці Java з-за наяўнасці асяроддзя выканання, якія былі справядлівымі ў першай палове 1990-х гадоў, у выніку лавінападобнага росту прадукцыйнасці камп’ютараў і з’яўлення больш эфектыўнай тэхнікі выканання (JIT) у значнай меры страцілі актуальнасць. Мова C++, у сваю чаргу, развівалася, і шэраг яе недахопаў выпраўлены ў апошніх версіях стандарту (напрыклад, з’явіўся механізм частковай спецыфікацыі шаблонаў).

Далёка не ўсе праграмісты з’яўляюцца прыхільнікамі толькі аднае з моў. Паводле меркавання большасці праграмістаў, Java і C++ не з’яўляюцца канкурэнтамі, таму што ў іх розныя вобласці ўжытку. Іншыя лічаць, што выбар мовы для большасці задач з’яўляецца пытаннем асабістага густу.

Добрыя якасці і недахопы мовы

[правіць | правіць зыходнік]

Перш за ўсё, неабходна падкрэсліць, што ацэньваць добрыя якасці і, асабліва, недахопы C++ неабходна з улікам тых прынцыпаў, на якіх будавалася мова, і патрабаванняў, якія да яе першапачаткова прад’яўляліся.

Добрыя якасці

[правіць | правіць зыходнік]

C++ — надзвычай магутная мова, якія мае сродкі для стварэння эфектыўных праграм амаль любога прызначэння, ад нізкаўзроўневых утыліт і драйвераў да складаных праграмных комплексаў самага рознага прызначэння. У прыватнасці:

  • Падтрымліваюцца розныя стылі і тэхналогіі праграмавання, у тым ліку традыцыйнае дырэктыўнае праграмаванне, ААП, абагульненае праграмаванне, метапраграмаванне (шаблоны, макрасы).
  • Прадказальнае выкананне праграм з’яўляецца важнай якасцю пры пабудове сістэм рэальнага часу. Увесь код, які няяўна ствараецца кампілятарам для рэалізацыі моўных магчымасцей (напрыклад, пры пераўтварэнні зменнай да іншага тыпу), вызначаны ў стандарце. Таксама строга вызначаны месцы праграмы, у якіх гэты код выконваецца. Гэта дае магчымасць замяраць ці разлічваць час рэакцыі праграмы на вонкавую падзею.
  • Аўтаматычны выклік дэструктараў аб’ектаў пры іх знішчэнні, прычым у парадку, зваротным выкліку канструктараў. Гэта спрашчае (дастаткова аб’явіць зменную) і павядичвае надзейнасць вызвалення рэсурсаў (памяці, файлаў, семафораў і т. п.), а таксама дазваляе гарантавана выконваць пераходы паміж станамі праграмы, не абавязкова звязаныя з вызваленнем рэсурсаў (напрыклад, запіс у часопіс).
  • Карыстальніцкія функцыі-аператары дазваляюць коратка і ёміста запісваць выразы над карыстальніцкімі тыпамі ў натуральнай алгебраічнай форме.
  • Мова падтрымлівае паняцці фізічнай (const) і лагічнай (mutable) нязменнасці (канстантнасці). Гэта павялічвае надзейнасць праграмы, бо, напрыклад, дазваляе кампілятару знаходзіць памылковыя спробы змены значэння зменнай. Аб’яўленне з апісальнікам канстантнасці дае праграмісту, які чытае тэкст праграмы, дадатковае ўяўленне пра правільнае выкарыстанне класаў і функцый, а таксама можа быць падказкай для аптымізацыі. Перагрузка функцый-членаў па прыкмеце канстантнасць дазваляе вызначаць знутры аб’екта мэты выкліку метаду (канстантны для чытання, неканстантны для змянення). Апісальнік mutable дазваляе захоўваць лагічную канстантнасць пры выкарыстанні кэшаў і лянівых вылічэнняў.
  • Выкарыстоўваючы шаблоны, можна ствараць абагульненыя кантэйнеры і алгарытмы для розных тыпаў даных, а таксама спецыялізаваць і вылічваць на этапе кампіляцыі.
  • Магчымасць імітацыі пашырэння мовы для падтрымкі парадыгмаў, якія не падтрымліваюцца кампілятарамі напрамую. Напрыклад, бібліятэка Boost.Bind дазваляе звязваць аргументы функцый.
  • Магчымасць стварэння ўбудаваных прадметна-арыентаваных моў праграмавання. Такі падыход выкарыстоўвае, напрыклад бібліятэка Boost.Spirit, якая дазваляе задаваць EBNF-граматыку сінтаксічных разборшчыкаў прама ў кодзе C++.
  • Выкарыстоўваючы шаблоны і множнае наследаванне можна імітаваць класы-прымесі і камбінаторную параметрызацыю бібліятэк. Такі падыход ужыты ў бібліятэцы Loki, клас SmartPrt якой дазваляе, кіруючы ўсяго некалькімі параметрамі часу кампіляцыі, згенераваць каля 300 відаў «разумных указальнікаў» для кіравання рэсурсамі.
  • Пераноснасць: стандарт мовы накладвае мінімальныя патрабаванні на ЭВМ для запуску скампіляваных праграм. Каб вызначыць сапраўдныя уласцівасці сістэмы выканання ў стандартнай бібліятэцы прысутнічаюць адпаведныя магчымасці (напрыклад, std::numeric_limits <T>). Кампілятары даступны для вялікай колькасці платформ, на мове C++ распрацоўваюць праграмы для самых розных платформ і сістэм.
  • Эфектыўнасць. Мова распрацавана так, каб даць праграмісту магчымасць усебаковага кантролю над унутранай структурай праграмы і парадкам яе выканання. Ніводная з моўных магчымасцей, якая прыводзіць да дадатковых накладных выдаткаў, не з’яўляецца абавязковай для выкарыстання — пры неабходнасці мова дазваляе забяспечыць максімальную эфектыўнасць праграмы.
  • Існуе магчымасць працы на нізкім узроўні з памяццю, адрасамі.
  • Высокая сумяшчальнасць з мовай Сі, што дазваляе выкарыстоўваць увесь існуючы Сі-код (код на Сі можна з нязначнымі пераробкамі сабраць кампілятарам C++; бібліятэкі, напісаныя на Сі, звычайна можна выклікаць непасрэдна з праграмы на C++ без якіх-небудзь дадатковых выдаткаў, у тым ліку і на ўзроўні функцый зваротнага выкліку, што дазваляе бібліятэкам, напісаным на Сі, выклікаць код, напісаны на Сі++).

Большасць сваіх недахопаў мова C++ атрымала ў спадчыну ад мовы-продка — Сі, — і выкліканы яны першапачаткова пастаўленым патрабаваннем як мага большай сумяшчальнасці з Сі. Гэта такія недахопы, як:

  • Сінтаксіс, які проста падштурхоўвае на памылкі:
    • Аперацыя прысвойвання абазначаецца як =, а аперацыя параўнання як ==. Іх лёгка зблытаць[9], пры гэтым аперацыя прысвойвання вяртае значэнне, таму прысвойванне на месцы выразу з’яўляецца сінтаксічна дапушчальным, а ў канструкцыях цыклу і галінавання з’яўленне ліку на месцы лагічнага значэння таксама дапушчальна, у выніку недарэчны выраз з’яўляецца сінтаксічна правільным. Тыповы прыклад падобнай памылкі:
      if (x=0) { аператары }
      
      Тут ва ўмоўным аператары памылкова напісана прысвойванне замест параўнання. У выніку, замест таго, каб параўнаць бягучае значэнне x з нулём, праграма прысвоіць зменнай x нулявое значэнне, а потым вытлумачыць яго як значэнне ўмовы ў аператары if. А як нуль адпавядае лагічнаму значэнню «фальш» (false), то блок аператараў ва ўмоўнай канструкцыі не выканаецца ніколі. Памылкі такога роду вельмі цяжка выяўляць, але ў многіх сучасныя кампілятары могуць выяўляць некаторыя падобныя канструкцыі.
    • Аператары прысвойвання (=), адзінкавага прыросту (інкрэмента) (++), адзінкавага змяншэння (дэкрэмента) (--) і іншыя вяртаюць значэнне. У спалучэнні з багаццем аператараў гэта дазваляе, хоць і не абавязвае, ствараць неразборлівыя выразы. Наяўнасць гэтых аперацый у Сі было выклікана жаданнем атрымаць прыладу ручной аптымізацыі кода, але ў наш час аптымізуючыя кампілятары звычайна ствараюць аптымальны код і на традыцыйных выразах. З іншага боку, адзін з асноўных прынцыпаў моў Сі і C++ — дазваляць праграмісту пісаць у любым стылі, а не навязваць «добры» стыль.
    • Макрасы (#define) з’яўляюцца магутным, але дужа небяспечным сродкам. Іх пакінулі ў C++ нягледзячы на тое, што неабходнасць у іх, дзякуючы шаблонам і ўбудавальным функцыям, не такая ўжо і вялікая. У атрыманых у спадчыну стандартных Сі-бібліятэках шмат магчыма небяспечных макрасаў.
    • Некаторыя пераўтварэнні тыпаў працуюць не так, як можна было б меркаваць. У прыватнасці, аперацыя над бяззнакавым і знакавым лікамі дае бяззнакавы вынік.
    • C++ дазваляе прапускаць break у галінах аператара switch з мэтай паслядоўнага выканання некалькіх галін. Такі ж падыход прыняты ў мове Java [10]. Існуе меркаванне, што гэта абцяжарвае разуменне кода. Напрыклад, у мове C# неабходна заўсёды пісаць або break, або выкарыстоўваць goto case N для яўнага ўказання парадку выканання [11].
  • Прэпрацэсар, атрыманы ў спадчыну ад Сі, вельмі сціплы. Гэта прыводзіць з аднаго боку да таго, што з яго дапамогай нельга (ці цяжка) развязаць некаторыя задачы метапраграмавання, а з другога боку, з-за сваёй неразвітасці ён часта прыводзіць да памылак і патрабуе шмат дзеянняў для абыходу магчымых праблем. Некаторыя мовы праграмавання (напрыклад, Scheme і Nemerle) маюць намнога магутнейшыя і бяспечнейшыя сістэмы метапраграмавання (якія таксама называюцца макрасамі, аднак яны мала нагадваюць макрасы Сі/C++).
  • Слабая падтрымка модульнасці (па сутнасці, у класічным Сі модульнасць на ўзроўні мовы адсутнічае, яе забеспячэнне перакладзена на прэпрацэсар і кампілятар). Падключэнне інтэрфейсу вонкавага модуля праз прэпрацэсарнае ўключэнне загалоўкавага файла (#include) можа істотна запаволіць зборку пры падключэнні вялікай колькасці модуляў (бо выніковы файл, які апрацоўваецца кампілятарам, становіцца вельмі вялікім). Гэта схема без змен перанесена ў C++. Каб пазбавіцца ад гэтай заганы, многія кампілятары рэалізуюць механізм папярэдняй зборкі загалоўкавых файлаў (англ.: Precompiled header).

Да ўласных недахопаў C++ можна аднесці:

  • Складанасць і празмернасць, з-за якіх C++ цяжка вывучаць, а пабудова кампілятара спалучана з вялікай колькасцю праблем. У прыватнасці:
    • Многія канструкцыі C++ дазваляюць рабіць тое ж самае, што і канструкцыі Сі, таксама прысутныя ў C++. Гэта часам збівае з панталыку пачаткоўцаў. Напрыклад, прывядзенне тыпаў пры дапамозе dynamic_cast дазваляе прывесці указальнік ці спасылку строга ў межах іерархіі класаў. Гэта павялічвае надзейнасць кода, робіць яго больш выразным і дазваляе знаходзіць прывядзенні ў межах іерархіі пры дапамозе прылад, падобных да grep. Аднак з прычыны патрабавання высокай ступені сумяшчальнасці з Сі старое прывядзенне тыпаў усё яшчэ падтрымліваецца.
    • Падтрымка множнага наследавання рэалізацыі ў ААП-падсістэме мовы выклікае цэлы шэраг лагічных праблем, а таксама стварае дадатковыя цяжкасці ў рэалізацыі кампілятара. Напрыклад, указальнік на клас, які мае некалькі розных продкаў, больш не можа разглядацца (з выкарыстаннем старога прывядзення тыпу ў стылі Сі) як указальнік на аб’ект тыпу аднаго з класаў-продкаў, бо бацькоўская частка аб’екта можа быць размешчана з некаторым зрушэннем адносна пачатку аб’екта (то бок адносна значэння указальніка). Па гэтай жа прычыне нельга прыводзіць указальнік на бацькоўскі клас да тыпу ўказальніка на вытворны без выкарыстання аператараў прывядзення C++ (dynamic_cast).
    • Часам шаблоны прыводзяць да параджэння кода вельмі вялікага аб’ёму[12]. Каб паменшыць памер машыннага кода можна адмысловым чынам падрыхтаваць зыходны код[13]. Іншым развязкам праблемы з’яўляецца стандартызаваная яшчэ ў 1998 годзе магчымасць экспарту шаблонаў. Некаторыя аўтары лічаць, што яе цяжка рэалізаваць і таму яна даступна не ва ўсіх кампілятарах[14][15][16]. Праблема «раздзімання» машыннага кода з-за выкарыстання шаблонаў часта перабольшваецца, і сучасныя кампілятары ў многіх выпадках паспяхова прадухіляюць гэту з’яву[17].
  • Метапраграмаванне на аснове шаблонаў C++ складанае і пры гэтым абмежавана па сваіх магчымасцях. Яно складаецца з рэалізацыі сродкамі шаблонаў C++ інтэрпрэтатара прымітыўнай функцыйнай мовы праграмавання, які выконваецца падчас кампіляцыі. Сама па сабе гэта магчымасць вельмі прывабная, але такі код вельмі цяжка ўспрымаць і адладжваць. Меней распаўсюджаныя[18] мовы Lisp/Scheme, Nemerle маюць больш магутныя і адначасова прасцейшыя для ўспрымання падсістэмы метапраграмавання. Акрамя таго, у мове D рэалізавана параўнальная па магчымасцях, але значна прасцейшая ва ўжыванні падсістэма шаблоннага метапраграмавання.
  • Яўная падтрымка функцыйнага праграмавання прысутнічае толькі ў будучым стандарце c++0x. Гэты прабел запаўняецца рознымі бібліятэкамі (Loki, Boost), якія выкарыстоўваюць сродкі метапраграмавання для пашырэння мовы функцыйнымі канструкцыямі (напрыклад, падтрымкай лямбда-метадаў (або т.зв. безыменных функцый)), але якасць падобных рашэнняў значна саступае якасці сродкаў, убудаваных у функцыйныя мовы. Такія магчымасці функцыйных моў, як супастаўленне з узорам, наогул вельмі складана дасягнуць сродкамі метапраграмавання.
  • Некаторыя лічаць недахопам мовы C++ адсутнасць убудаванай сістэмы зборкі смецця. З іншага боку, сродкі C++ дазваляюць рэалізаваць зборку смецця на ўзроўні бібліятэкі [19]. Праціўнікі зборкі смецця мяркуюць, што RAII з’яўляецца больш годнай альтэрнатывай. C++ дазваляе карыстальніку самому выбіраць як кіраваць рэсурсамі.

Зноскі

  1. Страўструп Б. 2.1. Что такое C++? // Язык праграммирования C++. Указ. соч. — С. 57.
  2. а б в Страуструп Б. 1.4. Исторические замечания // Язык программирования C++. Указ. соч. — С. 46.
  3. Страуструп Б. Дизайн і эволюция C++ = The Design and Evolution of C++. — Спб.: Піцер, 2007. — 445 с. — ISBN 5-469-01217-4.
  4. Гл. артыкул «Як ключавое слова export мовы C++ дапамагае пры памылцы звязвання шаблонаў» [1]
  5. http://www.stlport.org/
  6. Class Arrays, JavaTM 2 Platform Std. Ed. v1.4.2 Архівавана 7 мая 2010.
  7. The Java ™ Tutorials. A Closer Look at the «Hello World!» Application
  8. З "C++ Template Metaprogramming, " by David Abrahams and Aleksey Gurtovoy. Copyright (c) 2005 by Pearson Архівавана 29 студзеня 2010.
  9. Цяжка знайсці пачаткоўца (і не толькі), які хоць раз (а часам і не раз) не напароўся б на гэту жорсткую, каварную, бязглуздую і бязлітасную памылку :)))
  10. The Java ™ Tutorials: The switch Statement
  11. MSDN: The switch statement in C#
  12. Dave Gottner. Templates Without Code Bloat // Dr. Dobb's Journal. — студзень 1995.
  13. Adrian Stone.. Minimizing Code Bloat: Redundant Template Instantiation. Game Angst (22 верасня 2009). Праверана 19 студзеня 2010.
  14. Herb Sutter. C++ Conformance Roundup // Dr. Dobb's Journal. — студзень 2001.
  15. Are there any compilers that implement all of this?(недаступная спасылка). comp.std.c++ frequently asked questions / The C++ language. Comeau Computing (англ.) (10 снежня 2008). Архівавана з першакрыніцы 30 красавіка 2009. Праверана 19 студзеня 2010.
  16. vanDooren.. C++ keyword of the day: export(недаступная спасылка). Blogs@MSMVPs (24 верасня 2008). — «The export keyword is a bit like the Higgs boson of C++. Theoretically it exists, it is described by the standard, and noone has seen it in the wild. … There is 1 C++ compiler front-end in the world which actually supports it»  Архівавана з першакрыніцы 6 мая 2009. Праверана 19 студзеня 2010.
  17. Scott Meyers.. Code Bloat due to Templates. comp.lang.c++.moderated. Usenet (16 мая 2002). Праверана 19 студзеня 2010.
  18. TIOBE Programming Community Index for January 2010 Архівавана 2 ліпеня 2013.
  19. Boehm-Demers-Weiser garbage collector for C and C++ Архівавана 21 студзеня 2012.
Артыкулы і кнігі, бібліятэкі матэрыялаў па C++
Форумы
Класы, бібліятэкі
Асяроддзі распрацоўкі