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


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

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



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

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

В языке 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 new_len); void Free(); inline double Elem(int i); inline void ChangeElem(int i, double x);protected: int len; double* val;}; void Array::Alloc(int new_len) {if (len>0) Free(); len=new_len; val=new double[new_len];}void Array::Free() {delete [] val; len=0;}inline double Array::Elem(int i) {assert(i>=0 && i<len); return val[i];}inline void Array::ChangeElem(int i, double x) {assert(i>=0 && i<len); val[i]=x;}

И далее

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

Здесь массив a имеет 4 публичных функции-члена и 2 защищённых поля. Описатель inline означает, что вместо вызова функции её код подставляется в точку вызова, что решает проблему неэффективности.

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

В теле класса можно указать только заголовок функции, а можно описать всю функцию. Во втором случае она считается встроенной (inline), например:

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

и так далее.

Конструкторы и деструкторы

Однако в приведённом примере не решена важная проблема: функции Alloc и Free по-прежнему надо вызывать вручную. Другая проблема данного примера — опасность оператора присваивания.
Для решения этих проблем в язык были введены конструкторы и деструкторы. Конструктор вызывается каждый раз, когда создаётся объект данного типа; деструктор - при уничтожении. При преобразованиях типов, присваивании, передаче параметра тоже вызываются конструкторы и при необходимости деструкторы.
С конструкторами и деструктором класс выглядит так:

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

Здесь Array::Array - конструктор, а Array::~Array - деструктор. Конструктор копирования (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; то получится x=4 и y=4. Исходя из этого, можно сделать вывод, что логичнее было бы назвать язык не Си++, а ++Си. Однако оба выражения c++ и ++c увеличивают c, а кроме того выражение c++ более распространено.
Педанты также могут заметить, что введение языка Си++ не изменяет самого Си, поэтому самым точным именем было бы «С+1».

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

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

typedef struct mystr { int a; int b; } mystr;

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

#include <stdio.h>; int main(){ printf("%s\n", (sizeof('a') == sizeof(char))? "C++": "C"); return 0;}См. также статью язык Си.

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

Пример №1

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

int main(){ return 0;}

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

Пример №2

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

int main(){return 0;}

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

Пример №3

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

#include // это необходимо для 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 > 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)(Вместе с тем, основная масса компиляторов выдаёт в таких случаях предупреждение.) Избежать ошибку такого типа можно, если писать все операции сравнения в таком виде: if (0==i). К тому же многие языки (Бейсик, Паскаль) используют символ "=" именно в операциях сравнения.
Операции присваивания (=), инкрементации (++), декрементации (--) и другие возвращают значение. В сочетании с обилием операций это позволяет, но не обязывает, программиста создавать трудночитаемые выражения. С другой стороны, один из основных принципов языков C и C++ — позволять программисту писать в любом стиле, а не навязывать «хороший» стиль. К тому же это иногда позволяет компилятору создавать более оптимальный код.
Макросы (#define) являются мощным, но опасным средством. В языке C++, в отличие от C, необходимость в опасных макросах появляется значительно реже благодаря шаблонам и встроенным функциям. Но в унаследованных стандартных С-библиотеках много потенциально опасных макросов.
Некоторые считают недостатком языка C++ отсутствие системы сборки мусора. С другой стороны, в C++ есть достаточно средств (классы с конструкторами и деструкторами, стандартные шаблоны, передача параметров по ссылке), позволяющих почти исключить использование опасных указателей. Тем не менее, отсутствие встроенной сборки мусора позволяет пользователю самому выбрать стратегию управления ресурсами.

39. Константа, переменная — это основополагающие понятия в любом языке программирования. Дадим определения этим понятиям.

Константа — это величина, которая при выполнении программы остаётся неизменной.

Переменная — это ячейка памяти для временного хранения данных. Предполагается, что в процессе выполнения программы значения переменных могут изменяться.



Поделиться:


Последнее изменение этой страницы: 2016-08-06; просмотров: 880; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.238.228.191 (0.041 с.)