Си++/Основные отличия Си++ от Си: различия между версиями

Содержимое удалено Содержимое добавлено
→‎Правила перегрузки: - моноширинный шрифт протёк
м <source> -> <syntaxhighlight> (phab:T237267)
Строка 11:
Два слеша подряд (//) означают, что дальше до конца строки — комментарий. Вместе с тем комментарии старого образца (/*...*/) тоже разрешены. Внутри символьных строк, ограниченных кавычками, символы определяющие комментарии, не распознаются компилятором и остаются частью строки.
 
<big><sourcesyntaxhighlight lang=cpp>
int a; // это комментарий
float b; /* и это тоже комментарий */
char *s1="Текстовая строка с двумя слешами //, которые не обозначают начало комментария.";
char *s2="/* Это тоже не комментарий! */";
</sourcesyntaxhighlight></big>
Однако, учитывая правила синтаксического разбора текста, описываемого Стандартом языка, не все символы комментария будут игнорироваться компилятором. Так, следующий код может поначалу удивить:
<big><sourcesyntaxhighlight lang=cpp>
int x = 0, y = 0, z = 0;
 
Строка 25:
++z; // увеличиваем z???
std::cout << x << '\t' << y << '\t' << z << std::endl;
</sourcesyntaxhighlight></big>
Здесь только первый инкремент будет распознан компилятором как не комментарий. Т.к. сначала компилятор выполняет разбор [[w:Триграф (языки Си)|триграфов]], строка со вторым инкрементом в результате будет преобразована в заканчивающуюся на \, затем он выполняет "склейку" соседних строк, в которых первая заканчивается на \, в единую физическую строку, и лишь только затем он начинает делить текст программы на токены, подготавливая его для обработки препроцессором, и удалять комментарии, заменяя каждый одним пробелом. Т.о. все три строки с инкрементами на самом деле являются одной строкой, где начавшийся с // комментарий продолжает вплоть до конца всех трёх "склеенных" строк.
 
Строка 54:
Допустим, мы хотим определить возведение в квадрат для вещественных чисел и назвать его <tt>Sqr</tt>. В Си это можно сделать двумя способами:
 
<big><sourcesyntaxhighlight lang=c>
/* Способ 1 */
double Sqr(double x) {return x*x;}
</sourcesyntaxhighlight></big>
 
<br />
 
<big><sourcesyntaxhighlight lang=c>
/* Способ 2 */
#define Sqr(x) ((x)*(x))
</sourcesyntaxhighlight></big>
 
Какой из них лучше? В первом случае, помимо собственно умножения, имеют место накладные расходы, связанные с вызовом функции: помещение параметра в стек или регистр (в последнем случае, возможно, возникнет необходимость сохранить старое значение регистра и восстановить его впоследствии), передача управления функции, возврат из функции, освобождение стека. Компилятор может подставить тело функции вместо её вызова и избавиться таким образом от накладных расходов лишь в том случае, если это тело находится в текущей единице трансляции (модуле) (в противном случае, можно лишь надеяться на встраивание компоновщиком). Но обычную функцию стандарт разрешает описывать лишь в одной единице трансляции. Может быть, второй? Но макросы в Си приводят к куче проблем, o чём см. любое руководство по Си или Си++. Так что на вопрос «какой лучше» правильный ответ — '''оба хуже'''.
Строка 72:
Так например, если написать
 
<big><sourcesyntaxhighlight lang=cpp>
inline double Sqr(double x) {return x*x;}
</sourcesyntaxhighlight></big>
 
то <tt>Sqr(x)</tt> будет (почти во всех реализациях) вычисляться так же быстро, как <tt>x*x</tt>, но <tt>x</tt> предварительно приводится к типу <tt>double</tt>. По сути, <tt>Sqr(x)</tt> будет заменено на что-то вроде <tt>(double y=x, y*y)</tt>, если бы такие выражения были разрешены.
Строка 83:
 
И в то же, несмотря ни на что, время тело встроенной функции, подобно макросу, пишется там же, где её заголовок; обычно это заголовочный файл. Так, например, <font color=red>неправильно, если sqr.h включается более чем в одном cpp-шнике</font>, а потому, так писать <font color=red>очень нежелательно</font>:
<big><sourcesyntaxhighlight lang=cpp>
// Файл sqr.h
inline double Sqr(double x);
Строка 93:
// Файл foo.cpp
#include <sqr.h>
</sourcesyntaxhighlight></big>
 
А так правильно:
 
<big><sourcesyntaxhighlight lang=cpp>
// Файл sqr.h
inline double Sqr(double x) {return x*x;}
Строка 103:
// Файл foo.cpp
#include <sqr.h>
</sourcesyntaxhighlight></big>
 
И так тоже правильно:
 
<big><sourcesyntaxhighlight lang=cpp>
// Файл sqr.h
inline double Sqr(double x);
Строка 117:
// Файл foo.cpp
#include <sqr.h>
</sourcesyntaxhighlight></big>
 
Следует отметить, что <tt>inline</tt> — лишь рекомендация (а не команда) компилятору заменять вызов функции её телом. Компилятор может посчитать встраивание нецелесообразным, так как оно ведёт к разбуханию кода, а это, в свою очередь, к более частым промахам кэша команд, что в конечном итоге может даже снизить производительность. В некоторых случаях компилятор попросту не может встроить функцию (например, если в текущей единице трансляции отсутствует её тело). Кроме того, современные компиляторы обычно встраивают функции с описателем inline, если нет серьёзных причин обратному, и даже могут встраивать функции и без подсказки программиста, если сочтут это уместным и возможным.
 
Следует также отметить, что вызовы рекурсивных функций могут быть частично или даже полностью «размотаны». Например
<big><sourcesyntaxhighlight lang=cpp>
long long int Factorial(long long int x){x > 1 ? x*Factorial(x - 1) : 1;}
</sourcesyntaxhighlight></big>
Вызов функции Factorial(5) хороший компилятор может заменить числом 120, или, например, 5*(4*(3*Factorial(2))), или 20*Factorial(3) и т. д.
 
Строка 131:
Один или больше последних аргументов функции могут задаваться по умолчанию:
 
<big><sourcesyntaxhighlight lang=cpp>
void f(int x, int y=5, int z=10); // f(1), f(1,5) и f(1,5,10) - одно и то же
 
Строка 139:
f(1, 2); // будет вызвано f(1, 2, 10)
f(1, 2, 3); // будет вызвано f(1, 2, 3)
</sourcesyntaxhighlight></big>
 
Для каждого параметра значение по умолчанию можно указать не более одного раза, но каждое последующее объявление функции, а также определение функции может назначать параметрам значения по умолчанию. При этом, после каждого объявления/определения значения по умолчанию должны иметь лишь последние параметры:
 
<big><sourcesyntaxhighlight lang=cpp>
// Файл foo.h
void foo(int a, int b=3); // OK, параметр b имеет значение по умолчанию
Строка 172:
 
}
</sourcesyntaxhighlight></big>
 
Умолчания параметров строго равносильны конструкциям с inline функциями:
 
<big><sourcesyntaxhighlight lang=cpp>
void foo(int x, int y, double z);
inline void foo(int x, int y) { foo(x, y, 10); }
inline void foo(int x) { foo(x, 5); }
</sourcesyntaxhighlight></big>
 
Выражение для умолчания одного параметра не может использовать имена других параметров. Это связано с тем, что порядок вычисления фактических параметров вызова не определен языком и зависит от реализации.
Строка 186:
Если же такое очень хочется, то можно написать явно:
 
<big><sourcesyntaxhighlight lang=cpp>
void f(int x, int y);
inline void f(int x){ f(x, x); }
</sourcesyntaxhighlight></big>
 
==== Функции без аргументов и с неизвестными аргументами ====
Строка 199:
* Известные аргументы должны быть в начале списка
 
<big><sourcesyntaxhighlight lang=cpp>
void f(); // функция без аргументов
void g(void); // тоже функция без аргументов
Строка 207:
// (Стандарт позволяет, проверьте!)
// int bad2(..., char* fmt); // Неправильно! Известные аргументы должны быть в начале списка
</sourcesyntaxhighlight></big>
 
Доступ к дополнительным аргументам такой функции в её теле требует средств из stdarg.h
Строка 218:
 
Если в Си переменные могут быть описаны только в начале блока, в Си++ их можно описывать где угодно: например, в середине блока
<big><sourcesyntaxhighlight lang=cpp>
{
// тут что угодно
Строка 224:
// область видимости i - отсюда до конца блока
}
</sourcesyntaxhighlight></big>
и даже внутри <tt>for</tt>:<big><sourcesyntaxhighlight lang=cpp>
for (int i=0; i<10; i++) {
// областью видимости i должен быть этот блок, согласно стандарту ANSI C++
Строка 231:
// и расширяют область видимости i до конца внешнего блока
}
</sourcesyntaxhighlight></big>
 
Зачем это нужно? Затем, что согласно философии Cи++, описание переменной сразу же инициализирует её легитимным объектом данного типа. Поэтому, переменные описываются, по крайней мере, не раньше, чем их можно инициализировать. Более того, хорошим тоном считается описать переменную именно тогда, когда она нужна. Помимо прочего, это делает программу более читабельной.
 
<big><sourcesyntaxhighlight lang=cpp>
/*Вариант 1*/
{
Строка 244:
// ему придётся лезть в начало блока
}
</sourcesyntaxhighlight></big>
<big><sourcesyntaxhighlight lang=cpp>
/*Вариант 2*/
{
Строка 252:
// х описан, когда он понадобился.
}
</sourcesyntaxhighlight></big>
 
Особенно полезно описание в середине блока для классов с конструктором (см. ниже).
Строка 261:
 
