Перейти до вмісту

C++

Матеріал з Вікіпедії — вільної енциклопедії.
(Перенаправлено з С++)
C++
ПарадигмаМультипарадигмова: об’єктно-орієнтована, узагальнена, процедурна
Дата появи1985
ТворціБ'ярн Страуструп
РозробникISO/IEC JTC1/SC22[en]/WG21
Останній релізC++20 (грудень 2020)
Система типізаціїстатична, номінативна
Основні реалізаціїG++, Microsoft Visual C++, Borland C++ Builder, Clang
ДіалектиISO/IEC 14882:1998, 2003, 2011, 2014, 2017, 2020
Під впливом відSmalltalk, C, Simula, Ada 83, ALGOL 68, CLU, ML
Вплинула наAda 95, C#, Java, PHP, D, Aikido, Dao
Звичайні розширення файлів.cc, .cpp, .cxx, .c, .c++, .h, .hpp, .hh, .hxx або .h++
Репозиторій вихідного кодуgithub.com/cplusplus/draft
Вебсайтisocpp.org

C++ (Сі-плюс-плюс) — мова програмування загального призначення[1] з підтримкою кількох парадигм програмування: об'єктно-орієнтованої, узагальненої, процедурної та ін. Б'ярн Страуструп (англ. Bjarne Stroustrup) почав створювати C++ в AT&T Bell Laboratories (Мюррей-Хілл[en], Нью-Джерсі) у 1979 році. На етапі зародження мова мала назву «Сі з класами». Згодом Страуструп перейменував мову на C++ у 1984 р. Має крпіеея в мові програмування С. Вперше описана міжнародним стандартом ISO/IEC 14882:1998 (C++98), найбільш актуальним же є стандарт ISO/IEC 14882:2020 (C++20).

У 1990-х роках C++ стала однією з найуживаніших мов програмування загального призначення. Мову використовують для системного програмування, розробки прикладного програмного забезпечення, написання драйверів, потужних серверних та клієнтських програм, а також для розробки розважальних програм, наприклад, відеоігор. C++ суттєво вплинула на інші популярні сьогодні мови програмування: C# та Java.

Історія

[ред. | ред. код]

Хронологія

[ред. | ред. код]
Б'ярн Страуструп, творець мови

Історія розвитку мови програмування C++ містить такі ключові події[1]:

  • квітень 1979 — початок роботи над Ci з класами (C with Classes)
  • жовтень 1979 — робоча версія Ci з класами (з CPre компілятором)
  • серпень 1983 — Ci з класами вперше використовується в Bell Labs
  • 1984 — назва C++
  • лютий 1985 — перший зовнішній випуск компілятора C++ — Cfront[en] Relese E (Educational — випуск для навчальних закладів)
  • жовтень 1985 — перший комерційний випуск — Cfront 1.0
  • лютий 1987 — Cfront 1.2
  • грудень 1987 — перший випуск GNU C++ (1.13)
  • 1988 — Перші випуски Oregon Software C++ і Zortech C++
  • червень 1989 — Cfront 2.0
  • 1989 — книга «The Annotated C++ Reference Manual» (ARM); Засновано комітет ANSI C++
  • 1990 — перша технічна зустріч комітету ANSI C++; прийнято шаблони (templates), виняткові ситуації (exceptions); перший випуск Borland C++
  • 1991 — Перша зустріч комітету ISO/IEC; Cfront 3.0 (з шаблонами); книга «The C++ Programming Language» (2-га редакція)
  • 1992 — Перші випуски IBM, DEC, Microsoft C++
  • 1993 — RTTI (Run-time type identification — визначення типу під час виконання) прийнято; простори назв (namespaces) і string (шаблонний за символьним типом) прийнято
  • 1994 — прийнято STL
  • 1997 — остаточне голосування комітету за завершений стандарт
  • 1998 — ратифіковано стандарт ISO/IEC 14882:1998 C++ (так званий C++98)
  • 2003 — ратифіковано стандарт ISO/IEC 14882:2003 C++ (так званий C++03); початок роботи над C++0x

Подальший розвиток описаний в статтях про відповідні версії C++.

В 2023 році, у категорії найпопулярніших мов програмування серед IT-спеціалістів згідно з рейтингом IEEE Spectrum, який охоплював 59 мов програмування, топ-5 виглядав так: Python, Java, C++, C, JavaScript[2].

Історія назви

[ред. | ред. код]

Назва «Сі++» була вигадана Ріком Масситті (Rick Mascitti) і вперше було використана в грудні 1983 року. Раніше, на етапі розробки, нова мова називалася «Сі з класами». Ім'я, що вийшло у результаті, походить від оператора Сі «++» (збільшення значення змінної на одиницю) і поширеному способу присвоєння нових імен комп'ютерним програмам, що полягає в додаванні до імені символу «+» для позначення поліпшень. Згідно зі Страуструпом, «ця назва указує на еволюційну природу змін Ci». Виразом «С+» називали ранішню, не пов'язану з Сі++, мову програмування.

