Файл: Лабораторная работа 1 Изучение среды разработки программ 3 Лабораторная работа 2 Исследование базовых типов данных языка Си 18.doc
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 05.12.2023
Просмотров: 266
Скачиваний: 3
СОДЕРЖАНИЕ
Лабораторная работа № 1Изучение среды разработки программ
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 2Исследование базовых типов данных языка Си
Лабораторная работа № 4Применение управляющих инструкций языка для организации ветвлений в программе
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
ЗАДАНИЕ ДЛЯ САМОСТОЯТЕЛЬНОЙ РАБОТЫ
Лабораторная работа № 5Исследование циклов
Лабораторная работа № 6Применение массивов и указателей для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 7Исследование массивов и указателей
Лабораторная работа № 8Применение функций работы со строками для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 6Использование функций для работы с массивами
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 7Программирование рекурсивных алгоритмов
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Практическое занятие № 8Применение производных типов данных для решения прикладных задач
ОСНОВНЫЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
Лабораторная работа № 5Исследование методов доступа к файлам данных
Лабораторная работа № 6Исследование связанных списков данных
Цель занятия:
-
Совершенствование навыков разработки программ в среде программирования MS Visual C++ -
Исследование диапазонов представимости стандартными типами данных числовой информации -
Исследование допустимых преобразований типов данных в языке Си
Время на выполнение работы: 2 часа
Программа исследований:
-
Исследование размерности типов данных и диапазона их представимости -
Исследование порядка преобразования типов данных
Подготовка к выполнению работы:
-
Изучить материал настоящего руководства и рекомендованную литературу (структура программы на языке высокого уровня, алфавит и элементарные конструкции языка Си, переменные и константы, стандартные типы данных). -
Практически освоить порядок работы на ПЭВМ в среде программирования Visual C++.
Материалы для подготовки к занятию:
-
Конспект лекций -
[1] стр. 22-26
Содержание отчета:
-
Цели исследования. -
Программа работы. -
Листинги программ. -
Результаты исследований. -
Выводы по каждому пункту и общий вывод.
МЕТОДИЧЕСКИЕ РЕКОМЕНДАЦИИ
Как известно, принцип микропрограммного управления предполагает хранение и программы и данных в памяти вычислительной системы. Каждая отдельная единица этих данных хранится в ячейках памяти, имеющих свои адреса. С точки зрения принципа действия ЭВМ заранее неизвестно, в каких конкретно ячейках памяти и с какими адресами будут храниться эти данные. Поэтому удобно абстрагироваться от знания конкретного адреса той или иной ячейки памяти, в которую будет записано то или иное данное. Удобно позволить программисту дать некоторое смысловое имя той области памяти, в которую он планирует записать данные, а операционную систему обязать выделять ячейки памяти для хранения этих данных. При этом, операционная система (а не программист!) будет отслеживать соответствие между этим смысловым именем и адресами ячеек, в которых хранятся данные, помеченные смысловым именем. Программист же будет помнить не конкретный физический адрес хранения своих данных, а понятное и легко запоминаемое смысловое имя. Поэтому в языках программирования введено понятие переменной, позволяющее отвлечься от конкретных адресов и обращаться к содержимому памяти с помощью идентификатора (смыслового имени или имени переменной). При этом имя переменной будет указывать на хранимое в памяти значение, о реальном адресе и способе хранения которого можно забыть.
В языке Си определены требования к назначению имен идентификаторов:
-
идентификатор может содержать только буквы латинского алфавита, цифры и символ подчеркивания «_»; -
идентификатор не должен начинаться с цифры; -
в имени идентификатора не допускаются пробелы; -
в идентификаторе строчные и прописные буквы различаются; -
идентификатор не должен совпадать ни с одним из ключевых слов.
Приведем примеры правильных и неправильных идентификаторов (имен переменных):
Правильно | Неправильно | Правильно | Неправильно |
Step10 | 10Step | F_I_O | F.I.O |
Step_10 | Step-10 | MyAge | My Age |
_Step10 | _Step 10 | Floor3 | Этаж3 |
При этом, переменные MyName, Myname, myname, myName, MYNAME являются разными!!!
Таким образом, переменные предназначены для хранения данных. Здесь уместен вопрос – а каких данных? Ведь данные бывают разные. Например 3.14159 – это вещественное число, ‘W’ – символ, а «встретимся завтра в 8» - набор символов, называемых строкой. И число, и символ, и строка – это данные. Следовательно, для хранения этих данных необходимо различное количество ячеек памяти. Тогда, целесообразно выделить основные типы данных (то есть, произвести их классификацию) и для каждого типа данных выделить свое количество ячеек памяти, достаточное для хранения этих данных.
В языке Си выделены следующие основные типы данных:
-
целое число – тип данных int; -
вещественное число – тип данных float, double; -
символ – тип данных char; -
логическая величина – тип данных bool.
Теперь необходимо вспомнить из курса «Информатика», что вся память ЭВМ разбита на ячейки памяти длиной по 8 бит, или 1 байт. В одну ячейку памяти можно записать 256 различных комбинаций 0 и 1, так как данные в памяти хранятся в двоичной системе исчисления. Таким образом, в одну ячейку памяти можно записать, например, целые числа от 0 до 255, в две – до 65535, в четыре – до 4294967295 и так далее. При этом, числа бывают не только положительными, но и отрицательными. Следовательно, к стандартным типам данных необходимо добавить некоторые модификаторы, которые говорили бы, какое это число. Например, двухбайтное знаковое целое – диапазон от – 32768 до + 32767.
Итак, в языке Си существует четыре спецификатора типа, уточняющих внутреннее представление и диапазон значений стандартных типов:
-
short – короткий; -
long – длинный; -
signed – знаковый; -
unsigned – беззнаковый.
В соответствии с этими модификаторами приведем классификацию основных типов данных (размеры для 32-х битного процессора):
Таблица 1 – Классификация основных типов данных
Тип | Диапазон значений | Размер (байт) | ||
bool | true | и | false | 1 |
signed char | -128 | … | +127 | 1 |
unsigned char | 0 | … | 255 | 1 |
signed short int | -32768 | … | +32767 | 2 |
unsigned short int | 0 | … | 65535 | 2 |
signed long int | -2147483648 | … | +2147483647 | 4 |
unsigned long int | 0 | … | 4294967295 | 4 |
float | -3.4*1038 | … | +3.4*1038 | 4 |
double | -1.7*10308 | … | +1.7*10308 | 8 |
long double | -3.4*104932 | … | +3.4*104932 | 10 |
Необходимо обратить внимание на то, что ЭВМ хранит вещественные числа в научном формате, который отличен от общепринятого. Так, число -1.7*10308, записанное в обычном формате, в научном будет выглядеть следующим образом: -1.7e308, или, например +1.7*10-308 будет выглядеть как 1.7е-308.
Любая переменная в языке Си имеет еще одну немаловажную характеристику, о которой необходимо поговорить отдельно. Любой объект или процесс в окружающем нас мире (как и сама наша жизнь) имеет свою продолжительность, или время жизни (существования). Вечна разве что Вселенная. Аналогично, и любая переменная имеет свое время жизни. Только для переменных время жизни определяется местом ее определения. Переменные в языке Си классифицируются по области видимости и делятся на две категории: локальные и глобальные. Рассмотрим их.
Локальная переменная – это такая переменная, которая объявлена внутри какого-либо блока (под блоком в языке Си понимается последовательность операторов, заключенная в фигурные скобки). Память под эту переменную выделяется в момент выполнения оператора ее объявления. По завершении работы блока эта переменная разрушается, а занимаемая ей память высвобождается. Таким образом, время жизни такой переменной ограничено тем блоком, в котором она объявлена.
Глобальная переменная – это такая переменная, которая объявлена вне любого блока. Следовательно, время ее жизни – время жизни самой программы.
Еще одним отличием локальной переменной от глобальной является процедура их инициализации. Любая глобальная переменная при объявлении автоматически обнуляется, локальная – нет. Поэтому, если начальное значение локальной переменной при объявлении еще не известно, лучше ее принудительно обнулить.
Резюмируя все вышесказанное, можно сделать вывод, что прежде чем описывать какие-либо данные, необходимо выполнить следующие действия:
-
-
выбрать имя переменной (описать идентификатор); -
определить исходя из необходимого диапазона представления чисел тип переменной; -
определить область видимости переменной; -
обнулить или проинициализировать переменную начальным значением.
-
Пример 1.
signed short int a=0.5; - объявлена двухбайтная знаковая целая переменная с именем a, которая проинициализирована начальным значением 0.5
unsigned char letter=’G’; - объявлена беззнаковая символьная переменная с именем letter, которая проинициализирована начальным значением ‘G’. Обратите внимание на то, что значения символьных переменных берутся в знаки апостроф – ‘’, а не в кавычки “”!
Для упрощения формы записи типов переменных некоторые наиболее часто используемые типы можно использовать по умолчанию без модификаторов. Так:
-
char равносильно signed char; -
int равносильно signed long int; -
long равносильно signed long int.
Одинаковость записи по умолчанию для int и long обусловлена тем, что ранее для 16-разрядного процессора под тип int по умолчанию выделялось 2 байта памяти, а не 4, как для 32-разрядного.
Неправильный выбор типа переменной зачастую ведет к потере исходных данных.
Рассмотрим пример 2.
. . .
signed short int a=-12350,b=0;
b=a*5;
cout<<b;
. . .
Вполне ожидаемый результат: - 61750. Но этого результата мы не увидим. На экране появится 3786!!! Откуда же это число. Давайте разберемся в том, что же произошло.
Под переменные a и b в памяти отведено по два байта. Причем, переменные объявлены как знаковые, следовательно, под значение самого числа выделено не 16 бит, а только 15, так как первый бит указывает на знак числа. Следовательно, диапазон представления числа не от 0 до 65535, а от -32768 до 32767. Число -61750 выходит за диапазон представления выбранного типа данных. Тогда как же оно связано с числом 3786?
Запустим программу в режиме отладки и выполним оператор b=a*5;. Затем откроем окно просмотра ассемблерного кода программы (Debug -> Windows -> Disassembly) и найдем там команду
0040106B mov word ptr [ebp-8],ax
О чем эта команда? 0040106B – это адрес команды mov word ptr [ebp-8],ax в сегменте кода программы. mov – команда пересылки данных между регистрами и памятью: она пересылает содержимое регистра ax в область памяти по адресу [ebp-8], где ebp – адрес начала сегмента стека программы, а 8 – смещение относительно начала этого сегмента. Другими словами, эта команда как раз и заносит результат операции b=a*5;
в аккумулятор процессора. Давайте посмотрим, что же находится в этом самом регистре ax. Для этого откроем окно просмотра состояния регистров процессора (Debug -> Windows -> Registers), где найдем строку EAX = FFFF0ECA. FFFF0ECA – это и есть записанный в шестнадцатеричном формате результат нашей операции. Но этот результат четырехбайтный, так как регистры процессора 32-разрядные. Наша же переменная двухбайтная, поэтому в нее запишется только 2 байта, то есть, 0ECA. Давайте запустим стандартный Windows-калькулятор и приведем его к инженерному виду. Затем переключим представление чисел в Hex-формат и введем число 0ECA. Затем переключим в Dec-формат (десятичный) и увидим число 3786!!!
Давайте рассмотрим другой пример.
Пример 3.
. . .
float a=-1.34872e23;
unsigned int b=3856956264;
cout<<"\na="<
cout<<"\nb="<
. . .
В этом фрагменте программы нет ничего странного. Но это на первый взгляд! Мы знаем, что все данные хранятся в памяти ЭВМ. Давайте посмотрим, как будут храниться эти два числа в памяти. Для этого запустим программу в режиме отладки. Затем откроем окно просмотра ассемблерного кода программы (View -> Debug Windows -> Disassembly) и найдем там команды:
10: float a=-1.34872e23;
00401038 mov dword ptr [ebp-4],0E5E47B68h
11: unsigned int b=3856956264;
0040103F mov dword ptr [ebp-8],0E5E47B68h
Очевидно, что эти два разных не только по значению, но и по типу числа запишутся в памяти абсолютно одинаково! Но ведь в последующем они опять считаются из памяти как два разных числа!!! Именно из этого вытекает главное назначение типизации данных:
Типизация данных предназначена для задания размера памяти под хранение значения переменной и определения формата записи и считывания значения этой переменной из памяти ЭВМ |
Преобразование типов. Если в выражении появляются операнды различных типов, то они преобразуются к некоторому общему типу, при этом к каждому арифметическому операнду применяется такая последовательность правил:
-
Если один из операндов в выражении имеет тип long double, то остальные тоже преобразуются к типу long double. -
В противном случае, если один из операндов в выражении имеет тип double, то остальные тоже преобразуются к типу double. -
В противном случае, если один из операндов в выражении имеет тип float, то остальные тоже преобразуются к типу float. -
В противном случае, если один из операндов в выражении имеет тип unsigned long, то остальные тоже преобразуются к типу unsigned long. -
В противном случае, если один из операндов в выражении имеет тип long, то остальные тоже преобразуются к типу long. -
В противном случае, если один из операндов в выражении имеет тип unsigned, то остальные тоже преобразуются. к типу unsigned. -
В противном случае все операнды преобразуются к типу int. При этом тип char преобразуется в int со знаком; тип unsigned char в int, у которого старший байт всегда нулевой; тип signed char в int, у которого в знаковый разряд передается знак из сhar; тип short в int (знаковый или беззнаковый).
Предположим, что вычислено значение некоторого выражения в правой части оператора присваивания. В левой части оператора присваивания записана некоторая переменная, причем ее тип отличается от типа результата в правой части. Здесь правила преобразования очень простые: значение справа от оператора присваивания преобразуется к типу переменной слева от оператора присваивания. Если размер результата в правой части больше размера операнда в левой части, то старшая часть этого результата будет потеряна.
В языке С++ можно явно указать тип любого выражения. Для этого используется операция преобразования ("приведения") типа. Она применяется следующим образом:
(тип) выражение
(здесь можно указать любой допустимый в языке Си тип).
Рассмотрим пример 4:
int a = 30000; float b;
........
b = (float) a * 1000000;
(переменная a целого типа явно преобразована к типу float; если этого не сделать, то результат будет потерян, т.к. a * 1000000 > 2147483647).
Другой пример.
Пример 5.
. . .
int a=850; char b;
b=a;
cout<<b;
. . .
Возникает вопрос: что появится на экране? Ответ – символ с кодом 82, или “R”.
Переменная типа int занимает 4 байта в памяти, а переменная типа char – 1 байт. При присвоении значения переменной a переменной b в переменную b занесется только младший байт числа a, то есть:
a = 00000000 00000000 00000011 01010010(2) = 00 00 03 52(16) = 850(10)
b = 01010010(2) = = 00000052(16) = 82(10)
Как видно из примера, две значащие единицы 2-го байта числа a были в результате преобразования типа отброшены. Аналогичного результата можно было достичь вычитанием из числа 850 числа 256 до тех пор, пока не получится число, которое меньше 256:
850 – 256 = 594 > 256
594 – 256 = 338 > 256
338 – 256 = 82 < 256 СТОП
Почему это происходит именно так, необходимо сформулировать самому в выводах по лабораторной работе.