Описание пользователя
Глава 3. Использование стандартных модулей автокомпиляции
§3.6. Принципы создания автокомпилируемых моделей блоков
§3.6.2. Работа со статическими переменными блока
§3.6.2.3. Модели с массивами
Рассматривается использование массивов в моделях блоков и описываются функции для работы с ними.
Технически, массивы в RDS – это матрицы, состоящие из единственной строки. Они хранятся в памяти точно так же, как и матрицы, и могут соединяться с матрицами при помощи связей. Однако, с точки зрения пользователя, массивы отличаются от матриц наличием одного индекса вместо двух: при подключении связи к отдельному элементу матрицы вводятся оба индекса элемента (например, «M[1,2]»), а при подключении связи к элементу массива – только один (например, «A[3]»).
Чаще всего массивы используются для создания блоков с произвольным числом входов и выходов, обрабатываемых одинаково. Сумматор, модель которого рассмотрена в §3.2, рассчитан только на два входа. Если пользователю потребуется складывать три числа, придется добавить в блок еще одну статическую переменную «x3» и изменить модель блока на «y=x1+x2+x3;». Гораздо лучше было бы создать сумматор, число входов которого заранее не задано и определяется числом подсоединенных к нему связей. Этого можно добиться, сделав вход блока массивом вещественных чисел с нулевым значением элемента по умолчанию и выдавая на выход сумму всех элементов этого массива. Пользователь при этом сможет подключить столько связей к отдельным элементам этого входного массива, сколько ему будет нужно – размер массива будет определяться самым большим индексом элемента, к которому подключена связь. Если, например, подключить связи к элементам с номерами 0, 1, 2 и 5, размер массива окажется равным шести (к элементам 3 и 4 связи не подключены, но они все равно будут присутствовать в массиве). Интерфейс RDS позволяет достаточно просто подключать связи к последовательным элементам массива – при присоединении очередной связи пользователю сразу предлагается следующий, еще не использованный, номер элемента – поэтому такой сумматор будет удобен в работе. Создадим такую модель, но сначала разберемся с классами, автоматически создаваемыми для доступа к массиву, и их функциями-членами.
Классы доступа для массивов в целом похожи на классы матриц, но отличаются от них поддержкой единственного индекса. В класс блока rdsbcppBlockClass добавляется по одному объекту для каждого массива, имена этих объектов совпадают с именами переменных блока. Для доступа к конкретному элементу массива используется стандартный синтаксис языка C с квадратными скобками: элемент n массива A записывается в программе как «A[n]». Рассмотрим основные функции-члены классов массивов (во всех примерах предполагается, что X и Y – массивы переменных типа double):
- тип_элемента & operator[](int n)
- Обращение к элементу массива. Здесь n – целый номер элемента, начинающийся с
нуля, а тип_элемента – тип элемента массива (для матриц вещественных чисел,
например, это будет тип double). Таким образом, «X[n]» позволяет
обратиться к элементу массива X с номером n. Такая запись
может находиться как в левой, так и в правой части выражения, то есть можно не только получать значения
элементов массивов, но и присваивать их. Проверка допустимости индекса элемента по умолчанию не производится,
и попытка обратиться к элементу за пределами текущего размера массива вызовет критическую ошибку. Проверку
индексов можно включить, установив в параметрах модели флажок
«», при этом попытка обращения к элементу
за пределами массива оператором «[ ]» вызовет остановку расчета и сообщение об ошибке.
Следует учитывать, что включение этой проверки замедляет работу модели, поэтому проверять индексы желательно не
автоматически, а вручную, перед обращением к элементам массива. Примеры использования оператора:
X[n]=2.0; double x=X[5]; double y=sin(X[0]);
- тип_элемента & Item(int n)
- Обращение к элементу массива при помощи функции. Здесь n – целый номер
элемента, начинающийся с нуля, тип_элемента – тип элемента массива.
Эта функция всегда выполняет проверку допустимости индексов независимо от установок параметров модели,
поэтому она работает медленнее оператора «[ ]». Как и указанный оператор, ее вызов
может находиться и в левой, и в правой части выражения. Примеры использования функции:
X.Item(n)=2.0; double x=X.Item(5); double y=sin(X.Item(0));
- BOOL IsEmpty(void)
- Возвращает TRUE, если массив пустой (не содержит элементов), и
FALSE в противном случае. Пример использования функции:
if(X.IsEmpty()) return; // В массиве нет элементов - BOOL HasData(void)
- Возвращает TRUE, если в массиве есть элементы, и FALSE,
если он пустой. Пример использования функции:
if(X.HasData()) // Обработка элементов массива { … } - int Size(void)
- Число элементов в массиве. Для пустого массива возвращается 0. Пример использования функции:
// Суммирование элементов double s=0.0; for(int i=0;i<X.Size();i++) s+=X[i]; - BOOL Resize(int size,BOOL keep=FALSE)
- Изменяет размер массива. Здесь size – новое число элементов, необязательный
параметр keep – TRUE, если при изменении размера
нужно сохранить текущее содержимое массива, и FALSE, если его нужно заполнить значением
элемента по умолчанию. Если параметр keep не указан, после изменения размера весь массив
будет заполнен значением по умолчанию. Функция возвращает TRUE, если изменение размера
выполнено успешно (если не пытаться создавать массивы огромных размеров, не умещающиеся в память, результат
возврата функции можно не проверять). Если передать в параметре size нулевое значение,
массив станет пустым. Примеры использования функции:
// Установить размер в три элемента X.Resize(3); // Добавить в конец массива три новых элемента X.Resize(X.Size()+3,TRUE); // Очистить массив X.Resize(0);
- класс_массива & operator=(const класс_массива &arr)
- Оператор присваивания, позволяющий копировать один массив в другой (оба должны иметь элементы
одинаковых типов). Здесь класс_массива – имя класса, созданного
модулем автокомпиляции для массивов
с данным типом элементов, arr – копируемый массив. Следует учитывать, что
оба массива должны обязательно быть одного и того же типа – нельзя, например, скопировать таким образом
массив целых чисел в массив вещественных (копирование массивов разного типа необходимо производить вручную
поэлементно). Пример использования оператора:
Y=X; // Скопировать массив X в массив Y
Рассмотрев функции, с помощью которых можно работать с массивами, создадим модель сумматора с произвольным числом входов, использующего входной массив вещественных чисел. Наш блок будет иметь следующую структуру переменных:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 1 |
| Ready | Сигнал | Выход | 0 | |
| X | Массив double | Вход | ✓ | [ ] 0 |
| y | double | Выход | 0 |
Создадим новый блок с автокомпилируемой моделью, зададим для него запуск по сигналу и введем в его модель эту структуру переменных. На вкладке «» в правой части окна редактора введем следующий текст:
y=0.0; for(int i=0;i<X.Size();i++) y+=X[i];
Модель получилась очень простой: сначала мы обнуляем выход y, а затем, в цикле, по очереди добавляем к нему все элементы входного массива X. Введенный нами фрагмент программы будет выполнен при первом запуске расчета (начальное значение сигнала запуска Start – единица), а также при срабатывании любой связи, соединенной с входом X или с каким-либо его отдельным элементом (это обеспечит флажок в колонке «» напротив входа X). Здесь, как и в примере модели, работающей с матрицами (см. §3.6.2.2), мы не сравниваем элементы входного массива со значением rdsbcppHugeDouble – для операции сложения это не обязательно.
Рис. 372. Тестирование модели
сумматора с входом-массивом
Для тестирования созданной модели можно собрать схему, изображенную на рис. 372, в которой к отдельным элементам массива «X» подключены поля ввода. Если запустить расчет, на индикаторе, подключенном к выходу «y», появится сумма элементов массива. Для того, чтобы к блоку было удобнее подключать связи, параметры его внешнего вида изменены: в окне параметров для него было назначено изображение в виде прямоугольника с текстом (в данном случае текст пустой) и разрешено масштабирование, после чего его вертикальный размер был увеличен.
Рис. 373. Матрица на входе-массиве
Созданную нами модель можно использовать не только для суммирования чисел, поданных по связям на отдельные элементы массива. Поскольку в RDS разрешается соединять связями массивы и матрицы, мы можем подать на вход нашего блока какую-либо матрицу или массив с выхода другого блока – например, с выхода стандартного библиотечного блока ввода матрицы (рис. 373). В этом случае наша модель вычислит сумму всех элементов матрицы, даже если в ней не одна строка, а несколько: внутри модели матрица будет считаться одним длинным массивом. Например, если мы подадим на вход блока матрицу размером 2×3 элемента, мы получим массив размером в шесть элементов.
В приведенном выше примере мы имели дело с массивом-входом, размер которого задается без нашего участия: его определяет либо число связей, подключенных к элементам массива, как на рис. 372, либо размер массива или матрицы на выходе блока, соединенного связью со всем входом «X», как на рис. 373. Рассмотрим теперь модель, в которой размер массива-выхода мы будем задавать самостоятельно. Создадим модель блока-демультиплексора, выходом которого будет массив вещественных чисел «Y». Блок будет иметь целый вход «N» и вещественный вход «x», в процессе работы он будет копировать значение «x» в элемент выходного массива с номером, определяемым входом «N».
Блок будет иметь следующую структуру переменных:
| Имя | Тип | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|
| Start | Сигнал | Вход | ✓ | 1 |
| Ready | Сигнал | Выход | 0 | |
| x | double | Вход | ✓ | 0 |
| N | int | Вход | ✓ | 0 |
| Y | Массив double | Выход | [ ] 0 |
Создадим новый блок с автокомпилируемой моделью, зададим для него запуск по сигналу и введем в его модель эту структуру переменных. На вкладке «» в правой части окна редактора введем следующий текст:
if(N<0) return; // Недопустимый номер // Проверка и увеличение размера массива if(Y.Size()<N+1) Y.Resize(N+1,TRUE); // Запись x в элемент массива N Y[N]=x;
Введенный нами фрагмент программы, как и в предыдущей модели, будет выполнен при первом запуске расчета (начальное значение сигнала запуска Start – единица), а также при срабатывании любой связи, соединенной с входами блока (это обеспечат флажки в колонке «» у обоих входов).
Первым оператором мы принудительно завершаем модель при отрицательных N – элементы массива не имеют отрицательных номеров. Затем мы проверяем, есть ли на данный момент элемент с номером N в массиве Y: чтобы он существовал, размер массива Y.Size() должен быть не меньше N+1 (если в массиве, например, три элемента, самый последний его элемент имеет номер два, поскольку номера начинаются с нуля: 0, 1, 2). Если размер массива окажется меньше, мы принудительно устанавливаем его в N+1, вызывая у объекта Y функцию-член Resize. Во втором параметре этой функции передано значение TRUE, чтобы при увеличении размера массива его старое содержимое сохранилось, а не было заменено на значение по умолчанию. После этого мы просто присваиваем элементу Y[N] значение x. В этой модели не нужно никаких проверок входного значения x на равенство значению-индикатору ошибки rdsbcppHugeDouble: модель вообще не выполняет никаких математических вычислений, а просто переписывает на выход значение со своего входа.
Рис. 374. Тестирование
модели демультиплексора
Для тестирования модели можно собрать схему, изображенную на рис. 374. Здесь, как и в прошлом примере, размер нашего блока увеличен, чтобы к нему удобнее было подключать связи. К отдельным элементам массива «Y» подключены индикаторы, к входам «x» и «N» – поля ввода. Если запустить расчет и изменять значение на входе «x», можно будет увидеть, что вместе с ним изменяется значение того индикатора, который подключен к элементу массива с номером «N».
Созданная нами модель имеет один недостаток: независимо от значения «N», при ее срабатывании активируются все ее выходные связи. Так устроена логика работы блоков и связей в RDS – перед выполнением реакции блока на такт расчета его сигнал готовности «Ready» получает единичное значение, а в конце такта для всех блоков с ненулевыми сигналами готовности запускается передача выходов по связям (см. §1.3). В схеме на рис. 374 к выходам блока подключены индикаторы, поэтому это не важно: многократного повторного срабатывания «лишних» связей мы не заметим, поскольку по ним будут передаваться те же самые числа. В частности, по связям, идущим от «Y[0]» и «Y[2]»> при каждом изменении «x» будут повторно передаваться нули, несмотря на то, что «N» на рисунке равно единице. Но лучше подавлять передачу ненужных данных: во-первых, это ускоряет работу схемы, поскольку не активируются модели подключенных к «лишним» связям цепочек блоков, во-вторых, некоторые сложные блоки реагируют не только на число, пришедшее по связи, но и на факт ее срабатывания. В §3.6.2.8 мы изменим нашу модель так, чтобы на выходе блока срабатывала только одна связь – та, которая подключена к элементу массива, соответствующему текущему значению «N».