В Си аргументы всегда передаются единственным образом, часто называемым «передачей по значению». Например, пусть есть функция
<big><sourcesyntaxhighlight lang=c>
void foo (int x)
{
x = 17;
}
</sourcesyntaxhighlight></big>
 
Мы можем вызывать её любым из следующих способов:
<big><sourcesyntaxhighlight lang=c>
int main ()
{
Строка 277:
foo (125);
}
</sourcesyntaxhighlight></big>
 
Разумеется, ни один из этих вызовов не изменяет значения переменной <tt>z</tt>. (Для последних двух вызовов это совершенно естественно, а вот в первом случае вы могли засомневаться.)
 
Что же делать, если мы хотим дать возможность функции <tt>foo</tt> изменять значение переданной переменной? Мы можем передать ей не саму переменную, а указатель (и для C, и для C++). Перепишем предыдущий пример так:
<big><sourcesyntaxhighlight lang=c>
void foo (int *x)
{
Строка 293:
/* остальные варианты больше не имеют смысла, т.к. z теперь равно 17 */
}
</sourcesyntaxhighlight></big>
 
Такая передача аргументов, однако, опасна: легко забыть звёздочку в теле функции <tt>foo</tt> или амперсанд — в её вызове. Представьте себе, что <tt>foo</tt> состоит из тысячи строк, и везде, где употребляется <tt>x</tt>, нужна звёздочка. А ещё она вызывается 56 раз в разных местах программы — при этом, иногда нужен амперсанд, иногда нет. Это — стандартная ситуация для реальных, а не учебных, программ.
Строка 303:
В Си++ можно ''передавать параметры по ссылкам''. Пример из предыдущего абзаца теперь выглядит так:
 
<big><sourcesyntaxhighlight lang=c>
void foo (int& x)
{
Строка 313:
foo (z); // теперь z=17
}
</sourcesyntaxhighlight></big>
Однако, в таком варианте использования ссылок, программисту приходится быть осторожным при передаче значений в функцию стороннего разработчика, если не планируется изменение передаваемой переменной: только функции с прототипом
<big><sourcesyntaxhighlight lang=c>
void foo (const int& x);
</sourcesyntaxhighlight></big>
гарантируют неизменность передаваемого значения.
 
Строка 335:
Итак, мы можем передавать по ссылкам параметры. Но ссылки могут существовать и сами по себе, вне всякой связи с передачей параметров. Пример:
 
<big><sourcesyntaxhighlight lang=cpp>
int x = 10;
int& y = x; // Теперь y - ссылка на int, указывающая на x.
Строка 348:
b = 1.5; // то же самое, что а[3]=1.5;
b++; // то же самое, что а[3]++ . Теперь а[3]==2.5
</sourcesyntaxhighlight></big>
 
Кроме того, функции могут и возвращать значение по ссылке:
 
<big><sourcesyntaxhighlight lang=cpp>
double& Third(double* x) {return x[2];}
double a[5];
Third(a)=1.5; // теперь а[2]=1.5
Third(a)++; // теперь а[2]=2.5
</sourcesyntaxhighlight></big>
 
В таком случае, вызов функции становится lvalue. Более того, вызов функции есть lvalue тогда и только тогда, когда функция возвращает ссылку.
Строка 368:
 
Например, вот так написать вообще нельзя:
<big><sourcesyntaxhighlight lang=cpp>
int &x;
</sourcesyntaxhighlight></big>
Ссылку обязательно инициализировать! Подойдёт любой из следующих способов:
<big><sourcesyntaxhighlight lang=cpp>
int z;
int *pz = &z;
Строка 384:
Память не будет освобождена автоматически, в отличии от других случаев.
Это нужно будет сделать вручную. */
</sourcesyntaxhighlight></big>
 
Ссылку, как только что было сказано, нельзя заставить ссылаться на другой объект. Раз уж она начала на что-то ссылаться, то она будет на это ссылаться до конца жизни. Например, заведём указатель и ссылку и попытаемся их поменять:
<big><sourcesyntaxhighlight lang=cpp>
int z = 3, zz = 5;
int *pz = &z; /* теперь pz указывает на z, *pz == z == 3 */
Строка 394:
x = zz; /* но x ведь ссылалась на z! значит, мы написали «z = zz».
теперь x == z == zz == 5 */
</sourcesyntaxhighlight></big>
 
Изменить указатель, скрытый за ссылкой <tt>x</tt>, нельзя — просто нет в Си++ оператора, позволяющего это сделать.
Строка 401:
 
Разве что мы специально постараемся...:
<big><sourcesyntaxhighlight lang=cpp>
int& g = *(int*)(0);
int* h = &g; /* h == 0 */
g = 2; /* Ошибка выполнения */
</sourcesyntaxhighlight></big>
 
