Система типов C++

Концепция типа важна в C++. Каждая переменная, аргумент функции и возвращаемое значение функции должны иметь тип, чтобы их можно было скомпилировать. Кроме того, все выражения (включая литеральные значения) неявно задают тип компилятором перед их вычислением. Некоторые примеры типов включают встроенные типы, например int для хранения целых значений, double хранения значений с плавающей запятой или типов стандартной библиотеки, таких как класс std::basic_string для хранения текста. Вы можете создать собственный тип, определив или classstruct. Тип указывает объем памяти, выделенной для переменной (или результата выражения). Тип также указывает типы значений, которые могут храниться, как компилятор интерпретирует битовые шаблоны в этих значениях, а также операции, которые можно выполнять с ними. Эта статья содержит неформальный обзор основных особенностей системы типов C++.

Терминология

Скалярный тип: тип, содержащий одно значение определенного диапазона. Скаляры включают арифметические типы (целочисленные или значения с плавающей запятой), элементы типа перечисления, типы указателей, типы указателей на члены и std::nullptr_t. Основные типы обычно скалярные.

Составной тип: тип, который не является скалярным типом. Составные типы включают типы массивов, типы функций, типы классов (или структуры), типы объединения, перечисления, ссылки и указатели на нестатические члены класса.

Переменная: символическое имя количества данных. Имя можно использовать для доступа к данным, которые он ссылается на область кода, в котором он определен. В C++ переменная часто используется для ссылки на экземпляры скалярных типов данных, а экземпляры других типов обычно называются объектами.

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

Тип POD (обычные старые данные): эта неформальная категория типов данных в C++ относится к типам, которые являются скалярными (см. раздел "Основные типы") или являются классами POD. Класс POD не имеет статических элементов данных, которые также не являются POD, и не имеет определяемых пользователем конструкторов, определяемых пользователем деструкторов или определяемых пользователем операторов назначения. Кроме того, класс POD не имеет виртуальных функций, базового класса и ни закрытых, ни защищенных нестатических данных-членов. Типы POD часто используются для внешнего обмена данными, например с модулем, написанным на языке С (в котором имеются только типы POD).

Указание типов переменных и функций

C++ — это строго типизированный язык и статически типизированный язык. Каждый объект имеет тип и этот тип никогда не изменяется. При объявлении переменной в коде необходимо явно указать его тип или использовать auto ключевое слово для указания компилятору выводить тип из инициализатора. При объявлении функции в коде необходимо указать тип возвращаемого значения и каждого аргумента. Используйте возвращаемый тип void значения, если значение не возвращается функцией. Исключением является использование шаблонов функций, которые позволяют использовать аргументы произвольных типов.

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

При объявлении переменной типа POD настоятельно рекомендуется инициализировать ее, то есть дать ему начальное значение. Пока переменная не инициализирована, она имеет "мусорное" значение, определяемое значениями битов, которые ранее были установлены в этом месте памяти. Это важный аспект C++ для запоминания, особенно если вы поступаете из другого языка, который обрабатывает инициализацию для вас. При объявлении переменной типа класса, отличного от POD, конструктор обрабатывает инициализацию.

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

int result = 0;              // Declare and initialize an integer.
double coefficient = 10.8;   // Declare and initialize a floating
                             // point value.
auto name = "Lady G.";       // Declare a variable and let compiler
                             // deduce the type.
auto address;                // error. Compiler cannot deduce a type
                             // without an intializing value.
age = 12;                    // error. Variable declaration must
                             // specify a type or use auto!
result = "Kenny G.";         // error. Can't assign text to an int.
string result = "zero";      // error. Can't redefine a variable with
                             // new type.
int maxValue;                // Not recommended! maxValue contains
                             // garbage bits until it is initialized.

Базовые (встроенные) типы

В отличие от некоторых других языков, в C++ нет универсального базового типа, от которого наследуются все остальные типы. Язык включает множество фундаментальных типов, также известных как встроенные типы. Эти типы включают числовые типы, такие как int, doublelong, boolи charwchar_t типы для символов ASCII и ЮНИКОД соответственно. Большинство целочисленных фундаментальных типов (за исключением bool, doublewchar_tи связанных типов) имеют unsigned версии, которые изменяют диапазон значений, которые переменная может хранить. Например, int32-разрядное целое число со знаком может представлять значение от -2 147 483 648 до 2 147 483 647. Объект unsigned int, который также хранится как 32 бита, может хранить значение от 0 до 4 294 967 295. Общее количество возможных значений в каждом случае одинаково, отличается только диапазон.

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

На следующем рисунке показаны относительные размеры встроенных типов в реализации Microsoft C++:

Diagram of the relative size in bytes of several built in types.

В следующей таблице перечислены наиболее часто используемые основные типы и их размеры в реализации Microsoft C++:

