На последнем семинаре мы познакомились со структурами в Си. Это лексическое средство открывает для нас, наконец, возможность придумать действительно практически-полезные домашние задания. Ввиду того что, согласно недавнему голосованию, слушатели понимают актуальность домашки, а так же учитывая требования предстоящей программы, настала пора сформулировать первое задание на самостоятельную работу.
Разработать структуру данных (struct Hist1D
) представляющую одномерную
гистограмму с равномерным биннингом $b_i, i = [1, ...N]$ и выделенными бинами для
underflow/overflow. Структуру и сопутствующие функции целесообразно выделить в
библиотеку, которую мы затем будем использовать как в простых рассчётных
приложениях, так и для извлечения статистических характеристик Geant4.
Библиотека должна предоставлять следующие операции вокруг структуры, которые необходимо выразить в виде самостоятельных функций:
v
,
подлежащее перенормировке: hst1d_fill(struct Hist1D *, float v)
float hst1d_sum(struct Hist1D *)
;hst1d_mean(struct Hist1D *)
;hst1d_weights(struct Hist1D *, float w[])
;float hst1d_stddev(struct Hist1D *)
.Разумеется, необходимо реализовать хотя бы один конструктор, инициализирующий
поля данных структуры и динамически выделяющий блок данных "на куче" (on heap,
посредством malloc()
), и деструктор, осуществляющий очистку этого блока
данных.
Целесообразно, так же, всюду использовать переопределённые типы данных для
гибкой параметризации библиотеки (в шаблоне ниже BinCount_t
-- тип данных
бина, Value_t
-- тип данных измеряемой величины).
Поскольку мы все будем использовать затем эту реализацию в последующей работе, очень важно всем выдержать единообразный интерфейс. Чтобы сэкономить немного времени, которое вами наверняка будет потрачено на непродуктивную борьбу с лексером компилятора Си, а так же зафиксировать интерфейс, я подготовил рекомендуемый шаблон реализации.
Этот шаблон констиуирует основу, которую вы вольны менять до определённых пределов: важно сохранить сигнатуры функций ровно в том виде, в котором они здесь объявлены, однако вы можете добавить по своему усмотрению какие-то дополнительные поля структуры (например, может показаться не лишённым смысла расчитывать сумму как собственный атрибут и хранить её в той же структуре), а также какие-то дополнительные функции.
Кроме того, в шаблоне используются некоторые дополнительные конструкции, о которых я не говорил на семинарах вовсе, или говорил совсем немного:
size_t
-- знаковый целочисленный тип данных (как правило, long int
),
широко используемый стандартной библиотекой для хранения размера или дистанции
указателей в памяти. Это typedef
на обычный тип данных, объявленный где-то
в заголовочных файлах стандартной библиотеки (при желании, вы даже можете
где-то выловить grep'ом декларацию typedef
на него).const
в сигнатурах функций. В принципе,
делает то же, что и при обычной декларации переменной. Целесообразно
использовать его для гарантии неизменности данных внутри функции, которая, в
силу своего предназначения не должна ничего изменять в данном объекте, а должна
извлекать из него информацию: печатать в консоль, считать и возвращать сумму,
среднее и т.д. Важно, что const
никак не влияет на физические данные во
время работы приложения, а есть только лексическое средство, применяемое
программистом для контроля самого себя. Стоящий справа от астериска в типе
указателя, он гарантирует константность данных по этому указателю, но не
неизменность самого указателя. Т.е., для переменной const Hist1D * hstPtr
,
вы можете записать hstPtr = &otherHist
, но запись hstPtr->vMin = 42
будет рассмотрена компилятором как ошибочная.assert()
. Понять, что делает эта функция достаточно просто: она прервёт
процесс работающего приложения с ошибкой во времени выполнения программы, если
выражение в скобках окажется ложным. Вы можете использовать эту функцию всюду,
где хотите проконтролировать истинность некоторых алгоритмических предположений.
Однако понять контекст использования её несколько сложнее: она не должна
применяться для валидации входных данных всему приложению или алгоритму в
целом -- для этого обычно используют выделенные механизмы сообщения об ошибке.
То есть, это ещё один (наряду с const
) механизм программисту контролировать
правильность своей работы, и она не должна применяться для контроля
корректности данных. По этой причине, эта конструкция имеет эффект только
при активном определении макроса DEBUG
(см. конец
заметки
про определения препроцессора): её применяют на этапе отладки кода, для
контроля за простыми предположениями. В нашем случае, я всюду проверяю, верно
ли, что указатель BinCount_t * bins
структуры Hist1D
отличен от нуля, иными
словами, я пытаюсь защитить релевантные функции от вызова с объектом для
которого уже был вызван деструктор. Вы вольны переделать код который мы уже написали для простого представления гистограммы в предыдущих заметках. Кроме того, хорошим тоном будет написать небольшой тест для всех этих функций в точке входа, который вы можете изготовить из нашего самого первого примера с прямым методом Неймана.
Я ожидаю, что при незначительных но регулярных усилиях, у вас получится подготовить работоспособную версию такой программы к середине февраля. Там мы и устроим следующий семинар, на котором сделаем из ваших программ полноценные динамические библиотеки для собственного последующего использования (желательно, но вовсе необязательно будет принести на семинар ноутбуки).