Не бывает массивов ссылок и указателей на ссылки: такое не предусмотрено синтаксисом языка Си++. Сделано это намеренно, ибо позволение указателей на ссылки разрешало бы менять значение ссылки, что противоречит семантической неизменности ссылок после их определения и инициализации. Запрет на массивы ссылок следует из запрета на указатели на ссылки. Кроме того, ссылки на ссылки в редакции C++98 также запрещены, как и указатели на ссылки, и по той же причине. Однако в шаблонном обобщённом коде это правило может мешать, так как делает ссылочные типы несимметричными по свойствам с нессылочными. Поэтому, в редакции C++03 были введены правила сведения ссылок, которые, по-прежнему, не позволяет существовать ссылкам на ссылки, однако, сводит исходно ссылочные типы к ним же, не меняя ничего в итоге и не добавляя ещё одного уровня косвенности, но в то же время нессылочные типы сводит к ссылкам. Именно такое поведение обычно и желают программисты, создавая обобщённый шаблонный код.
Строка 411:
Если ссылка – поле класса, то класс обязан иметь явно написанный конструктор, и все такие поля обязаны быть инициализированы в [[Си++#Конструктор|ctor-инициализаторе]]:
 
<big><sourcesyntaxhighlight lang=cpp>
 
int i;
Строка 423:
};
 
</sourcesyntaxhighlight></big>
 
Неконстантная ссылка на тип <tt>T</tt> может быть инициализирована только <tt>lvalue</tt>, и только типа <tt>T</tt> — запрещены даже простейшие преобразования типа <tt>short</tt> в <tt>long</tt> и <tt>Derived</tt> к <tt>Base</tt>.
Строка 429:
Показанное ниже неверно:
 
<big><sourcesyntaxhighlight lang=cpp>
 
Derived d;
Строка 437:
long& lr = 1; // не есть lvalue
 
</sourcesyntaxhighlight></big>
 
С константной ссылкой эти ограничения снимаются, путем создания временного безымянного объекта, на который и будет ссылаться ссылка.
 
<big><sourcesyntaxhighlight lang=cpp>
 
Derived d;
const Base& cbr = d; // работает
 
</sourcesyntaxhighlight></big>
 
На деле это означает:
 
<big><sourcesyntaxhighlight lang=cpp>
 
Derived d;
Строка 456:
const Base& cbr = __tmp1;
 
</sourcesyntaxhighlight></big>
 
==== Зачем нужны ссылки? ====
Строка 464:
Но даже, если вам это не нужно, для большого класса или структуры передача по ссылке гораздо быстрее и экономит память.
 
<big><sourcesyntaxhighlight lang=cpp>
void f(Monster x);
void g(const Monster& x);
Строка 471:
f(barmaley); // будет создаваться копия монстра barmaley, а после выполнения функции — уничтожаться.
g(barmaley); // Функция получит адрес barmaley. Kопию создавать не надо.
</sourcesyntaxhighlight></big><!--
Кто-то написал:
 
Строка 487:
 
Ссылки полезны и в циклах по элементам массива.
<big><sourcesyntaxhighlight lang=cpp>
for (int i = 0; i < 10; i ++)
{
Строка 497:
x = x * x;
}
</sourcesyntaxhighlight></big>
 
Во всех этих случаях можно было бы использовать указатель, и в Си так и делается, но, как сказано выше, указатели опасны. К тому же, писать звёздочки и амперсанды — утомительно.
Строка 516:
 
…А главное отличие — их теперь можно использовать! Например, можно написать так:
<big><sourcesyntaxhighlight lang=cpp>
const int N = 10;
int A [N];
</sourcesyntaxhighlight></big>
 
Возможно, вы не поверите, но в Си этого сделать было нельзя. Хоть значение <tt>N</tt> и известно во время компиляции, в Си компилятор «закрывал на это глаза». (Безусловно, у них были свои причины на это, для интересующихся — причины зовут «extern», но всё равно получилось не очень хорошо.)
Строка 530:
 
Константы обязательно инициализировать, например:
<big><sourcesyntaxhighlight lang=cpp>
const int foo = 10; /* можно */
const int bar; /* нельзя */
</sourcesyntaxhighlight></big>
 
Это логично, поскольку если мы не инициализируем её сразу, то вообще никогда не сможем ничего ей присвоить, поэтому в таком случае она окажется бесполезной.
Строка 540:
 
Константы очень интересно сочетаются с указателями и ссылками. По крайней мере, это интересно выглядит. Посмотрите сами:
<tt><sourcesyntaxhighlight lang="cpp">const int *foo</sourcesyntaxhighlight></tt>
или
<tt><sourcesyntaxhighlight lang="cpp">int const *foo</sourcesyntaxhighlight></tt>
: Указатель на <tt><sourcesyntaxhighlight lang="cpp">const int</sourcesyntaxhighlight></tt>. Значение указателя изменить можно (так, чтобы он указывал на что-нибудь другое), а вот значение переменной, ''на которую'' он указывает, менять нельзя.
<tt><sourcesyntaxhighlight lang="cpp">int *const foo = &x</sourcesyntaxhighlight></tt>
: Константный (неизменный) указатель на <tt>int</tt>. Значение указателя менять нельзя (будто это ссылка, а не указатель). Значение того, на что он указывает, менять можно. Заметьте, что константный указатель обязательно инициализировать, как и любую другую константу.
<tt><sourcesyntaxhighlight lang="cpp">const int *const foo = &x</sourcesyntaxhighlight></tt>
: Смесь двух предыдущих пунктов. Ничего нельзя изменить: ни значение указателя, ни значение того, на что он указывает. Опять же, инициализация обязательна.
 
У ссылок разнообразия значительно меньше, ибо «указательная» часть ссылки и так всегда константа. Значит, бывает только:
<tt><sourcesyntaxhighlight lang="cpp">const int &foo = x</sourcesyntaxhighlight></tt>
: Ссылка на <tt>int</tt>, который мы (с помощью этой ссылки) не сможем изменить.
 
Для константных ссылок можно:
 
<big><sourcesyntaxhighlight lang=cpp>
const int& i = 1;
// равносильно
int __tmp = 1;
const int& i = __tmp;
</sourcesyntaxhighlight></big>
 
И, наконец, пусть наши заявления про неизменность ссылок обретут некий формальный вид. Следующие строки очень похожи по смыслу:
<big><sourcesyntaxhighlight lang=cpp>
int &foo = x;
int *const bar = &x;
</sourcesyntaxhighlight></big>
 
Фактически, после этого <tt>foo</tt> и <tt>bar</tt> отличаются только синтаксически (везде нужно писать <tt>*bar</tt>, но и просто <tt>foo</tt>), и если мы везде заменим <tt>foo</tt> на <tt>*bar</tt> или наоборот, ничего не изменится.
Строка 624:
 
Иногда хочется легко реализовать перемещение (с «выниманием внутренностей») и для <tt>lvalue</tt>, явно указав на это. Для этого в STL есть функция <tt>std::move</tt>, которая превращает <tt>lvalue</tt> в <tt>rvalue</tt>. Возможность появилась недавно. Так, например, компилятор Microsoft поддерживает ее только начиная с Visual Studio 2010. Аналогично перемещающим конструкторам существуют так же и перемещающие операции присваивания. Это перегруженные operator =, принимающие такую же rvalue-ссылку. Пользоваться перемещающим присваиванием можно точно так же и тогда же, как и перемещающим копированием. Например:
<big><sourcesyntaxhighlight lang=cpp>
class Object
{
Строка 648:
o4 = o3; // полное присваивание, с полным выделением ресурсов для o4, о3 не меняется
o5 = std::move(o4); // быстрое присваивание, но очень вероятно, что с разрушением прежнего состояния o4
</sourcesyntaxhighlight></big>
Эти возможности широко используются внутри современных STL, для перекладывания объектов при росте вектора и т.д. Потому у объектов, которые будут помещаться в контейнеры STL, желательно реализовывать <tt>move constructor</tt> и <tt>move assignment</tt>.
 
Строка 668:
 
Ключевое слово «<tt>enum</tt>» теперь создаёт новый полноценный тип. Если в Си (стандарт 1989-го года (C89)) после
<big><sourcesyntaxhighlight lang=c>
enum color {red, green, blue};
</sourcesyntaxhighlight></big>
нужно было всегда называть тип «<tt>enum color</tt>», то в Си++ и С99 (Си стандарт от 1999-го года) его название — просто «<tt>color</tt>». Аналогичную метаморфозу претерпели и типы, определяемые ключевыми словами «<tt>struct</tt>» и «<tt>union</tt>». Это действительно почти никого не волнует, ибо такие типы в практически любом реальном коде на Си объявлялись через typedef.
 
Для хранения enum'ов используются целочисленные значения. Без явного указания нумерация начинается с 0 и увеличивается на 1. Если вы хотите указать значение — можете его записать после знака '=' и последующие значения будут увеличиваться, начиная с этого индекса. Имена в перечислении получают значение, начиная с нуля. Если же указано явное значение, то последующие имена получают значения явное+1, явное+2 и так далее, и так до конца типа или до следующего явного значения.
<big><sourcesyntaxhighlight lang=cpp>
enum color {red, blue, green};
cout << red << ' ' << blue << ' ' << green; // "0 1 2"
enum color2 {yellow, cyan=100, magenta};
cout << yellow << ' ' << cyan << ' ' << magenta; // "0 100 101"
</sourcesyntaxhighlight></big>
 
Как вы могли заметить, использовать имена, записанные в enum'е, можно как константы.
Строка 687:
Обратное приведение автоматически не делается, но может быть сделано вручную:
 
<big><sourcesyntaxhighlight lang=cpp>
 
enum color {red, green, blue};
Строка 693:
color c2 = static_cast<color>(1); // правильно
 
</sourcesyntaxhighlight></big>
 
<tt>sizeof()</tt> перечисления совсем не обязан быть равен <tt>sizeof(int)</tt>, может быть и 1, и может меняться от перечисления к перечислению. Компилятор вправе выбирать представление перечислений (как один из целочисленных типов) персонально для каждого перечисления. Некоторые компиляторы имеют ключ командной строки «treat enums as ints», который подавляет эту возможность. Строго говоря, C++ в отличие от C, вводит понятие представимого типа для перечисления, которым должен быть любой подходящий целочисленный тип, обладающий достаточной мощностью для хранения всех указанных (включая неявные, полученные инкрементом предыдущего значения) значений элементов перечисления. Суть требований Стандарта сводится к определению минимального количества бит, которые обязан вмещать представимый тип. При этом учитывается также знак значений. Например, для
<big><sourcesyntaxhighlight lang=cpp>
enum some_enum { val1 = 1, val2, val3 = 5 };
</sourcesyntaxhighlight></big>
для хранения всех указанных значений достаточно трёх бит. Поэтому минимальным значением перечисления является 0, максимальным - 7. Однако стоит добавить какое-нибудь val4 = -1, как количество бит увеличивается до четырёх, а минимальным значением типа перечисления должно быть уже -8 (если используется дополнительное кодирование целочисленных значений; в случае использования прямого или обратного кодирования оно будет равно -7), а вот val4 = -10 не только увеличит количество бит до пяти, и минимальное значение станет уже -16, но и максимальное будет равно 15.
 
Строка 705:
====Главное отличие перечислений Си++ от Си====
В стандарте 2000-x появятся «strongly typed enums»:
<big><sourcesyntaxhighlight lang=cpp>
enum class E{value1=100,value2,value3};
...
Строка 712:
// этого можно добиться с помощью
enum class E:unsigned int{value1=100,value2,value3};
</sourcesyntaxhighlight></big>
 
=== Статические массивы: что да как? ===
Строка 719:
 
Массивы — это та часть языка Си, которая не подверглась изменениям при эволюционировании языка в C++. Поэтому их объявление и работа с ними на обоих этих языках совпадает. Чтобы создать в памяти новый массив, используется такая запись:
<big><sourcesyntaxhighlight lang=cpp>
int m[10];
</sourcesyntaxhighlight></big>
 
<tt>int</tt> — это тип элементов массива, одинаковый для всех них. Конечно, Вы можете использовать любой другой тип (кроме <tt>void</tt>, ссылок и типов с неизвестным размером), чтобы задавать массивы. Квадратные скобки обозначают, что это массив, целое число в скобках (обязательно константа, известная на момент компиляции) — его размер, то есть количество элементов. <i>m</i> — это имя нашей переменной-массива.
Строка 727:
Важно заметить, что в C/C++ типы <i>«массив»</i> и <i>«указатель»</i> тесно связаны, а именно: массив практически всегда автоматически приводится к указателю на свой первый элемент. Поэтому в функцию, которая требует указатель, вполне законно передавать массив:
 
<big><sourcesyntaxhighlight lang="cpp">
void MyFunc( int *arr );
MyFunc(m);
</sourcesyntaxhighlight></big>С другой стороны, среди формальных параметров функции можно объявить и обычный массив, и даже
массив без указания размера. Если тип параметра — <tt>T[N]</tt> или <tt>T[]</tt>, то он автоматически меняется на <tt>T*</tt>. Так что следующие три объявления функций абсолютно идентичны: <big><sourcesyntaxhighlight lang="cpp">
void MyFunc1( int *arr ); // компилятор посчитает
void MyFunc2( int arr[] ); // эти три объявления
void MyFunc3( int arr[15] ); // абсолютно идентичными
</sourcesyntaxhighlight></big>Однако можно передать массив по ссылке, так как для параметров-ссылок таких замен не производится:<big><sourcesyntaxhighlight lang="cpp">
void MyFunc4( int (&arr)[15] ); // Сюда можно передать массив из 15 элементов типа int (но ни из
// какого другого количества)
Строка 742:
// тоже нельзя, так как они являются псевдонимами массивов, а не
// указателями.
</sourcesyntaxhighlight></big>Если нужно передать массив по значению, то нужно объявить структуру с полем типа массив и передавать объект этой структуры.
 
Имя переменной-массива трактуется тождественно указателю на первый элемент только в value context.
Строка 757:
на элемент. Например, для массива <tt>mas</tt> из трёх элементов типа <tt>int</tt> верно:
 
<big><sourcesyntaxhighlight lang="cpp">
typeid(&mas) == typeid(int(*)[3])
typeid((int*)mas) == typeid(int*)
</sourcesyntaxhighlight></big>
 
при этом:
 
<big><sourcesyntaxhighlight lang="cpp">
(void*)&mas == (void*)(int*)mas
</sourcesyntaxhighlight></big>
 
 
Строка 775:
 
Но при всём том, ''массив и указатель — это различные типы''. Так, если написать в одном файле
<big><sourcesyntaxhighlight lang="cpp">
int a[10];
</sourcesyntaxhighlight></big>
а в другом
<big><sourcesyntaxhighlight lang="cpp">
extern int *a;
</sourcesyntaxhighlight></big>
— компилятор, скорее всего, выдаст Вам ошибку (компилятор об этом даже не узнает, ошибку даст линкер, и только в том случае, если переменная объявлена вне блока <tt>extern "C"</tt>).
 
Строка 787:
 
Если создаётся глобальный массив, то изначально все его элементы по умолчанию нули, однако зачастую возникает необходимость присвоить им другие начальные значения. Процесс присваивания начальных значений и называется <i>инициализацией</i>. В C/C++ инициализация осуществляется с помощью знака =, который пишется после имени переменной. Этот знак не есть оператор присваивания, поэтому если Вы будете таким образом инициализировать экземпляры классов, то будет вызван конструктор, а не функция <tt>operator=</tt>.
<big><sourcesyntaxhighlight lang="cpp">
double dbl = 1.0; /* инициализация простой переменной */
double *m[5] = {NULL, NULL, &dbl}; /* инициализация массива указателей */
</sourcesyntaxhighlight></big>
 
Как видно из данного примера, значения для инициализации массива пишутся через запятую, причём вся
Строка 796:
 
Кроме указанного способа, массив символов можно инициализировать непосредственно с помощью строкового литерала:
<big><sourcesyntaxhighlight lang="cpp">
char str[10] = "Aspid";
</sourcesyntaxhighlight></big>
 
Но при этом надо помнить, что sizeof строки на самом деле на единицу больше, чем видимое число символов в ней, поскольку она содержит ещё и символ завершающего нуля.
 
Наконец, инициализация массива позволяет избежать явного объявления размера. Массив автоматически будет создан такого размера, сколько элементов содержится в списке инициализации:
<big><sourcesyntaxhighlight lang="cpp">
int m[] = {2, 4, 6}; // создаётся массив из трёх элементов
char c[] = "Sample String"; // создаётся массив содержащий строку, размерность
// подсчитывается автоматически
</sourcesyntaxhighlight></big>
 
Массив переменных классовых типов можно инициализировать синтаксисом явного вызова конструктора:
 
<big><sourcesyntaxhighlight lang="cpp">
complex ca[2] = { complex(1, 2), complex(3, 4) };
</sourcesyntaxhighlight></big>
 
Но в случае, когда такой массив создается оператором <tt>new</tt>, это невозможно. Запись <tt>new MyClass[size]</tt> обязательно требует наличия у класса конструктора по умолчанию.
Строка 821:
Предположим, у нас имеется массив <i>m</i> (или, что то же самое, указатель на его начало). Как
нам обратиться к самому первому его элементу? Ко второму? К (<i>k</i> + 1)-му? Правильно, так:
<big><sourcesyntaxhighlight lang="cpp">
*m
*(m + 1)
*(m + k)
</sourcesyntaxhighlight></big>
Вот это число, которое прибавляется к указателю <i>m</i>, и есть индекс элемента в массиве. В языках C/C++ индексация массива начинается с нуля, поэтому самый первый элемент массива всегда имеет индекс 0. К счастью, язык предоставляет гораздо более удобное средство обращения к элементу с индексом <i>k</i>, а именно, квадратные скобки:
<big><sourcesyntaxhighlight lang="cpp">
m[k] = 17;
</sourcesyntaxhighlight></big>
В данном примере в ячейку с индексом <i>k</i> записывается число 17. Существует также альтернативный способ записи, который приводит к ровно такому же результату. Возможность такой записи вытекает из коммутативности сложения указателя и целого числа. Вот он, этот альтернативный способ:
<big><sourcesyntaxhighlight lang="cpp">
k[m] = 17;
0[&x] = x + x; // контрольный вопрос : что делает эта строчка?
</sourcesyntaxhighlight></big>
Правда, я ещё ни разу не видел, чтобы такая экзотическая запись где-нибудь использовалась.
 
Строка 840:
 
Кстати, если массив <i>m</i> объявлен в программе где-то далеко, то вы можете «на ходу» узнать количество элементов в нём в помощью вот такой конструкции:
<big><sourcesyntaxhighlight lang="cpp">
sizeof(m) / sizeof(m[0])
</sourcesyntaxhighlight></big>
На самом деле, этот метод предпочтительнее, чем прямое указание размера, потому что если вдруг размер массива нужно изменить, то переписать нужно всего одно число при объявлении, вместо того, чтобы ползать по коду, выискивая места, где ещё понадобилось это значение.
 
Основное удобство при работе с массивами заключается в том, что с ними можно работать посредством циклов, а не обращаясь к каждому элементу по отдельности
<big><sourcesyntaxhighlight lang="cpp">
int* a=new int [10];
for(int i=0;i<10;i++)
Строка 854:
//...
delete []a;
</sourcesyntaxhighlight></big>
 
==== Многомерные массивы ====
 
Одна из самых приятных особенностей языка — возможность создавать массив из массивов (т.н. <i>двухмерные массивы</i>), из таких массивов собрать ещё один массив (тогда получится <i>трёхмерный массив</i>) и т.д. Интуитивно понятно, как это делается:
<big><sourcesyntaxhighlight lang="cpp">
int m[5][8];
</sourcesyntaxhighlight></big>
Такой код генерирует массив из 5 элементов, каждый из которых является массивом из 8 элементов
типа <tt>int</tt>. Можно обратиться к любому из 5 подмассивов непосредственно (<tt>m[3]</tt>), либо к
Строка 878:
 
В Си для этого было две главных функции: одна называлась <tt>malloc()</tt> и выделяла непрерывный кусок памяти, другая, <tt>free()</tt>, этот кусок освобождала. Вот как выглядит код на Си для работы с динамической памятью (она на жаргоне называется <i>кучей</i>, <i>heap</i>):
<big><sourcesyntaxhighlight lang="c">
int *Piece;
Piece = (int*)malloc(sizeof(int)); /* аргумент функции malloc() - число байт, которые надо выделить */
Строка 888:
...
free(Piece); /* аргумент free() - указатель на уже ненужный кусок памяти */
</sourcesyntaxhighlight></big>
 
Если возникала необходимость выделить память под несколько переменных одного типа, расположенных рядом (то есть под массив), аргумент <tt>malloc()</tt>'а просто домножали на нужное количество ячеек массива:
<big><sourcesyntaxhighlight lang="c">
int *Piece = (int*)malloc(15 * sizeof(int));
</sourcesyntaxhighlight></big>
Был, правда, у <tt>malloc()</tt>'а один «недостаток»: выделяя память, он не изменял содержимое ячеек,
поэтому там могло оказаться совершенно произвольное значение. С этим боролись либо с помощью специальной
функции <tt>memset(ptr, c, n)</tt> (она заполняет <i>n</i> байт памяти начиная с места, на которое указывает <i>ptr</i>, значением <i>c</i>), либо с помощью <tt>calloc()</tt>'а. Функция <tt>calloc()</tt> принимает два параметра: число ячеек массива, под которые надо выделить память, и размер этой ячейки в байтах; делает эта функция следующее: выделяет нужное количество памяти (непрерывный кусок) и обнуляет все значения в нём. Таким образом такой код:
<big><sourcesyntaxhighlight lang="c">
int *Piece = (int*)malloc(15 * sizeof(int));
memset(Piece, 0, 15 * sizeof(int));
</sourcesyntaxhighlight></big>
 
эквивалентен такому:
<big><sourcesyntaxhighlight lang="c">
int *Piece = (int*)calloc(15, sizeof(int));
</sourcesyntaxhighlight></big>
 
==== Операторы new и delete ====
 
Идеология языка C++ предполагает, что каждый объект создаётся (объявляется) именно в том месте, где он нужен, и является работоспособным сразу после создания. Для этого каждый класс имеет определённый набор '''конструкторов''' — функций, которые должны автоматически запускаться при создании объекта (экземпляра данного класса) и инициализировать его члены (<i>data members</i>). Конструкторы одного класса отличаются только количеством и типом передаваемых параметров, то есть являются перегруженными функциями. Однако, к сожалению, функции <tt>malloc()</tt> и <tt>сalloc()</tt> не умеют автоматически запускать конструкторы, и потому непригодны для динамического создания объектов. В языке Си++ им имеется адекватная замена — оператор <tt>new</tt>. Рассмотрим пример:
<big><sourcesyntaxhighlight lang="cpp">
MyClass *mc = new MyClass(5);
</sourcesyntaxhighlight></big>
 
В данном случае создаётся экземпляр класса <tt>MyClass</tt>, после чего с помощью его конструктора, принимающего в качестве параметра целое число (в данном случае, число 5), объект "инициализируется" этим числом. Адрес вновь созданного объекта присваивается указателю <i>mc</i>.
Если для класса определён конструктор по умолчанию, после имени класса допускается не указывать пустые скобки. Писать их или нет — это, как говорится, дело вкуса:
<big><sourcesyntaxhighlight lang="cpp">
new MyClass(); // эти две строки кода
new MyClass; // абсолютно эквивалентны
</sourcesyntaxhighlight></big>
 
Естественно, ничто не мешает использовать оператор <tt>new</tt> для простых скалярных переменных (например, целых чисел или других указателей).
Строка 927:
 
Для каждого класса, помимо конструкторов, определён ещё и '''деструктор''', то есть функция, отвечающая за корректное уничтожение объекта. Деструктор никогда никаких параметров не принимает, и потому не может быть перегружен. Проблема с деструктором возникает та же, что и с конструктором: функция <tt>free()</tt> не умеет его вызывать. Поэтому в Си++ введён ещё один оператор — <tt>delete</tt>. Синтаксис его очень прост:
<big><sourcesyntaxhighlight lang="cpp">
delete mc;
</sourcesyntaxhighlight></big>
где <i>mc</i> — указатель на класс. Именно для этого класса и вызовется деструктор, поэтому,
если Вы объявили его как <i>«указатель-на-что-угодно»</i>, деструктор не будет вызван вообще. Собственно, именно поэтому <tt>void *</tt> не рекомендуется использовать. Другой пример:
<big><sourcesyntaxhighlight lang="cpp">
class Base
{
Строка 952:
return 0;
}
</sourcesyntaxhighlight></big>
<!--тут надо бы ссылку внутри книги -->
 
Строка 963:
 
Вы, возможно, уже заметили, что при создании объектов с помощью оператора <tt>new</tt> мы выделяем память ровно под один экземпляр класса, в то время как используя <tt>malloc()</tt> или <tt>calloc()</tt> имеем возможность создать целый массив из произвольного числа элементов. На самом деле, синтаксис языка C++ позволяет использовать для этих целей конструкцию, аналогичную оператору <tt>new</tt>:
<big><sourcesyntaxhighlight lang="cpp">
MyClass *mc = new MyClass[15];
</sourcesyntaxhighlight></big>
 
С формальной точки зрения, эта конструкция (называемая оператором <tt>new[]</tt> (разница в скобках)) не есть оператор <tt>new</tt>, описанный ранее. В частности, при перегрузке оба эти оператора описываются отдельно друг от друга и вообще никак не связываются.
 
Итак, в нашем примере будет выделена память под массив из 15 объектов класса <tt>MyClass</tt>, и каждый из них будет инициализирован с помощью конструктора по умолчанию. Если такой конструктор не определён, то попытка использовать <tt>new[]</tt> приведёт к ошибке. Применить к элементам созданного динамического массива какой-либо другой конструктор, увы, нельзя; поэтому такая запись вызовет легкое недоумение компилятора:
<big><sourcesyntaxhighlight lang="cpp">
MyClass *mc1 = new MyClass("hello, world!")[134];
MyClass *mc2 = new MyClass()[2]; // это тоже ошибка, нельзя комбинировать
// два типа скобок в одном new
</sourcesyntaxhighlight></big>
В пару к оператору <tt>new[]</tt> введён оператор <tt>delete[]</tt>. Система сама помнит, сколько памяти было выделено под этот динамический массив, поэтому указывать число элементов не требуется. Просто напишите:
<big><sourcesyntaxhighlight lang="cpp">
delete[] mc;
</sourcesyntaxhighlight></big>
и компьютер сделает всё за Вас. Предостережение при использовании <tt>delete[]</tt> такие же, как и для <tt>delete</tt>: если хотите избежать утечек памяти (<i>memory leak</i>), следите за тем, чтобы вызывались "правильные" деструкторы.
 
Строка 1002:
 
Что же такое перегрузка? В Си может быть только одна функция с одним именем. Например, представим себе семейство функций, которые выводят на экран некоторое значение (число или строку). В Си мы могли бы описать их так:
<big><sourcesyntaxhighlight lang="c">
void print_int (int v);
void print_float (float v);
Строка 1012:
print_zts ("Hello, world!\n");
}
</sourcesyntaxhighlight></big>
 
Однако на самом деле, когда мы вызываем «<tt>print_нечто (10)</tt>», компилятор уже знает, что 10 — это целое число. Но он никак не даёт нам воспользоваться этим знанием, и нам приходится вручную говорить, что печатаем мы именно <tt>int</tt>: «<tt>print_int (10)</tt>».
Строка 1019:
 
Си++ как раз позволяет предоставить компилятору задачу по выбору нужного варианта функции. Для этого мы можем определить набор функций, каждая из которых имеет имя «<tt>print</tt>», но которые принимают разные аргументы. Предыдущий пример тогда будет выглядеть следующим образом:
<big><sourcesyntaxhighlight lang="cpp">
void print (int v);
void print (float v);
Строка 1029:
print ("Hello, world!\n");
}
</sourcesyntaxhighlight></big>
 