Тип Size Комментарии
int 4 байта Выбор по умолчанию для целочисленных значений.
double 8 байт Выбор по умолчанию для значений с плавающей запятой.
bool 1 байт Представляет значения, которые могут быть или true, или false.
char 1 байт Используйте для символов ASCII в старых строках в стиле C или в объектах std::string, которые никогда не будут преобразовываться в Юникод.
wchar_t 2 байта Представляет "расширенные" символы, которые могут быть представлены в формате Юникод (UTF-16 в Windows, в других операционных системах возможно другое представление). wchar_t — это тип символа, используемый в строках типа std::wstring.
unsigned char 1 байт C++ не имеет встроенного типа байтов. Используется unsigned char для представления байтового значения.
unsigned int 4 байта Вариант по умолчанию для битовых флагов.
long long 8 байт Представляет гораздо больший диапазон целых значений.

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

Тип void.

Тип void является специальным типом; нельзя объявить переменную типа, но можно объявить переменную типа voidvoid * (указатель voidна), которая иногда необходима при выделении необработанной (нетипизированной) памяти. Однако указатели на небезопасные void и их использование не рекомендуется использовать в современном C++. В объявлении функции возвращаемое значение означает, void что функция не возвращает значение; использование функции в качестве возвращаемого типа является общим и приемлемым.void Хотя язык C требует функций, имеющих нулевые параметры для объявления void в списке параметров, например, fn(void)эта практика не рекомендуется в современном языке C++; следует объявить fn()функцию без параметров. Дополнительные сведения см. в разделе "Преобразования типов" и "Безопасность типов".

const квалификатор типов

Любой встроенный или определяемый пользователем тип может быть квалифицирован ключевое слово const . Кроме того, функции-члены могут быть constквалифицированы и даже constперегружены. Значение const типа нельзя изменить после инициализации.

const double PI = 3.1415;
PI = .75; //Error. Cannot modify const variable.

Квалификатор const широко используется в объявлениях функций и переменных и "константной правильности" является важной концепцией в C++; по сути это означает, что для const обеспечения гарантии во время компиляции значения не изменяются непреднамеренно. Дополнительные сведения см. в разделе const.

const Тип отличается от егоconst версии, const int например, является отдельным типом.int Оператор C++ const_cast можно использовать в тех редких случаях, когда необходимо удалить const-ness из переменной. Дополнительные сведения см. в разделе "Преобразования типов" и "Безопасность типов".

Строковые типы

Строго говоря, язык C++ не имеет встроенного типа строки; char и wchar_t сохранить одинарные символы — необходимо объявить массив этих типов, чтобы приблизить строку, добавив завершающее значение NULL (например, ASCII '\0') в элемент массива за последним допустимым символом (также называемой строкой в стиле C). Строки в стиле C требовали написания гораздо большего объема кода или использования внешних библиотек служебных функций. Но в современном C++у нас есть типы std::string стандартной библиотеки (для 8-разрядных charстрок символов типа) или std::wstring (для 16-разрядных wchar_tстрок символов типа). Эти контейнеры стандартной библиотеки C++ можно рассматривать как собственные типы строк, так как они являются частью стандартных библиотек, включенных в любую соответствующую среду сборки C++. Используйте директиву #include <string> , чтобы сделать эти типы доступными в программе. (Если вы используете MFC или ATL, класс также доступен, CString но не является частью стандарта C++.) Использование массивов символов, завершаемых значением NULL (строки в стиле C ранее упоминание) не рекомендуется использовать в современном C++.

Определяемые пользователем типы

При определении class, structunionили enum, эта конструкция используется в остальной части кода, как если бы это был фундаментальный тип. Он имеет известный размер в памяти, и в его отношении действуют определенные правила проверки во время компиляции и во время выполнения в течение срока использования программы. Основные различия между базовыми встроенными типами и пользовательскими типами указаны ниже:

  • Компилятор не имеет встроенных сведений о пользовательском типе. Он узнает о типе при первом обнаружении определения во время процесса компиляции.

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

Типы указателей

Как и в самых ранних версиях языка C, C++ продолжает объявлять переменную типа указателя с помощью специального декларатора (звездочка * ). Тип указателя хранит адрес расположения в памяти, в котором хранится фактическое значение данных. В современном C++эти типы указателей называются необработанными указателями, и они доступны в коде с помощью специальных операторов: * (звездочка) или -> (дефис с большей, чем часто называется стрелкой). Эта операция доступа к памяти называется деreferencing. Какой оператор вы используете, зависит от того, выполняется ли разыменовка указателя на скаляр или указатель на элемент в объекте.

Работа с типами указателя долгое время была одним из наиболее трудных и непонятных аспектов разработки программ на языках C и C++. В этом разделе описаны некоторые факты и методики, которые помогут вам использовать необработанные указатели. Однако в современном C++больше не требуется (или рекомендуется) использовать необработанные указатели для владения объектами вообще из-за эволюции интеллектуального указателя (рассматривается больше в конце этого раздела). Это по-прежнему полезно и безопасно использовать необработанные указатели для наблюдения объектов. Тем не менее, если их необходимо использовать для владения объектами, следует сделать это с осторожностью и тщательно учитывать, как объекты, принадлежащие им, создаются и уничтожаются.

