Скачиваний:
2
Добавлен:
03.01.2024
Размер:
2.34 Mб
Скачать

СПбГУТ им. проф. М.А. Бонч-Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и ВТ)

ПРОГРАММИРОВАНИЕ

Единственный способ изучать новый язык программирования - писать на нем программы.

Брайэн Керниган

Лекция 4: Система типов в языке Си

1.Тип данных (тип)

2.Переменные и константы

3.Преобразование типов

4.Составные типы и указатели

Санкт-Петербург, 2021г.

Введение

В языках программирования у любого «кусочка» данных

(переменной, константы, аргумента функции) есть тип.

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

Например, в этом участке псевдокода переменная age имеет тип «число»:

number age = 12 // Тип указан явно перед именем переменной

В разных языках по-разному устроена работа с типами.

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

Вообще, существуют «безтиповым языки», где вся работа с типами возлагается на разработчика.

Но таких языков немного и они довольно редкие. Один из примеров — язык ассемблера (очень низкоуровневый язык программирования).

Вязыке различают понятия:

тип данных

модификатор типа

Тип данных — это, например, целый, а модификатор — со знаком или без знака.

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

Целое без знака — только положительные значения.

Данные

Структурированные

Составные

Массивы Перечисления Объединения

Структуры

Классы (С++)

Простые

Числа

Целые

Вещественные

Символы

Указатели

адреса объектов в памяти

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

char — символьный;

int — целый;

float — вещественный;

double — вещественный двойной точности;

void — не имеющий значения.

2

Введение

Тип данных – фундаментальное понятие языка программирования (ЯПр).

Тип данных определяет:

что именно представляют собой данные;

как они хранятся в памяти;

какие операции с ними можно выполнять.

Изначально, типы данных делятся на простые и составные.

Простой – это тип данных, объекты (переменные или постоянные) которого не имеют доступной программисту внутренней структуры.

Для объектов составного типа данных, в противовес простому, программист может работать с элементами внутренней его структуры.

Числовой тип данных разработан для хранения естественно чисел. Символьный - для хранения одного символа. Логический тип имеет два значения: истина и ложь. Перечислимый тип может хранить только те значения, которые прямо указаны в его описании.

Для простых типов данных определяются границы диапазона

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

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

00011011011100010110010000111011 ...

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

Типизация – способ задания типа объекта. А что такое тип?

Простым языком тип – это смысл, который несет объект.

Тип ограничивает область допустимых значений и допустимых действий над объектом.

Безтиповом ассемблер – это как?

Никаких переменных нет, есть только память

Но на память можно «смотреть» по-разному (как на int или как на char)

Где же «тип» в командах ассемблера?

«Как мы смотрим» на память – задается с помощью постфиксов: LDR – загрузить слово (word, 4 байта)

LDRH – загрузить полуслово (halfword, 2 байта) LDRB – загрузить байт (byte)

LDRSB – загрузить знаковый байт (signed byte)

LDRSH – загрузить знаковое полуслово (signed halfword) LDRD – загрузить двойное слово (double word, 8 байт) LDRM – загрузить много байт (multiple)

Си – «кроссплатформенный ассемблер».

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

Очень много вещей – это наследство от старых времен (PDP-11).

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

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

3

Для чего нужны типы?

1)Выявление ошибок:

Статическая проверка типов -- помогает раньше обнаруживать некоторые ошибки в программах.

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

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

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

2) Абстракция (Иерархия) – как средство победы над сложностью:

Поддержание дисциплины программирования.

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

Типы появляются в интерфейсах модулей (или близких по смыслу структур, таких, как классы).

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

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

Более абстрактный стиль мышления повышает качество проектов.

3)Документация

Типы полезны также при чтении программ.

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

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

4)Безопасность языков

Термин «безопасный язык» еще более расплывчат, чем

«система типов».

Неформально, впрочем, можно сказать, что безопасный язык

ограждает программиста от совершения фатальных ошибок.

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

4

Для чего нужны типы?

Для чего нужны типы (продолжение):

5)Эффективность:

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

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

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

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

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

Система типов данных языка — это система, которая ставит в соответствие каждому значению, вычисленному

6)Другие приложения (в информатике и близких дисциплинах):

Все большее значение приобретает использование систем типов в области безопасности компьютеров и сетей. Например, статическая типизация лежит в основе модели безопасности Java и архитектуры автоматического конфигурирования (plug and play) сетевых устройств JINI

Алгоритмы проверки и вывода типов встречаются во многих инструментах анализа программ, помимо компиляторов.

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

Растет интерес к системам типов и в сообществе специалистовпо базам данных. Это связано с популярностью «сетевых метаданных», использующихся для описания структурированных данных на XML, таких как DTD (Document Type Definitions, XML 1998) и других видов схем (таких, как новый стандарт XMLSchema, XS 2000). Новые языки для запросов к XML и обработки XML-данных обладают мощными статическими системами типов, прямо основанными на этих языках схем.

Совершенно отдельная область приложения систем типов вычислительная лингвистика, где типизированные лямбдаисчисления лежат в основе таких формализмов, как категориальная грамматика (categorial grammar).

Список типизаций:

неявная

безопасная

номинативная

бестиповая

статическая

вывод типов

строгая

динамическая

структурная

мягкая

типизированная

нестрогая

явная

в процессе выполнения программы, свой тип данных.

5

1. Тип данных (тип)

– Что такое данные?

Когда человек задаёт вопрос: он передаёт информацию, а другой человек получает информацию.

Когда прозвучит ответ: человек задавший вопрос получит информацию, а человек ответивший на него передаст информацию. Это называется обмен данными. Но что здесь данные? Давайте заглянем в терминологию:

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

(ISO/IEC/IEEE 24765-2010).

Краткий вывод: "Все – есть данные".

Соответственно, на них же всё и держится.

Когда мы создаём переменную: мы передаём в неё данные. int var = 123; // var = 123

Но что если мы передадим в переменную... Другую переменную? Без проблем:

int var = 123; // var = 123

int foo = var; // foo = var = 123

Что такое тип данных?

Тип данных — допустимое множество значений.

Тип (данных) – это способ доступа к значению, хранящемуся в памяти или являющемуся результатом вычислений

Тип данных (тип) — множество значений и операций над

этими значениями (IEEE Std 1320.2-1998)

Типы данных – одна из важнейших частей языка Си. Переменные, функции, поля класса и даже другие типы данных – ничто без данных.

Тип данных характеризует одновременно:

множество допустимых значений, которые могут принимать данные, принадлежащие к этому типу;

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

Тип задается выражением языка Си:

Функциональные типы – описывают функции

Полные типы – полностью описывают объекты

Неполные типы – описывают объекты, но не позволяют определить их размер в байтах (void и др.)

Производные vs непроизводные

Промежуточный вывод: в переменной есть данные. Но какие?

Это мы должны спросить у конкретных типов данных. Например в int помещается значение одного из адресов

оперативной памяти (ОП): когда мы создаём переменную под неё выделяется ОП, и значение которое было по адресу нашей новой переменной, присваивается переменной.

А как же void? Это же пустота. В void 0 байт. void ничему не равен… Проверим:

void var;

// var is empty

int var_size;

// var_size = junk

var_size = sizeof(var);

// var_size = 1 (1 byte)

На самом деле тут нет ничего странного. Любая переменная в хранится в ОП. И void тоже. Соответственно, void должна что-то хранить.

Но что она хранит – мы не можем узнать. Так как void хранит значение, мы можем получить адрес void переменной.

Вам известно, что в памяти компьютера все числа хранятся в виде последовательности 0 и 1. Так вот, для того чтобы записать любое число только ноликами и единичками существуют определённые правила. Эти

правила довольно сильно отличаются для целых чисел и для

 

вещественных чисел. В памяти компьютера числа "3" и "3.0" будут

6

записаны совершенно по-разному.

 

Тип данных (тип)

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

Простые данные – это целые и вещественные числа, символы и указатели (адреса объектов в памяти).

Целые числа не имеют, а вещественные имеют дробную часть.

Структурированные данные – это массивы и структуры; они будут рассмотрены ниже.

Вязыке различают понятия "тип данных" и "модификатор типа".

Тип данных – это, например, целый, а модификатор – со знаком или без знака.

Целое со знаком будет иметь как положительные, так и отрицательные значения, а целое без знака – только положительные значения.

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

char – символьный; int – целый;

float – вещественный;

double – вещественный двойной точности; void – не имеющий значения.

Дадим им краткую характеристику:

Переменная типа char имеет размер 1 байт, ее значениями являются различные символы из кодовой таблицы, например: 'ф', ':', 'j' (при записи в программе они заключаются в одинарные кавычки).

Размер переменной типа int в стандарте языка Си не определен. В большинстве систем программирования размер переменной типа int соответствует размеру целого машинного слова.

Например, в компиляторах для 16-разрядных процессоров переменная типа int имеет размер 2 байта. В этом случае знаковые значения этой переменной могут лежать в диапазоне от -32768 до

32767.

Ключевое слово float позволяет определить переменные вещественного типа. Их значения имеют дробную часть, отделяемую точкой, например: -5.6, 31.28 и т.п. Вещественные числа могут быть записаны также в форме с плавающей точкой, например: -1.09e+4. Число перед символом "е" называется мантиссой, а после "е" - порядком. Переменная типа float занимает в памяти 32 бита. Она может принимать значения в диапазоне от 3.4е-38 до 3.4e+38.

Ключевое слово double позволяет определить вещественную переменную двойной точности. Она занимает в памяти в два раза больше места, чем переменная типа float (т.е. ее размер 64 бита). Переменная типа double может принимать значения в диапазоне от 1.7e-308 до 1.7e+308.

Ключевое слово void (не имеющий значения) используется для нейтрализации значения объекта, например, для объявления функции, не возвращающей никаких значений.

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

unsigned

Модификаторы записываются перед

signed

спецификаторами типа, например:

unsigned char.

short

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

