matrix与vector
使用二维数组(matrix):
- 静态大小:
二维数组的大小在编译时确定,无法动态改变。这意味着你需要在定义时指定行数和列数,并且在程序运行时无法调整这些维度。- 简单直观:
对于固定大小的矩阵,使用二维数组可能更加简单和直观。访问元素时使用matrix[i][j]的语法比较直接。- 内存布局:
二维数组的内存布局是连续的,这可能有利于一些性能优化,尤其是在大型数据集上的操作。- 缺点:
静态大小是二维数组的一个限制,如果需要动态调整矩阵的大小,使用二维数组就不再适用。
使用std::vector<std::vector>:
- 动态大小:
std::vector允许动态分配内存,并且可以在运行时动态调整大小,包括行和列数。这使得它更加灵活,能够处理动态变化的数据需求。- 灵活性:
可以通过push_back()、resize()等函数动态地添加行和列,或者改变现有行和列的大小。这对于需要动态处理数据的情况非常有用。- 通用性:
std::vector是标准库提供的通用容器,它提供了更多的功能和算法支持,如排序、查找等操作,而不仅仅局限于矩阵操作。- 缺点:
内存布局可能不是连续的,尤其是对于std::vector<std::vector>来说,每个std::vector在内存中是分开存储的,这可能导致访问效率略有降低。
选择适当的场景:
固定大小的矩阵:如果矩阵的大小在编译时已知且不会变化,使用二维数组可能更合适,因为它更简单和直接。 动态大小的矩阵:如果矩阵的大小需要在运行时动态调整,或者需要使用std::vector提供的其他灵活性和算法支持,那么std::vector<std::vector>更适合,尽管在性能上可能会稍有损失。性能要求:如果性能是关键问题,尤其是对于大型数据集和复杂的数学运算,可以考虑使用专门的线性代数库如Eigen或Armadillo,它们在性能和功能上都有优势。
综上所述,选择二维数组还是std::vector<std::vector< T >>取决于你的具体需求,包括矩阵的大小是否固定、是否需要动态调整大小以及对性能和灵活性的需求。
矩阵操作
- 使用二维数组
#include <iostream>
using namespace std;
int main() {
const int rows = 3;
const int cols = 3;
int matrix[rows][cols] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
// 输出矩阵
for(int i = 0; i < rows; ++i) {
for(int j = 0; j < cols; ++j) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
- 使用std::vector
#include <iostream>
#include <vector>
using namespace std;
int main() {
const int rows = 3;
const int cols = 3;
vector<vector<int>> matrix(rows, vector<int>(cols, 0));
// 初始化矩阵
int value = 1;
for(int i = 0; i < rows; ++i) {
for(int j = 0; j < cols; ++j) {
matrix[i][j] = value++;
}
}
// 输出矩阵
for(const auto& row : matrix) {
for(int elem : row) {
cout << elem << " ";
}
cout << endl;
}
return 0;
}
你可以创建一个辅助函数来简化二维vector的初始化:
#include <vector>
std::vector<std::vector<int>> createMatrix(int rows, int cols, int initial_value = 0) {
return std::vector<std::vector<int>>(rows, std::vector<int>(cols, initial_value));
}
int main() {
int rows = 3;
int cols = 4;
auto matrix = createMatrix(rows, cols);
// Now matrix is a 3x4 matrix initialized with 0s
}
再看下三维vector的声明:
std::vector<std::vector<std::vector<int>>> vec3d(x, std::vector<std::vector<int>>(y, std::vector<int>(z, 0)));
vector 的应用
std::vector 是 C++ 标准库中的一个容器,提供了一系列用于管理动态数组的方法和函数。以下是 std::vector 常用的内置函数(成员函数):
构造和析构:
vector():默认构造函数,创建一个空的向量。
vector(size_type count):构造函数,创建包含 count 个默认构造的元素的向量。
vector(size_type count, const T& value):构造函数,创建包含 count 个值为 value 的元素的向量。
vector(const vector& other):拷贝构造函数,复制另一个向量 other 的所有元素到新向量。
~vector():析构函数,释放向量的资源。
// 默认构造函数 vector():创建一个空的向量。
#include <vector>
#include <iostream>
int main() {
// 默认构造一个空的vector
std::vector<int> vec1; // vec1 是一个空的整数向量
// 带有指定数量元素的构造函数 vector(size_type count):创建包含指定数量默认构造的元素的向量。
// 创建包含5个默认构造的整数元素的向量
std::vector<int> vec2(5); // vec2 包含 {0, 0, 0, 0, 0}
// 带有指定数量和值的构造函数 vector(size_type count, const T& value):创建包含指定数量给定值的元素的向量。
// 创建包含3个值为 10 的整数元素的向量
std::vector<int> vec3(3, 10); // vec3 包含 {10, 10, 10}
// 拷贝构造函数 vector(const vector& other):从另一个向量拷贝所有元素到新向量。
std::vector<int> source = {1, 2, 3, 4, 5};
// 使用拷贝构造函数创建新向量,拷贝source中的元素
std::vector<int> vec4(source); // vec4 包含 {1, 2, 3, 4, 5}
// 修改原向量不会影响新向量
source.push_back(6);
// 输出新向量的内容
for (int num : vec4) {
std::cout << num << " ";
}
std::cout << std::endl; // 输出:1 2 3 4 5
// 析构函数 ~vector():释放向量的资源。在使用std::vector时,我们不需要显式调用析构函数,它会在对象超出范围时自动调用。
// 创建一个向量
std::vector<int> vec5 = {1, 2, 3, 4, 5};
// vec5 离开作用域时,析构函数会自动释放向量的资源
return 0;
}
这些例子展示了如何使用 std::vector 的不同构造和析构函数来创建、复制和销毁向量对象。
容量管理:
size():返回向量中元素的个数。
resize(size_type count):重新设置向量的大小为 count,超出部分进行值初始化。
resize(size_type count, const T& value):重新设置向量的大小为 count,超出部分使用 value 进行填充。
empty():检查向量是否为空。
reserve(size_type new_cap):保留至少能容纳 new_cap 个元素的存储空间,但不改变元素个数。
访问元素:
operator[]:通过索引访问元素。
at(size_type pos):通过索引访问元素,带有边界检查。
front():返回第一个元素的引用。
back():返回最后一个元素的引用。
data():返回指向第一个元素的指针。
当涉及C++的std::vector容量管理时,以下是具体的示例代码,演示了 size(), capacity(), resize(), empty() 和 reserve() 这些函数的使用:
#include <iostream>
#include <vector>
int main() {
// 创建一个空的vector
std::vector<int> vec;
// 判断向量是否为空
if (vec.empty()) {
std::cout << "Vector is empty." << std::endl;
}
// 添加一些元素
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
// 获取向量的当前大小和容量
std::cout << "Current size: " << vec.size() << std::endl; // 输出:3
// 调整向量的大小
vec.resize(5); // 使用默认值进行值初始化
std::cout << "After resizing to 5 elements:" << std::endl;
std::cout << "Current size: " << vec.size() << std::endl; // 输出:5
// 使用指定值进行resize
vec.resize(8, 10); // 使用值10填充剩余的元素
std::cout << "After resizing to 8 elements with value 10:" << std::endl;
std::cout << "Current size: " << vec.size() << std::endl; // 输出:8
// 检查向量是否为空
if (!vec.empty()) {
std::cout << "Vector is not empty." << std::endl;
}
// 保留至少能容纳10个元素的存储空间,为向量分配了足够的内存,以便将来可以更高效地添加元素
vec.reserve(10);
std::cout << "After reserving capacity for at least 10 elements:" << std::endl;
std::cout << "Current size: " << vec.size() << std::endl; // 输出:8
return 0;
}
- 修改容器:
push_back(const T& value):在向量末尾添加一个元素。
pop_back():移除向量末尾的元素。
insert(iterator pos, const T& value):在指定位置 pos 插入一个元素 value。
erase(iterator pos):移除指定位置 pos 处的元素。
clear():移除所有元素,使向量为空。
当使用这些方法时,通常是针对容器类(比如 vector、string)或者类似数组的数据结构。下面是一些具体的例子:
// 使用 operator[] 访问元素
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 访问第三个元素(索引为2)
int element = vec[2];
std::cout << "Element at index 2: " << element << std::endl;
return 0;
}
// 使用 at(size_type pos) 访问元素(带边界检查)
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 访问第四个元素(索引为3),带边界检查
int element = vec.at(3);
std::cout << "Element at index 3: " << element << std::endl;
// 尝试访问超出边界的索引,会抛出 std::out_of_range 异常
// try { ... }: 用来包裹可能抛出异常的代码。如果try块中的代码抛出了异常,程序将不会立即终止,而是跳转到相应的catch块。
// catch (const std::out_of_range& e) { ... }: 这是一个catch块,用来捕获并处理try块中抛出的std::out_of_range类型的异常。e是异常对象的引用,它包含了异常的相关信息。
// std::cerr << "Caught exception: " << e.what() << std::endl;: 如果捕获到异常,这行代码将输出异常的消息到标准错误流std::cerr。e.what()是一个返回异常描述的函数,它返回一个C风格的字符串,描述了异常的原因。
try {
int element_out_of_bounds = vec.at(10);
} catch (const std::out_of_range& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
// 使用 front() 返回第一个元素的引用
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取第一个元素的引用
// first_element是对vec中第一个元素的引用,所以对first_element的任何操作都会影响到vec的第一个元素。例如,如果执行first_element = 10;,那么vec的第一个元素将变成10。
int& first_element = vec.front();
std::cout << "First element: " << first_element << std::endl;
return 0;
}
// 使用 back() 返回最后一个元素的引用
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取最后一个元素的引用
int& last_element = vec.back();
std::cout << "Last element: " << last_element << std::endl;
return 0;
}
// 使用 data() 返回指向第一个元素的指针
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
// 获取指向第一个元素的指针
int* data_ptr = vec.data();
// 输出数组中所有元素,数组在内存中是连续存储的
// 不是连续储存的,如list无法使用此方法
for (size_t i = 0; i < vec.size(); ++i) {
std::cout << "Element at index " << i << ": " << data_ptr[i] << std::endl;
}
return 0;
}
- 其他:
swap(vector& other):交换当前向量和 other 向量的内容。
emplace_back(Args&&… args):在向量末尾就地构造一个元素。
emplace(iterator pos, Args&&… args):在指定位置 pos 就地构造一个元素。
这些函数提供了对 std::vector 内部元素进行增、删、改、查以及容量管理的全面支持。
// swap函数示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vecA = {1, 2, 3};
std::vector<int> vecB = {4, 5, 6};
std::cout << "Before swap:" << std::endl;
std::cout << "vecA: ";
for (int num : vecA) std::cout << num << " ";
std::cout << "\nvecB: ";
for (int num : vecB) std::cout << num << " ";
std::cout << std::endl;
// 使用swap函数交换两个向量的内容
vecA.swap(vecB);
std::cout << "After swap:" << std::endl;
std::cout << "vecA: ";
for (int num : vecA) std::cout << num << " ";
std::cout << "\nvecB: ";
for (int num : vecB) std::cout << num << " ";
std::cout << std::endl;
return 0;
}
// emplace_back函数示例:
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec;
// 使用emplace_back在向量末尾构造一个元素
vec.emplace_back(10); // 直接构造一个值为10的int
vec.emplace_back(20); // 直接构造一个值为20的int
for (int num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
// emplace函数示例:
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> vec = {"Hello", "World"};
// 使用emplace在指定位置构造一个元素
// 假设我们想在第一个元素之后插入一个新字符串
auto it = vec.begin() + 1; // 获取第二个元素的迭代器
vec.emplace(it, "sorry"); // 在迭代器指向的位置构造一个字符串
for (const auto& str : vec) {
std::cout << str << " ";
}
std::cout << std::endl;
return 0;
}
在emplace_back的例子中,我们直接在vec的末尾构造了两个整数值分别为10和20的int类型元素。emplace_back允许你传递参数给构造函数,而不需要先构造一个临时对象,这样可以提高效率。
在emplace的例子中,我们在vec的第一个元素之后的位置插入了一个新的字符串"sorry"。emplace同样允许传递参数给构造函数,并且可以在向量的任何位置插入新元素,而不仅仅是末尾。
这些函数的使用可以提高代码的效率和可读性,特别是当你需要在容器中添加新元素时。
在深度学习中多种库对于矩阵的支持
在深度学习应用中,有几个流行的C++库支持矩阵和张量运算。以下是其中一些常用的库:
Eigen:Eigen是一个开源的C++模板库,提供了线性代数、矩阵运算和向量运算的功能。它可以作为深度学习库的基础,用于实现矩阵和张量的基本操作。
DLIB:DLIB是一个包含机器学习算法和工具的C++库,它包括了矩阵、线性代数和图像处理等方面的功能,可以用于深度学习模型的实现和训练。
ArrayFire:ArrayFire是一个高性能的并行计算库,提供了对多维数组(包括矩阵和张量)的快速运算功能,适用于深度学习中大规模数据的处理。
Armadillo:Armadillo是一个C++模板库,提供了高性能的线性代数运算和矩阵运算功能,可以用于深度学习模型的实现和优化。
opencv: OpenCV(Open Source Computer Vision Library)主要是用于计算机视觉领域的开源库,虽然它的主要目标是图像处理和计算机视觉任务,但也包含了一些基本的矩阵和张量运算功能。它提供了对多维数据结构(如矩阵)的支持,并且可以用于某些深度学习任务中的数据处理和预处理。
一些库的运用
Eigen是一个高性能的矩阵和线性代数库,在SLAM运用中,熟练使用Eigen库是基本需求,它提供了丰富的功能并且易于使用。
Eigen 是一个高性能的线性代数库,广泛应用于科学计算和工程领域。它具有以下特点:
高性能:Eigen 实现了多种优化技术,如矢量化、缓存友好算法等,能提供高效的矩阵运算。
简单易用:Eigen 提供了丰富的接口,并且使用起来非常直观。
广泛支持:支持各种矩阵和向量操作,包括基本的代数运算、解线性方程组、特征值分解等。
良好的文档:Eigen 有详细的文档和示例,便于学习和使用。
#include <iostream>
#include <Eigen/Dense>
using namespace std;
using namespace Eigen;
int main() {
Matrix3f matrix; // 创建3x3的float类型矩阵
// 初始化矩阵
matrix << 1, 2, 3,
4, 5, 6,
7, 8, 9;
cout << "Matrix:\n" << matrix << endl;
// 转置矩阵
Matrix3f transpose = matrix.transpose();
cout << "Transpose:\n" << transpose << endl;
// 矩阵乘法
Matrix3f result = matrix * transpose;
cout << "Matrix * Transpose:\n" << result << endl;
return 0;
}
Armadillo库
Armadillo是另一个高效的线性代数库,语法接近MATLAB。(这个我没用过)需要安装Armadillo库:
#include <iostream>
#include <armadillo>
using namespace std;
using namespace arma;
int main() {
mat A = {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}};
cout << "Matrix A:\n" << A << endl;
// 转置矩阵
mat At = A.t();
cout << "Transpose of A:\n" << At << endl;
// 矩阵乘法
mat B = A * At;
cout << "A * Transpose of A:\n" << B << endl;
return 0;
}
OpenCV 中,矩阵是通过 cv::Mat 类来表示的。
创建矩阵
你可以使用不同的构造函数来创建 cv::Mat 对象,例如:
通过指定行数、列数和数据类型来创建矩阵:
cv::Mat mat1(rows, cols, CV_8UC3); // 8-bit 无符号整数,3通道颜色图像
通过传入已有的数据来创建矩阵:
float data[] = {1.2, 2.5, 3.7, 4.1};
cv::Mat mat2(2, 2, CV_32FC1, data); // 32-bit 浮点数,单通道图像
通过复制已有的矩阵来创建新的矩阵:
cv::Mat mat3 = mat1.clone();
访问矩阵元素
你可以通过 at 方法或者 ptr 方法来访问矩阵的元素,例如:
float value = mat1.at<float>(i, j); // 访问第 i 行、第 j 列的元素
或者:
for (int i = 0; i < mat2.rows; i++) {
float* row_ptr = mat2.ptr<float>(i); // 获取第 i 行的指针
for (int j = 0; j < mat2.cols; j++) {
float value = row_ptr[j]; // 访问第 i 行、第 j 列的元素
}
}
矩阵操作
OpenCV 提供了丰富的矩阵操作函数,包括矩阵加法、减法、乘法、转置、逆矩阵计算等。例如,你可以使用 cv::add 函数来进行矩阵加法:
cv::Mat result;
cv::add(mat1, mat2, result); // 将 mat1 和 mat2 相加,结果存储到 result 中
使用矩阵进行图像处理
由于 OpenCV 主要用于图像处理,因此 cv::Mat 类也经常用来表示图像。你可以通过 cv::imread 函数加载图像为 cv::Mat 对象,然后对图像进行各种操作,比如改变亮度、对比度、图像滤波、边缘检测等。
总之,OpenCV 中的 cv::Mat 类提供了灵活、高效的多维数组表示,适用于图像处理和其他计算机视觉应用。它不仅可以用于表示图像数据,还可以用于一般的矩阵运算和线性代数计算。