Как видим, компилятор и сам неплохо справляется с выбором — поэтому не нужно утруждать себя, указывая <tt>print_int</tt> или <tt>print_float</tt>.
Строка 1041:
 
Не влияет на сигнатуру, и, следовательно, перегрузку тип возвращаемого значения. Они могут быть одинаковые или разные; на возможность перегрузки это не отразится. Так делать нельзя, хотя иногда и хочется:
<big><sourcesyntaxhighlight lang="cpp">
/* нельзя! */
int get_item (int index);
const char *get_item (int index);
</sourcesyntaxhighlight></big>
(Идея в том, чтобы компилятор посмотрел, какой тип вам нужен, и в зависимости от этого выбрал нужную функцию. Си++ так не умеет,
как и подавляющее большинство других языков. Поэтому люди начинают считать данное правило ''слишком'' естественным и само собой разумеющимся. Между тем, ''есть'' языки, в которых функции можно перегружать по типу возвращаемого значения.)
 
Что же такое «принципиально разные» типы? Рассмотрим следующий ''неправильный'' пример:
<big><sourcesyntaxhighlight lang="cpp">
/* так нельзя! */
typedef char byte;
void print (char v); /* напечатать символ */
void print (byte v); /* напечатать значение байта (НЕ как символ) */
</sourcesyntaxhighlight></big>
 