long

компилятор предполагает, что этим спецификатором

 

 

является int.

 

 

7

 

Тип данных (тип)

Таким образом, следующие строки:

Основные типы данных языка Си

long а;

 

long int а;

 

являются идентичными и определяют объект а как длинный целый.

Таблица ниже иллюстрирует возможные сочетания модификаторов (unsigned, signed, short, long) со спецификаторами (char, int, float и double), а также показывает размер и диапазон значений объекта (для 16-разрядных компиляторов).

имеет два основных типа данных (переменных): целое и символ, объявляемые как int и char, соответственно.

Нет отдельной булевой переменной (Си 89)

В качестве булевой переменной используется переменная int. Если эта переменная содержит 0, то это означает ложь/false, а любое другое значение означает истина/true.

также имеет и типы с плавающей точкой.

К типу int можно применять «прилагательные» short, long или unsigned, которые определяют (зависящий от компилятора) диапазон значений. Большинство процессоров 8088 используют 16-битные целые числа для int и short int и 32-битные целые числа для long int. Целые числа без знака (unsigned int) на процессоре 8088 имеют диапазон от 0 до 65535, а не от -32768 до +32767, как это у обычных целых чисел (int).

Символ занимает 8 бит.

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

8

Базовые типы данных языка Си

Тип данных

Типичный

Минимально допустимый диапазон значений

 

размер в битах

 

 

 

 

 

 

 

 

char

8 (или 1 байт)

от –128 до 127

 

 

 

 

 

unsigned char

8

от 0 до 255

 

 

 

 

 

signed char

8

от –127 до 127

 

 

 

 

 

int

16 или 32

от –32767 до 32767

 

 

 

 

 

unsigned int

16 или 32

от 0 до 65535

 

 

 

 

 

signed int

16 или 32

от –32767 до 32767

 

 

 

 

 

short int

16

от –32767 до 32767

 

 

 

 

 

unsigned short int

16

от 0 до 65535

 

 

 

 

 

signed short int

16

от –32767 до 32767

 

 

 

 

 

long int

32

от –2147483647 до 2147483647

 

 

 

 

 

long long int

64

от –(263–1 ) до (263–1) для С99

 

signed long int

32

от –2147483647 до 2147483647

 

 

 

 

 

unsigned long int

32

от 0 до 4294967295

 

 

 

 

 

unsigned long long int

64

от 0 до (264–1) для С99

 

float

32

от 1Е–37 до 1Е+37 (с точностью не менее 6 значащих десятичных цифр)

 

 

 

 

 

 

 

 

double

64

от 1Е–37 до 1Е+37 (с точностью не менее 10 значащих десятичных цифр)

 

 

 

 

 

 

 

 

long double

80

от 1Е–37 до 1Е+37 (с точностью не менее 10 значащих десятичных цифр)

 

 

 

9

 

 

 

Пример: вещественные типы данных

Числа вещественного типа данных задаются в форме чисел с

плавающей запятой.

Плавающая запятая — форма представления действительных чисел, в которой число хранится в форме мантиссы и показателя степени.

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

N=M 10p

где N — записываемое число; M — мантисса;

p (целое) — порядок.

Например:

14441544 = 1,4441544*107;

0,0004785 = 4,785*10-4.

Компьютер же на экран выведет следующие числа:

1.4441544E+7; 4.785E-4

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

Рассмотрим на примера типа данных, который хранится в 8 байтах или 64 битах.

В данном случае, мантисса составляет 53 бита: 1 для знака числа и 52 для её значения; порядок 10 битов: 1 бит для знака и

10 для значения.

Мы можем в данном случае говорить о диапазоне точности, то есть насколько малое и насколько большое число может хранить данный тип данных: 4,94×10−324 до 1.79×10308.

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

Вывод: вещественные типы данных, в отличии от целочисленных, характеризуются диапазоном точности и количеством значащих разрядов.

Вязыке существует три типа чисел с плавающей точкой:

float

double (двойной точности)

long double.

Также существует три формата вывода вещественных чисел,

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

Вещественные числа могут иметь высокую точность, очень маленькое или очень большое значение.

Если выполнить функции printf() с такими параметрами:

double a = 0.0005;

То на экране мы увидим следующее:

printf("%f\n", a);

0.000500

printf("%g\n", 0.0005);

0.0005

printf("%g\n", 0.00005);

5e-05

printf("%e\n", 0.0005);

5.000000e-04

 

 

В первом случае (%f) выводится число в обычном виде. По умолчанию точность представления числа равна шести знакам после точки.

Во втором случае (%g) число выводится как обычно, если количество значащих нулей не больше четырех. Если количество значащих нулей четыре и больше, то число выводится в нормализованном виде (третий случай).

Запись 5e-5 означает 5 * 10-5, что равно 0.00005. А, например, запись 4.325e+3 является экспоненциальной записью 4.325 * 103, что равно 4325.

Четвертый формат (%e) выведет число исключительно в нормализованном виде, каким бы это вещественное число ни было. 10

Соседние файлы в папке Лекции