Первое, что следует знать, заключается в том, что объявление необработанной переменной указателя выделяет достаточно памяти для хранения адреса: расположение памяти, которое указывает указатель, когда оно разоменовывается. Объявление указателя не выделяет память, необходимую для хранения значения данных. (Эта память также называется резервным хранилищем.) Другими словами, объявляя необработанную переменную указателя, вы создаете переменную адреса памяти, а не фактическую переменную данных. Если вы разыменуете переменную указателя, прежде чем убедиться, что она содержит допустимый адрес для резервного хранилища, это приводит к неопределенному поведению (обычно неустранимой ошибке) в программе. В следующем примере демонстрируется подобная ошибка:

int* pNumber;       // Declare a pointer-to-int variable.
*pNumber = 10;      // error. Although this may compile, it is
                    // a serious error. We are dereferencing an
                    // uninitialized pointer variable with no
                    // allocated memory to point to.

Пример разыменовывает тип указателя без выделения памяти для хранения фактических целочисленных данных или без выделенного допустимого адреса памяти. В следующем коде исправлены эти ошибки:

    int number = 10;          // Declare and initialize a local integer
                              // variable for data backing store.
    int* pNumber = &number;   // Declare and initialize a local integer
                              // pointer variable to a valid memory
                              // address to that backing store.
...
    *pNumber = 41;            // Dereference and store a new value in
                              // the memory pointed to by
                              // pNumber, the integer variable called
                              // "number". Note "number" was changed, not
                              // "pNumber".

В исправленном примере кода используется локальной память стека для создания резервного хранилища, на который указывает указатель pNumber. Базовый тип используется для простоты. На практике резервные хранилища для указателей чаще всего представляют собой определяемые пользователем типы, динамически выделенные в области памяти, называемой кучей (или свободное хранилище), с помощью new выражения ключевое слово (в программировании в стиле C используется старая malloc() функция библиотеки среды выполнения C). После выделения эти переменные обычно называются объектами, особенно если они основаны на определении класса. Память, выделенная с new помощью соответствующей delete инструкции (или, если вы использовали malloc() функцию для выделения, функция free()среды выполнения C).

Однако легко забыть удалить динамически выделенный объект, особенно в сложном коде, что приводит к ошибке ресурса, называемой утечкой памяти. По этой причине использование необработанных указателей не рекомендуется использовать в современном C++. Почти всегда лучше упаковать необработанный указатель в умный указатель, который автоматически освобождает память при вызове его деструктора. (То есть, когда код выходит из область для интеллектуального указателя.) Используя интеллектуальные указатели, вы практически устраняете целый класс ошибок в программах C++. В следующем примере предположим, что MyClass — это пользовательский тип, который имеет открытый метод DoSomeWork();

void someFunction() {
    unique_ptr<MyClass> pMc(new MyClass);
    pMc->DoSomeWork();
}
  // No memory leak. Out-of-scope automatically calls the destructor
  // for the unique_ptr, freeing the resource.

Дополнительные сведения о смарт-указателях см. в разделе "Умные указатели".

Дополнительные сведения о преобразованиях указателей см. в разделе "Преобразования типов" и "Безопасность типов".

Дополнительные сведения о указателях в целом см. в разделе "Указатели".

Типы данных Windows

В классическом программировании Win32 для C и C++большинство функций используют типдефы и #define макросы windows (определенные в windef.h) для указания типов параметров и возвращаемых значений. Эти типы данных Windows в основном являются специальными именами (псевдонимами) для встроенных типов C/C++. Полный список этих определений typedefs и препроцессора см. в разделе "Типы данных Windows". Некоторые из этих типов, таких как HRESULT и LCID, являются полезными и описательными. Другие, например INT, не имеют специального значения и являются просто псевдонимами для основных типов C++. Прочие типы данных Windows имеют имена, которые сохранились с эпохи программирования на языке C для 16-разрядных процессоров, и не имеют смысла или значения на современном оборудовании или в современных операционных системах. Существуют также специальные типы данных, связанные с библиотекой среда выполнения Windows, перечисленные как среда выполнения Windows базовые типы данных. В современном C++общее руководство заключается в том, чтобы предпочесть базовые типы C++, если только тип Windows не сообщает некоторое дополнительное значение о том, как следует интерпретировать значение.

Дополнительные сведения

Дополнительные сведения о системе типов C++ см. в следующих статьях.

Типы значений
Описывает типы значений вместе с проблемами, связанными с их использованием.

Преобразования типов и безопасность типов
Описание типовых проблем преобразования типов и способов их избежать.

См. также

Возвращение к C++
Справочник по языку C++
Стандартная библиотека C++