Для вас <tt>byte</tt> и <tt>char</tt> — разные вещи, однако для компилятора это одно и то же. Поэтому, хотя они и выглядят по-разному, на самом деле это одинаковые типы. Перегруженные функции не могут отличаться ''только'' тем, что аргумент одной имеет тип <tt>byte</tt>, а соответствующий аргумент другой — тип <tt>char</tt>.
Строка 1062:
 
Также стоит остерегаться неоднозначности другого вида. Смотрим пример:
<big><sourcesyntaxhighlight lang="cpp">
void print (char v);
void print (char v, bool uc = true); /* Сигнатуры функций различны, здесь ошибки нет */
Строка 1075:
/* а где-то в программе ... */
print ('a'); /* Ошибка, т.к. компилятор не может выбрать подходящую функцию */
</sourcesyntaxhighlight></big>
 
Константные и неконстантные версии функций с одинаковыми аргументами также различны. Например:
<big><sourcesyntaxhighlight lang="cpp">
class foo {
void print (char v);
Строка 1089:
const foo F2;
F2.print('z'); /* используется константная версия print() */
</sourcesyntaxhighlight></big>
 
==== Когда использовать перегрузку ====
Строка 1113:
 
Области видимости блока в какой-то мере только что были рассмотрены. Обычно вложенные {} являются телами структурных операторов вроде <tt>for()</tt>, <tt>while()</tt>, <tt>if()</tt>, <tt>try()</tt> итп. Однако в соответствии с синтаксисом C++ программист имеет право окаймить {} любой фрагмент исполняемого кода по своему усмотрению, не применяя для этого особых структурных операторов, и это создаст новую вложенную область видимости со всеми её свойствами и вытекающими отсюда следствиями. Например:
<big><sourcesyntaxhighlight lang="c">
int x;
 
Строка 1139:
/* здесь продолжает делаться что-то "другое полезное" */
}
</sourcesyntaxhighlight></big>
В целом работа с блоками кода и свойства вводимых ими областей видимости в С++ не отличаются от таковых в C. Есть только два отличия:
* в C определения локальных в блоке сущностей могут располагаться только в его начале, тогда как в C++ — в любом месте блока (но не внутри других самостоятельных конструкций, например, внутри выражений);
* сущности, определённые в заголовках циклов (<tt>for()</tt>, <tt>while()</tt>) или условных операторов (<tt>if()</tt>, <tt>switch()</tt>), т.е. за пределами их блоков {} (даже если его нет, и тело оператора т.о. состоит из одного-единственного оператора, наличие блока подразумевается синтаксисом), она тем не менее считается локальной в этом блоке, т.е. как будто бы определённой внутри него:
<big><sourcesyntaxhighlight lang="c">
for (int i=0; i<10; ++i) {
/* здесь делается что-то полезное */
}
std::cout << "Цикл закончился. i равно " << i; // ошибка: переменная i является локальной для блока цикла и не существует за его пределами
</sourcesyntaxhighlight></big>
Однако тут есть подводный камень с несколькими инициализирующими выражениями. Если в первом выражении <tt>for()</tt> требуется выполнить несколько инициализаций, для чего обычно используется операция , (запятая), то эта операция будет синтаксически не операцией, а разделителем списка определений:
<big><sourcesyntaxhighlight lang="c">
int j;
for (int i=0, j=0; i<10 || j<10; ++i, ++j) {
Строка 1156:
}
std::cout << "Цикл закончился. j равно " << j; // ошибка: использование неинициализированной j
</sourcesyntaxhighlight></big>
Здесь инициализация <tt>j=0</tt> на самом деле является продолжением определения, начатого <tt>int i=0</tt>, поэтому внутри цикла используется локальная <tt>j</tt>, скрывшая ту, что определена в окаймляющей области видимости.
 
Строка 1166:
 
Самое главное отличие (в рассматриваемом контексте) классов C++ от структур C — это возможность наследования. Каждый класс в иерархии имеет свою область видимости. При этом производный класс имеет окаймляющей областью область видимости базового, что позволяет любому производному классу иметь сущности с теми же именами, что встречаются у базового. О сокрытии имён помнят все, но многие забывают о нём в контексте перегрузки. Например:
<big><sourcesyntaxhighlight lang="c">
struct X
{
Строка 1176:
void foo(float);
};
</sourcesyntaxhighlight></big>
Здесь не так уж мало людей посчитают, что метод <tt>foo(float)</tt> в производном классе перегружает метод <tt>foo(int)</tt> из базового. На самом деле этого не произойдёт — в области видимости производного класса имя <tt>foo()</tt> скрыло это же имя из окаймляющей области видимости базового. Чтобы перегрузка всё-таки работала, что обычно и является целью автора производного класса, следует имя <tt>X::foo</tt> явным образом внести в область видимости класса Y:
<big><sourcesyntaxhighlight lang="c">
struct Y: X
{
Строка 1184:
void foo(float);
};
</sourcesyntaxhighlight></big>
Вот теперь перегрузка будет работать, как задумывалось.
 
