Простые типы данных. Представление в памяти ЭВМ символов, целых и вещественных чисел.
БЛОК 1)
Простые типы данных. Представление в памяти ЭВМ символов, целых и вещественных чисел.
Простые типы данных к ним относятся порядковые типы данных , вещественные типы данных
К порядковым относятся целочисленные , символьные, логические.
Для представления информации в памяти ЭВМ (как числовой, так и не числовой) используется двоичный способ кодирования.
Элементарная ячейка памяти ЭВМ имеет длину 8 бит (байт). Каждый байт имеет свой номер (его называют адресом). Наибольшую последовательность бит, которую ЭВМ может обрабатывать как единое целое, называют машинным словом. Длина машинного слова зависит от разрядности процессора и может быть равной 16, 32, 64 битам и т.д.
Для кодирования символа достаточно 1 байт который используют таблицу кодировки ASCII или 2 байта для кодировки Unicode
Целые тип данных может быть знаковым или без знака в случаи со знаком за старший бит отвечает за знак 1 отрицательны 0 положительный остальное число храниться в 2чном коде в оставшихся яцейках
Диапозоны 1 байт знаковый -128..127
1 байт без знака 0..255
2 байта знаковый -32768.32767
Без знака 0.65535
4 байта -2^31 2^31-1
Вещественный тип храниться по принципу M*10^p Где М мантисса числа P смещение
Размер, байт
Диапазон абсолютных величин
Точность, количество десятичных цифр
4 байта диапазон значения от 3.4Е—38 до 3.4Е+38 точность количество десятичных цифр 7
8 байта диапазон значения от 1.7Е—308 до 1 .7Е+308 точность количество десятичных цифр15
63 бит знак 62-52 бит порядок 51- 0 мантисса
Структурированные типы данных. Представление в памяти ЭВМ массивов, структур, битовых полей и т.д.
Массив - это пронумерованная последовательность величин одинакового типа, обозначаемая одним именем. Элементы массива располагаются в последовательных ячейках памяти, обозначаются именем массива и индексом. Каждое из значений, составляющих массив, называется его компонентой (или элементом массива).
Массив данных в программе рассматривается как переменная структурированного типа. Массиву присваивается имя, посредством которого можно ссылаться как на массив данных в целом, так и на любую из его компонент.
Структуры, как и массив, образуется из отдельных компонентов, но, в отличие от массива, типы входящих в запись данных могут быть различны. Компоненты записи имеют специальное название - поля. Доступ к полям осуществляется по имени.
Метод использования битовых полей для доступа к битам основан на структурах. Битовое поле, на самом деле, - это просто особый тип структуры, определяющей, какую длину имеет каждый член. Стандартный вид объявления битовых полей следующий:
struct имя структуры {
тип имя1: длина;
тип имя2: длина;
...
тип имяN: длина;
}
Битовые поля должны объявляться как int, unsigned или signed. Битовые поля длиной 1 должны объявляться как unsigned, поскольку 1 бит не может иметь знака. Битовые поля могут иметь длину от 1 до16 бит для 16-битных сред и от 1 до 32 бит для 32-битных сред. В Borland С++ самый левый бит является знаковым.
3)Классы в С++.
Класс — разновидность абстрактного типа данных в объектно-ориентированном программировании (ООП), характеризуемый способом своего построения
Класс позволяет вашим программам группировать данные и функции которые выполняют операции над этими данными. Большинство книг и статей об объектно-ориентированном программировании называют функции класса методами. Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов и закрывающая фигурная скобка:
class class_name
{
int data_member; // Элемент данных
void show_member(int); // Функция-элемент
};
После определения класса вы можете объявлять переменные типа этого класса (называемые объектами), как показано ниже:
class_name object_one, object_two, object_three;
Следующее определение создает класс employee, который содержит определения данных и метода:
class employee
{
public:
char name[64] ;
long employee_id;
float salary;
void show_employee(void)
{
cout << "Имя: " << name << endl;
cout << "Номер служащего: " << employee_id << endl;
cout << "Оклад: " << salary << endl;
};
};
Инкапсуляция, наследование, полиморфизм
Все языки OOP, включая С++, основаны на трёх основополагающих концепциях, называемых инкапсуляцией, полиморфизмом и наследованием. Рассмотрим эти концепции.
Инкапсуляция
Инкапсуляция (encapsulation) - это механизм, который объединяет данные и код, манипулирующий зтими данными, а также защищает и то, и другое от внешнего вмешательства или неправильного использования. В объектно-ориентированном программировании код и данные могут быть объединены вместе; в этом случае говорят, что создаётся так называемый "чёрный ящик". Когда коды и данные объединяются таким способом, создаётся объект (object). Другими словами, объект - это то, что поддерживает инкапсуляцию.
Внутри объекта коды и данные могут быть закрытыми (private). Закрытые коды или данные доступны только для других частей этого объекта. Таким образом, закрытые коды и данные недоступны для тех частей программы, которые существуют вне объекта. Если коды и данные являются открытыми, то, несмотря на то, что они заданы внутри объекта, они доступны и для других частей программы. Характерной является ситуация, когда открытая часть объекта используется для того, чтобы обеспечить контролируемый интерфейс закрытых элементов объекта.
На самом деле объект является переменной определённого пользователем типа. Может показаться странным, что объект, который объединяет коды и данные, можно рассматривать как переменную. Однако применительно к объектно-ориентированному программированию это именно так. Каждый элемент данных такого типа является составной переменной.
Полиморфизм
Полиморфизм (polymorphism) (от греческого polymorphos) - это свойство, которое позволяет одно и то же имя использовать для решения двух или более схожих, но технически разных задач. Целью полиморфизма, применительно к объектно-ориентированному программированию, является использование одного имени для задания общих для класса действий. Выполнение каждого конкретного действия будет определяться типом данных. Например для языка Си, в котором полиморфизм поддерживается недостаточно, нахождение абсолютной величины числа требует трёх различных функций: abs(), labs() и fabs(). Эти функции подсчитывают и возвращают абсолютную величину целых, длинных целых и чисел с плавающей точкой соответственно. В С++ каждая из этих функций может быть названа abs(). Тип данных, который используется при вызове функции, определяет, какая конкретная версия функции действительно выполняется. В С++ можно использовать одно имя функции для множества различных действий. Это называется перегрузкой функций (function overloading).
В более общем смысле, концепцией полиморфизма является идея "один интерфейс, множество методов". Это означает, что можно создать общий интерфейс для группы близких по смыслу действий. Преимуществом полиморфизма является то, что он помогает мнижать сложность программ, разрешая использование того же интерфейса для задания единого класса действий. Выбор же конкретного действия, в зависимости от ситуации, возлагается на компилятор. Вам, как программисту, не нужно делать этот выбор самому. Нужно только помнить и использовать общий интерфейс. Пример из предыдущего абзаца показывает, как, имея три имени для функции определения абсолютной величины числа вместо одного, обычная задача становится более сложной, чем это действительно необходимо.
Полиморфизм может применяться также и к операторам. Фактически во всех языках программирования ограниченно применяется полиморфизм, например, в арифметических операторах. Так, в Си, символ + используется для складывания целых, длинных целых, символьных переменных и чисел с плавающей точкой. В этом случае компилятор автоматически определяет, какой тип арифметики требуется. В С++ вы можете применить эту концепцию и к другим, заданным вами, типам данных. Такой тип полиморфизма называется перегрузкой операторов (operator overloading).
Ключевым в понимании полиморфизма является то, что он позволяет вам манипулировать объектами различной степени сложности путём создания общего для них стандартного интерфейса для реализации похожих действий.
Наследовние
Наследование (inheritance) - это процесс, посредством которого один объект может приобретать свойства другого. Точнее, объект может наследовать основные свойства другого объекта и добавлять к ним черты, характерные только для него. Наследование является важным, поскольку оно позволяет поддерживать концепцию иерархии классов (hierarchical classification). Применение иерархии классов делает управляемыми большие потоки информации. Например, подумайте об описании жилого дома. Дом - это часть общего класса, называемого строением. С другой стороны, строение - это часть более общего класса - конструкции, который является частью ещё более общего класса объектов, который можно назвать созданием рук человека. В каждом случае порождённый класс наследует все, связанные с родителем, качества и добавляет к ним свои собственные определяющие характеристики. Без использования иерархии классов, для каждого объекта пришлось бы задать все характеристики, которые бы исчерпывающи его определяли. Однако при использовании наследования можно описать объект путём определения того общего класса (или классов), к которому он относится, с теми специальными чертами, которые делают объект уникальным. Наследование играет очень важную роль в OOP.
Перегрузка функций
Перегрузка функций позволяет вам указать несколько определений для одной и той же функции. В процессе компиляции C++ определит, какую функцию следует использовать, основываясь на количестве и типе передаваемых параметров.Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров.
1. Перегрузка функций предоставляет несколько "взглядов" на одну и ту же функцию внутри вашей программы.
2. Для перегрузки функций просто определите несколько функций с одним и тем же именем и типом возвращаемого значения, которые отличаются только количеством и типом параметров.
3. В процессе компиляции C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых параметров.
4. Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.
#include <iostream.h>
int add_values(int a,int b)
{
return(a + b);
)
int add_values (int a, int b, int c)
(
return(a + b + c);
)
void main(void)
{
cout << "200 + 801 = " << add_values(200, 801) << endl;
cout << "100 + 201 + 700 = " << add_values(100, 201, 700) << endl;
}
Перегрузка операций.
С++ позволяет переопределить действие большинства операций так, чтобы при использовании с объектами конкретного класса они выполняли заданные функции. Это дает возможность использовать собственные типы данных точно так же, как стандартные. Обозначения собственных операций вводить нельзя.
При перегрузке унарной операции операндом является вызывающий класс.
При перегрузке бинарной операции в качестве первого операнда выступает вызывающий класс, второй операнд задается в качестве аргумента.
Синтаксис:
тип_результата operator операция(формальные_параметры);
Например,
Massiv operator +(int k);
Шаблоны функций.
Шаблоны функций - специальные функции, которые могут работать с универсальными типами. Это позволяет нам создавать шаблон функции, функциональность которого может быть адаптирована больше чем к одному типу или классу, не повторяя весь код для каждого типа.
В C++ это может быть достигнуто, используя шаблонные параметры. Шаблонный параметр - специальный вид параметра, который может использоваться, чтобы передать тип как параметр: точно так же, как параметры регулярной функции могут использоваться, чтобы передать значения к функции, шаблонные параметры позволяют передавать также типы к функции. Эти шаблоны функций могут использовать эти параметры, как будто они были любым другим нормальным шрифтом.
Формат для того, чтобы объявить шаблоны функций с параметрами типа:
template <class identifier> function_declaration;
Чтобы использовать этот шаблон функции, мы используем следующий формат для вызова функции:
function_name <type> (parameters);
#include <iostream>
using namespace std;
template <class T>
T GetMax (T a, T b) {
T result;
result = (a>b)? a : b;
return (result);
}
int main () {
int i=5, j=6, k;
long l=10, m=5, n;
k=GetMax<int>(i,j);
n=GetMax<long>(l,m);
cout << k << endl;
cout << n << endl;
return 0;
}
Использование шаблонов функций уменьшает объем программирования, позволяя компилятору C++ генерировать операторы для функций, которые отличаются только типами возвращаемых значений и параметров.
Шаблоны классов.
По мере того как количество создаваемых вами классов растет, вы обнаруживаете, что некоторый класс, созданный для одной программы (или, возможно, для этой), очень похож на требующийся вам сейчас. Во многих случаях классы могут отличаться только типами. Другими словами, один класс работает с целочисленными значениями, в то время как требующийся вам сейчас должен работать со значениями типа float. Чтобы увеличить вероятность повторного использования существующего кода, C++ позволяет вашим программам определять шаблоны классов. Если сформулировать кратко, то шаблон класса определяет типонезависимый класс, который в дальнейшем служит для создания объектов требуемых типов. Если компилятор C++ встречает объявление объекта, основанное на шаблоне класса, то для построения класса требуемого типа он будет использовать типы, указанные при объявлении. Позволяя быстро создавать классы, отличающиеся только типом, шаблоны классов сокращают объем программирования, что, в свою очередь, экономит ваше время.
Теперь предположим, что вашей программе необходимо работать с массивом значений с плавающей точкой, кроме того, что она работает с целочисленным массивом. Один из способов обеспечить поддержку массивов различных типов состоит в создании разных классов. С другой стороны, используя шаблоны классов, вы можете избавиться от необходимости дублировать классы. Ниже представлен шаблон класса, который создает общий класс array:
template<class T, class T1> class array
{
public:
array(int size);
T1 sum (void);
T average_value(void);
void show_array(void);
int add_value(T);
private:
T *data;
int size;
int index;
};
По умолчанию, все функции и переменные, объявленные в классе, становятся закрытыми (private). Т.е. они доступны только из других членов этого класса. Для объявления открытых членов класса используется ключевое слово public. Все функции-методы и переменные, объявленные после слова public, доступны и для других членов класса, и для любой другой части программы, в которой содержится класс.
Этот шаблон определяет символы типов T и T1. В случае массива целочисленных значений Т будет соответствовать int, а T1 — long. Аналогичным образом для массива значений с плавающей точкой значения Т и Т1 равны float. Теперь потратьте время, чтобы убедиться, что вы поняли, как компилятор С++ будет подставлять указанные вами типы вместо символов Т и Т1.
Далее, перед каждой функцией класса вы должны указать такую же запись со словом template. Кроме того, сразу же после имени класса вы должны указать типы класса, например array <T, T1>::average_value. Следующий оператор иллюстрирует определение функции average_value для этого класса:
template<class Т, class T1> Т array<T, T1>::average_value(void)
{
T1 sum = 0;
int i;
for (i = 0; i < index; i++) sum += data[i] ;
return (sum / index);
}
После создания шаблона вы можете создавать класс требуемого типа, указывая имя класса, а за ним в угловых скобках необходимые типы, как показано ниже:
Имя шаблона //----> array <int, long> numbers (100); <------//Типы шаблона
array <float, float> values(200);
БЛОК 2)
8)Структура микропроцессора (на примере Е97).
Главным блоком компьютера служит 16-разрядный процессор "Е97", способный работать как с двухбайтовыми словами, так и с отдельными байтами. Впервые в рамках учебной модели реализована возможность оперировать с данными разной длины. Познакомившись с тем, как "Е97" обрабатывает разные типы информации, читатель легко сможет в будущем обобщить логику "один байт или много" на случай большей разрядности процессора. В процессоре имеются внутренние регистры памяти, при помощи которых реализован метод косвенной адресации к ОЗУ. Очевидно, что полное 16-разрядное адресное пространство "Е97" позволяет напрямую адресовать до 64 Кбайт памяти; для учебной ЭВМ это более чем достаточно.
Процессор содержит программно доступные регистры: четыре регистра общего назначения (R0-R3), указатель стека (SP — stack pointer); служебные регистры: счетчик адреса команд (PC — programm counter), регистр состояния процессора (PS); и программно недоступные (внутренние), которые процессор использует в своих целях: регистр команд (РК), регистры операндов (Рг1, Рг2) и сумматор (См).
Счетчик адреса команд PC при работе программы всегда указывает на адрес очередной команды, выполняя которую, автоматически изменяется согласно основному алгоритму работы процессора. Регистры общего назначения можно рассматривать как внутреннюю память процессора. Они используются для промежуточного сохранения данных и адресов данных.
Указатель стека SP чаще всего используется при организации подпрограмм.
Регистр словосостояния (точнее говоря, отдельные его биты) анализируются при организации переходов, прерываний и т.д. Реально мы будем использовать только два бита: Z и N. Значения этих битов при возможных значениях результатов операций указаны ниже в таблице.
Результат | Значение бита Z | Результат | Значение бита N |
Нулевой (=0) | Отрицательный (<0) | ||
Ненулевой (≠ 0) | Неотрицательный (>= 0) |
При работе программы в регистр команд считывается очередная команда для дешифрования и дальнейшего выполнения; в регистры операндов считываются согласно выполняемой команде операнды из регистров общего назначения или из оперативной памяти; в сумматоре выполняется действие, и сохраняется его результат до тех пор, пока не будет выполнено следующее действие. Последние четыре регистра доступны лишь для наблюдения, их содержимое программист изменить не может (в отличие от программно доступных).
Минимальной адресуемой ячейкой памяти в микрокомпьютере E97, как и в любой современной персональной ЭВМ, является байт. Номера байтов (адреса) лежат в диапазоне от 0000(16) до FFFF(16). Гораздо чаще приходится работать с машинными словами (в E97 они двухбайтовые — напомним, что это связано с разрядностью процессора). Адрес машинного слова совпадает с номером младшего байта, входящего в слово, поэтому адреса всех машинных слов четные.
Для размещения программы и данных в распоряжении программиста ОЗУ (с адреса 0000 до адреса 00FE включительно). Программа и данные записываются в память E97 (в том числе и в регистры) в шестнадцатеричном коде. При этом целые числа задаются в дополнительном коде.
9) Система команд процессора (на примере Е97).
Команды процессора E97 можно разделить на :
1)безадресные (без операндов). Безадресная команда имеет формат
Третий полубайт (отсчет ведется справа налево), как и в других командах, занимает код операции (КОП) — то действие, которое необходимо выполнить. Другие полубайты могут содержать все, что угодно, так как они не задействованы; по традиции туда записывают нули.
К безадресным относятся команды нет операции (0) и стоп (F). Согласно принятым соглашениям в полной форме эти команды запишутся так:
· 0000 (нет операции);
· 0F00 (стоп).
Всякую программу обязательно должна завершать команда СТОП.
2)двухадресные (с двумя операндами). Двухадресная команда имеет формат
На приведенной схеме модификатор (МОД) — некоторый вспомогательный код; ОП1 и ОП2 — операнды в команде.
Вот список таких команд
Команда | Код | Команда | Код |
Переписать | Логическое И (AND) | ||
Сложить | Логическое ИЛИ (OR) | ||
Вычесть | ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR) | ||
Сравнить | Ввести в порт | A | |
Умножить | Вывести из порта | B | |
Разделить |
Действия выполняются по схеме ОП2 операция ОП1 ==> ОП2. Как видно из схемы, конечный результат операции всегда помещается во второй операнд.
Особое место занимает операция сравнения: при ее выполнении содержимое операндов не изменяется, а действие ОП2 - ОП1 выполняется лишь для установления значений управляющих битов Z и N регистра состояния PS.
В каждой из указанных здесь команд значение МОД (модификатора) равно нулю. Модификатор может быть отличен от нуля, об этом — позднее.
Приведем примеры использования команд.
1) 0123 — переслать содержимое регистра R2 в регистр R3, содержимое R2 сохраняется;
2) 0534 — умножить содержимое регистра R3 на содержимое ячейки памяти, адрес которой указан в регистре R0, результат поместить в эту ячейку памяти.
3)одноадресные (с одним операндом).
КОП ОП1 комментарии
E1NOT оп1
E2оп1 ==> стек
E3стек ==> оп1
E4 SP + оп1 ==> SP
E5 SP - оп1 ==> SP
E6 оп1 ==> SP
E7SP ==> оп1
E8 0 PS ==> стек
E9 0 стек ==> PS
EA сдвиг влево оп1
EB сдвиг вправо op1
EC арифметический сдвиг вправо оп1
10) Основной алгоритм работы процессора. Способы адресации данных (на примере Е97).
основной алгоритм работы процессора:
1. считывается команда из оперативной памяти по адресу, указанному в счетчике команд, и записывается в регистр команд;
2. счетчик команд автоматически увеличивается так, чтобы он содержал адрес следующей команды (в E97 автоматически увеличивается на 2);
3. содержимое регистра команд дешифруется, из памяти выбираются необходимые данные, и выполняется указанное действие. В случае необходимости результат записывается в ОЗУ;
4. осуществляется переход к п. 1.
Согласно этому алгоритму процессор исполняет программу до тех пор, пока не встретится команда СТОП.
Способы адресации данных в E97. Основных здесь два — прямой регистровый, когда данные для обработки содержатся в регистрах, и косвенный регистровый, когда данные расположены в ОЗУ, а их адреса находятся в регистрах общего назначения. Указанные способы адресации кодируются следующим образом:
Код операнда | ||
Регистр | Регистровая адресация | Косвенная адресация |
R0 | ||
R1 | ||
R2 | ||
R3 |
Условным обозначением косвенной адресации, в отличие от регистровой, служит название регистра, заключенное в круглые скобки. Например (R2).
Кроме рассмотренных способов адресации имеется еще адресация по командному счетчику PC. Если в качестве операнда указано значение D, то соответствующий операнд входит непосредственно в команду и расположен в ОЗУ по следующему за командой адресу; если — E, то по следующему за командой адресу указан адрес, где хранится величина. Более подробно об этих способах адресации — в примерах.
11) Формат и назначение команд процессора (на примере Е97).
Перейдем к самому важному – системе команд, которые умеет выполнять наш учебный процессор. Машинная команда состоит из операционнойи адреснойчастей: первая указывает, что надо сделать с данными, а вторая - где их взять и куда поместить результат.
Итак, рассмотрим структуру команды "Е97". В зависимости от конкретной операции, ее формат может иметь некоторые особенности, но в наиболее полной форме он состоит из 4 частей по 4 бита каждая (см. рис. 4б): модификатор МОД, код операции КОПи два операнда ОП1и ОП2.
Что же касается МОД, то он указывает варианты реализации команды, например, адресовать байт или слово, по каким управляющим битам переходить и т.п.
Наиболее простой формат команд из всех возможных,имеют две – нет операции (ее код 0) и останов (код F). Как видно из рис. 4а, в этих командах задействован только КОП, остальные 12 бит значения не имеют.
Основная масса команд, коды которых заключены в
интервале от 1 до B, является двухадресной и соответствует
уже упоминавшемуся ранее рис. 4б, к ним относятся:
1 – перепись,
2 – сложение,
3 – вычитание,
4 – сравнение,
5 – умножение,
6 – деление,
7 – логическое "И",
8 – ИЛИ,
9 – ИСКЛЮЧАЮЩЕЕ ИЛИ,
A – ввод из порта,
B – вывод из порта.
Операция переписи выполняется достаточно тривиально: считывается информация из ОП1 и копируется в ОП2. Совершенно аналогично работают ввод и вывод из порта, с той лишь разницей, что в качестве одного из операндов указывается номер порта. Все остальные двухадресные команды с кодами 2-9 представляет собой определенные действия над двумя данными, выполняемые по универсальной схеме
ОП2 операция ОП1 ==> ОП2.
Например, по команде деления процессор извлекает ОП2,делит его на ОП1 и результат помещает вместо первоначального значения ОП2.
Некоторую особенность имеет команда сравнения.
При ее исполнении производится вычитание ОП2 – ОП1, но результат никуда не записывается. Тогда для чего же вычитать? Исключительно ради установки управляющих битов, которые в дальнейшем могут быть проанализированы командами условных переходов.
Для закрепления рассмотрим небольшой довольно абстрактный пример, который производит вычисление по формуле R0 = (R1+R2)*R3 и результат из R0 выводит в порт номер 3:
адресадрес код команда
0000 0110 R1 ==> R0
0002 0220 R0 + R2 ==> R0
0004 0530 R0 * R3 ==> R0
0006 0B03 R0 ==> порт 3
0008 0F00 останов
вы поняли, как работают все двухадресные команды с кодами от 1до B.
12) Организация переходов. Развилка и цикл (на примере Е97).
Перейдем теперь к рассмотрению команд переходов.
Как мы уже знаем, они бывают абсолютные, когда значение адреса для перехода задается явно, и относительные,когда адрес следующей команды вычисляется путем значения текущего программного счетчика и указанного в теле команды смещения. В соответствии с этим в "Е97" есть два
типа переходов с кодами операций C и D; их форматы
представлены на рис. 4в и г.
Начнем с абсолютного перехода, код которого равен C.
1C0D
то следующей будет выполняться команда с адресом 56.
Иными словами, адрес перехода берется из самой команды.
По другому обстоит дело с относительным переходом, код которого D. В качестве примера возьмем команду 1D06.
Для определенности будем считать, что эта команда находится в памяти по адресу 42. В строгом соответствии с основным алгоритмом работы процессора, после выборки рассматриваемой команды счетчик адреса команд РС автоматически увеличивается до 44. Затем, выполняя расшифрованную команду перехода, процессор прибавит к текущему содержимому РС смещение 06 и тем самым осуществит переход на адрес 44 + 6 = 4А. Обратите внимание, что итоговый адрес в случае относительного перехода зависит от расположения команды перехода в ОЗУ.
При отрицательном смещении возможно получение адреса меньше исходного
Наиболее наблюдательные читатели, видимо, заметили, что при обсуждении команд перехода мы незаметно включили в работу модификатор команд. Для переходов его роль проста и наглядна: МОД показывает, по какому условию осуществляется переход. Таблица всех используемых в "Е97" значений модификаторов выглядит так:
0 – возврат из подпрограммы
1 – безусловный переход
2 – N = 0 (>=0)
3 – N = 1 (<0)
4 – Z = 0 (<>0)
5 – Z = 1 (=0)
6 – N = 1 or Z=1 (<=0)
7 – N = 0 and Z = 0 (>0)
9 – вызов подпрограммы.
Становится очевидным, что рассмотренные в обоих
предыдущих примерах команды с МОД = 1 являются наиболее простым вариантом перехода – безусловным.
Для работы с условными переходами следует твердо
запомнить следующее правило: если анализируемое условие справедливо, т.е. состояние управляющих признаковсовпадает с требуемым, то переход происходит. В противном случае никаких действий не производится, а значит переход просто игнорируется и процессор, как
обычно, выбирает следующую команду.
А вот пример для закрепления. Пусть в R1 и R2 хранятся некоторые двоичные коды. Если они совпадают, требуется занести в R3 ноль, иначе – оставить без изменения.
Один из возможных вариантов решения имеет вид:
адрес код команда, пояснения
0000 0110 R1 ==>R0
0002 0920 R0 xor R2 ==>R0
0004 4D02 если результат <>0, к адресу 8
0006 0103 R0 (т.е. 0) в R3
0008 0F00 останов
В программе используется свойство операции XOR -"исключающее или" - давать нулевой результат при побитном совпадении кодов.
И еще об одном виде перехода хочется поговорить особо – о переходе с возвратомили о переходе к подпрограмме, как его часто называют. Эта команда, как мы видели при анализе существующих моделей, в учебных ЭВМ никогда ранее не реализовывалась, хотя на практике подпрограммы (процедуры) и функции играют очень важную роль.
Подпрограммы полезны, когда в разных местах программы требуется выполнить одни и те же действия. В этом случае имеет смысл оформить повторяющиеся действия в виде подпрограммы, а затем просто вызывать ее в нужных местах. В каком-то смысле это похоже на публикацию текстов песен, когда припев пишется один раз, а в дальнейшем просто ставится ссылка на него в виде слова "ПРИПЕВ".
Наиболее важное отличие перехода к подпрограмме от безусловного перехода состоит в том, что требуется иметь возможность вернуться из подпрограммы в то же самое место, откуда она была вызвана. Это напоминает покупку билета с обратом по сравнению со сменой места жительства.
13) Принципы работы с массивами (на примере Е97).
Массив — это совокупность величин одинакового типа, расположенных в памяти ЭВМ последовательно друг за другом.
Для работы с элементами массива достаточно знать адрес первого элемента и их количество. Номер элемента массива называют его индексом. Чаще всего при решении задач с массивом его элементы просматриваются поочередно, с первого до последнего.
Типовые задачи на обработку массивов данных:
· поиск элементов с определенными свойствами;
· сортировка (расположение в порядке возрастания или убывания);
· изменение элементов по определенному правилу;
· заполнение массива по определенному правилу.
На примере Е97 можно рассмотреть следующий пример решения одной из таких задач.
Постановка задачи звучит следующим образом: Поменять местами первый отрицательный элемент массива (гарантируется, что хотя бы один такой элемент есть) и его максимальный элемент.
При прочтении задания можно сразу же выделить основную идею решения: Просматривая массив, необходимо запомнить адреса первого отрицательного элемента и максимального элемента. По окончанию просмотра совершить обмен.
План решения задачи можно оформить в виде некого алгоритма действий, который описан ниже.
1. i := 2
2. max := 1
3. otr := 0
4. Если a[1]<0, то k:=1, otr:= 1.
5. Сравнить i с n.
6. Если i>n, перейти к п. 11.
7. Если a[i]<0 и otr=0, то k:=i, otr:= 1.
8. Если a[i]>a[max], то max := i.
9. i := i + 1.
10. Перейти к п. 5.
11. vsp := a[k].
12. a[k] := a[max].
13. a[max] := vsp.
14. Стоп.
После чего перейдем к распределению памяти
Сама же программа с подробными комментариями, которые ссылаются уже на составленный алгоритм действий.
Адрес Команда Действие Замечания0000 01D5 2 => (R1) i := 20002 00020004 0103 R0 => R3 адрес максимального элемента0006 02D1 R1 := R1 + 2 адрес otr0008 0002000A 01D5 0 => (R1) otr := 0000C 0000000E 04D4 сравнить a[1] с 0 (R0) - 00010 00000012 2D06 если a[1]>=0, переход на 6 байт0014 0106 R0 => (R2) адрес первого отрицательного элемента0016 01D5 1 => (R1) otr := 10018 0001001A 02D2 R2 := R2 + 2 адрес n001C 0002001E 03D1 R1 := R1 - 2 адрес i0020 00020022 02D0 R0 := R0 + 2 адрес 2-го элемента массива0024 00020026 0456 сравнить (R1) с (R2) (i с n) (R2) - (R1)0028 3D32 если <0, переход на 32 байта переход на обмен значений002A 02D1 R1 := R1 + 2 адрес otr002C 0002002E 04D4 сравнить (R0) c 0 (R0) - 00030 00000032 2D14 если >= 0, переход на 14 байт0034 04D5 сравнить (R1) c 0 (R1) - 00036 00000038 4D0E если <>0, переход на E байт003A 03D2 R2 := R2 - 2 адрес k003C 0002003E 0106 R0 => (R2) адрес первого отрицательного элемента0040 01D5 1 => (R1) otr := 10042 00010044 02D2 R2 := R2 + 2 адрес n0046 00020048 0447 ср. (R0) с (R3) (a[i] c a[max]) (R3) - (R0)004A 2D02 если >=0, переход на 2 байта если a[max]>=a[i]004C 0103 R0 => R3 адрес максимального элемента004E 03D1 R1 := R1 - 2 адрес i0050 00020052 02D5 (R1) := (R1) + 1 i:= i + 10054 00010056 02D0 R0 := R0 + 2 адрес i-го элемента0058 0002005A 1DCA переход на -36(16) байт на сравнение i с n005C 03D2 R2 := R2 - 2 адрес k005E 00020060 0162 (R2) => R20062 0161 (R2) => R1 vsp := a[k]0064 0176 (R3) => (R2) a[k] := a[max]0066 0117 R1 => (R3) a[max] := a[k]0068 0F00 стоп14) Организация подпрограмм (на примере Е97)
Подпрограммы (вспомогательные алгоритмы) целесообразно использовать в тех случаях, когда исходная задача является достаточно сложной, а ее решение — громоздким. Тогда разумно выделить в ней отдельные подзадачи, решение каждой из которых оформить в виде подпрограммы. Еще более естественным является использование подпрограмм в тех случаях, когда некоторый фрагмент программы повторяется.
При обращении к подпрограммам в E97 используется стек — структура данных, организованная по принципу "последний пришел — первый ушел". Стек позволяет по окончанию работы подпрограммы обеспечить возврат в ту точку основной программы, которая следует сразу же за вызовом подпрограммы. При обращении к вспомогательному алгоритму адрес в указателе стека (SP) уменьшается на 2, по вновь полученному адресу записывается адрес, следующий за вызовом подпрограммы. Подпрограмма исполняется, и когда встречается команда возврата (0D00), в счетчик команд помещается адрес, на который указывает SP, значение SP увеличивается на 2. Таким образом исполнение основной программы продолжается.
Из сказанного выше следует, что в SP нужно поместить адрес, свободный от данных и программы; несколько предшествующих адресов также должны быть свободны, поскольку стек стоится в сторону уменьшения адресов.
Рассмотрим более подробно механизм обращения к подпрограмме. По схеме ниже можно проследить, как осуществляются переход к вспомогательному алгоритму и возврат в вызывающую программную единицу.
Следует заметить, что регистр SP может быть использован не только при работе с подпрограммами, но и для промежуточного хранения величин.
Заметим также, что подпрограммы могут обращаться и сами к себе — прямо или косвенно (через другие подпрограммы). Подпрограмма, вызывающая себя, называется рекурсивной.
Продемонстрируем это на классическом примере рекурсивного алгоритма — вычислении факториала натурального числа. С одной стороны, n! определяется как произведение последовательных натуральных чисел от 1 до n включительно. С другой стороны это и есть рекурсивное определение факториала, ко