Основные операции с типами данных в C/С++
| Вернуться в Доки-Токи
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Автор: Andrey Rjavskov(Rzhavskov) as rjaan <rjaan@yandex.ru> Обычно начинающие программисты забывают уделять должного внимание изучению основных типов и операций над ними, и сразу же преступают к изучению использования более сложных конструкций, производными которых являются основные типы данных, которые мы называем фундаментальными. Поэтому пользуясь случаем решил написать небольшое пособие для изучения типов данных и основных операций над ними. Кроме того, много раз на форумах мне доводилось читать, что некоторые программисты игнорируют операции над типами данных с использованием указателей, считая их использование только прерогативой Си. Но, на самом деле этот не так, потому что операции с указателями являются основными при работе с объектами, позволяя, таким образом тратить намного меньше оперативной памяти чем игнорируя их. Типом называется размерность представления данных, которая создает объект, находящийся в оперативной памяти. Он может задавать не только размерность, но и диапазон действительных значений. Эти значение можно назвать свойствами объекта. Краткое обозначение Т. Объект – это структурированные данные, соответствующие определённому типу, по которому была выделена память. Краткое обозначение О. С помощью выражения формируется объект. Выражения бывают двух типов: 4.2 lvalueПонятие lvalue происходит от английского left value – значение в левой части. Обозначает адресное выражение ссылающееся на объект. т.е. E1 = E2 , где E1 – адресное выражение; E2 – выражение типа или его адреса . 5. Фундаментальные типыФундаментальные типы(встроенные типы) являются базовыми типами на которых строятся производные типы и типы определённые пользователем. К фундаментальным типам относят:
5.2 Логические типыК таким типам относится тип bool. Он может принимать только два значения TRUE или FALSE. Если применить интегральное продвижение значения типа bool будут иметь следующие значения:
Нужно помнить, что в языке Си этот тип отсутствует, поэтому необходимо назначить пользовательский тип с помощью перечисления(enum) и через typedef определить его как пользовательский тип точно так, как это показано ниже.
5.3 Символьные типыchar относиться к символьному типу. Используется для объявления символьных переменных, содержащих один символ. Может быть представлен с учетом знака, так и без него. Без учётом знака к типу прибавляется префикс unsigned. Таким образом, выражение будет иметь вид:
Без учёта знака для всех фундаментальных типов будет верна следующая запись:
5.3.2 Тип wchar_tРазмерность wchar_t зависит от реализации. Он используется для хранения символов большего размера. Символы большего размера могут быть представлены последовательностью из четырех и восьми символов:
Любые отклонения от количества цифр в последовательности является ошибкой. Этот тип обычно используют в строках, где символы имеют кодировку Unicode. 5.3.3 Символьный литералИногда к символьным типам относится символьный литерал. Хотя на самом деле, он относится к производному типу cоnst char, который будет рассматриваться позднее. Символьный литерал имеет следующее представление:
5.4 Целые типыК целым типам относятся: int, signed int, unsigned int. Целые типы имеют три размерности: short int, int, long int, где имеют быть следующее тождества:
5.4.2 Целые литералыЦелые литералы являются числовым значениями объекта, которые могут иметь следующие представления:
Десятичное значение 255, что означает 8-мь единиц в байте может быть представлено в шестнадцатеричном виде как 0xff, а в восьмеричном виде как 377. 5.4.2 Размерность типов long и intВ некоторых старых учебниках авторы ссылаясь на стандарты C89 и С99 часто пишут, что размерность типа int составляет два байта. А на самом деле в ОС Linux он имеет 4 байта, потому что мы имеем дело с 32-х разрядной архитектурой процессора, которая определяет размерность типов данных. В 64-х разрядной архитектуре, соответственно, тип int может иметь будет размерность 8 байт, но это не факт о чем скажу ниже. А вот как раз на 16-ти разрядной архитектуре, часто используемая в контроллерах, этот тип будет иметь размерность в два байта. Поэтому, когда используем разные архитектуры с отличающийся друг от друга разрядностью обращаем внимание на то, какую размерность имеют типы данных. В ОС Linux разрядность и диапазоны значений определены в заголовочном файле >limits.h< системной библиотеки, и если мы посмотрим, то что определено в этом файле касательно int увидим следующее
5.5 Типы с плавающей точкойТип с плавающей точкой представляющий числа с плавающей точкой может быть представлено тремя типами:
5.5.2 ЛитералДля объектов содержащие вещественные числа с одинарной точностью требуется добавлять суффикс fили F. А для объектов содержащие целочисленные значения расширенной точности L или l. Так же для целочисленных чисел без знака добавляется суффикс U или u. 5.6 Размерность арифметических типовК Арифметическими типами относятся: логические, символьные, целые, типы с плавающей точкой. Арифметические типы образуют объекты с некоторым диапазоном значений и размерностью. Диапазон значений задается максимальными и минимальными экстремумами, а размерность количество байт памяти, которое будет выделено при создании объекта.
5.7. Интегральное продвижение типовПри формировании выражений, которые предусматривает использование целых значений, могут использоваться как с учетом знака, так и без знака. Если значение целого(без знака) может вместить в себя все возможные значения исходного типа со знаком, то оно преобразуется в целое типа. Иначе, оно будет преобразовано в без знаковое целое. Таким образом, неявные преобразования, которые не изменяют значений целого, принято называть интегральным продвижением. Такое продвижение так же используется во время операций с объектами арифметических типов, когда нужно из объектов из целых типов bool, char или short создать объекты с более крупными, такими как int или long. 5.8. Преобразование знаковое в без знаковоеПреобразование между знаковыми и безнаковыми целыми производится в два этапа. Сначала знаковое целое преобразуется в знаковый эквивалент большого типа, а затем в беззнаковое значение. 6. Тип voidТип void не может создавать объекты O. А может быть только частью сложного типа, например как указатель на функцию. Код:
Давайте рассмотрим некоторые сообщения компилятора об ошибках связанные с использованием типа void. Сообщение компилятора: "const from 'void*' to 'char' loses precision" Объяснение: Преобразование типов 'void*' к 'char' утрачивает верность. Т.е. такое преобразование не может быть выполнено. Нельзя преобразовывать 'void*', который является lvalue к переменной типа 'char'. Код:
Сообщение компилятора: "invalid type argument оf 'unary*' " Код:
Сообщение компилятора: "__FILE__:__LINE__: error: «‘void*’ is not a pointer-to-object type" Объяснение: "Сложный тип 'void*' не является типом указатель на объект" Код:
7. Производные типыЯвляются производной от фундаментальных типов, описанные нами ранее . К производным типам относиться:
7.2 Массивы данного типаНабор элементов одного типа является массивом, исключая списки(enum) и объединения(union). Нумерация в массиве начинается с нуля по size-1. Обозначение массива: T O[size], где size – размер массива. Элементы массива O[0],O[1], O[2], ... ,O[size-1], где каждый объект самостоятельный объект O. 7.2.2 Многомерные массивыМногомерные массивы описывается, как массив, состоящий из m элементов производного типа: T' = T[size], где T[size] – одномерный массив. Запись T' O[m] = T[m][size] O, где m – количество одномерных массивов, которые содержит объект O. 7.2.3 Инициализация7.2.3.1 Для одномерных массивовПусть имеется массив T O[size], где T = int, а size = 8, array – имя объекта O:
Первым четырем элементам будет присвоены значения: 1,2,3,4 – а последующим четырем элементам значение нуль. Это обнуление производится всегда для не про инициализированных элементов одномерного массива. 7.2.3.2 Для многомерных массивовПусть имеется массив T O[m][size], где T= int, а m = 2, size = 8, array – имя объекта O:
Первый массив объектов array[0], состоящий из 8-ми однородных объектов типа int будет про инициализирован, как это было бы сделано для однородного массива, который рассматривался ранее. Второй массив объектов array[1] состоящий из 8-ми однородных объектов типа int будет про инициализирован нулями. Хотя была бы правильнее такая запись
7.2.3.3 Инициализация пустого массива разных типов
7.2.4 Размер массиваРазмер одного элемента массива T O[n] будет равна:
тогда размер всего массива будет равна
где n – число объектов массива; i – любой из n однородных объектов массива. 7.3 УказателиУказателем является lvalue имеет постфиксный оператор '*' после объявление типа T. Объявление указателя производится следующим образом: T* p; где p – указатель на объект произвольного типа. Под произвольным типом имеется в виду, что тип объекта может отличаться от того что имеет указатель p. Для присвоение адреса объекта того же типа T, что и указателя, следует пользоваться префиксным или унарным '&', т.е. T* p = &O; Для присвоение адреса объекта другого типа, отличного от указателя, рекомендуется пользоваться преобразованием типа: T0 O; T1* p= (T1*)&O; Для двойного указателя T** присвоение адреса одинарного указателя производится по тому же правилу, как адрес объекта одинарному указателю. T0* op; T0** dp = &op; 7.2 Операции с указателямиСуществуют три основных операторов:
Оператор '*' называется оператором разыменования. Он производит получение значения по адресу на который ссылается указатель T*. Оператор разыменования является унарным(префиксным). T* p; T O = *p; Для равнозначных типов существует оператор присвоение '=': 7.3.3 Строковый литералСтроковым литералов называется последовательность символов, заключенных в двойные кавычки. На его адрес указывает lvalue, ссылающиеся на адрес однородного массива объектов, к которым относятся символы последовательности. Пример. Инициализации строкового литерала( lvalue имеет тип int*).
Создание такого lvalue с типом 'int*' является ошибкой, потому что для строкового литерала тип lvalue должен быть const char*. Сообщение компилятора: __FILE__:__LINE__: error: cannot convert ‘const char*’ to ‘int*’ in initialization Объяснение: При инициализации невозможно преобразовать тип const char* к int* Код: см. пример ниже «Инициализации строкового литерал» Объявляем lvalue, как требует от нас следующее правило: lvalue строкового литерала должен быть всегда тип const char*. Пример. Инициализации строкового литерал lvalue имеет тип const char*.
При вызове функции f() на стандартное устройство ввода/вывода будет выведено: f:it's 1st my of characters literal lvalue является указателем типа 'const char*' на постоянный объект, в данном случае на однородный массив объектов типа 'const char' (константы и указатели будут рассмотрены в ПП 4.3 «Указатели» настоящего документа). Поэтому выше сказанное, что lvalue должен быть указатель типа 'const char*' не со всем верно. Можно использовать указатель 'char*'. (см. пример ''Использование в качестве возвращаемое значения функции''); Размер массива будет (n-1) * 1 байт, т.к. n-1 символом является невидимый символ 'конца строки', которым эквивалентен символьному литерал '\0'. Память под объект 'строковый литерал' выделяется в стеке и поэтому его можно использовать в качестве возвращаемого значения функции. Пример. Использование в качестве возвращаемое значения функции
Итак, строковый литерал является постоянным, т.е. неизменяемым объектом. Поэтому его можно использовать, при инициализации однородного массива, при условии что выполняется следующее условие: m ≤ n, где m – размерность массива, а n – размерность литерала. Для того, чтобы не задумываться сколько должено быть выделено памяти под однородный массив для инициализации его литералом, можно использовать неопределённый массив. В этом случае, всю головную боль за Вас возьмёт компилятор. Пример. Инициализация однородного массива с помощью литерала.
При вызове функции f(), на стандартное устройство ввода/ввода будет выведено все та же строчка, что и в примере «Инициализации строкового литерал(где lvalue имеет тип 'const char*' ): f:it's 1st my of characters literal Но, в отличии от первого примера, где происходила инициализация литералом постоянного объекта, в этом случае мы инициализируем модифицируемый объект.Пример. Модификация однородного массива, инициализируемого строковым литералом.
При вызове функции f() на стандартное устройство ввода/ввода вместо строки '' It's a string's literal'' будет выведено: f:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 7.3.4 указатели на массивы7.3.4.1 Первый элемент массиваИмя массива всегда ассоциируются с его адресом, который указывает на его первый элемент. Таким образом,T M[24]; Массив M – является адресом на первый элемент массива, состоящего из 24-х элементов типа T. Следующий пример показывает, что адрес первого элемента и массива равнозначны.
Результат выполнения функции f(): array's address 0xbf8bdfd8 first element address 0xbf8bdfd8 Для вывода адреса в этом примере используется форматирование потока вывода объекта cout. Он управляет выводом на буфер потока, который ассоциируются с объектом stdout(Стандартное устройство вывода), декларируемое в <cstdio>. По умолчанию, потоки стандартов C и C++ являются синхронизируемыми. 7.3.4.2 Операция присвоение адреса массиваПри операции присвоения (=) происходит назначение адреса массива указателю. Допустим имеется массив из 14-ти символов, который при создании будет инициализирован.
Операция присвоение адреса указателю можно так же назвать операцией преобразования типов, потому что тип T[] преобразуется к T*. Следующий пример иллюстрирует как тип T[] преобразуется к T*.
Результат выполнения функции f(): Characters message(array type):Hello world! 7.3.4.3 Вывод адреса указателя, массиваВывод адреса указателя, массива на стандартное устройства ввода/вывода производится cout из стандартной библиотеки C++. Следующий пример иллюстрирует вывод адреса массива и указателя с помощью преобразования типов.
Результат выполнения: Characters message(array type):Hello world! В четвертой строке определения функции f() производится явное преобразование типа char[] к unsigned long int*, который определен как пользовательский тип paddr_t в начале примера. 7.3.4.4 Доступ к элементам массиваИндексация или доступ к элементам массива осуществляется оператором []. Следующий пример иллюстрирует использование этого оператора:
В результате выполнения функции f() будет выведен каждый элемент массива. 0's element: H Второй способ доступа к элементам массива — по указателю. Для этого используется оператор разыменования ∗. Индексация производится с помощью операторов +,− и перегруженных операторов ++, −−. Следующий пример иллюстрирует такой способ доступа к элементам массива.
Результат выполнения функции f() будет таким. 0's element:H 7.3.5 указатели и константыПрефиксное помещение const перед типом T* не делает указатель константой. const T* P; Такая форма записи декларирует, что этот указатель содержит адрес константного объекта. Для объявление константного указателя необходимо сделать постфиксное помещение const вместе с типом T*. const T* const P; Константный указатель P содержит адрес на константный объект. Таким образом, существуют следующие типы указателей содержащие const: const T* const P;// Константный указатель на объект типа T Сообщение компилятора: __FILE__:__LINE__: error: assignment of read_only location Объяснение: Попытка записи в область памяти доступной только для чтения Код:
Сообщение компилятора: __FILE__:__LINE__: error: invalid conversion from 'const T* const' to 'T*' Объяснение: Нельзя присваивать адрес указателя на константный объект константному указателю. В данном случае, p2 является константным указателем, а p1 указателем на константный объект. Код:
Сообщение компилятора: __FILE__:__LINE__: error: uninitialized const ‘p3’ Объяснение: константный указатель p3 – не был проинициализирован. Код: см. предыдущий пример. 7.4 СсылкиСсылка это тип, который указывает на объект, которому реально выделена память. Т.к. как понятие переменная в С++ отсутствует, то инициализатором ссылки s является lvalue или объект адрес которого можно получить. T& s = O; где s – ссылка. В случае, если ссылкой является константный объект, то инициализатор для const T& не обязан быть lvalue. const T& s = (const)C; где s – ccылка, С – константный объект. В таких случаях:
Сообщение компилятора: __FILE__:__LINE__: error: invalid initialization of reference of type 'long int &' from expression of type 'int' Объяснение: Неправильная инициализация ссылки типа ''int &' от выражения типа 'int'. Т.о. тип ссылки и тип объекта должен быть равнозначным. Неправильная инициализация ссылки типа ''int &' от выражения типа 'int'. Т.о. тип ссылки и тип объекта должен быть равнозначным. Код:
Отсюда, если имеется O размерности Т, то и ссылка должна иметь тип такой же размерности. Т& s = O: To == T&s 7.4.2 Временные переменныеВременная переменная существует до конца области видимости, где она была объявлена. T t = T(n); где t – временная переменная, n – значение такой переменной. Для ссылок будут верными следующие выражения: T t = T(n); При использовании такого инициализатора создается временная переменная с присвоением значения n, которое используется как инициализатор. В отличии от lvalue, временная переменная может иметь тип большей размерности чем ссылка. И обратно, может иметь тип меньшей размерности. А при использовании типов не равной размерности происходит явное преобразование типов. 7.5 Использование спецификатора typedefCпецификатор typedef не создает новых типов в памяти, а присваивает идентификатор синтаксически эквивалентный ключевому слову спецификатора типа T. Поэтому, при использование данного спецификатора нельзя говорить о создание новых типов, а лишь о возможности присваивания им синонимов (P_t) существующим типам используемого стандарта C/C++. typedef T P_t; Возможность создания P_t от типа T используется различными библиотеками системного и прикладного программного обеспечения для задания базового набора, применяемых типов используемого стандарта C/C++. Одним из таких примеров является библиотека Gtk+, являющейся мультиплатформенным инструментарием для создания графических пользовательских интерфейсов. В ней задается базовый набор типов языка Си, призванный облегчить программисту жизнь при их использовании и дальнейшей портируемости. Библиография
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Copyright © 2010 rjaan as Andrey Rjavskov(Rzhavskov) <rjaan@yandex.ru> <arjavskov@gmail.com> |
![]() |