Если у класса имеется несколько базовых классов, в совокупности все их области видимости являются окаймляющей областью видимости для производного. При этом каждый базовый класс на равных правах участвует в наполнении окаймляющей области видимости именами сущностей. Например:
<big><sourcesyntaxhighlight lang="c">
struct X
{
Строка 1203:
void bar() { foo(123u); } // неоднозначность вызова
};
</sourcesyntaxhighlight></big>
Другими словами окаймляющая область видимости производного класса наполняется '''всеми''' именами из '''всех''' его непосредственных базовых классов. Однако это не значит, что области видимости базовых классов просто слепо объединяются в одну. Если имена из разных базовых классов конфликтуют друг с другом так, что будучи размещёнными в некой действительно единой отдельной области видимости, вызовут ошибку, то в случае наполнения такими именами окаймляющей области видимости производного класса ошибки не будет, но безусловно ошибка будет детектирована, когда к таким конфликтующим именам произойдёт обращение, если только специально не озаботиться разрешением неоднозначности:
<big><sourcesyntaxhighlight lang="c">
struct X
{
Строка 1221:
void bar2() { X::foo(123); } // нет неоднозначности
};
</sourcesyntaxhighlight></big>
Заметим, что в наполнении именами окаймляющей области видимости участвуют только непосредственные базовые классы. Если те в свою очередь также имеют базовые классы, те будут наполнять '''их''' окаймляющие области видимости, что являются ещё более внешними для рассматриваемого производного. Это создаёт довольно сложное вложение областей видимости, которые, подобно эдаким мультиматрёшкам, кольцами вложений опоясывают область видимости производного. Если по некоему направлению роста иерархии базовые классы закончились, а по другим нет, более короткие маршруты больше не участвуют в наполнении именами более внешних слоёв областей видимости, и во внимание принимаются только оставшиеся. И только когда с очередным кольцом вложения базовых классов больше не остаётся, окаймляющей областью видимости становится область видимости определения рассматриваемого производного класса. И причём только его, области видимости определений никаких его базовых классов, ни непосредственных, ни опосредованных, не рассматриваются. Например:
<big><sourcesyntaxhighlight lang="c">
namespace XY
{
Строка 1245:
/* ну и тут тоже */
}
</sourcesyntaxhighlight></big>
Здесь кольца областей видимости будут сформированы следующим образом (для простоты квалификация <tt>XY::</tt> опущена):
# T
Строка 1257:
 
Второе важное отличие классов C++ от структур C — классы, определённые в области видимости другого класса, в C++ входят в его область видимости, тогда как структуры внутри других структур в C принадлежат глобальной области видимости. Например:
<big><sourcesyntaxhighlight lang="c">struct X
{
struct Y
Строка 1267:
struct Y y1; // вполне законная конструкция в C, но вызывающая ошибку компиляции в C++
struct X::Y y2; // правильная конструкция в C++, но невозможная в C, т.к. в нём отсутствует операция ::
</sourcesyntaxhighlight></big>
Окаймляющей областью видимости для вложенных классов является область видимости класса, в пределах которого определён рассматриваемый вложенный класс. Так, если в предыдущем примере класс T был бы определён не в блоке функции f(), а внутри некоего класса, то в пятом кольце вложения фигурировал бы именно он. Далее же, в сторону ещё более внешних колец вложения, рассматривались бы его окаймляющие области видимости, как было описано выше.
 
Строка 1284:
В итоге глобальная область видимости в языке C имеет два уровня локальности: уровня единицы трансляции (иногда называемой модулем за сходство с термином "модуль" из других языков, но это не является официальным названием, да и сходство большей частью внешнее) и уровня приложения. Возможность объявить некое имя локальным для единицы трансляции предоставляется путём ключевого слова <tt>static</tt>. Если его нет в объявлении сущности, имя получает видимость уровня приложения. Локальные в единице трансляции имена, хоть и расположены в глобальной области видимости, но невидимы из других единиц, тогда как остальные видны отовсюду (если только не скрыты, конечно, какой-нибудь локальной областью видимости). Например:
 
<big><sourcesyntaxhighlight lang="c">
/* файл source1.c */
 
Строка 1311:
/* если же компилятору дать объявление y, то будет ошибка линковки, ибо он не найдёт публичного глобального имени y */
}
</sourcesyntaxhighlight></big>
 
Грамотное использование <tt>static</tt> позволяет имитировать приватные сущности классов, если в качестве класса рассматривать единицу трансляции. В этом ключе отсутствие <tt>static</tt> определяет публичную сущность "класса". Обычно объявления всех публичных сущностей помещают в заголовочный файл единицы трансляции, и он т.о. документирует её публичный интерфейс. Все, кого интересует подзадача, решаемая в рамках этой единицы трансляции (в частности и другие заголовки, которые определяют свои интерфейсы, если они зависят от этого интерфейса), просто подключают этот заголовок, и компилятору содержащихся там объявлений вполне хватает, а ежели чего не хватает, он отдаёт на откуп линкеру. Определения же в заголовках никогда не размещают (за исключением типов и встраиваемых — <tt>inline</tt> — функций... ну ещё макросов, но они не относится к рассматриваемому здесь аспекту языка). Определения всегда располагаются в том .c файле, который реализует задокументированный в заголовочном файле интерфейс.
Строка 1323:
===== Пространства имён в C =====
 
В C структуры («<tt>struct</tt>»), объединения («<tt>union</tt>») и перечисления («<tt>enum</tt>») могли не просто иметь (а могли и не иметь) имена, они могли иметь совпадающие имена. Дело в том, что их определения не создавали новых именованных типов, они только давали имена типам структуры, объединения или перечисления. Формально в C имеется четыре пространства имён: имена структур, объединений, перечислений и все остальные. Т.о.<big><sourcesyntaxhighlight lang="c">struct Foo { /* что-нибудь */ };
union Foo { /* что-либо */ };
enum Foo { /* что-то */ };
int Foo; /* и даже так */</sourcesyntaxhighlight></big>вполне могли сосуществовать и не конфликтовать. Когда некое имя встречается в программе, оно обязательно должно быть квалифицировано одним из ключевых слов <tt>struct</tt>, <tt>union</tt> или <tt>enum</tt>, чтобы указать, в каком пространстве имён его искать. Не увидев ничего, компилятор осуществлял поиск имени в общем пространстве, где размещались все остальные идентификаторы — типы, функции, переменные. Зачастую можно встретить в C-программе конструкции вида
<big><sourcesyntaxhighlight lang="cpp">
typedef struct Foo_tag
{
/* ... */
} Foo;
</sourcesyntaxhighlight></big>
которые использовали для имён в не общих пространствах никогда больше нигде не встречающиеся идентификаторы. <tt>typedef</tt> является единственным способом внести имена пользовательских типов в общее пространство, и после такого внесения им можно пользоваться на общих основаниях без квалификации, что конечно в большинстве случаев удобнее.
 