Деякі програмісти на Сі можуть відмітити, що якщо виконуються вирази x=3; y=x++; то в результаті вийде x=4 і y=3, тому що x збільшується тільки після присвоєння його у. Проте якщо другий вираз буде y=++x; то вийде x=4 і y=4. Виходячи з цього, можна зробити висновок, що логічніше було б назвати мову не Сі++, а ++Сі. Проте обидва вирази c++ і ++c збільшують с, а крім того вираз c++ поширеніший.

Педанти також можуть відмітити, що введення мови Сі++ не змінює самого Сі, тому найточнішим ім'ям було б «С+1».

Стандарт C++11

[ред. | ред. код]
Докладніше: C++11

У серпні 2011 року завершилася тривала епопея з прийняттям нового стандарту для мови Сі++. Комітет ISO зі стандартизації C++ одноголосно затвердив специфікацію C++0X як міжнародний стандарт «C++11»[3]. Стандарт C++0X планувалося випустити ще в 2008 році, але його прийняття постійно відкладалося. Більшість представлених в стандарті можливостей вже підтримуються в таких компіляторах, як GCC, IBM C++, Intel C++ і Visual C++. стандартні бібліотеки з підтримкою C++ були реалізовані в рамках проекту Boost.

Новий стандарт розвивався понад 10 років і прийшов на зміну стандартам C++98 і C++03. Відзначається, що якщо відмінності між стандартами C++98 і C++03 були настільки незначними, що їх можна було не помітити, то стандарт C++11 містить низку кардинальних покращень, як самої мови, так і стандартної бібліотеки. За словами Б'ярна Страуструпа, творця C++, C++11 відчувається як нова мова, частини якої краще поєднуються одна з одною. У C++11 високорівневий стиль програмування став природнішим. Крім того, мова стала простішою для вивчення.

Стандарт C++14

[ред. | ред. код]
Докладніше: C++14

Повна назва: «International Standard ISO/IEC 14882:2014(E) Programming Language C++»[4]. C++14 можна розглядати як невелике розширення для C++11, яке в основному містить виправлення помилок і незначні покращення.

Комітет з розробки нового стандарту опублікував чернетку N3690 15 травня 2013[5]. Робочий варіант чернетки N3936 був опублікований 2 березня 2014 року, фінальний період голосування закритий 15 серпня 2014 року, а результат (одноголосне схвалення) оголошений 18 серпня 2014 року. Дата офіційного випуску C++14 — 15 грудня 2014[6].

Оскільки розробка стандарту була досить тривалою, і не було визначено року випуску, в період розробки також мав поширену назву «C++1y», аналогічно до того, як стандарт C++11 до його випуску називали «C++0x»(випуск цієї версії очікували до 2010 року).

Описані нижче можливості мови відповідають чернетці N3797 [Архівовано 11 січня 2020 у Wayback Machine.]. Вони можуть дещо відрізнятися від остаточної версії стандарту [Архівовано 29 січня 2020 у Wayback Machine.].

Стандарт C++17

[ред. | ред. код]
Докладніше: C++17

Стандарт C++17[7][8] дійшов до чорнової версії стандарту в березні 2017 року і, був остаточно затверджена 8 вересня 2017 року[9]. Стандарт офіційно опублікований у грудні 2017[10].

У C++17 було внесено ряд змін в мову, зокрема додано декілька нових бібліотек(<string_view>, <execution>), алгоритмів(std::exclusive_scan, std::for_each) та класів до STL, поліпшено роботу з лямбда-виразами, оновлено бібліотеку <thread> для мультипотокового програмування.

Стандарт C++20

[ред. | ред. код]
Докладніше: C++20

Стандарт C++20 затверджено 4 вересня 2020 року. Стандарт офіційно опублікований у грудні 2020. Ця версія має набагато більше нововведень в порівнянні з C++14 та C++17.

Зокрема, ця версія вводить «концепції», співпрограми, новий оператор порівняння <=> тощо.

Особливості

[ред. | ред. код]

При створенні C++ прагнули зберегти сумісність з мовою С. Більшість програм на С справно працюватимуть і з компілятором C++. C++ має синтаксис, заснований на синтаксисі С (див. список операторів мов С та C++).

Нововведеннями C++ порівняно з С є:

Приклади

[ред. | ред. код]

Приклад програми «Hello, world!»

[ред. | ред. код]

Нижче наведено приклад простої програми на C++, яка виводить на стандартний потік виводу рядок Привіт, світе!.

#include <iostream> // Підключення файлу з функціями та полями імен

using namespace std; // Використання поля імен

int main() // Це головна функція 
{
        cout << "Привіт, світе!" << endl;
        return 0; // Каже те що програма завершилася нормально
}
// - це позначка початку коментаря


Приклад програми зі змінною

[ред. | ред. код]

