Как сделать свой тип данных c

Обновлено: 07.07.2024

Объявление позволяет ввести некую переменную в программу. Данная переменная будет обладать неким типом данных: целочисленный, с плавающей запятой, символьный в случае базовых типов данных, или кастомный тип (структура данных, класс). Тип переменной определяет набор операций, которые можно произвести над переменной. Объявление переменной определяет выделение области памяти, которая требуется для неё. А также значение этой переменной, которое с точки зрения компьютера будет являться последовательностью битов. Объявление переменной также несёт в себе то имя, по которому программист будет обращаться к данной переменной в программном коде.

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

  1. 1. Фундаментальные типы данных
    1. 1. void
    2. 2. nullptr
    3. 3. boolean
    4. 4. Символьные типы
    5. 5. int
    1. 1. Знаковые модификаторы
    2. 2. Модификаторы размера
    1. 1. auto

    Фундаментальные типы данных

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

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

    nullptr

    nullptr - особый тип данных, который сам себе не является типом как таковым, поскольку его нельзя установить в качестве типа переменной, но он может использоваться в качестве нулевого указателя. Данный тип был введён в стандарте С++11 вместо определяемой реализацией нулевой макроконстанты NULL.

    boolean

    bool - логический тип данных, которые принимает значение true или false . Размер памяти, которую занимает данный тип данных может отличаться от 1 в зависимости от реализации в целевой системе. Определить размер можно с помощью оператора sizeof(bool).

    Символьные типы

    char - Символьные типы используются для представления текстовых символов. Размер символьного типа char 1 байт, что позволяет содержать 256 различных символов. Представление всех символов можно найти в таблице символов ASCII.

    Символьные типы данных делятся на три типа:

    • signed char - знаковый тип
    • unsigned char - беззнаковый тип
    • char - отдельный тип, который может быть как знаковым, так и беззнаковым, в зависимости от того, как отработает код компилятор.

    Различие в диапазоне значений, например:

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

    Также имеются особые типы символьных данных:

    • wchar_t - тип для представления символов, которым недостаточно одного байта. Это может быть 32 бита для ОС, поддерживающих UNICODE, или 16 бит в нотации Windows для UTF-16.
    • char16_t - тип для представления UTF-16, введён в стандарте C++11 .
    • char32_t - тип для представления UTF-32, введён в стандарте C++11 .

    int - целочисленный тип данных. Могут использоваться модификаторы, определяющие размер памяти, выделяемый под этот тип данных. Если нет модификаторов, то гарантируется, что размер типа данных не менее 16-ти бит. Однако, на большинстве 32/64 разрядных систем гарантируется, что занимаемый размер не менее 32-х бит.

    Модификаторы

    Знаковые модификаторы

    • signed - представление знакового типа данных (если опущено, то подразумевается по умолчанию)
    • unsigned - представление беззнакового типа данных.

    Модификаторы размера

    • short - целевой тип оптимизируется, чтобы размер был не менее 16 бит
    • long - целевой тип оптимизируется, чтобы размер был не менее 32 бит

    Модификатор long можно применять к типу данных дважды, что даёт оптимизацию занимаемого переменной пространства не менее 64 бит. Данная оптимизация введена в стандарте C++11.

    long long int

    Модификаторы размера и знаковости можно также комбинировать.

    signed long long int

    Типы данных с плавающей точкой

    • float - 32-х разрядный тип данных с плавающей точкой.
    • double - 64-х разрядный тип данных с плавающей точкой.
    • long double - расширенный тип данных с плавающей точкой, введён в стандарте C++11.

    Кстати, при разработке программного обеспечения можно заметить по использованию этих типов данных, какой разработчик начинал с чистого C, а какой начинал с C++. Поголовное использование float характерно для разработчиков, которые начинали с C, double же характерен для C++ разработчиков.

    Переменные

    Таким образом, переменные могут иметь типы данных, перечисленные выше, например:

    Инициализация переменных может производиться несколькими способами.

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

    Также для объявления переменных в стандарте C++11 был введён спецификатор auto , который позволяет объявлять переменную, без указания типа. В данном случае тип выводится из инициализатора, то есть значения, которое будет присвоено переменной. Таким образом auto невозможно использовать без инициализатора, то есть

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

    Арифметика

    Над переменными базовых типов можно выполнять различные арифметически операции:

    Также возможно использование операций сравнение:

    В дополнение к арифметическим и логическим операциям функционал C++ предлагает более специфические операции:

    Рекомендуем хостинг TIMEWEB

    Рекомендуем хостинг TIMEWEB

    Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

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

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

    Чаще переменные структур объявляются так:

    Здесь объявляются три структуры типа circle и одна структура типа book. Можно объявлять типы структур и их переменные по-иному, но мы для избежания путаницы рассматривать другие способы не будем.

    Каждая переменная типа circle содержит четыре элемента (или поля) — x, y, dia, color. Можно сказать, что они представляют собой вложенные переменные. Причем эти переменные разных типов. Таким образом переменная-структура позволяет объединить под одним именем ряд разнородных данных. Обычно это нужно для удобства обработки данных. Если нельзя было бы создавать структуры, то пришлось бы создавать множество независимых переменных или ряд массивов, явной взаимосвязи между которыми не было бы. Структуры же позволяют объединять взаимосвязанные данные. Это конечно еще не объектно-ориентированное программирование, но уже взгляд в его сторону.

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

    Значение элементов структуры можно сразу определять при объявлении переменной, что похоже на инициализацию массивов:

    Значение переменной-структуры можно присвоить переменной того же типа:

    В четвертой строке кода данные переменной old присваиваются new. В итоге вторая структура содержит копию данных первой. То, что можно выполнять присваивание по отдельным полям должно быть понятно.

    Структуры и функции

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

    Примечание. При компиляции программы в GNU/Linux команда выглядит так: gcc program.c -lm . Это связано с использованием библиотеки с математическими функциями.

    • Объявляется структура circle как глобальный тип данных. Таким образом любая, а не только main() , функция может создавать переменные этого типа.
    • Функция new_circle() возвращает структуру, а функция cross() принимает структуру по значению. Следует отметить, что можно создавать функции, которые как принимают (возможно, несколько структур) так и возвращают структуру.
    • В функции new_circle() создается переменная new типа struct circle, поля которой заполняются пользователем. Функция возвращает значение переменной new в функцию main() , где это значение присваивается переменной a, которая также принадлежит типу sctruct circle.
    • Функция cross() определяет, пересекает ли круг начало координат. В ее теле вычисляется расстояние от центра круга до начала координат. Это расстояние является гипотенузой прямоугольного треугольника, длина катетов которого равна значениям x и у. Далее, если гипотенуза меньше радиуса, то круг пересекает начало координат, т.е. точку (0, 0).
    • В функции main() при вызове cross() данные, содержащиеся в переменной a, копируются и присваиваются переменной c.

    Указатели и структуры

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

    Тогда функция reader() должна иметь примерно такое объявление:

    Возникает вопрос, как при использовании указателей обращаться к элементам структур? Во первых надо получить саму структуру, т.е. если pnew указатель, то сама структура будет *pnew . Далее можно уже обращаться к полям через точку: *pnew.title . Однако это выражение не верно, т.к. приоритет операции "точка" (обращение к полю) выше операции "звездочка" (получить значение по адресу). Таким образом приведенная запись сначала пытается получить значение поля title у указателя pnew, но у pnew нет такого поля. Проблема решается с помощью скобок, которые изменяют порядок выполнения операций: (*pnew).title . В таком случае сначала извлекается значение по адресу pnew, это значение является структурой. Затем происходит обращение к полю структуры.

    В языке программирования C записи типа (*pnew).title часто заменяют на такие: pnew->title , что позволяет синтаксис языка. Когда в программе вы видите стрелку (тире и скобка) всегда помните, то, что написано до стрелки, — это указатель на структуру, а не переменная-структура.

    Пример кода с использованием указателей:

    Массивы структур

    Обычно создание в программе одной переменной структурного типа не имеет особого смысла. Чаще структурами пользуются, когда необходимо описать множество похожих объектов, имеющих разные значения признаков. Значения каждого объекта следует объединить вместе (в структуру) и тем самым отделить от значений других объектов. Например, описание ряда книг или множества людей. Таким образом мы можем организовать массив, где каждый элемент представляет собой отдельную структуру, а все элементы принадлежат одному и тому же структурному типу.

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

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

    • Массив структур инициализируется при его объявлении.
    • Функции viewer() и changer() принимают указатели на структуру computer.
    • В теле viewer() указатель инкрементируется в заголовке цикла; таким образом указывая на следующий элемент массива, т.е. на следующую структуру.
    • В выражении (comp+i)->qty скобки необходимы, т.к оператор -> имеет более высокий приоритет. Скобки позволяют сначала получить указатель на i-ый элемент массива, а потом обратиться к его полю.
    • Декрементирование i в функции changer() связано с тем, что индексация начинается с нуля, а номера элементов массива, которые пользователь видит на экране, с единицы.
    • Для того, чтобы уменьшить количество компьютеров, при запросе надо ввести отрицательное число.

    Пример результата работы программы:

    Придумайте свою программу, в которой бы использовался массив структур, или перепишите приведенную выше программу и дополните ее функцией, которая позволяет добавлять в массив новый элемент-структуру.

    Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

    Языки программирования и типы данных

    В зависимости от принятой системы типов и способов с ней работать различают языки программирования:

    • со статической и динамической типизацией;
    • с сильной и слабой типизацией;
    • с явной и неявной типизацией.

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

    Примеры языков с динамической типизацией: Python , PHP . Пример кода на Python :

    В языках с сильной типизацией операции над значениями и присваивания можно производить только над переменными одного типа. Иногда это приведение выполняется автоматически, например:

    В этом случае, при выполнении второй строки в первую очередь будет выполнено приведение переменной v1 к типу double , а потом сложение. Но следующий код вызовет ошибку:

    Так как “4” – это значение строкового типа, а 0.123 имеет тип double .

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

    В результате код скомпилируется, если его запустить, то в переменной double будет лежать численное значение.

    Языки со слабой типизацией – это C , C++ .

    Явная типизация предполагает явное указание типа переменной:

    В этом примере, мы объявляем переменную value типа int и явно это указываем.

    В языке с неявной типизацией этого делать не нужно, пример на Python :

    Общая система типов (CTS)

    Объявление и инициализация переменных

    Задание значения переменной можно произвести в момент инициализации:

    либо после инициализаций:

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

    В примерах мы не будем приводить код импорта и объявления класса. В конце главы будет приведен листинг программы со всеми примерами из данного урока.

    Ключевое слово new

    Ключевое слово new , как правило, используется при инициализации переменных, которые имеют ссылочный тип данных. О том, что это такое мы расскажем чуть ниже. Пусть у нас есть класс Rectangle :

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

    Создадим переменную класса Rectangle :

    Переменные типа int , double и т.п. также можно проинициализировать с помощью ключевого слова new , в этом случае будет присвоено значение по умолчанию:

    Ключевое слово var. Неявная типизация

    При объявлении переменной вместо явного задания типа можно поставить ключевое слово var . В этом случае будет использована система вывода типов для определения типа переменной по ее значению.

    При работе с var необходимо помнить следующее:

    • использовать var можно только для объявления локальных переменных;
    • var нельзя использоваться для объявления типа возвращаемого значения, типов полей и параметров;
    • при объявлении переменной с использованием var она обязательно должна быть проинициализирована, при этом использовать для этого null запрещено;
    • объявлять переменную допускающую null -значение с использованием лексемы ? через var нельзя.


    Типы значения

    Переменные типа-значения располагаются в стеке, что позволяет их быстро создавать и уничтожать. Фактически время жизни такой переменной определяется контекстом, в которой она объявлена. Сама переменная представляется в виде локальной копии. Типы-значения являются классами наследниками от System.ValueType , который, в свою очередь, наследуется от System.Object . К типам-значениям относятся: простые типы, типы перечисления, типы структур, типы значений, допускающие NULL , типы значений кортежей. Далее, обзорно будут рассмотрены указанные выше типы.

    Простые типы

    К простым типа относятся:

    • Целочисленные типы;
    • Типы с плавающей точкой;
    • Тип bool для представления логических значений;
    • Тип char для представления символьных значений.

    Целочисленные типы

    Примеры работы с целыми числами:

    Для явного указания, что число имеет тип long или ulong необходимо добавить суффикс L или l для long и UL и все возможные комбинации регистров эти символов для ulong .

    Число может быть представлено в десятичном, шестнадцатеричном и двоичном виде:

    Для всех целочисленных простых типов значение по умолчанию: 0 , оно присваивается переменной при инициализации с помощью ключевого слова new .

    Типы с плавающей точкой

    Сводная таблица с типами с плавающей точкой:

    Для явного указания типа данных при записи числа, можно использовать литералы:

    Для чисел с плавающей точкой значение по умолчанию: 0.0 с соответствующей литерой в конце.

    Тип bool

    Значение по умолчанию для типа bool : false .

    Тип char

    Переменной типа char можно задать значение:

    • в виде символа:
    • escape -последовательности Юникода (начинаются с префикса \u ):
    • шестнадцатеричной escape -последовательности (начинается с префикса \x ):
    • через приведение типа:

    Значением по умолчанию для char : ‘ \u0000′ .

    Типы перечисления (enum)

    Перечисления являются набором целочисленных именованных констант. Переменные типа перечисления создаются с помощью ключевого слово enum, после которого следует имя типа и набор значений в фигурных скобках. Создадим enum для представления дней недели:

    Каждому значению перечисления соответствует целое число.

    Пример создания переменной типа Day :

    Более подробно про работу с enum будет рассказано в одном из следующих уроков.

    Типы структур

    Структуры по своей внутренней организации похожи на классы, они содержат набор полей и методов. Как правило, их используют для объявления типов, которые определяются только значениями полей и не имеют индивидуальности. Например, объекты, описывающие транзакции, несмотря на то, что значения их полей могут совпадать не будут тождественными, то есть нам их нужно уметь различать несмотря на внешнее сходство. А точки на геометрической плоскости, которые задаются двумя координатами, такой индивидуальности не имеют, и если координаты двух точек совпадают, то это значит, что речь идет об одной и той же точке. Именно для таких типов хорошо подходят структуры. Для их объявления используется ключевое слово struct :

    Типы значений, допускающие null

    Про типы значений, допускающих null см. ниже “ Nullable -типы (нулевые типы) и операция ??” .

    Типы значений кортежей

    Кортежи используются для группировки данных, которые могут иметь разные типы в единую именованную сущность. Они являются объектами типа System.ValueTuple . Объявим кортеж, состоящий из двух элементов типа double:

    Поля кортежа могут быть именованными:

    Более подробно про кортежи типов System.ValueTuple (тип-значение) и System.Tuple (ссылочный тип) будет рассказано в одном из следующих уроков.

    Ссылочные типы

    Переменные ссылочного типа располагаются в куче, за их уничтожение отвечает сборщик мусора, поэтому про них нельзя точно сказать, когда занимаемая ими память будет освобождена. Переменная представляется в виде ссылки на соответствующее место в куче. Ссылочные типы являются наследниками от System.Object .

    Типы классов

    Типы интерфейсов

    Создадим интерфейс для описания человека, у которого есть два свойства имя: Name , и возраст: Age :

    Изменим объявление класса Persone, так, чтобы он представлял реализацию интерфейса IPersone:

    Объявим переменную типа IPersone:

    Более подробно про интерфейсы будет рассказано в одном из следующих уроков.

    Типы массивов

    Создание и инициализация одномерного массива:

    Пример прямоугольного массива, в нем строки имеют одинаковую длину:

    Пример зубчатого ( jagged ) массива, в нем строки могут иметь разную длину:

    Более подробно про массивы будет рассказано в одном из следующих уроков.

    Типы делегатов

    Делегаты являются аналогом указателей на функции из языков C / C++ . Они используются в случаях, когда нужно передать некоторую функциональность как аргумент, перенаправлять вызовы и т.д.

    Nullable-типы (нулевые типы) и операция ??

    Объявление и инициализация Nullable-переменных

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

    На практике, особенно при работе с базами данных, может возникнуть ситуация, когда в записи из таблицы пропущены несколько столбцов (нет данных), в этом случае, соответствующей переменной нужно будет присвоить значение null , но она может иметь тип int или double , что приведет к ошибке.

    Можно объявить переменную с использованием символа ? после указания типа, тогда она станет nullable -переменной – переменной поддерживающей null-значение:

    Использование символа ? является синтаксическим сахаром для конструкции Nullable , где T – это имя типа. Представленные выше примеры можно переписать так:

    Проверка на null. Работа с HasValue и Value

    Для того чтобы проверить, что переменная имеет значение null можно воспользоваться оператором is с шаблоном типа:

    Также можно воспользоваться свойствами класса Nullable :

    • Nullable .HasValue
      • Возвращает true если переменная имеет значение базового типа. То есть если она не null .
      • Возвращает значение переменной если HasValue равно true , иначе выбрасывает исключение InvalidOperationException .

      Приведение Nullable-переменной к базовому типу

      При работе с Nullable -переменными их нельзя напрямую присваивать переменным базового типа. Следующий код не будет скомпилирован:

      Для приведения Nullable -переменной к базовому типу можно воспользоваться явным приведением:

      В этом случае следует помнить, что если значение Nullable -переменной равно null , то при выполнении данной операции будет выброшено исключение InvalidOperationException .

      Второй вариант – это использование оператора . при этом нужно дополнительно задаться значением, которое будет присвоено переменной базового типа если в исходной лежит значение null :

      Второй вариант позволяет более лаконично обрабатывать ситуацию, когда вызов какого-то метода может возвращать null , а результат его работы нужно присвоить типу-значению, при этом заранее известно, какое значение нужно присвоить переменной в этой ситуации:

      Ключевое слово dynamic

      Ниже приведены несколько примеров, на которых можно разобраться с тем, как работать с dynamic :

      Как вы можете видеть значение и тип переменной dval1 менялись в процессе выполнения программы. При этом нужно помнить, что если вы присвоили переменной dynamic , какое-то значение, которое определило ее тип, а пытаетесь с ней работать как с переменной другого типа, то будет вызвано исключение:

      Оператор default

      Оператор default создает значение по умолчанию для указанного типа, используется оно следующим образом: default(T) , где T – это тип, для которого нужно создать соответствующее значение.

      Объявим переменную типа int и присвоим ей значение по умолчанию с помощью new :

      Тоже самое можно сделать с помощью оператора default :

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

      Вызовем эту функцию:

      Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

      “Задание значения переменной можно произвести в момент инициализации:

      int radius = 10;
      string name = “John”;

      “Задание значения переменной можно произвести в момент инициализации” – Присвоение значения ранее объявленной переменной и есть инициализация.

      “либо после инициализаций: – *Либо после объявления, инициализация происходит во второй строке

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

      Лучшая практика

      Называйте свои псевдонимы typedef с суффиксом _t , чтобы указать, что это имя является псевдонимом типа, и чтобы помочь предотвратить конфликты имен с другими типами идентификаторов.

      После определения имя typedef можно использовать везде, где требуется тип. Например, мы можем создать переменную с именем typedef в качестве типа:

      Когда компилятор встречает имя typedef , он подставляет тип, на который указывает typedef . Например:

      Этот код печатает:

      В приведенной выше программе мы сначала определяем typedef distance_t как псевдоним для типа double .

      Затем мы определяем переменную с именем milesToDestination типа distance_t . Поскольку компилятор знает, что distance_t – это typedef , он будет использовать тип, на который указывает псевдоним, то есть double . Таким образом, переменная milesToDestination фактически компилируется как переменная типа double , и во всех отношениях она будет вести себя как double .

      Наконец, мы печатаем значение milesToDestination , которое печатается как значение double .

      typedef не определяет новый тип

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

      Это позволяет нам делать вещи, которые синтаксически корректны, но семантически бессмысленны. Например:

      Хотя концептуально мы предполагаем, что miles_t и speed_t имеют разные значения, оба они являются просто псевдонимами для типа long . Это фактически означает, что значения типа miles_t , speed_t и long могут использоваться взаимозаменяемо. И действительно, когда мы присваиваем значение типа speed_t переменной типа miles_t , компилятор видит только то, что мы присваиваем значение типа long переменной типа long , и он не будет жаловаться.

      Предупреждение

      Следует проявлять осторожность, чтобы не смешивать значения псевдонимов типов, которые должны быть семантически разными.

      В качестве отступления.

      Область видимости typedef

      Поскольку область видимости является свойством идентификатора, идентификаторы typedef подчиняются тем же правилам области видимости, что и идентификаторы переменных: typedef , определенный внутри блока, имеет область видимости блока и может использоваться только внутри этого блока, тогда как typedef , определенный в глобальном пространстве имен, имеет область видимости файла и может использоваться до конца файла. В приведенном выше примере miles_t и speed_t можно использовать только в функции main() .

      Определения typedef , включенные таким образом, будут импортированы в глобальное пространство имен и, следовательно, будут иметь глобальную область видимости.

      Проблемы typedef

      Однако у typedef есть несколько синтаксических проблем. Во-первых, легко забыть, что на первом месте: имя типа или определение типа. Что правильно?

      Это легко перепутать. К счастью, в таких случаях компилятор пожалуется.

      В приведенном выше определении typedef имя нового типа ( fcn_t ) скрыто в середине определения, что затрудняет чтение определения.

      Псевдонимы типов

      Чтобы помочь решить эти проблемы typedef был добавлен улучшенный синтаксис, имитирующий способ объявления переменных. Этот синтаксис называется псевдонимом типа (type alias).

      Например, следующий typedef :

      можно объявить как следующий псевдоним типа:

      Псевдонимы типов функционально эквивалентны определениям typedef , но имеют преимущество в более удобном синтаксисе определения.

      Вот трудно читаемый typedef , который мы представили выше, вместе с эквивалентным (и немного более легким для чтения) псевдонимом типа:

      Лучшая практика

      При создании типов с псевдонимами отдавайте предпочтение синтаксису псевдонима (type alias) типа вместо синтаксиса typedef .

      Когда мы должны использовать псевдонимы типов?

      Теперь, когда мы рассмотрели, что такое typedef и псевдонимы типов, давайте поговорим о том, для чего они полезны.

      Использование псевдонимов типов для кодирования, независимого от платформы

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

      Чтобы гарантировать, что каждый псевдоним типа преобразуется в тип нужного размера, псевдонимы типа такого рода обычно используются в сочетании с директивами препроцессора:

      Эта программа печатает:

      Поскольку std::int_least8_t обычно определяется как псевдоним типа для одного из типов char , переменная x будет определена как типа char . А типы char печатают свои значения как символы ASCII, а не как целые числа.

      Использование псевдонимов типов для упрощения сложных типов

      Хотя до сих пор мы имели дело только с простыми типами данных, в продвинутом C++ типы могут быть сложными и длинными для ввода. Например, вы можете увидеть функцию и переменную, определенные следующим образом:

      Ввод std::vector > везде, где вам нужно использовать этот тип, может оказаться громоздким. Гораздо проще использовать псевдоним типа:

      Намного лучше! Теперь нам нужно вводить только pairlist_t вместо std::vector > .

      Не волнуйтесь, если вы еще не знаете, что такое std::vector , std::pair или все эти сумасшедшие угловые скобки. Единственное, что вам действительно нужно понять, это то, что псевдонимы типов позволяют вам брать сложные типы и давать им простые имена, что упрощает работу с этими типами и их понимание.

      Вероятно, это лучшее использование псевдонимов типов.

      Использование псевдонимов типов для повышения читабельности

      Псевдонимы типов также могут помочь в документации и понимании кода.

      Что касается переменных, у нас есть идентификатор переменной, который помогает документировать назначение переменной. Но рассмотрим случай значения, возвращаемого функцией. Типы данных, такие как char , int , long , double и bool , хороши для описания того, какой тип возвращает функция, но чаще мы хотим знать, какой цели служит возвращаемое значение.

      Например, рассмотрим следующую функцию:

      Мы видим, что возвращаемое значение является целым числом, но что означает целое число? Буквенная оценка? Количество пропущенных вопросов? Идентификационный номер студента? Код ошибки? Кто знает! Тип возвращаемого значения int мало что говорит нам. Если нам повезет, где-то существует документация по этой функции, к которой мы можем обратиться. Если нам не повезет, мы должны прочитать код и определить назначение сами.

      Теперь давайте создадим эквивалентную версию, используя псевдоним типа:

      Использование типа возвращаемого значения testScore_t делает очевидным, что функция возвращает тип, представляющий результат теста.

      По нашему опыту, создание псевдонима типа только для документирования типа возвращаемого значения отдельной функции не стоит усилий (вместо этого используйте комментарий). Но если вы уже создали typedef по другим причинам, это может быть приятным дополнительным преимуществом.

      Использование псевдонимов типов для упрощения поддержки кода

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

      Однако с псевдонимом типа всё, что вам нужно сделать, это изменить studentID_t = short; на studentID_t = long; .

      Хотя это кажется приятным преимуществом, необходимо соблюдать осторожность при изменении типа, поскольку поведение программы также может измениться. Это особенно верно при изменении типа псевдонима типа на тип из другого семейства типов (например, целочисленный тип на значение с плавающей запятой или наоборот)! Новый тип может иметь проблемы со сравнением или делением целых чисел на числа с плавающей запятой или другие проблемы, которых не было у старого типа. Если вы измените существующий тип на какой-либо другой, ваш код следует тщательно повторно протестировать.

      Минусы и заключение

      Хотя псевдонимы типов предлагают некоторые преимущества, они также вводят в ваш код еще один идентификатор, который необходимо понимать. Если это не компенсируется какими-либо преимуществами для удобочитаемости или понимания, тогда псевдоним типа приносит больше вреда, чем пользы.

      Плохо используемый псевдоним типа может взять знакомый тип (например, std::string ) и скрыть его за пользовательским именем, которое необходимо найти. В некоторых случаях (например, с умными указателями, которые мы рассмотрим в будущей главе) скрытие информации о типе также может быть вредным для понимания того, как этот тип должен работать.

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

      Лучшая практика

      Разумно используйте псевдонимы типов, если они дают явное преимущество для читабельности или поддерживаемости кода.

      Небольшой тест

      Вопрос 1

      Возьмем следующий прототип функции:

      Преобразуйте возвращаемое значение int в псевдоним типа с именем error_t . В ответ включите обе инструкции: инструкцию псевдонима типа, и обновленный прототип функции.

      Читайте также: