C++

Материал из ЭНЭ
Перейти к: навигация, поиск

Шаблон:Карточка языка программирования

Си++ (англ. C++) — компилируемый строго типизированный язык программирования общего назначения. Поддерживает разные парадигмы программирования: процедурную, обобщённую, функциональную; наибольшее внимание уделено поддержке объектно-ориентированного программирования.

В 1990-х годах язык стал одним из наиболее широко применяемых языков программирования общего назначения.

При создании Си++ стремились сохранить совместимость с языком Си. Большинство программ на Си будут исправно работать и с компилятором Си++. Си++ имеет синтаксис, основанный на синтаксисе Си.

Нововведениями Си++ в сравнении с Си являются:

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

Название «Си++» происходит от Си, в котором унарный оператор ++ обозначает приращение.

Дизайн Си++

В книге «Дизайн и развитие C++» (ISBN 0-201-54330-3) Бьёрн Страуструп описывает некоторые правила, которые он использовал при проектировании Си++. Знание этих правил может помочь понять, почему Си++ такой, каким он стал. Вот некоторые из этих правил (подробности можно найти в «Дизайне и развитии C++»).

Си++:

  • разработан как универсальный язык со статическими типами данных, эффективностью и переносимостью языка Си.
  • разработан так, чтобы непосредственно и всесторонне поддерживать множество стилей программирования (процедурное программирование, абстракцию данных, объектно-ориентированное программирование и обобщённое программирование).
  • разработан так, чтобы давать программисту свободу выбора, даже если это даёт ему возможность выбирать неправильно.
  • разработан так, чтобы максимально сохранить совместимость с Си, тем самым делая возможным лёгкий переход от программирования на Си.
  • избегает таких особенностей, которые зависят от платформы или не являются универсальными.
  • не накладывает никакой избыточной нагрузки на программу, не использующую какие-либо возможности.
  • разработан так, чтобы не требовать слишком усложнённой среды программирования.

История

Страуструп начал работать над «Си с классами» в 1979 году. Идея создания нового языка берёт начало от опыта программирования Страуструпа для диссертации. Он обнаружил, что язык моделирования Симула (Simula) имеет такие возможности, которые были бы очень полезны для разработки большого программного обеспечения, но работает слишком медленно. В то же время язык BCPL достаточно быстр, но слишком близок к языкам низкого уровня и не подходит для разработки большого программного обеспечения. Страуструп начал работать в «Bell Labs» над задачами теории очередей (в приложении к моделированию телефонных вызовов). Попытки применения существующих в то время языков моделирования оказались неэффективными. Вспоминая опыт своей диссертации, Страуструп решил дополнить язык Си (преемник BCPL) возможностями, имеющимися в языке Симула. Язык Си, будучи базовым языком системы UNIX, на которой работали компьютеры «Bell» является быстрым, многофункциональным и переносимым. Страуструп добавил к нему возможность работы с классами и объектами. В результате, практические задачи моделирования оказались доступными для решения как с точки зрения времени разработки (благодаря использованию Симула-подобных классов) так и с точки зрения времени вычислений (благодаря быстродействию Си). В начале в Си были добавлены классы (с инкапсуляцией), производные классы, строгая проверка типов, inline-функции и аргументы по умолчанию.

Разрабатывая Си с классами (позднее Си++), Страуструп также написал программу Cfront, транслятор, перерабатывающий исходный код Си с классами в исходный код простого Си. Новый язык, неожиданно для автора, приобрел большую популярность среди коллег и вскоре Страуструп уже не мог лично поддерживать его, отвечая на тысячи вопросов.