Після запуску програми на екрані з'явиться повідомлення Скільки вам років?, а курсор буде розміщений в наступному рядку. Там потрібно ввести цілочислове значення та натиснути клавішу Enter. Приклад: вводимо число 25, тоді з'явиться повідомлення Вам 25 років.

   #include <iostream>
   
   using namespace std;
   
   int main() 
   {
            int age; // Змінна для запису віку
            cout << "Скільки вам років?\n";
            cin >> age; // Зчитування віку
            cout << "Вам " << age << " років\n"; // Результат
       
            return 0;
   }

Технічний огляд

[ред. | ред. код]

В 1998 році мова C++ була стандартизована Міжнародною організацією стандартизації під номером 14882:1998 — Мова Програмування C++. Поточний стандарт — C++11, він був прийнятий у 2011 році робочою групою МОС після десятирічної підготовки.

Стандарт C++ на 1998 рік складається з двох основних частин: ядра мови і стандартної бібліотеки. Стандартна бібліотека C++ увібрала в себе бібліотеку шаблонів STL, що розроблялася одночасно із стандартом. Зараз назва STL офіційно не вживається, проте в колах програмістів на C++ ця назва використовується для позначення частини стандартної бібліотеки, що містить визначення шаблонів контейнерів, ітераторів, алгоритмів і функторів.

Стандарт C++ містить нормативне посилання на стандарт Сі від 1990 року і не визначає самостійно ті функції стандартної бібліотеки, які запозичуються із стандартної бібліотеки С.

Поза тим, існує величезна кількість бібліотек C++, котрі не входять в стандарт. У програмах на C++ можна використовувати багато бібліотек С.

Стандартизація визначила мову програмування C++, проте за цією назвою можуть ховатися також неповні, обмежені достандартні варіанти мови. Спочатку мова розвивалася поза формальними рамками, спонтанно, у міру завдань, що ставилися перед ним. Розвиток мови супроводив розвиток кросс-компілятора Cfront. Нововведення в мові відбивалися в зміні номера версії кросс-компілятора. Ці номери версій кросс-компілятора розповсюджувалися і на саму мову.

Актуальний стандарт ухвалений в 2023 році C++23.

Стандартна бібліотека

[ред. | ред. код]

Стандартна бібліотека C++ включає стандартну бібліотеку С з невеликими змінами, які роблять її відповіднішою для мови C++. Інша велика частина бібліотеки C++ заснована на Стандартній Бібліотеці Шаблонів (STL). Вона надає такі важливі інструменти, як контейнери (наприклад, вектори і списки) і ітератори (узагальнені вказівники), що надають доступ до цих контейнерів як до масивів. Крім того, STL дозволяє схожим чином працювати і з іншими типами контейнерів, наприклад, асоціативними списками, стеками, чергами.

Використовуючи шаблони, можна писати узагальнені алгоритми, здатні працювати з будь-якими контейнерами або послідовностями, доступ до членів яких забезпечують ітератори.

Так само як і в С, можливості бібліотек активізуються використанням директиви #include для включення стандартних файлів. Всього в стандарті C++ визначено 50 таких файлів.

STL до включення в стандарт C++ була сторонньою розробкою, на початку — фірми HP, а потім SGI. Стандарт мови не називає її «STL», оскільки ця бібліотека стала невіддільною частиною мови, проте багато людей досі використовують цю назву, щоб відрізняти її від решти частини стандартної бібліотеки (потоки введення/виведення (Iostream), підрозділ Сі тощо). Проект під назвою STLport [Архівовано 7 червня 2018 у Wayback Machine.], заснований на SGI STL, здійснює постійне оновлення STL, IOstream і рядкових класів. Деякі інші проєкти також займаються розробкою приватних застосувань стандартної бібліотеки для різних конструкторських завдань. Кожен виробник компіляторів C++ обов'язково поставляє якусь реалізацію цієї бібліотеки, оскільки вона є дуже важливою частиною стандарту і широко використовується.

Причиною успіху STL, зокрема її вхід до стандартної бібліотеки C++, була націленість на широке коло завдань і узагальнена структура. В цьому сенсі, близькою за духом STL на сьогодні є бібліотека Boost. Boost теж є бібліотекою загального застосування і теж впливає на формування стандартної бібліотеки C++.

Нові можливості в порівнянні з С

[ред. | ред. код]