В Си++ такого деления больше нет. Хотя ''typedef'' никто не отменял, так что С-конструкции в духе приведённой вполне себе определяли типы данных аналогично C, но являются избыточными. Для пущей совместимости со старыми проектами на C эти пространства имён всё же имеются, и компилятор даже пытается вести себя как примерный C-компилятор. Однако в суровых реалиях C++ это не несёт никакой выгоды, т.к. каждое имя структуры, класса, объединения и перечисления автоматически помещается так же и в общее пространство, зато может привести к путанице. Например, вы можете написать просто:
<big><sourcesyntaxhighlight lang="cpp">
struct Foo
{
Строка 1343:
 
Foo var;
</sourcesyntaxhighlight></big>
Т.о. пространства имён в терминах языка C в языке C++ более не нужны, и этот термин был переопределён с совершенно иным смыслом.
 
Строка 1351:
 
В C++ дела обстоят несколько лучше, кольца областей могут быть построены на уровне областей видимости классов. Поэтому распространённой практикой для публикации интерфейсов подзадач стало использовать классы. При этом классы, обладая куда большими возможностями, чем требуется для этой цели, оказываются с одной стороны избыточными, т.к. формально используется только малая часть их потенциала, с другой стороны являются слишком грубым инструментом, т.к. не все потребности документирования интерфейсов классы могут предоставить. К примеру, такой типичный класс и некое его использование могли выглядеть так:
<big><sourcesyntaxhighlight lang="cpp">
struct ISomeInterface
{
Строка 1377:
 
ISomeInterface::func2(ISomeInterface::var1, ISomeInterface::func1(ISomeInterface::SomeBlackBox());
</sourcesyntaxhighlight></big>
Несложно увидеть, что использовать такой класс будет не очень удобно в виду особенностей свойств областей видимости классов, но и сами свойства классов используются весьма консервативно. К примеру, нет смысла создавать экземпляры подобных классов, что в иных случаях является чуть ли не основным преимуществом, предлагаемых классами.
 
Пространства имён были призваны избавить проектные решения от этих недостатков. И с честью с этим справились. Давайте посмотрим на тот же код, но в "правильном" виде:
<big><sourcesyntaxhighlight lang="cpp">
namespace ISomeInterface
{
Строка 1411:
 
func2(ISomeInterface::var1, func1(ISomeInterface::SomeBlackBox());
</sourcesyntaxhighlight></big>
Текст программы почти не изменился, разве что исчезли "паразитные" <tt>static</tt>, возможно совмещение объявлений и определений сущностей, где это выгодно, а использование имён из локальной области видимости пространства имён стало возможным упростить подавлением обязательной их квалификации (что и было сделано в примере).
 
В целом пространств имён создаёт локальную область видимости, обладающую всеми качествами глобальной. Но при этом они могут при необходимости иметь окаймляющую область видимости, что позволяет на их основе создавать иерархии колец, подобных классовым.
<big><sourcesyntaxhighlight lang="cpp">
namespace Foo {
namespace Bar {
Строка 1426:
Foo::Bar::Baz::z = 17;
}
</sourcesyntaxhighlight></big>
Тут также видно, что именованные пространства имён, т.е. те, которые сами имеют имена, позволяют однозначно идентифицировать их локальные сущности посредством квалификации, причём используемый при этом синтаксис не отличается от квалификации имён для сущностей из классовых областей видимости. Фактически именованные пространства имён похожи на классы с публичными статическими элементами. Но есть и важные отличия.
 
* Пространства имён открыты. Их можно дополнять в разных местах программы. Пример:
<big><sourcesyntaxhighlight lang="cpp">
namespace MyFavouriteTypes {
typedef unsigned char byte;
Строка 1447:
/* … */
}
</sourcesyntaxhighlight></big>
 
При этом неважно записано ли всё это в одном файле или разных. Вот ещё пример:
Строка 1454:
<dt><tt>mytypes.h</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
namespace MyFavouriteTypes {
typedef unsigned char byte;
}
</sourcesyntaxhighlight></big>
 
<dt><tt>histypes.h</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
namespace MyFavouriteTypes {
typedef char *string;
typedef const char *const_string;
}
</sourcesyntaxhighlight></big>
 
<dt><tt>main.cpp</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
#include "mytypes.h"
#include "histypes.h"
Строка 1480:
}
</sourcesyntaxhighlight></big>
</dl>
 
Строка 1491:
<dt><tt>var.h</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
namespace Foo {
namespace Bar {
Строка 1499:
}
}
</sourcesyntaxhighlight></big>
 
<dt><tt>var.c</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
#include "var.h"
int Foo::Bar::Baz::z = 0;
</sourcesyntaxhighlight></big>
 
<dt><tt>user1.c</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
#include "var.h"
void func1 () {
Foo::Bar::Baz::z = 1;
}
</sourcesyntaxhighlight></big>
 
<dt><tt>user2.c</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
#include "var.h"
void func2 () {
Foo::Bar::Baz::z = 2;
}
</sourcesyntaxhighlight></big>
</dl>
 
Строка 1539:
# Неименованное пространство имён неявно всегда видимо в текущей единице трансляции.
Фактически следующая конструкция
<big><sourcesyntaxhighlight lang="cpp">
namespace
{
/* ... */
}
</sourcesyntaxhighlight></big>
аналогична
<big><sourcesyntaxhighlight lang="cpp">
namespace Некое_Уникальное_Имя
{
Строка 1552:
}
using namespace Некое_Уникальное_Имя;
</sourcesyntaxhighlight></big>
где <tt>Некое_Уникальное_Имя</tt> единственно и неповторимо для единицы трансляции, компилятор должен сам гарантировать их уникальность для каждой. Собственно Стандарт языка именно так неименованные пространства имён и определяет.
 
Строка 1560:
 
Зачастую в учебных пособиях роль пространств имён преподносится как простая возможность избавится от конфликтов имён в глобальной области видимости. В общем-то это так, но это является весьма частным случаем их применения, фактически очень прозрачным следствием. Ранее было показано, что пространства имён в C++ предназначены главным образом для инкапсуляции интерфейсов. Действительно, предположив, что математическая абстракция "комплексное число" может быть адекватно отображена на C++ просто классом, мы столкнулись бы с рядом неудобств. Нетрудно написать что-то вроде
<big><sourcesyntaxhighlight lang="cpp">
class complex
{
Строка 1571:
/* ... */
};
</sourcesyntaxhighlight></big>Инкапсуляция методов позволяет чётко документировать интерфейс нашего класса, вопросов нет. Однако одной только арифметикой комплексные числа не исчерпываются. Для них вполне определены и элементарные функции, для них было бы удобным иметь перегруженные операции ввода/вывода и т.п. Но мы как-то привыкли писать <tt>sin(x)</tt> вместо <tt>x.sin()</tt>, а библиотека потоков ввода/вывода оперирует глобальными перегруженными операторами, а отнюдь не методами классов. Делать нечего, ради удобства использования без глобальных сущностей не обойтись.
 
Совсем другое дело, если инкапсулировать интерфейс не в класс, а в пространство имён.
<big><sourcesyntaxhighlight lang="cpp">
namespace complex_numbers
{
Строка 1607:
const complex i = complex(0, 1);
}
</sourcesyntaxhighlight></big>
Несложно видеть, как пространство имён изящно решает эту проблему. Интерфейс нашей сущности состоит из класса (одного или более), свободных функций, переменных, типов, но в то же время совершенно не захламляет глобальную область видимости и чётко обозначает границы распространения влияния имён принадлежащих интерфейсу сущностей. Так, никакое другое имя <tt>sin</tt> или <tt>i</tt> более не будет конфликтовать с <tt>complex_numbers::sin</tt> или <tt>complex_numbers::i</tt>.
 
Строка 1613:
 
На самом деле квалификация имён в перспективе очень помогает, т.к. явно указывает целевую сущность, тогда как отсутствие квалификации помогает только меньше писать и экономить пространство на диске или трафик в сетях. Понятно, что имена пространств имён —- это тоже имена, и их определения тоже принадлежат некой области видимости. И эти имена также подвержены непреднамеренным конфликтам, как и любые другие. С этой точки зрения для уменьшения вероятности таких коллизий имена для них следует выбирать подлиннее и попонятнее, чтобы те отражали суть абстракции, интерфейс к каковой в этом пространстве имён инкапсулирован. При этом квалифицированные имена могут стать настолько длинными, что несмотря на все преимущества явной квалификации работать и ними будет настолько неудобно, что минусы перевесят преимущества. Для нейтрализации негативного эффекта слишком длинных квалифицированных имён в C++ предусмотрена простая возможность переименования пространств имён. Например:
<big><sourcesyntaxhighlight lang="cpp">
void foo()
{
Строка 1628:
/* ... */
}
</sourcesyntaxhighlight></big>На самом деле "переименование" не совсем точный термин. Это скорее аналог <tt>typedef</tt>, ибо старое имя никуда не девается. Новое имя позволяет существенно сократить длину квалифицированных идентификаторов, причём в каждой локальной области видимости новое имя можно подбирать индивидуально, чтобы оно было и коротким, и понятным, и не вызывало коллизий.
 
Несмотря на то, что квалификация более полезна, чем неудобна, тем не менее, когда целевые сущности явно оговорены (например, в документации) или локализованы (например, внутри функции), то явная квалификация уже перестаёт играть настолько важную роль. Поэтому для подавления обязательности явной квалификации имеются средства. Ключевое слово «<tt>using</tt>» — одно из них.
Строка 1635:
 
«<tt>using</tt>» может использовать двояко. Во-первых, вы можете просто указать, какие сущности из интерфейса нужно включить в текущую область видимости, чтобы для них квалификация более не требовалась. Выглядит это примерно так:
<big><sourcesyntaxhighlight lang="cpp">
#include <iostream>
 
Строка 1644:
cout << "Hello, world!" << endl;
}
</sourcesyntaxhighlight></big>
Эта конструкция называется <tt>using</tt>-объявлением. Здесь имена <tt>cout</tt> и <tt>endl</tt> были внесены в глобальную область видимости, что позволило обойтись без <tt>std</tt> при их использовании. В принципе подобные имена действительно весьма редко используются в других интерфейсах, тогда как интерфейс <tt>std</tt> используется наоборот, очень часто. Формально такое внесение часто оправдано. Однако не всегда. <tt>using</tt>-объявление называется так не случайно. Дело в том, что она ''имеет семантику'' именно что объявления. Другими словами теперь в глобальной области видимости объявлены имена <tt>cout</tt> и <tt>endl</tt> так, как будто бы (ну почти, есть некоторая разница, но не весьма существенная) они там определены изначально. Это играет свою роль в точности так же, как и любые другие объявления. Например:
<big><sourcesyntaxhighlight lang="cpp">
//double f; // точка 0
namespace X
Строка 1662:
// точка 2
double f; // точка 3
</sourcesyntaxhighlight></big>
В точке 1 в текущей области видимости переобъявлены оба имени <tt>f</tt> из пространства имён <tt>X</tt>. Т.к. пространства имён открыты, то всегда, в частности и позже такого переобъявления, их можно дополнить, что и осуществляется чуть ниже. Однако это никак не отразится на списке (пере)объявленных ранее имён, так что в точке 2 в текущей области новое перегруженное имя ''не появится''. Что же касается точки 3, то с ней вообще всё плохо. Функции-то могут быть перегружены, но имя переменной по-любому не может перегружать что-либо, так что там будет банальная ошибка компиляции из-за конфликта с именами, внесённых в точке 1. И наоборот, если перенести определение из точки 3 в точку 0, то теперь уже <tt>using</tt>-объявление в точке 1 вызовет точно такую же ошибку.
 
Итог: использование <tt>using</tt>-объявлений следует делать как можно более локальными. Иначе от пространств имён не будет никакого толку. Т.о. исходный пример лучше было бы написать вот так:
<big><sourcesyntaxhighlight lang="cpp">
#include <iostream>
 
Строка 1675:
cout << "Hello, world!" << endl;
}
</sourcesyntaxhighlight></big>
Здесь текущей областью видимости является блок функции, а не глобальная, так что влияние переобъявления тоже локализовано, и более не мешает ничему снаружи функции <tt>main()</tt>.
 
Строка 1682:
<dt><tt>mytypes.h</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
namespace MyFavouriteTypes
{
typedef unsigned char byte;
}
</sourcesyntaxhighlight></big>
 
<dt><tt>main.c</tt>
<dd>
<big><sourcesyntaxhighlight lang="cpp">
#include "mytypes.h"
 
Строка 1700:
/* … */
}
</sourcesyntaxhighlight></big>
</dl>
 
Строка 1706:
 
Теперь, если в приведённом выше примере попробовать изменить <tt>using X::f</tt> на <tt>using namespace X</tt>, то в точке 2 будут видны все три функции, а компиляция в точке 3 (или 1, если перенести её в точку 0) пройдёт успешно. Тем не менее использование имени <tt>f</tt> всё равно будет вызывать ошибку из-за конфликта имён, разрешать который придётся явно и вручную (конечно же явной квалификацией). Поэтому и эту директиву имеет смысл локализовывать, например внутри функции. Тогда её действие распространяется только на эту функцию:
<big><sourcesyntaxhighlight lang="cpp">
#include "mytypes.h"
 
Строка 1714:
/* … */
}
</sourcesyntaxhighlight></big>
 
В заключение следует отметить, что многие пишут «<tt>using namespace std</tt>» и радуются, но, возможно, пример, приведённый выше, демонстрирует несколько лучший стиль программирования.
Строка 1740:
Полное квалифицированное имя может иметь две разные формы. Во-первых, имя может в качестве квалификатора иметь полный маршрут к себе, начиная с глобальной области видимости. Такие имена начинаются с <tt>::</tt> и содержат последовательность имён областей видимости, в которой каждая последующая должна быть определена в предыдущей как окаймляющей. В простейшем случае вся квалификация может ограничиваться только <tt>::</tt>, что означает обращение к имени из глобальной области видимости. В более сложных случаях маршрут может состоять из серии имён, разделённых <tt>::</tt>. Именами тут могут быть как пространства имён, так и классы, однако в последнем случае, если используется операция <tt>::</tt>, то компонентами полного квалифицированного имени не могут быть нестатические поля и методы классов. Однако это не всегда так. Имеются контексты, когда такие нестатические элементы классов могут рассматриваться как не привязанные к конкретному его экземпляру. Примером такого контекста является указатель на элементы класса. В таких случаях использование нестатических полей и методов справа от <tt>::</tt> допускается.
 
С именами из областей видимости классов вообще не всё так просто. Поначалу компоненты пути могут представлять имена пространств имён. Но если с некоей позиции маршрута компонентом пути встретилось имя класса, то последующими компонентами могут быть только базовые или вложенные классы. И только самым последним компонентом квалифицированного имени (собственно искомым именем) может являться любая сущность класса. Вернуться же обратно к областям видимости пространств имён более невозможно. Любопытно, что вложенные классы по-прежнему ведут к внутренним кольцам областей, тогда как базовые наоборот, к внешним. Но ведь это означает совершенно разные направления! Смотрите:<big><sourcesyntaxhighlight lang="cpp">struct A1
{
struct B1
Строка 1757:
 
::A1::B1::x;
::A2::B2::x;</sourcesyntaxhighlight></big>
Из этого следует, что компилятор не просто должен идти по указанному маршруту, он обязан связывать '''каждое''' имя маршрута, чтобы выяснить, что оно обозначает, и т.с. правильно расшифровать семантику квалифицированного имени.
 
Во-вторых, квалификация может начинаться с имени экземпляра класса или указателя на него, в частности <tt>this</tt>. В этом случае компонентами маршрута могут быть только элементы этого экземпляра класса, в частности статические. Разделителями компонентов маршрута могут являться <tt>.</tt> для экземпляров элементов, или <tt>-></tt> для указателей на экземпляры элементов, или <tt>::</tt> для элементов, не являющихся экземплярами. Примеры каждого:
<big><sourcesyntaxhighlight lang="cpp">
void g();
 
Строка 1786:
delete b;
}
</sourcesyntaxhighlight></big>Здесь полная квалификация имени подразумевает, что над областью видимости ''самого базового'' класса для ''самого внешнего'' класса больше нет никаких окаймляющих, поэтому выйти на уровень областей видимости пространств имён невозможно. Так что если раскомментировать строку с вызовом якобы метода <tt>g()</tt> класса <tt>A</tt> для экземпляра <tt>a</tt>, компилятор выдаст ошибку, а не попробует вызвать глобальную функцию <tt>g()</tt>.
 
Разумеется эти два способа полной квалификации можно комбинировать. В уже приведённом примере операция <tt>::</tt> использовалась для перехода в окаймляющую область видимости <tt>A</tt> — класса, базового для <tt>B</tt>, чьим экземпляром является <tt>b</tt>, и в результате был вызван нестатический метод. Аналогично, начиная с некоторого компонента пути от глобальной области видимости, могут быть встречены операции <tt>.</tt> и <tt>-></tt> в маршруте, и стало быть далее будут рассматриваться экземпляры классов или указатели на них, что позволяет использовать их нестатические поля и методы.
Строка 1801:
 
Во-вторых, есть интересное следствие из описанных правил, которое лежит на поверхности, однако мало кто обращает на него внимание. Следует сразу сказать, что и тут описание упрощено, иначе пришлось бы разговаривать о (автоматическом) внесении имён в область видимости класса. Пока обойдёмся без этого. Рассмотрим пример:
<big><sourcesyntaxhighlight lang="cpp">
struct A
{
Строка 1827:
}
};
</sourcesyntaxhighlight></big>
Мы видим у <tt>B</tt> приватный базовый класс <tt>A</tt>, который в <tt>C</tt> уже полностью недоступен. Причина такой жёсткой инкапсуляции может быть какой угодно, не суть важно, главное то, что <tt>B</tt> т.о. закрыл доступ к своему подобъекту <tt>A</tt>. Но что делать <tt>C</tt>, если ему требуется работать с экземплярами класса <tt>A</tt>? Ведь со стороны <tt>B</tt> глупо закрывать доступ к любым экземплярам <tt>A</tt>, его интересов касается только его собственный подобъект, а не вообще всевозможные объекты этого типа. Почему же <tt>C</tt> не должен иметь возможности работать с экземплярами <tt>A</tt> просто через их публичный интерфейс, если они никак не связаны с <tt>this</tt>? Строки 1-4 демонстрируют безуспешные попытки добраться до публичного интерфейса A, и их безуспешность правильна: неполное квалифицированное имя метода <tt>f()</tt> видимо, успешно связывается, но в этих точках недоступно для использования.
 
Строка 1839:
 
Поиск имён, зависящий от типов аргументов, впервые был предложен Эндрю Кёнигом (Andrew Koenig) для решения следующей проблемы использования пространств имён. Рассмотрим пример:
<big><sourcesyntaxhighlight lang="cpp">
namespace X
{
Строка 1854:
x + y; // 1
X::operator+(x, y); // 2
</sourcesyntaxhighlight></big>
С точки зрения организации программы тут всё сделано правильно. Имеется интерфейс, опубликованный пространством имён <tt>X</tt>. Им предлагается класс <tt>Foo</tt> и перегруженная для него операция сложения. Пользователь интерфейса <tt>X</tt> не пожелал использовать <tt>using</tt>, и это не просто его право, как было сказано выше, неиспользование <tt>using</tt> без настоятельной необходимости только приветствуется. Вот только использование перегруженной операции весьма затруднено, ведь она расположена в области видимости, недоступной без явной квалификации. Поэтому пользователь вместо интуитивного и привычного синтаксиса использования операторов в точке 1 вынужден или использовать функциональный стиль, как в точке 2, или так или иначе применить <tt>using</tt>.
 
Строка 1865:
* ADL можно подавить, заключив имя функции в круглые скобки. Т.е. встретив <tt>(abs)(CM::i)</tt> вместо <tt>abs(CM::i)</tt>, компилятор не будет заглядывать в пространство имён <tt>CM</tt>, а ограничится при поиске имени <tt>abs</tt> только текущей областью видимости. Об этом есть явное упоминание в Стандарте языка, но на самом деле оно там необязательно. Отключение ADL для имён в () следует из остальной грамматики языка, однако это следствие неочевидно, потому оговорено явно для акцентирования внимания на этой возможности.
* ADL конфликтует с <tt>using</tt>-директивой в том смысле, что имена, найденные посредством ADL и видимые посредством <tt>using</tt>-директивы, равноправны. Например:
<big><sourcesyntaxhighlight lang="cpp">
namespace Y { class A {}; void f(A); }
namespace X { void f(Y::A); }
Строка 1874:
 
f(a); // неоднозначность
</sourcesyntaxhighlight></big>
 
Может сложиться впечатление, что ADL - это не более чем просто удобно. Ну за исключением операторов, там да, ADL даёт более ощутимое удобство, чем для функций. Однако нет. В контексте шаблонов ADL выходит на качественно новый уровень значимости. Абсолютно без преувеличения можно сказать, что без ADL использование шаблонов было бы ужасно неудобным и в ряде случаев просто невозможным.
Строка 1891:
Блок отлавливающий (<tt>try</tt>) и обрабатывающий (<tt>catch</tt>) исключения заданного типа выглядит так:
 
<big><sourcesyntaxhighlight lang="cpp">
try
{
Строка 1900:
...
}
</sourcesyntaxhighlight></big>
 
Оператор <tt>throw</tt> имеет необязательный параметр — объект. Использование <tt>throw</tt> без
Строка 1929:
Любая функция может быть помечена спецификацией исключений:
 
<big><sourcesyntaxhighlight lang="cpp">
void f() throw(CFileException, CAccessDeniedException);
</sourcesyntaxhighlight></big>
 
Функция с такой пометкой может вызывать только те исключения, что перечислены в пометке.