В 1983 г. произошло переименование языка из Си с классами в Си++ по соображениям маркетинга. Кроме того, в него были добавлены новые возможности, такие как виртуальные функции, перегрузка функций и операторов, ссылки, константы, пользовательский контроль над управлением свободной памятью, улучшенная проверка типов и новый стиль комментариев (//). Его первый коммерческий выпуск состоялся в октябре 1985 г.. В 1985 г. вышло первое издание «Языка программирования Си++», обеспечивающее первое описание этого языка, что было чрезвычайно важно из-за отсутствия официального стандарта. В 1989 г. состоялся выход Си++ версии 2.0. Его новые возможности включали множественное наследование, абстрактные классы, статические функции-члены, функции-константы и защищённые члены. В 1990 г. вышло «Комментированное справочное руководство по C++», положенное впоследствии в основу стандарта. Последние обновления включали шаблоны, исключения, пространства имён, новые способы приведения типов и булевский тип.

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

После многих лет работы совместный комитет ANSI-ISO стандартизировал Си++ в 1998 г. (ISO/IEC 14882:1998). В течение нескольких лет после официального выхода стандарта комитет обрабатывал сообщения об ошибках и в итоге выпустил исправленную версию стандарта Си++ в 2003 году.

Никто не обладает правами на язык Си++, он является свободным. Однако к сожалению, сам документ стандарта языка недоступен бесплатно.

С удовлетворением подводя итоги своей деятельности, создатель языка недавно заметил: «В начале своей деятельности я мечтал, что настанет время, когда компьютером будет так же легко пользоваться, как и телефоном. Мне удалось дожить до осуществления моей мечты. Теперь, прежде чем первый раз позвонить по телефону новой конструкции я должен изучить 500-страничную инструкцию.»

Технический обзор

В 1998 году язык Си++ был стандартизован Международной организацией стандартизации под номером 14882:1998 — Язык Программирования Си++. В настоящее время рабочая группа МОС работает над новой версией стандарта под кодовым названием C++09 (ранее известный как C++0X), который должен выйти в 2009 году.

Стандарт Си++ на 1998 год состоит из двух основных частей: ядра языка и стандартной библиотеки.

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

Стандарт Си++ содержит нормативную ссылку на стандарт Си от 1990 года и не определяет самостоятельно те функции стандартной библиотеки, которые заимствуются из стандартной библиотеки Си.

Кроме того, существует огромное количество библиотек Си++, не входящих в стандарт. В программах на Си++ можно использовать многие библиотеки Си.

Стандартизация определила язык программирования Си++, однако за этим названием могут скрываться также неполные, ограниченные достандартные варианты языка. В первое время язык развивался вне формальных рамок, спонтанно, по мере ставившихся перед ним задач. Развитию языка сопутствовало развитие кросс-компилятора Cfront. Новшества в языке отражались в изменении номера версии кросс-компилятора. Эти номера версий кросс-компилятора распространялись и на сам язык, но применительно к настоящему времени речь о версиях языка Си++ не ведут.

Новые возможности по сравнению с Си

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

В Си++ появились комментарии в виде двойной косой черты («//»), которые были в предшественнике Си — языке BCPL.

Некоторые особенности Си++ позднее были перенесены в Си, например ключевые слова const и inline, объявления в циклах for и комментарии в стиле Си++ («//»). В более поздних реализациях Си также были представлены возможности, которых нет в Си++, например макросы vararg и улучшенная работа с массивами-параметрами.

Необъектно-ориентированные возможности

В этом разделе описываются возможности, непосредственно не связанные с объектно-ориентированным программированием (ООП). Многие из них, однако, особенно важны в сочетании с ООП.

  • Описатель inline означает, что функция является хорошим кандидатом на оптимизацию, при которой в местах обращения к функции компилятор вставит тело этой функции, а не код вызова. Пример: inline double Sqr(double x) {return x*x;}
  • Вместо функций malloc и free (которые оставлены только для обратной совместимости), введены новые операции new и delete. Если T — произвольный тип, то
    • new T выделяет память, достаточную для размещения одного объекта типа Т, возможно, инициализирует объект в этой памяти, и возвращает указатель типа Т*.
    • new T[n] выделяет память, достаточную для размещения 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);

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

  • Добавлены пространства имён 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. Если мы в каком-то файле хотим обращаться к ним непосредственно, мы можем написать

using namespace Foo;

Или же

using namespace Foo::T;

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

namespace {
   ...
}

Все имена, описанные в нём, доступны в текущем файле и больше нигде, как если бы мы к каждому описанию приписали static.

  • Добавлен новый тип bool , имеющий значения true и false . Операции сравнения возвращают тип bool . Выражения в скобках после if , while приводятся к типу bool .
  • // означает, что вся оставшаяся часть строки является комментарием.
  • Добавлены шаблоны (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;

Стандартная библиотека

Стандартная библиотека Си++ включает стандартную библиотеку Си с небольшими изменениями, которые делают её более подходящей для языка Си++. Другая большая часть библиотеки Си++ основана на Стандартной Библиотеке Шаблонов (STL). Она предоставляет такие важные инструменты, как контейнеры (например, векторы и списки) и итераторы (обобщённые указатели), предоставляющие доступ к этим контейнерам как к массивам. Кроме того, STL позволяет сходным образом работать и с другими типами контейнеров, например, ассоциативными списками, стеками, очередями.

Используя шаблоны, можно писать обобщённые алгоритмы, способные работать с любыми контейнерами или последовательностями, определяемыми итераторами.

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

STL до включения в стандарт Си++ была сторонней разработкой, в начале — фирмы HP, а затем SGI. Стандарт языка не называет её «STL», так как эта библиотека стала неотъемлемой частью языка, однако многие люди до сих пор используют это название, чтобы отличать её от остальной части стандартной библиотеки (потоки ввода/вывода (Iostream), подраздел Си и др.).

Проект под названием STLport, основанный на SGI STL, осуществляет постоянное обновление STL, IОstream и строковых классов. Некоторые другие проекты также занимаются разработкой частных применений стандартной библиотеки для различных конструкторских задач. Каждый производитель компиляторов Си++ обязательно поставляет какую-либо реализацию этой библиотеки, так как она является очень важной частью стандарта и широко используется.

Объектно-ориентированные особенности языка

Си++ добавляет к Си объектно-ориентированные возможности. Он вводит классы, которые обеспечивают три самых важных свойства ООП: инкапсуляцию, наследование и полиморфизм.

Проблемы старого подхода

В языке 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. Программист может забыть вызвать одну из этих функций, или вызвать её слишком рано/поздно, или дважды, или с указателем на неправильный массив. Всё это приводит к труднообнаруживаемым ошибкам.
  • Функции Elem и ChangeElem медленны.
  • Нет никакого способа помешать программистам создавать и другие функции для работы со структурой Array. Эти функции могут делать с полями len и val всё что угодно.
  • Нет никакого способа помешать программистам непосредственно менять поля len и val.
  • Присваивание объектов типа struct Array приведёт к тому, что их поля val будут указывать на одну и ту же область памяти. Нет никакого способа ни запретить присваивание, ни изменить такое поведение.

Язык Си++ , используя ООП, устраняет все эти проблемы.

Инкапсуляция

Основным способом организации информации в Си++ являются классы. В отличие от типа структура (struct) языка Си, состоящей только из полей, класс (class) Си++ состоит из полей и функций-членов (member functions). Поля бывают публичными (public), защищёнными (protected) и собственными (приватными, private). В Си++ тип структура аналогичен типу класс, отличие в том, что по умолчанию поля и функции-члены у структуры публичные, а у класса - собственные.

С публичными полями можно делать снаружи класса всё, что угодно. К защищённым и собственным полям нельзя обращаться извне класса, чтобы не нарушить целостность данных класса. Попытка такого обращения вызовет ошибку компиляции. К таким полям могут обращаться только функции-члены класса (а также так называемые функции-друзья и функции-члены классов-друзей; о понятии друзей в C++ см. ниже.) Вне тела функций-членов (а также друзей) защищённые и собственные поля недоступны даже для чтения. Такая защита полей называется инкапсуляцией.

Используя инкапсуляцию, автор класса может защитить свои данные от некорректного использования. Кроме того, она задумывалась для облегчения совместной разработки классов. Имелось ввиду, что при изменении способа хранения данных, если они объявлены как защищенные или собственные, не требуется соответствующих изменений в классах, которые используют измененный класс. Например, если в старой версии класса данные хранились в виде линейного списка, а в новой версии - в виде дерева, те классы, которые были написаны до изменения формата хранения данных, переписывать не потребуется, если данные были приватными или защищенными (в последнем случае - если использующие классы не были классами-наследниками), так как ни один из них этих классов не мог бы напрямую обращаться к данным, а только через стандартные функции, которые в новой версии должны уже корректно работать с новым форматом данных. Даже оператор доступа operator [] может быть определён как такая стандартная функция.

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

Используя инкапсуляцию, структуру Array из предыдущего раздела можно переписать следующим образом:

class Array {
public:
    void Alloc(int _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 void 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 - деструктор. Конструктор копирования (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();}
   ...
};

Полиморфизм

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

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

Однако в С++ не действует правило «друг моего друга — мой друг».

Будущее развитие

Си++ продолжает развиваться, чтобы отвечать современным требованиям. Одна из групп, занимающихся языком Си++ в его современном виде и направляющих комитету по стандартизации Си++ советы по его улучшению — это Boost. Например, одно из направлений деятельности этой группы — совершенствование возможностей языка путём добавления в него особенностей метапрограммирования.

Стандарт Си++ не описывает способы именования объектов, некоторые детали обработки исключений и другие возможности, связанные с деталями реализации, что делает несовместимым объектный код, созданный различными компиляторами. Однако для этого третьими лицами создано множество стандартов для конкретных архитектур и операционных систем.

Тем не менее (по состоянию на время написания этой статьи) среди компиляторов Си++ всё ещё продолжается битва за полную реализацию стандарта Си++, особенно в области шаблонов — части языка, совсем недавно полностью разработанной комитетом стандартизации.

Одной из точек преткновения в этом вопросе является ключевое слово export, используемое также и для разделения объявления и определения шаблонов.

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

Оба этих компилятора основаны на внешнем интерфейсе EDG. Другие компиляторы, такие как Microsoft Visual C++ или GCC, вообще этого не поддерживают. Эрб Саттер ( Herb Sutter), секретарь комитета по стандартизации Си++, рекомендовал убрать export из будущих версий стандарта по причине серьёзных сложностей в полноценной реализации, однако впоследствии окончательным решением было решено его оставить.

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

История названия

Название «Си++» было придумано Риком Масситти (Rick Mascitti) и впервые было использовано в декабре 1983 года. Ранее, на этапе разработки, новый язык назывался «Си с классами». Имя, получившееся в итоге, происходит от оператора Си «++» (увеличение значения переменной на единицу) и распространённому способу присвоения новых имён компьютерным программам, заключающемся в добавлении к имени символа «+» для обозначения улучшений (например «Википедия+»). Согласно Страуструпу, «это название указывает на эволюционную природу изменений Cи». Выражением «С+» назывался более ранний, не связанный с Си++, язык программирования.

Некоторые программисты на Си могут заметить, что если выполняются выражения x=3; y=x++; то в результате получится x=4 и y=3, потому что x увеличивается только после присвоения его y. Однако если второе выражение будет y=++x; <tt> то получится <tt>x=4 и y=4. Исходя из этого, можно сделать вывод, что логичнее было бы назвать язык не Си++, а ++Си. Однако оба выражения c++ и ++c увеличивают c, а кроме того выражение c++ более распространено.

Педанты также могут заметить, что введение языка Си++ не изменяет самого Си, поэтому самым точным именем было бы «С+1».

Си++ не включает в себя Си

Несмотря на то, что большая часть кода Си будет справедлива и для Си++, Си++ не является надмножеством Си и не включает его в себя. Существует и такой верный для Си код, который неверен для Си++. Это отличает его от Объектного Си (Objective-C), ещё одного усовершенствования Си для ООП, как раз являющегося надмножеством Си.

Более того, код, верный для обоих Си и Си++, может давать разные результаты в зависимости от того, где он скомпилирован. Например, следующая программа печатает «С», если компилируется компилятором Си, и «Си++» — если компилятором Си++. Так происходит из-за того, что символьные константы в Си (например 'a') представлены целым типом int, а в Си++ — типом char.

#include <stdio.h>
 
int main()
{
     printf("%s\n", (sizeof('a') == sizeof(char)) ? "C++" : "C");
     printf("sizeof('a')=%i  sizeof(char)=%i\n",sizeof('a') , sizeof(char));
     return 0;
}

Конечно существуют и другие различия. Например Си++ не разрешает вызывать функцию main() внутри программы, в то время как в Си это действие правомерно. Кроме того Си++ более строг в некоторых вопросах, например он не допускает неявное приведение типов между несвязанными типами указателей и не разрешает использовать функции, которые не были ещё объявлены.

Смотрите статью язык Си.

Примеры программ на Си++

Пример №1

Это пример программы, которая не делает ничего. Она начинает выполняться и немедленно завершается. Она состоит из единственной вещи: функции main(), которая обозначает точку начала выполнения программы на Си++.

int main()
{
    return 0;
}

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

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

Завершение программы на Си++ с ошибкой традиционно обозначается путём возврата ненулевого значения.

Пример №2

Эта программа также ничего не делает, но более лаконична.

int main(){}

В Си++, если выполнение программы доходит до конца функции main(), то это эквивалентно return 0;. Это неверно для любой другой функции кроме main(). Строго говоря, эта программа не является программой на Си++, желающие могут обратиться к H.Sutter "Exceptional C++".

Пример №3

Это пример программы Hello World, которая выводит это знаменитое сообщение, используя стандартную библиотеку, и завершается.

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

Пример №4

Современный Си++ позволяет решать простым способом и более сложные задачи. Этот пример демонстрирует кроме всего прочего использование контейнеров стандартной библиотеки шаблонов (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\n";
}
 
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);
}

В этом примере для простоты используется директива использования пространства имён, в настоящей же программе обычно рекомендуется использовать объявления, которые аккуратнее директив:

#include <vector>
 
int main()
{
 using std::vector;
 
 vector<int> my_vector;
}

Заметьте, что здесь директива помещена в область функции, что уменьшает шансы столкновений имён (это и стало причиной введения в язык пространств имён). Использование объявлений, сливающих разные пространства имён в одно, разрушает саму концепцию пространства имён.

Сравнение C++ с языками Java и C#

Язык С++ с появлением первых трансляторов нашёл сразу же очень широкое распространение, на нём было создано огромное количество программ и приложений. По мере накопления опыта создания больших программных систем обнажились недостатки, которые привели к поиску альтернативных решений. Таким альтернативным решением стал язык Java, который в некоторых областях стал конкурировать по популярности с C++, а фирма Майкрософт предложила язык C# как новый язык, развивающий принципы C++ и использующий преимущества языка Java. В дальнейшем появился язык Nemerle, объединяющий достоинства C# с возможностью функционального программирования. В последнее время появилась попытка объединения эффективности C++, безопасности и скорости разработки, как в Java и C# - был предложен язык D, который пока не получил широкого признания.

Язык Java обладает следующими особенностями, которых нет в языке C++ :

  • Java является типобезопасным языком. Типобезопасность гарантирует отсутствие в программах труднообнаружимых ошибок связанных с неверной интерпретацией памяти компьютера. Это делает процесс разработки более надежным и предсказуемым, а стало быть более быстрым. Так же это позволяет привлекать к разработке программистов имеющих меньшую квалификацию и иметь большие группы разработчиков.
  • Java-код компилируются изначально не в машинный код, а в некий промежуточный код, который в дальнейшем интерпретируется или компилируется, тогда как многие C++ компиляторы ориентированны на компиляцию в машинный код заданной платформы.
  • В языке Java есть чётко определённые стандарты на ввод-вывод, графику, геометрию, диалог, доступ к базам данных и прочим типовым приложениям.
  • Благодаря этим особенностям, приложения на Java обладают гораздо лучшей переносимостью, чем на С++, и часто, будучи написаны для определённого компьютера и операционной системы, работают под другими системами без изменений. Программисты, пишущие на языке Java, не зависят от пакетов, навязанных разработчиками компиляторов на данную конкретную среду, что резко упрощает портирование программ.
  • В языке Java реализована полноценная сборка мусора, которой нет в C++. Нет в С++ и средств проверки правильности указателей. С другой стороны, C++ обладает набором средств (конструкторы и деструкторы, стандартные шаблоны, ссылки), позволяющих почти полностью исключить выделение и освобождение памяти вручную и опасные операции с указателями. Однако такое исключение требует определённой культуры программирования, в то время как в языке Java оно реализуется автоматически.
  • Язык Java является чисто объектно-ориентированным, тогда как C++ поддерживает как объектно-ориентированное, так и процедурное программирование.
  • В C++ отсутствует полноценная информации о типах во время исполнения RTTI. Эту возможность можно было бы реализовать в C++, имея полную информацию о типах во время компиляции CTTI.
  • В C++ возможность введения пользовательского синтаксиса с помощью #define может привести к тому, что модули в крупных пакетах программ становятся сильно связаны друг с другом, что резко понижает надёжность пакетов и возможность организации разделённых модулей. С другой стороны, С++ предоставляет достаточно средств (константы, шаблоны, встроенные функции) для того, чтобы практически полностью исключить использование #define.

Эти отличия приводят к ожесточённым спорам между сторонниками двух языков о том, какой язык лучше. Сторонники Java считают эти особенности преимуществами; сторонники C++ считают, что во многих случаях эти особенности являются недостатками, в частности

  • Ценой переносимости является требование наличия на компьютере виртуальной Java-машины, что приводит к замедлению вычислений и практической невозможности использования новых возможностей аппаратной архитектуры.
  • Сборка мусора приводит к потере эффективности.
  • Стандарты на графику, доступ к базам данных и т.д. являются недостатком, если программист хочет определить свой собственный стандарт.
  • Указатели во многих случаях являются мощным или даже необходимым средством, а их бесконтрольное использование опасно лишь в неумелых руках.
  • Поддержка процедурного программирования является полезной.

Далеко не все программисты являются сторонниками одного из языков. По мнению большинства программистов, Java и C++ не являются конкурентами, потому что обладают различными областями применимости. Другие считают, что выбор языка для многих задач является вопросом личного вкуса.

Достоинства языка C++

  • Масштабируемость. На языке C++ разрабатывают программы для самых различных платформ и систем.
  • Возможность работы на низком уровне с памятью, адресами, портами. Что, при неосторожном использовании, может легко превратиться в недостаток.
  • Возможность создания обобщенных алгоритмов для разных типов данных, их специализация, и вычисления на этапе компиляции, используя шаблоны.

Недостатки языка C++

  • Наличие множества возможностей нарушающих принципы типобезопасности приводит к тому, что в С++-программы может легко закрасться трудноуловимая ошибка. Вместо контроля со стороны компилятора разработчики вынуждены придерживаться весьма не тривиальным правилам кодирования. По сути эти правила ограничивают С++ рамками некого более безопасного подъязыка. Большинство проблем типобезопасности С++ унаследовано от С, но важную роль в этом вопросе играет и отказ автора языка от идеи использовать автоматическое управление памятью (например, сборку мусора).
  • Плохая поддержка модульности. Подключение интерфейса внешнего модуля через препроцессорную вставку заголовочного файла (#include) серьёзно замедляет компиляцию, при подключении большого количества модулей. Для устранения этого недостатка, многие компиляторы реализуют механизм прекомпиляции заголовочных файлов Precompiled Headers.
  • Недостаток информации о типах данных во время компиляции (CTTI).
  • Язык C++ является сложным для изучения и для компиляции.
  • Некоторые преобразования типов неинтуитивны. В частности, операция над беззнаковым и знаковым числами выдаёт беззнаковый результат.
  • Препроцессор С++ (унаследованный от С) очень примитивен. Это приводит с одной стороны к тому, что с его помощью нельзя (или тяжело) осуществлять некоторые задачи метапрограммирования, а с другой, в следствии своей не гигиеничности, он часто приводит к ошибкам и требует много действий по обходу потенциальных проблем. Некоторые языки программирования (например, Scheme и Nemerle) имеют намного более мощные и более безопасные системы метапрограммирования (так же называемые макросами, но мало напоминающие макросы С/С++).
  • С конца 20-го века в сообществе С++ получило распространение так называемое метапрограммирование на базе шаблонов. По сути, оно использует особенности шаблонов C++ в целях реализации на их базе интерпретатора примитивного функционального языка программирования выполняющегося во время компиляции. Сама по себе данная возможность весьма привлекательна, но в следствии вышесказанного такой код весьма трудно воспринимать и отлаживать. Языки Lisp/Scheme, Nemerle и некоторые другие имеют более мощные и одновременно более простые для восприятия подсистемы метапрограммирования. Кроме того, в языке D реализована сравнимая по мощности, но значительно более простая в применении подсистема шаблонного метапрограммирования.
  • Хотя декларируется, что С++ мултипарадигменный язык, но реально в языке отсуствет поддержка функционального программирования. Отчасти, данный пробел устраняется различными библиотеками (Loki, Boost) использующими средства метапрограммирования для расширения языка функциональными конструкциями (например, поддержкой лямбд/анонимных методов), но качество подобных решений значительно уступает качеству встроенных в функциональные языки решений. Такие возможности функциональных языков как сопоставление с образцом вообще крайне сложно эмулировать средствами метапрограммирования.

Критика языка C++

  • C++ унаследовал многие проблемы языка C:
    • Операция присваивания обозначается как = , а операция сравнения как == . Их легко спутать, и такая конструкция будет синтаксически правильной, но приведёт к труднонаходимому багу. Особенно часто это происходит в операторах if и while, например, программист может написать if (i=0) вместо if (i==0). (Вместе с тем, основная масса компиляторов выдаёт в таких случаях предупреждение.) К тому же многие языки (Бейсик, Паскаль) используют символ "=" именно в операциях сравнения.
    • Операции присваивания (=), инкрементации (++), декрементации (--) и другие возвращают значение. В сочетании с обилием операций это позволяет, но не обязывает, программиста создавать трудночитаемые выражения. С другой стороны, один из основных принципов языков C и C++ — позволять программисту писать в любом стиле, а не навязывать «хороший» стиль. К тому же это иногда позволяет компилятору создавать более оптимальный код.
    • Макросы (#define) являются мощным, но опасным средством. В языке C++, в отличие от C, необходимость в опасных макросах появляется значительно реже благодаря шаблонам и встроенным функциям. Но в унаследованных стандартных С-библиотеках много потенциально опасных макросов.
  • В C++ также есть недостатки, которых не было в C, в частности:
    • Трудночитаемые сообщения об ошибках при использовании шаблонов. Например,
 #include <list>
 #include <algorithm>
 int main()
 {
   std::list<int> list;
   std::sort(list.begin(), list.end());
 }

компилятор g++ 4.0.3 выдает

 /usr/lib/gcc/x86_64-linux-gnu/4.0.3/../../../../include/c++/4.0.3/bits/stl_algo.h: In function ‘void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]’:
 a.cpp:7:   instantiated from here
 /usr/lib/gcc/x86_64-linux-gnu/4.0.3/../../../../include/c++/4.0.3/bits/stl_algo.h:2569: error: no match for ‘operator-’ in ‘__last - __first’
 /usr/lib/gcc/x86_64-linux-gnu/4.0.3/../../../../include/c++/4.0.3/bits/stl_algo.h: In function ‘void std::__final_insertion_sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]’:
 /usr/lib/gcc/x86_64-linux-gnu/4.0.3/../../../../include/c++/4.0.3/bits/stl_algo.h:2570:   instantiated from ‘void std::sort(_RandomAccessIterator, _RandomAccessIterator) [with _RandomAccessIterator = std::_List_iterator<int>]’
 a.cpp:7:   instantiated from here
 /usr/lib/gcc/x86_64-linux-gnu/4.0.3/../../../../include/c++/4.0.3/bits/stl_algo.h:2213: error: no match for ‘operator-’ in ‘__last - __first’
...  всего 18 строк ...


  • Некоторые считают недостатком языка C++ отсутствие системы сборки мусора. С другой стороны, в C++ есть достаточно средств (классы с конструкторами и деструкторами, стандартные шаблоны, передача параметров по ссылке), позволяющих почти исключить использование опасных указателей. Тем не менее, отсутствие встроенной сборки мусора позволяет пользователю самому выбрать стратегию управленияю ресурсами, что дает серьёзные преимущества по сравнению с другими языками.


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

Статьи и книги

Форумы

Классы, библиотеки и приложения

Литература

В статье использованы материалы из Википедии.