Как сделать матрицу в c

Обновлено: 06.07.2024

  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Реализация класса для представления матриц

В этой лабораторной работе требуется реализовать класс для представления вещественных матриц и выполнения операций с ними. Класс должен хранить матрицу типа double в виде одномерного массива или массива массивов.

Требования к API

Конструкторы и деструктор

  • Mat(int m, int n) - создаёт матрицу указанных размеров.
  • Mat(std::vector vec) - создаёт матрицу размера vec.size() x 1 и заполняет её элементами вектора.
  • Mat(const double* mem, int m) - создаёт матрицу размеров m x 1 , заполняя её данными из массива.
  • Mat(const Mat& other) - создаёт матрицу тех же размеров, что и b и копирует в неё данные из other . Созданная матрица и матрица other должны иметь одинаковые размеры и значения, и использовать различные указатели для хранения данных.
  • Mat() - создаёт пустую матрицу размера 0 x 0 .
  • ~Mat() - освобождает память, выделенную под матрицу, если она не пустая.
  • std::pair size() const - возвращает размеры матрицы.
  • const double& get(int i, int j) - возвращает ссылку на элемент с индексами i, j . Индексация элементов начинается с 0.
  • double* data(int i) - возвращает указатель на начало i-ой строки.
  • norm() - возвращает Фробениосову норму матрицы.
  • uniform_() - заполняет матрицу случайными числами, равномерно распределёнными в дапазаоне [0;1].
  • Mat& operator=(const Mat& other) - оператор присваивания. Копирует матрицу other в текущую и возвращает ссылку на текущую матрицу. Процедура копирования та же, что в конструкторе копирования, только здесть потребуется сначала освободить память текущей матрицы, если её размеры не совпадают с other , а затем выделить память подходящего размера и скопировать в неё данные.
  • std::ostream& operator - вывод матрицы в поток. Возввращается ссылка не этот же поток.
  • bool operator==(const Mat& lhs, const Mat& rhs) - оператор, проверяющий поэлементное равенство матриц.
  • bool operator!=(const Mat& lhs, const Mat& rhs) - отрицание предыдущего оператора.
  • Mat operator+(const Mat& lhs, const Mat& rhs) - сложение матриц.
  • Mat operator-(const Mat& lhs, const Mat& rhs) - вычитание матриц.
  • Mat operator*(const Mat& lhs, const Mat& rhs) - умножение матриц.
  • Mat operator*(const Mat& lhs, const double alpha) - умножение матрицы на константу.
  • Все бинарные операторы должны возвращать новую матрицу, которая является результатом выполнения бинарной операции.
  • Вместо std::pair можно использовать свою структуру для представления размера. Также в C++ 11 есть возможность сделать алиас для типа и использовать тип по новому инени: using Size = std::pair ;
  • Релизация класса должна быть отделена от его декларации, т.е. должно быть два файла: .hpp для декларации и .cpp для реализации.
  • В случае, если совершить какую-либо операцию невозможно (например, размеры матриц для сложения разные), соответствующий оператор должен генерировать исключение.
  • Конструкторы также должны генерировать исключение в случае некорректных входных данных.

Пример генерации и обработки исключения:

  • Сделать класс матриц шаблонным. Допустимый параметр шаблона - любой числовой тип C++.
  • Реализовать функцию для решения уравнений вида A*x=b , где x, b - векторы-столюбы, A - невырожденная квадратная матрица. Прототип: Mat solve (const Mat & A, const Mat & b) . Функция должна возвращать x , если решение существует или пустую матрицу в противном случае. Стоит обратить внимание, что решить уравнение в общем случае возможно только над полем вещественных чисел, и это надо предусмотреть. Для этого можно использовать механизм проверки типов на этапе компиляции.

Статическая проверка типа

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

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

Каждый отдельный тестовый сценарий можно оформить в виде юнит-теста. Простейшую реализацию юнит-тестов см. здесь. Пример её использования:

Рассмотрим основные операции , выполняемые над матрицами (статическими и динамическими) при решении задач.

Матрицы, как и массивы, нужно вводить (выводить) поэлементно. Блок- схема ввода элементов матрицы x[n][m] изображена на рис. 6.3.

Ввод элементов матрицы

Блок-схема построчного вывода матрицы

При выводе матрицы элементы располагаются построчно, например:

