27
к статическим переменным класса. Следующий фрагмент верен, хотя бедные читатели вашей
программы придут в такое замешательство, что подобное можно проделывать только в последний день
перед увольнением:
Foo f;
f.Gfn();
// То же, что и Foo::GFn();
Структуры
Структура в C++ — почти что полноценный класс. Со структурой можно делать все, что можно делать
с классом. Например, структуры могут участвовать в наследовании; в них можно объявлять секции
public
,
private
,
protected
и даже виртуальные функции. Тем не менее, для структур действуют
несколько иные правила: по умолчанию все члены считаются открытыми (
public
), чтобы готовые
программы на С не приходилось переписывать заново под каноны C++.
Теория — вещь хорошая, но давайте вернемся на землю. Стоит ли демонстрировать свою «крутизну» и
объявлять структуру с множественным наследованием и виртуальными функциями? На практике
структуры используются вместо классов лишь при соблюдении следующих условий:
•
Структура не содержит виртуальных функций.
•
Структура не является производной от чего-либо, кроме разве что другой структуры.
•
Структура не является базовой для чего-либо, кроме разве что другой структуры.
Нормальные программисты C++ обычно используют структуры лишь для маленьких удобных наборов
данных с тривиальными функциями. В частности, структуры часто используются в ситуациях, когда
объект C++ должен быть совместим на битовом уровне с внешней структурой данных (особенно со
структурами С). При этом можно запросто объявлять конструкторы и невиртуальные функции (осо-
бенно тривиальные встроенные), поскольку для них не создается v-таблица, которая могла бы
нарушить битовую совместимость.
Объединения
Объединения C++ почти не отличаются от объединений С. Они позволяют сэкономить несколько байт
за счет наложения различных структур данных поверх друг друга. Объединения могут содержать
невиртуальные функции, в том числе конструкторы и деструкторы, но при этом они должны
подчиняться довольно жестким ограничениям:
•
Члены объединения не могут иметь конструкторов (хотя само объединение — может).
•
Объединение не может быть производным от чего-либо.
•
Ничто не может быть производным от объединения.
•
Деструкторы членов не вызываются, хотя деструктор самого объединения, если он есть,
вызывается.
Поскольку объединения не участвуют в иерархии наследования, нет смысла объявлять в них
виртуальные функции или защищенные члены. Члены объединений разрешается объявлять закрытыми
(
private
) или открытыми (
public
). Объединения пригодятся лишь тогда, когда вам действительно
нужно сэкономить память, когда вы не собираетесь делать объединение производным или базовым, а
также включать в него виртуальные функции или конструкторы. Иначе говоря, пользы от них не так
уж много.
Блоки
Все, что стоило бы сказать о блоках, уже известно вам из С или из предыдущего описания стековых
объектов.
Глобальные пространства имен
Глобальные пространства имен C++ настолько сложны, что в моем представлении процесс компиляции
глобальных конструкций напоминает магический ритуал с дымом благовоний и пением мантр. Я
постараюсь изложить эти правила как можно проще. Область действия глобальных типов
28
ограничивается файлом, в котором они объявляются. Глобальные переменные и функции к тому же
подчиняются правилам компоновки для нескольких исходных файлов. Рассмотрим следующую
ситуацию:
// В файле Foo.cpp
typedef int Symbol;
// В файле Bar.cpp
typedef void (*Symbol)();
Никакого конфликта не возникнет, если только по мазохистским соображениям вы не включите один
файл с расширением .срр в другой директивой
#include
. Символическое имя
Symbol
известно
компилятору лишь в тех исходных файлах, в которых оно встречается, поэтому в разных исходных
файлах его можно использовать по-разному. Следующий фрагмент неверен, поскольку на этот раз
символическое имя соответствует переменной, а не типу. Имя переменной должно быть уникальным
для всех файлов, передаваемых компоновщику.
// В файле Foo.cpp
int Symbol;
// В файле Bar.cpp
void (*Symbol)();
Единственное исключение из этого правила относится к перегрузке функций, о которой будет
рассказано в следующем разделе. Конечно, конфликты имен часто возникают в любом достаточно
большом проекте, в котором несколько программистов работают над разными исходными файлами.
Одно из возможных решений — использование статических членов; другое — объявление глобальных
переменных и функций статическими. Если переменная или функция объявляется статической, она
определена лишь в границах исходного файла.
// В файле Foo.cpp
static int Symbol;
// В файле Bar.cpp
static void (*Symbol)();
Увидев ключевое слово
static
, компилятор проследит за тем, чтобы компоновщик не перепутал две
разные версии одного символического имени при условии что исходные файлы не компилируются
вместе; будут сгенерированы две разные переменные.
К любому символическому имени, объявленному в глобальном пространстве имен, можно обратиться с
помощью оператора
::
без указания области действия:
::Fn();
//
Вызвать глобальную функцию с заданным именем
int x = ::i;
// Присвоить x значение глобальной переменной
::SomeType y;
// Использовать глобально объявленный тип
Явно заданная область действия всегда отменяет все символические имена, определенные локально —
например, внутри блока или класса.
Перегрузка
В C++ существует несколько способов многократного использования имен функций. В частности,
пространства имен функций формируются на основе классов. Одноименные функции в классах, не
связанных друг с другом, выполняют совершенно разные задачи. Перегрузка функций развивает
великую традицию разделения пространств имен функций и позволяет многократно использовать
имена функций в границах одной области действия.
Аргументы
Две функции с одинаковыми именами считаются разными, если они отличаются по количеству,
порядку или типу аргументов.
void Fn();
Do'stlaringiz bilan baham: |