Мова Сі++ багато в чому є надмножиною С. Нові можливості C++ включають оголошення у вигляді виразів, перетворення типів у вигляді функцій, оператори new і delete, тип bool, посилання, розширене поняття константності та змінності, функції, що підставляються, аргументи за умовчанням, перевизначення, простори імен, класи (включаючи і всі пов'язані з класами можливості, такі як успадкування, функції-члени (методи), віртуальні функції, абстрактні класи і конструктори), перевизначення операторів, шаблони, оператор ::, обробку винятків, динамічну ідентифікацію і багато що інше. Сі++ є також мовою строгого типування і накладає більше вимагань щодо дотримання типів, порівняно з Сі.

У C++ з'явилися коментарі у вигляді подвійної косої риски («//»), які були в попереднику С — мові BCPL.

Деякі особливості C++ пізніше були перенесені в С, наприклад ключові слова const і inline, оголошення в циклах for і коментарі в стилі C++ («//»). У пізніших реалізаціях С також були представлені можливості, яких немає в C++, наприклад макроси vararg і покращена робота з масивами-параметрами.

Не об'єктно-орієнтовані можливості

[ред. | ред. код]

В цьому розділі описуються можливості, безпосередньо не пов'язані з об'єктно-орієнтованим програмуванням (ООП). Багато які з них, проте, особливо важливі у поєднанні з ООП.

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

Як видно, однією з принципових відмінностей операторів new та delete від своїх попередників, malloc і free, є обов'язковість ініціалізації об'єктів, пам'ять під які було призначено. Іншою відмінністю є те, що загальна реалізація (тобто визначена за умовчанням) оператора new не повертає нульове значення вказівника у випадку помилки призначення пам'яті (наприклад з причини її браку). Натомість new кидає виняток (наприклад, std::bad_alloc в ситуації браку пам'яті). Так само як і для free, якщо значенням аргументу оператора delete є 0, ані звільнення пам'яті, ані деініціалізація не відбувається (при цьому, подібна ситуація не вважається помилковою).

  • Функції можуть приймати аргументи за посиланням. Наприклад, функція void f(int& x) {x=3;} присвоює своєму аргументу значення 3. Функції також можуть повертати результат за посиланням, і посилання можуть бути поза всяким зв'язком з функціями. Наприклад, {double& b=a[3]; b=sin(b);} еквівалентно а[3]=sin(а[3]);. Посилання певною мірою схожі з вказівниками, з такими особливостями: при описі посилання ініціалізувалися вказівником на наявне значення даного типу; посилання довічно указує на одну і ту ж адресу; при зверненні до посилання операція читання пам'яті за адресою посилання проводиться автоматично. На відміну від вказівників, посилання не може бути константним саме по собі, однак може посилатися на константний об'єкт. Наприклад, int const & const ref = a[3]; — є некоректним, з точки зору C++, виразом, на відміну від int const * const ref = &a[3];  своєю чергою, і int const & ref = a[3];, і int const * ref = &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);

Оператори нічим не відрізняються від (інших) функцій. Не можна описувати оператори над зумовленими типами (скажімо, перевизначати множення чисел); не можна вигадувати нові операції, яких немає в C++ (скажімо **); арність (кількість параметрів) і пріоритет операцій зберігається (скажімо, у виразі a+b*c спочатку виконуватиметься множення, а потім складання, до яких би типів не належали а, b і с.) Можна перевизначити оператор [] (з одним параметром) і () (з будь-яким числом параметрів).

  • Додані простори імен namespace. Наприклад, якщо написати
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. Якщо ми в якійсь одиниці трансляції (файл основного коду, наприклад myFile.cpp, та всі заголовкові файли що він включає) хочемо звертатися до них безпосередньо, ми можемо написати

using namespace Foo;

Або ж

 using Foo::T;

Також можна створити синонім на вже наявний простір імен (наприклад, аби уникнути постійно повторювати довгу назву простору)

namespace MyVeryOwnNameSpace {
   typedef std::vector< std::string > StringTable;
}
namespace My = MyVeryOwnNameSpace;

Простори імен потрібні, щоб не виникало колізій між пакетами, що мають однакові імена глобальних змінних, функцій і типів. Спеціальним випадком є безіменний простір імен

 namespace {
    ...
 }

Всі імена, описані в ньому, доступні в поточній одиниці трансляції і більше ніде, неначебто ми до кожного опису приписали static.

  • Доданий новий тип bool, що має значення true і false. Операції порівняння повертають тип bool. Вирази в дужках після if, while приводяться до типу bool.
  • // означає, що вся частина рядка, що залишилася, є коментарем.
  • Додані шаблони (template). Наприклад, template<class T> T Min(T x, T у) {return x<y?x:y;} визначає функцію Min для будь-яких типів. Шаблони можуть задавати не тільки функції, але і типи. Наприклад, template<class T> struct Array{int len; T* val;}; визначає масив значень будь-якого типу, після чого ми можемо писати Array<float> x;
  • Введена стандартна бібліотека шаблонів (STL, англ. Standard Template Library), що визначає шаблони і функції для векторів (одновимірних масивів довільної довжини), множин, асоціативних масивів (map), списків, символьних рядків, потоків введення-виводу та інші шаблони і функції.
  • Якщо описана структура, клас (про класи див. нижче), Шаблон:Union type (union) або перерахування (enum), її ім'я є ім'ям типу, наприклад:
 struct Time{int hh,mm,ss;};
 Time t1, t2;
  • Усередині структури або класу можна описувати нові типи, як через typedef, так і через опис інших структур або класів. Для доступу до таких типів поза структурою або класом, до імені типу додається ім'я структури і дві двокрапки:
 struct S {typedef int** T; T x;}; S::T у;

Об'єктно-орієнтовані особливості мови

[ред. | ред. код]

C++ додає до С об'єктно-орієнтовані можливості. Він вводить класи, які забезпечують три найважливіші властивості ООП: інкапсуляцію, успадкування і поліморфізм.

Проблеми старого підходу

[ред. | ред. код]

В мові C основним способом організації даних були структури. Структура складається з набору полів, які ніяк не захищені. Якщо елементи структури мають змінну довжину, їх представляють як вказівники. Виділення і звільнення пам'яті під ці вказівники роблять вручну. Наприклад, одновимірний масив змінної довжини в мові C з перевіркою меж може бути представлений так:

struct Array {
    double* val;
    int len;
};

void FreeArray (const struct Array*);
void AllocArray (const struct Array*, int len);
double Elem (const struct Array*, int i);
void ChangeElem (const struct Array*, int i, double x);

Така реалізація має такі недоліки:

  • Необхідно викликати FreeArray і AllocArray. Програміст може забути викликати одну з цих функцій, або викликати її дуже рано/запізно, або двічі, або з вказівником на неправильний масив. Все це приводить до помилок, що важко виявити.
  • Немає ніякого способу перешкодити програмістам створювати і інші функції для роботи зі структурою Array. Ці функції можуть робити з полями len і val будь-що.
  • Немає ніякого способу перешкодити програмістам безпосередньо міняти поля len і val.
  • Присвоєння об'єктів типу struct Array призведе до того, що їхні поля val указуватимуть на одну і ту ж область пам'яті. Засобами мови Сі відсутня можливість заборонити присвоєння, ні змінити таку поведінку.

Натомість, у мові C++, завдяки наявності засобів об'єктно-орієнтованого програмування, є можливість уникнути цих недоліків.

Інкапсуляція

[ред. | ред. код]

Основним способом організації інформації в C++ є класи. На відміну від типу, структура (struct) мови С, що складається тільки з полів, клас (class) C++ складається з полів і функцій-членів або методів (англ. member functions). Поля бувають публічними (public), захищеними (protected) і приватними (private). У C++ тип структура аналогічний типу клас, відмінність в тому, що за умовчанням поля і функції-члени у структури публічні, а у класу — приватні.

З публічними полями можна робити зовні класу все, що завгодно. До захищених і приватних полів не можна звертатися ззовні класу, щоб не порушити цілісність даних класу. Спроба такого звернення викличе помилку компіляції. До таких полів можуть звертатися тільки функції-члени класу (а також так звані функції-друзі і функції-члени класів-друзів; про поняття друзів в C++ дивись нижче.) Поза тілом функцій-членів (а також друзів) захищені і власні поля недоступні навіть для читання. Такий захист полів називається інкапсуляцією.

Використовуючи інкапсуляцію, автор класу може захистити свої дані від некоректного використання. Крім того, вона замислювалася для полегшення сумісної розробки класів. Малося на увазі, що зміна способу зберігання даних, якщо вони оголошені як захищені або приватні, не вимагає відповідних змін в класах, які використовують змінений клас. Наприклад, якщо в старій версії класу дані зберігалися у вигляді лінійного списку, а в новій версії — у вигляді дерева, ті класи, які були написані до зміни формату зберігання даних, переписувати не буде потрібно, якщо дані були приватними або захищеними (у останньому випадку — якщо використовуючи класи не були класами-нащадками), оскільки жоден з них цих класів не міг би безпосередньо звертатися до даних, а тільки через стандартні функції, які в новій версії мають вже коректно працювати з новим форматом даних. Навіть оператор доступу operator [] може бути визначений як така стандартна функція.

Функції-члени, як і поля, можуть бути публічними, захищеними і приватними. Публічні функції може викликати будь-хто, а захищені і власні — тільки функції-члени і друзі.

Використовуючи інкапсуляцію, структуру Array з попереднього розділу можна переписати таким чином:

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();

Тут масив а має 4 публічних функції-члена і 2 захищених поля. Описувач inline означає, що замість виклику функції її код підставляється в точку виклику, що розв'язує проблему неефективності.

Опис функцій в тілі класу

[ред. | ред. код]

В тілі класу можна вказати тільки заголовок функції, а можна описати всю функцію. У другому випадку вона вважається вбудованою (inline), наприклад:

class Array {
public:
    void Alloc(int _len) 
    {
            if (len > 0) 
                    Free(); 
            val = new double[len = _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 — деструктор. Конструктор копіювання (англ. copy constructor) 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);

І далі

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);
};

Нащадок  — це більш ніж базовий клас, тому він може використовуватися скрізь, де використовується базовий клас, але не навпаки.

Успадкування буває публічним, захищеним і приватним. При публічному успадкуванні, публічні і захищені члени базового класу зберігають свій статус, а до приватних не можуть звертатися навіть функції-члени нащадка. Захищене успадкування відрізняється тим, що при нім публічні члени базового класу є захищеними членами нащадка. При приватному успадкуванні, до жодного члена базового класу навіть функції-члени нащадка права звертатися не мають. Як правило, публічне успадкування зустрічається значно частіше за інші.

Клас може бути нащадком декількох класів. Це називається множинним успадкуванням. Такий клас володіє полями і функціями-членами всіх його предків. Наприклад, клас FlyingCat може бути нащадком класів Cat і FlyingAnimal.

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

Поліморфізм

[ред. | ред. код]

Мова програмування C++ підтримує як звичайний динамічний поліморфізм через механізм віртуальних методів та успадкування, так і статичний поліморфізм на основі шаблонів (наприклад, зокрема, через так звану ідіому дивно рекурсивного шаблону)[11]. При чому, обидва механізми можливо поєднувати для втілення потрібних архітектурних шаблонів (наприклад, домішки)[12].

Також елементи поліморфізму присутні в механізмах перевантаження операторів та функцій, які підтримує ця мова програмування[13].

Поява концептів у версії C++20 також спростила і роботу зі статичним поліморфізмом[14][15].

Приклад коду з використанням засобів динамічного поліморфізму наведений нижче.

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() залежно від виду фігури.

Чисто віртуальною функцією називається функція-член, яка не визначена в базовому класі, а тільки в нащадках:

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

Ось це = 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 вкладений клас не має прав доступу до закритих членів обхопного класу і не може бути оголошений його другом (це виходить з визначення терміну друг як нечлена класу). Проте, багато розповсюджених компіляторів порушують обидва ці правила (може, зважаючи на сукупну дивність цих правил).

Переваги та недоліки

[ред. | ред. код]

Переваги мови C++

[ред. | ред. код]
  • Стандартизація. C++ визначається міжнародним стандартом, а отже не контролюється якоюсь одною фірмою чи людиною.
  • Швидкодія. Швидкість роботи програм на C++ практично не поступається програмам на С, хоча програмісти отримали в свої руки нові можливості і нові засоби.
  • Ефективність. Рішення розроблені на C++ можуть використовувати мінімальну необхідну кількість ресурсів таких як пам'ять, ЦП, енергія та інші.
  • Масштабованість. На мові C++ розробляють програми для найрізноманітніших платформ і систем, які варіюються за розміром від кількох до мільйонів рядочків коду.
  • Можливість роботи на низькому рівні з пам'яттю, адресами, портами. (Що, при необережному використанні, може легко перетворитися на недолік.)
  • Можливість створення узагальнених алгоритмів для різних типів даних, їхня спеціалізація, і обчислення на етапі компіляції, з використанням шаблонів.
  • Підтримуються різні стилі та технології програмування, включаючи традиційне процедурне програмування, ООП, узагальнене програмування, метапрограмування (шаблони, макроси).

Недоліки мови C++

[ред. | ред. код]
  • Наявність безліч можливостей, що порушують принципи типобезпеки приводить до того, що в C++ програми може легко закрастися важковловна помилка. Замість контролю з боку компілятора розробники вимушені дотримуватися вельми нетривіальних правил кодування. По суті, ці правила обмежують C++ рамками якоїсь безпечнішої підмови. Більшість проблем типобезпеки C++ успадкована від С, але важливу роль в цьому питанні грає і відмова автора мови від ідеї використовувати автоматичне управління пам'яттю (наприклад, збірку сміття). Так візитною карткою C++ стали вразливості типу «переповнювання буфера».
  • Погана підтримка модульності. Підключення інтерфейсу зовнішнього модуля через препроцесорну вставку заголовного файлу (#include) серйозно уповільнює компіляцію, при підключенні великої кількості модулів. Для усунення цього недоліку, багато компіляторів реалізують механізм прекомпіляції заголовних файлів (англ. Precompiled Headers).
  • Недостача інформації про типи даних під час компіляції (CTTI).
  • Мова C++ є складною для вивчення і для компіляції.
  • Деякі перетворення типів неінтуїтивні. Зокрема, операція над беззнаковим і знаковим числами видає беззнаковий результат.
  • Препроцесор C++ (успадкований від C) дуже примітивний. Це приводить з одного боку до того, що з його допомогою не можна (або важко) здійснювати деякі завдання метапрограмування, а з іншого, внаслідок своєї примітивності, він часто приводить до помилок і вимагає багато дій з обходу потенційних проблем. Деякі мови програмування (наприклад, Scheme і Nemerle) мають набагато могутніші і безпечніші системи метапрограмування (також звані макросами, але макроси С/C++ вони мало нагадують).
  • З кінця 1990-х в спільноті C++ набуло поширення так зване метапрограмування на базі шаблонів. По суті, воно використовує особливості шаблонів C++ в цілях реалізації на їхній базі інтерпретатора примітивної функціональної мови програмування, що виконується під час компіляції. Сама по собі ця можливість вельми приваблива, але, внаслідок вищезгаданого, такий код вельми важко сприймати і зневаджувати. Мови Lisp/Scheme, Nemerle і деякі інші мають могутніші і водночас простіші для сприйняття підсистеми метапрограмування. Крім того, в мові D реалізована порівнянна за потужністю, але значно простіша в застосуванні підсистема шаблонного метапрограмування.
  • Хоча декларується, що C++ мультипарадигмова мова, реально в мові відсутня підтримка функціонального програмування. Частково, даний пропуск усувається різними бібліотеками (Loki, Boost) що використовують засоби метапрограмування для розширення мови функціональними конструкціями (наприклад, підтримкою лямбд/анонімних методів), але якість подібних рішень значно поступається якості вбудованих у функціональні мови рішень. Такі можливості функціональних мов, як зіставлення зі зразком взагалі украй складно емулювати засобами метапрограмування.

Критика мови C++

[ред. | ред. код]

C++ успадкувала багато проблем мови C:

  • Операція присвоювання позначається як =, а операція порівняння як == . Їх легко сплутати, і така конструкція буде синтаксично правильною, але приведе до важковловної помилки. Особливо часто це відбувається в операторах if і while, наприклад, програміст може написати if (i=0) замість if (i==0) (Разом з тим, основна маса компіляторів видає в таких випадках попередження.) Уникнути помилки такого типу можна, якщо писати всі операції порівняння у такому вигляді: if (0==i). До того ж багато мов (Бейсик, Паскаль) використовують символ "=" саме в операціях порівняння.
  • Операції присвоювання (=), інкрементації (++), декрементації (--) та інші повертають значення. У поєднанні з великою кількістю операцій це дозволяє, але не зобов'язує, програміста створювати код, що важко читається. З іншого боку, один з основних принципів мов C і C++ — дозволяти програмістові писати в будь-якому стилі, а не нав'язувати «хороший» стиль. До того ж це іноді дозволяє компілятору створювати оптимальніший код.
  • Макроси (#define) є потужним, але небезпечним засобом. У мові C++, на відміну від C, необхідність в небезпечних макросах з'являється значно рідше завдяки шаблонам і вбудованим функціям. Але в успадкованих стандартних С-бібліотеках багато потенційно небезпечних макросів.

Дехто вважає недоліком мови C++ відсутність системи збірки сміття. З іншого боку, в C++ є достатньо засобів (класи з конструкторами і деструкторами, стандартні шаблони, передача параметрів за посиланням), що дозволяють майже виключити використання небезпечних вказівників. А відсутність вбудованої збірки сміття дозволяє користувачеві самому вибрати стратегію управління ресурсами. Крім того, автоматична збірка сміття серйозно уповільнює роботу програми, і це недолік там, де продуктивність є критично важливою.

Порівняння C++ з мовами Java і C#

[ред. | ред. код]

Мова C++ з появою перших трансляторів знайшла відразу ж дуже широке розповсюдження, на ній було створено величезну кількість програм і застосунків. У міру накопичення досвіду створення великих програмних систем спливли недоліки, які спонукали до пошуку альтернативних рішень. Таким альтернативним рішенням стала мова Java, яка в деяких галузях стала конкурувати у популярності з C++, а фірма Майкрософт запропонувала мову C# як нову мову, що розвиває принципи C++ і що використовує переваги мови Java. Надалі з'явилася мова Nemerle, що об'єднує переваги C# з можливістю функціонального програмування. Останнім часом з'явилася спроба об'єднання ефективності C++, безпеки і швидкості розробки, як в Java і C# — була запропонована мова D, яка поки не отримала широкого визнання.

Мова Java має такі особливості, яких немає в мові C++ :

  • Java є типобезпечною мовою. Типобезпека гарантує відсутність у програмах помилок, які важко знайти і які пов'язані з неправильною інтерпретацією пам'яті комп'ютера. Це робить процес розробки надійнішим і передбачуваним, а отже швидшим. Так само це дозволяє залучати до розробки програмістів, що мають меншу кваліфікацію і мати великі групи розробників.
  • Java-код компілюється спочатку не в машинний код, а в певний проміжний код, який надалі інтерпретується або компілюється, тоді як багато C++ компіляторів орієнтовані на компіляцію в машинний код заданої платформи.
  • У мові Java є чіткі певні стандарти на введення-виведення, графіку, геометрію, діалог, доступ до баз даних і інших типових застосувань. Завдяки цим особливостям, застосунки на Java мають значно кращу кросплатформовість, ніж C++, і часто, бувши написані для певного комп'ютера й операційної системи, працюють під іншими системами без змін. Програмісти, що пишуть на мові Java, не залежать від пакунків, нав'язаних розробниками компіляторів на дане конкретне середовище, що різко спрощує портування програм.
  • У мові Java реалізовано повноцінне збирання сміття, якого немає в C++. Немає в C++ і засобів перевірки правильності вказівників. З іншого боку, C++ володіє набором засобів (конструктори і деструктори, стандартні шаблони, посилання), що дозволяють майже повністю виключити виділення і звільнення пам'яті вручну і небезпечні операції з вказівниками. Проте таке виключення вимагає певної культури програмування, тоді як в мові Java воно реалізується автоматично.
  • Мова Java є чисто об'єктно-орієнтованою, тоді як C++ підтримує як об'єктно-орієнтоване, так і процедурне програмування.
  • В C++ відсутня повноцінна інформація про типи під час виконання RTTI. Цю можливість можна було б реалізувати в C++, маючи повну інформацію про типи під час компіляції CTTI.
  • У C++ є можливість введення призначеного для користувача синтаксису за допомогою #define, що може привести до того, що модулі у великих пакетах програм стають сильно пов'язані один з одним. Це різко знижує надійність пакетів і можливість організації розділених модулів. З іншого боку, C++ надає достатньо засобів (константи, шаблони, вбудовані функції) для того, щоб практично повністю виключити використання #define.

Ці відмінності призводять до запеклих суперечок між прихильниками двох мов про те, яка мова найкраща. Прихильники Java вважають ці особливості перевагами; прихильники C++ вважають, що у багатьох випадках ці особливості є недоліками, зокрема

  • Ціною переносності є вимога наявності на комп'ютері віртуальної Java-машини, що приводить до уповільнення обчислень і практичної неможливості використання нових можливостей апаратної архітектури.
  • Автоматичне збирання сміття призводить до втрати контролю зі сторони розробника (що можна би було використати для покращення швидкодії чи ефективності використання пам'яті).
  • Стандарти на графіку, доступ до баз даних тощо є недоліком, якщо програміст хоче визначити свій власний стандарт.
  • Вказівники у багатьох випадках є могутнім, або навіть необхідним засобом, а їхнє безконтрольне використання небезпечне лише в невмілих руках.
  • Підтримка процедурного програмування є корисною.

Далеко не всі програмісти є прихильниками однієї з мов. На думку деяких програмістів, Java і C++ не є конкурентами, тому що мають різні галузі застосування. Інші вважають, що вибір мови для багатьох завдань є питанням особистого смаку. Деякі архітектори програмних рішень базують свій вибір мови програмування для того чи іншого рішення на основі сильних та слабких сторін мов програмування так загальному контексті розробки рішення.

Примітки

[ред. | ред. код]
  1. а б Evolving a language in and for the real world: C++ 1991—2006, Bjarne Stroustrup (PDF). Архів (PDF) оригіналу за 20 листопада 2007. Процитовано 20 листопада 2007.
  2. IEEE Spectrum опублікувала рейтинг найпопулярніших мов програмування 2023 року. // Автор: Артем Житкевич. 30.08.2023
  3. Спецификация C++0X принята в качестве международного стандарта C++11. Архів оригіналу за 27 листопада 2011. Процитовано 15 серпня 2011.
  4. ISO/IEC 14882:2014 — Information technology — Programming languages — C++. ISO. 14 January 2014. Архів оригіналу за 29 січня 2017. Процитовано 29 січня 2020.
  5. Committee Draft, Standard for Programming Language C++ (PDF). ISO. 15 мая 2013. Архів оригіналу (PDF) за 21 січня 2022. Процитовано 29 січня 2020.
  6. Sutter, Herb (18 серпня 2014), We have C++14!, архів оригіналу за 19 серпня 2014, процитовано 18 серпня 2014
  7. N4661 Editors' Report -- Programming Languages -- C++. 21 березня 2017. Архів оригіналу за 4 лютого 2020. Процитовано 21 березня 2017.
  8. ISO/IEC DIS 14882: Programming Languages — C++ (PDF). Архів оригіналу (PDF) за 25 березня 2017. {{cite web}}: Cite має пустий невідомий параметр: |df= (довідка) [Архівовано 2017-03-25 у Wayback Machine.]
  9. C++17 is formally approved. Архів оригіналу за 29 січня 2020. Процитовано 29 січня 2020.
  10. ISO/IEC 14882:2017. Архів оригіналу за 17 травня 2013. Процитовано 29 січня 2020.
  11. Rainer Grimm (25 лютого 2022). More about Dynamic and Static Polymorphism. Modernes C++.
  12. Michael Afanasiev (3 серпня 2016). Combining Static and Dynamic Polymorphism with C++ Mixin classes.
  13. Kateryna Bondarenko (6 травня 2019). Static Polymorphism in C++.
  14. Replacing CRTP Static Polymorphism With Concepts. Fluent C++. 11 вересня 2020.
  15. [C++] Static, Dynamic Polymorphism, CRTP and C++20’s Concepts. Coding with Thomas.

Посилання

[ред. | ред. код]

Онлайн-курси

[ред. | ред. код]

Статті та книги

[ред. | ред. код]

Див. також

[ред. | ред. код]