\begin</p>
<p>6&-9&7&13\\5&8&3&8\\3&7&88&33\\55&77&88&37\end

Алгоритм построчного вывода элементов матрицы приведён на рис. 6.4.

Ниже приведён текст программы на C++ ввода-вывода статической матрицы.

Цикл для построчного вывода матрицы можно записать и так.

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

Далее на примерах решения практических задач будут рассмотрены основные алгоритмы обработки матриц и их реализация в C++ . Перед этим давайте вспомним некоторые свойства матриц (рис. 6.5):

  • если номер строки элемента совпадает с номером столбца (), это означает, что элемент лежит на главной диагонали матрицы;
  • если номер строки превышает номер столбца (), то элемент находится ниже главной диагонали;
  • если номер столбца больше номера строки (i >

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

Решение:

Матрицу можно организовать как вектор из векторов ( std::vector > ), в академических целях иногда просят реализовать вектор самостоятельно, тогда можно взять там. Однако, ниже (с той же целью) приведена другая реализация — двумерная матрица тут задается массивом (линейным), где строки записаны последовательно друг за другом. Поэтому для получения j-того элемента i-той строки надо выполнить такое обрещение:

такой подход иногда применяется, ведь при нем выполняется меньше динамических аллокаций — что хорошо при большом количестве небольших матриц. Однако, он требует цельного куска в памяти для хранения всех элементов матрицы — поэтому не очень хорошо работает если нужны очень большие матрицы.

Алгоритм умножения матриц описан тут, а про транспонирование — там.

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

Для получения размерности добавлены функции:

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

При сложении с числом — число добавляется к каждому элементу, при вычитании — вычитается (при этом по факту выполняется сложение с -value ). При умножении — каждый элемент умножается на число.

Исходный код целиком (при копировании стоит разделить его на объявление в .h файле и реализацию в .cpp файле):

Введение в библиотеку матричных операций C ++ Eigen

Матричные арифметические библиотеки C ++ обычно используют Armadillo, Eigen, OpenCV, ViennaCL, PETSc и так далее. Я сам искал характеристики различных компьютерных библиотек в Интернете и, наконец, выбрал Eigen. Основная причина в том, что Eigen имеет небольшой размер и его не нужно устанавливать или компилировать. Библиотека представлена ​​в виде заголовочного файла и может быть напрямую добавлена ​​в наш собственный файл проекта. Нет необходимости в пересадке. Мы можемEigen официальный сайтЗагрузите исходный файл.

Версия Eigen I, которую я использую, - Eigen 3.3.3. Каталог исходного файла выглядит следующим образом:

Eigen

Вы можете напрямую открыть файл INSTALL с помощью Блокнота, в котором указано, как использовать его при компиляции, а не при компиляции. Метод, который я здесь использую - не компилировать файл.

  1. Найдите место для создания новой директории проекта, здесь я создаю новую папку MatrixTest на рабочем столе.
  2. Скопируйте папку Eigen из каталога Eigen в наш каталог MatrixTest.
  3. Создайте другой файл main.cpp в MatrixTest и напишите следующий код:
  1. Введите cmd в адресную строку каталога MatrixTest и используйте g++ main.cpp Скомпилируйте файл и выполните команду a , Вы можете видеть, что мы вывели нашу матрицу.

HelloMatrix

Использование Eigen подробно описано на официальном сайте, здесь я представлю основные операции, используемые в моем процессе обучения. Первое - это определение матрицы.
В матричном классе есть 6 параметров шаблона. В общем, нам нужно сосредоточиться только на первых трех параметрах. Первые три параметра шаблона следующие:

  1. Параметр Scalar - это тип элемента матрицы, который может быть int, float, double и т. Д.
  2. RowsAtCompileTime и ColsAtCompileTime - количество строк и столбцов матрицы.

Такие как Matrix M44 Он определяет матрицу 4 × 4, и элементы матрицы хранятся как float. Определение матрицы напрямую с использованием шаблона матрицы часто является громоздким, Eigen предоставляет определения псевдонимов для некоторых базовых матриц, таких как typedef Matrix Matrix4f Вот некоторые встроенные определения псевдонимов.Официальное руководство:

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

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

После введения выше мы должны быть в состоянии определить некоторые основные матрицы. Такие как:

Соответствуя динамической матрице, мы также можем дать пространство, занимаемое матрицей при построении, например:

Чтобы поддерживать согласованность, мы также можем использовать форму вышеуказанного конструктора для определения фиксированной матрицы, то есть Matrix3f a(3,3) Это также разрешено.

Вышеуказанная матрица не инициализируется во время процесса построения, а Eigen также предоставляет инициализаторы для некоторых небольших (столбцов) векторов. Такие как:

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

как m(index) Эта форма доступа не ограничивается векторами, но также может быть доступна для матриц. Это то же самое, что и Matlab. Мы знаем, что матрица (3,4) определена в Matlab. Если я получаю доступ к (5), это эквивалентно для доступа к (2,2). Это потому, что матрица хранится в столбцах , Это более гибко: матричные элементы по умолчанию также сохраняются по столбцам, но мы также можем изменить метод хранения с помощью параметра построения шаблона матрицы Options = RowMajor (этот параметр является четвертым параметром параметра построения матрицы, который мы не упомянули).

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

Айген предоставил rows() , cols() , size() Способ получить размер матрицы одновременно resize() Метод изменяет размер динамического массива.

Можно видеть, что мы можем изменить размер матрицы произвольно, но элементы матрицы изменятся после изменения размера. Если матрица с измененным размером имеет несколько неинициализированных элементов, чем предыдущая сборка. Если матрица с измененным размером хранится в столбцах (по умолчанию), то команда resize имеет тот же результат выполнения, что и изменение формы в matlab, за исключением того, что matlab требует, чтобы элементы до и после матрицы изменения формы были одинаковыми, то есть нерециализированные элементы не могут быть разрешены после изменения размера.
Хотя матрица фиксированного размера также поддерживает команду изменения размера, размер после изменения размера может быть только его собственным размером, в противном случае будет сообщено об ошибке. Потому что, если размер матрицы одинаков до и после изменения размера, изменение размера не будет выполнено. Если мы не хотим изменять соответствующие элементы матрицы после изменения размера, то мы можем использовать conservativeResize() Метод. Соответствуя матрице m в приведенной выше программе, мы называем m.conservativeResize(4,3) Полученные результаты следующие. Поскольку количество строк увеличилось, добавленные строки будут отображаться в неинициализированной форме.

Использование = in Eigen может напрямую скопировать матрицу в другую матрицу.Если размер скопированной и назначенной матрицы не совпадает, функция изменения размера будет автоматически выполняться на скопированной матрице. Конечно, если скопированная матрица является фиксированной, функция изменения размера не будет выполнена. Конечно, вы можете отменить этот процесс автоматического изменения размера через некоторые настройки.

Когда использовать фиксированную матрицу, а когда использовать динамическую матрицу? Проще говоря: когда размер матрицы относительно мал, используется фиксированная матрица (обычно меньше 16), а когда размер матрицы велик, используется динамическая матрица (обычно больше 32). Использование фиксированной матрицы имеет лучшую производительность, это позволяет избежать повторного динамического выделения памяти, а фиксированная матрица фактически является массивом. что Matrix4f mymatrix; На самом деле float mymatrix[16]; , Так что это действительно не требует времени выполнения. Вместо этого создание динамической матрицы требует
Распределяет пространство в куче. что MatrixXf mymatrix(rows,colums); На самом деле float *mymatrix = new float[rows*colums]; Кроме того, динамическая матрица также содержит значения строк и столбцов.
Конечно, существуют очевидные недостатки фиксированных матриц. Когда размер массива слишком велик, преимущество фиксированных массивов в скорости не столь очевидно, наоборот, слишком большие фиксированные массивы могут вызвать переполнение стека. В это время гибкость динамической матрицы становится очень важной.

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

  1. Опции: Этот параметр определяет, будет ли матрица храниться в строках или столбцах в процессе хранения. На этот метод хранения необходимо обратить внимание в матричном преобразовании, которое мы упоминали ранее. По умолчанию хранится по столбцу, мы можем отобразить использование Options=RowMajor Пусть матрица действительно хранится в строках. Такие как Matrix a; .
  2. MaxRowsAtCompileTime и MaxColsAtCompileTime: эти два значения задают максимальное количество строк и столбцов динамической матрицы при выделении пространства. Такие как Matrix ; .

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

Читайте также: