Пространство имен (namespace) является фундаментальной концепцией C++. Пространство имен - это группа имен, в которой имена не совпадают. Исключением являются имена перегружаемых функций и переменные с различными областями действия. Имена в различных пространствах имен не конфликтуют. Например, можно использовать имя my_adress в двух различных классах (это же относится к структурам и объединениям). C++ также позволяет определить пространства имен при помощи ключевого слова namespace . Такие пространства имен вводятся для снижения вероятности конфликта имен и полезны в случае использования имен из нескольких различных библиотек.
Спецификатор namespace определяет пространство имен функций, классов и переменных, находящихся в отдельной области видимости. Вообще namespace определяет группу символов, представляющих собой названия или имена функций, классов или переменных. Примером такого определения области видимости является область видимости членов класса.
Синтаксис:
{
// объявления и определения имен
}
Аргументом спецификатора namespace (то есть name ) является идентификатор пространства имен.
Чтобы обратиться к чему-нибудь в пространстве namespace_name, достаточно обратиться непосредственно к члену, используя оператор определения области видимости (::): namespace_name::member .
Однако, при многократном использовании члена member такая запись становится слишком громоздкой. Инструкция using заставляет компилятор признавать дальнейшее использование этому члену пространства namespace_name без дополнительного определения имени этого пространства:
using namespace_name::member;
Инструкция using namespace заставляет компилятор признавать все члены пространства имен namespace_name:
using namespace namespace_name;
В приведенном ниже примере глобальные переменные Cary и Hugh объявляются в пространстве имен grants:
{
int Cary = 0;
int Hugh = 1;
}
Вне пространства имен grants переменные могут быть упомянуты как grants::Cary и grants::Hugh. Но можно сократить эту запись, если добавить такую инструкцию:
using grants::Cary;
Теперь имя Cary может использоваться в последующих инструкциях без дополнительного определения. Чтобы иметь возможность ссылаться на обе переменные без определения имени пространства, достаточно написать
using namespace grants;
Операторы приведения типов (cast operators) позволяют изменять тип выражения: они вычисляют значение выражения, изменяют его тип и присваивают значение нового типа результату. В некоторых случаях это может привести к изменению внутреннего формата данных (изменению вида данных в памяти компьютера). Ниже приведены основные операторы приведения типов и способы их использования.
-
const_cast. Позволяет удалить модификатор const из указателя. Этот оператор предоставляет возможность передавать указатель, объявленный с модификатором const, аргументу, объявленному без этого модификатора.
-
dynamic_cast. Во время выполнения программы проверяет, что объект, на который ссылается указатель, имеет определенный тип.
-
reinterpret_cast. Выполняет преобразование указателей на пустой тип (void *) в указатели на более конкретные типы (int *, char * и т.д.)
-
static_cast. Обеспечивает преобразование родственных типов указателей или объектов. Этот оператор можно использовать для подавления выдачи предупреждений при компиляции в случае замены типа, занимающего больший объем памяти, на тип, занимающий меньший объем памяти.
Операторы приведения типов языка C поддерживают все эти операции, однако синтаксис является другим. Оператор приведения dynamic_cast является новым элементом ANSI C++.
Таким образом, ANSI C++ предоставляет четыре разных оператора приведения, каждый из которых обладает специфическими возможностями. Операторы C оставлены в C++ для обеспечения преемственности.
Оператор приведения старого стиля
Язык C и ранние версии C++ предоставляют всего один оператор приведения типов практически для всех ситуаций. В ANSI C++ этот важный оператор приведения также сохранен. Следующее выражение имеет то же значение, что и выражение expr, но оно приведено к типу, определенному в скобках:
(type) expr
Хотя выражение (type) expr теоретически имеет то же значение, что и выражение expr , действие оператора приведения типа может привести к преобразованию данных, которые изменят внутреннее представление числа в памяти.
Оператор приведения типа старого стиля также можно определить, используя альтернативный синтаксис:
type(expr)
Приведенный ниже пример позволяет вывести на печать текущее значение целого i в формате с десятичной точкой:
cout << (double) i;
В целом существует очень мало ситуаций, когда оператор приведения типов может как-то повлиять на поведение программы. Одна из таких ситуаций показана в следующем примере. Присваивая значения и вызывая функции, компилятор автоматически переводит целые числа в действительные, поэтому в таком случае явное выполнение приведения типов не нужно.
Операторы приведения типов языка C можно использовать во всех ситуациях, в которых используются операторы приведения типов ANSI C++ за исключением dynamic_cast. Два следующих оператора выполняют одни и те же действия:
char *p = reinterpret_cast<char *>(malloc(n));
Следующая пара операторов, которые подавляют предупреждение компилятора, также выполняют одно и те же действие:
bool flag1 = static_cast<bool>(12);
Удаление модификатора const
Оператор приведения типа const_cast предусмотрен для облегчения вызова функций, которые должны использовать модификатор const, но не используют его. Такие функции работают с параметром типа указатель и никогда не изменяют данные, на которые ссылается этот указатель, даже если программист не определил параметр как const. Правила C++ запрещают передачу константного указателя в качестве фактического параметра такой функции, оператор const_cast предоставляет возможность обойти этот запрет.
Следующее выражение имеет то же значение, что и expr. Обозначенный тип type должен быть таким же, что и тип expr, кроме отсутствия модификатора const или volatile. Как правило, type - это тип указателя:
const_cast<type>(expr)
В следующем примере функция display_num использует в качестве параметра указатель, но не данные, на которые он указывает (*p):
{
printf("The value is %6.3f\n", *p);
}
Так как заданные посредством указателя p данные не изменяются, должна быть возможность передачи в качестве параметра const-указателя. Правила C++ запрещают это из-за несоответствия типов указателей:
display_num(&x); // ошибка
Чтобы обойти этот запрет, нужно лишить указатель модификатора const. Следующая операция позволяет устранить запрет:
display_num(const_cast<double *>(&x));
При использовании оператора const_cast необходимо быть уверенным, что данные, на которые указывает указатель, не изменяются. При задании оператора const_cast и одновременной попытке изменить данные результаты выполнения программы будут непредсказуемы.
Оператор dynamic_cast предоставляет возможность проверить, что указатель базового класса указывает на объект определенного производного класса. Оператор dynamic_cast проверяет тип объекта во время выполнения программы, используя информацию RTTI.
Следующее выражение должно возвращать ссылку на заданный тип type. Во время выполнения задачи объект, указанный в аргументе expr, должен иметь определенный тип type или тип, производный от типа type, иначе приведение типа не выполняется и возвращается нулевой указатель:
dynamic_cast<type *>(expr)
Проверка типа во время выполнения программы
Оператор dynamic_cast также можно использовать со ссылочными типами. Если приведение типа не выполняется из-за того, что expr имеет тип , не совпадающий с типом type или производным от него, то вырабатывается исключение bad_cast .
Для этого оператора приведения типов существует единственное ограничение: при использовании его для приведения к указателю на производный класс (что является его главным назначением), класс выражения expr должен иметь, по крайней мере, одну виртуальную функцию. По этой причине оператор dynamic_cast часто называется оператором полиморфного приведения типа (polymorphic cast). Также возможно применение оператора dynamic_cast для приведения к указателю базового класса без каких-либо ограничений. Если типы не связаны вообще, компилятор не допускает преобразования.
Наиболее полезным в операторе dynamic_cast является то, что указатель базового класса может указывать на производные классы. Некоторые из этих классов поддерживают функции, которых нет в других классах.
Например, следующий фрагмент программы определяет базовый класс B и производный класс D, а затем присваивает его адрес указателю базового класса:
{
public:
virtual void func1(int);
};
class D
{
public:
void func2(void);
};
// ...
D od;
B *pb = &od;
В последней строке этого примера указателю на класс B присваивается адрес объекта производного класса D.
Следующая функция использует оператор dynamic_cast, чтобы проверить, действительно ли параметр типа B * указывает на объект класса D. Если это так, то вызывается функция func2, которая определена только в классе D и классах, производных от D:
{
D *pd;
pd = dynamic_cast<D *>(arg);
if (pd) pd->func2(void);
// ...
}
Если arg указывает на объект класса D или класса, производного от D, то преобразование будет выполнено, и функция process_B вызовет функцию D::func2 через указатель pd. Иначе операция преобразования не выполняется и pd присваивается значение NULL.
Приведение типа указателя
Оператор reinterpret_cast используется для приведения указателя к другому типу. Новый тип не обязательно должен быть связан со старым, этот же оператор позволяет приводить типы между указателями и целыми числами.
Следующий оператор возвращает то же значение выражения, что и выражение expr, но приведенное к заданному типу type. Этот тип, который обычно является типом указателя, может отличаться от типа выражения expr только интерпретацией (способом использования). Сами данные и их внутреннее представление в памяти при этом не изменяются.
reinterpret_cast<type>(expr)
Этот оператор нельзя использовать для удаления модификаторов const и volatile из выражения expr, так как это могло бы привести к возможности приведения типов с помощью оператора reinterpret_cast между указателями, а также между указателями и целыми числами без каких-либо ограничений.
Вообще оператор reinterpret_cast никогда не изменяет внутреннее представление данных, на которые ссылается указатель, определяемый выражением expr . Тем не менее, когда тип указателя и тип данных, на которые он указывает, не соответствуют друг другу, результат может быть просто драматичен.
Наиболее часто этот оператор используется для преобразования значения, возвращаемого как void *, к более конкретному типу. В C++ нельзя присвоить указатель, объявленный как void*, указателю другого типа без приведения типа. Например,
char *p = reinterpret_cast<char *>(malloc(100));
Хотя возвращенное значение функции malloc не обязательно переводить в другой тип указателя немедленно, такое приведение должно быть выполнено перед первым обращением к выделенной области памяти. Одним из преимуществ оператора new перед функцией malloc является отсутствие необходимости приведения типов.
Оператор reinterpret_cast может быть также полезен при определении функции, когда функция получает указатель типа void * в качестве параметра.
Преобразование типа между родственными объектами или указателями
Оператор static_cast - это оператор приведения типов между родственными объектами или типами указателей. Участвующие в этой операции классы должны быть связаны через наследственность, конструктор или функцию преобразования. Оператор static_cast также работает с простейшими числовыми типами данных.
Следующее выражение имеет то же значение, что и выражение expr, но позволяет переводить его в тип type:
static_cast<type>(expr)
Хотя численное значение результата не изменяется, при выполнении этого оператора может быть изменено внутреннее представление данных.
Оператор static_cast используется в следующих случаях:
-
для приведения типов между родственными типами указателей, если они указывают на классы, которые связаны через наследственность. В отличие от dynamic_cast можно выполнять приведение к типу указателя на производный класс даже при отсутствии виртуальных функций, проверка во время выполнения программы также выполняется;
-
приведение к простейшим типам данных;
-
приведение выражения типа A к типу B, когда B имеет соответствующий конструктор из типа A или A имеет функцию преобразования в B.
В принципе, можно использовать оператор static_cast всегда, когда допускается автоматическое преобразование в обратном направлении. Например, целые числа автоматически переводятся в действительные. Чтобы выполнить обратное преобразование, требуется оператор static_cast .
В основном оператор static_cast используется для приведения сложных типов к простым: при работе с простыми типами он подавляет предупреждение компилятора:
short i = static_cast<short>(j);
Этим как бы говорится: "Да, я на самом деле хочу это сделать". При этом компилятор не выдает предупреждения о возможной потере данных. При этом необходимо быть уверенным в том, что данные не будут слишком велики для приведения в другой тип.
Таким же образом возможно преобразование типов из указателя базового класса в производный без каких-либо ограничений. Но так как никакой проверки во время выполнения программы не делается, то забота о поддержке данных в должном виде возлагается на программиста (производный класс содержит все члены базового плюс члены производного класса). Например, в следующем примере B является базовым классом класса D:
// ...
D *pd = static_cast<D *>(pb);
В данном случае объект, на который указывает pb, фактически имеет тип D или производный от него. Однако при этом значения всех членов класса D, не входящих в класс B, не определены, и обращение к ним приведет к возникновению ошибок. В такой ситуации программист сам должен определить все недостающие члены с помощью присваивания. Можно пойти и в обратном направлении - выполнить присваивание указателю на базовый класс. В этом случае два следующих оператора эквивалентны.
pb = pd;
Другие случаи, в которых оператор static_cast может быть полезен, редко встречаются в программах. Например, с его помощью можно последовательно выполнить несколько преобразований типов данных. В следующем примере A и B - такие классы, в которых A имеет функцию преобразования в B, а B - функцию преобразования к типу int. Тогда объект типа A может быть преобразован в тип int следующим образом:
int i = static_cast<int>(static_cast<B>(oa));
Без использования этой цепочки преобразований (и при отсутствии функции преобразования A в тип int) преобразование oa в целое было бы невозможным.
На первый взгляд кажется, что при использовании оператора static_cast программисту придется выполнять больше работы, чем при программировании старыми методами. Однако при этом становятся доступными возможности ANSI C++ по разделению операторов приведения типа, что значительно облегчает просмотр больших программ.
Оператор static_cast также полезен при применении различных форматов данных или при необходимости вывода данных в формате, отличном от того, который определен для данного типа по умолчанию. Например, нужно вывести целое число i в формате с плавающей точкой:
cout << static_cast<double>(i);
Заметим, что для вывода на экран адреса строки надо использовать оператор reinterpret_cast, так как имя строки имеет тип указателя, а не простейший тип:
cout << hex << reinterpret_cast<int>(str);