C++11标准|用C++写一个矩阵(包含了如何用cmake构建一个工程)

如何用cmake构建一个工程?
构建一个工程的时候,需要做的几件事情(如果下面几件事你知道怎么做了,多大的工程就都不是问题了):
1.源代码在什么位置?
2.头文件在哪里?
3.怎么生成静态或者动态库?
4.程序链接的静态库在哪里?
5.如果工程的代码存放在很多地方,那又该怎么找到它们呢?
https://nicehuster.github.io/2018/04/20/Cmake/

矩阵以及矩阵相乘算法初始版本的代码实现(纯函数版本)
写代码的风格:
要把重复的代码抽象出其内部蕴含的某种逻辑形成积木(模块)比如下面代码中的 printMatrix()函数 就是从main函数的逻辑中里抽象出来的。写代码本身就是一个搭积木的过程,大的模块通过小的模块构成(积木)。 类名写成第一个字母大写的驼峰状,即如果名字有多个单词组成,每个单词的第一个字母都应该大写. 函数写成第一个字母小写的驼峰状. 驼峰法是针对如果名字有多个单词组成而言的

c++编译出错无需调试,编译出错其实就是语法的错误,编译器会有详细的错误信息给你,根据错误信息修改代码然后重新编译即可。或者,拿到错误信息可以利用搜索引擎:baidu、google,或者stackoverflow搜索类似问题,这里和python其实类似,python若有语法错误也不能调试,语法错误指程序中含有不符合语法规定的语句。调试是在运行时错误时候用来解决问题的。

main.cpp

#include <stdio.h>
#include <iostream>
#include "matrix.h"
#include <vector>
//尽量减少用指针,因为指针不好操控,所以定义数组就用vector,少用char a[]或char* a
#include <tuple>

//main.cpp里面只能有一个函数叫做 main
//写代码的风格:把重复的代码抽象出其内部蕴含的某种逻辑形成积木(模块),写代码本身就是一个搭积木的过程,大的模块通过小的模块构成(积木)
int main(int argc, char* argv[])
{

	std::vector<float> A = { 0.0f,-1.0f,2.0f,0.0f };
	std::vector<float> B = { 0.0f,-1.0f,2.0f,0.0f };

	//auto[C, NC, MC] = matrixMutiple(A, B, 2, 2, 2, 2);这是c++ 17的新特性,用了符号重载
	
	//C = std::get<0>(result);
	//NC = std::get<1>(result);
	//MC = std::get<2>(result);
	std::tuple<std::vector<float>,int,int> result = matrixMutiple(A, B, 2, 2, 2, 2);

	std::vector<float> C;
	int NC;
	int MC;
	std::tie(C, NC, MC) = result;

	if (C.size() == 0)
	{
		printf("ERROR");
	}
	printMatrix(C, NC, MC);
	return 0;
}

matrix.h:

#pragma once
#include <tuple>
#include <vector>//尽量减少用指针,因为指针不好操控,所以定义数组就用vector


//矩阵的乘法,返回值的类型是tuple的模板类,第一个元素是vector,第二个是行数,第三个是列数
std::tuple<std::vector<float>, int, int> matrixMutiple(const std::vector<float>& A, const std::vector<float>& B, int NA, int MA, int NB, int MB);

void printMatrix(const std::vector<float>& A, int N, int M);

matrix.cpp:

#include <stdio.h>
#include "matrix.h"


//参数用引用是减少复制的操作,提升程序效率
std::tuple<std::vector<float>, int, int> matrixMutiple(const std::vector<float>& A, const std::vector<float>& B, int NA, int MA, int NB, int MB)
{
	//定义一个数组C表示最后乘完以后的矩阵 [NA.MA]*[NB*MB】,所以MA=NB
	int NC = NA;
	int MC = MB;
	std::vector<float> C(NC * MC, 0);//定义一个大小为NC * MC的数组初始化为0,用数组来表示矩阵NC表示行数,MC表示列数
	
	
	//判断相乘的两个矩阵维度是否匹配
	if (MA != NB)
	{
		NC = 0;
		MC = 0;
		C = {}; //空的vector
		//应该在这就返回了,外面调用的人怎么知道它接受的数据是有问题的
		std::tuple<std::vector<float>, int, int> result = std::make_tuple(C, NC, MC);
		return result;

	}
	for (int i = 0; i < NC; i++)
	{
		for (int j = 0; j < MC; j++)
		{
			float sum = 0.0f;//整型和浮点型的存储结构不同,所以初始化不用float sum = 0
			for (int k = 0; k < MA; k++)
			{
				sum += A[i * MA + k] * B[k * MB + j];
			}
			C[i* MC + j] = sum;//MC表示行数
		}
	}
 //打包成元组返回

	std::tuple<std::vector<float>, int, int> result = std::make_tuple(C, NC, MC);

	return result;
	//打包成元组返回
	//return {C, NC, MC}; //这是c++ 17的新特性,用了符号重载
}

void printMatrix(const std::vector<float> & A, int N, int M)
{
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < M; j++)
		{
			printf("%f ", A[i * M + j]);
		}
		printf("\n");
	}
}

改进版1: 引入类
初始版本存在的问题就是:函数的参数太多了导致一行写下来太长,但这些函数的参数有几个是命运共同体即可以整合到一起(如果有多个函数, 并且每个函数都有多个参数的时候, 可以把相关的放到一个类里面, 只需要初始化的时候传入参数, 类里面的函数或者叫方法, 直接调用这些参数就行, 不需要每次调用都传入参数, 更少的出错.),也就是把命运共同体弄成一个类,所以解决方法是用类来抽象矩阵这个概念,让这个代码变得简洁好懂。
根据经验,类里面的变量只能强制定义为private类型,函数能够定义成public类型。

main.cpp

#include <stdio.h>
#include <iostream>
#include "matrix.h"
#include <vector>
#include <tuple>

//main.cpp里面只能有一个函数叫做 main
//写代码的风格:把重复的代码抽象出其内部蕴含的某种逻辑形成积木(模块),写代码本身就是一个搭积木的过程,大的模块通过小的模块构成(积木)
int main(int argc, char* argv[])
{

	//std::vector<float> A = { 0.0f,-1.0f,2.0f,0.0f };
	//std::vector<float> B = { 0.0f,-1.0f,2.0f,0.0f };

	auto[C, NC, MC] = matrixMutiple(A, B, 2, 2, 2, 2);这是c++ 17的新特性,用了符号重载
	//
	C = std::get<0>(result);
	NC = std::get<1>(result);
	MC = std::get<2>(result);
	//std::tuple<std::vector<float>,int,int> result = matrixMutiple(A, B, 2, 2, 2, 2);
	//std::vector<float> C;
	//int NC;
	//int MC;
	//std::tie(C, NC, MC) = result;

	//if (C.size() == 0)
	//{
	//	printf("ERROR");
	//}
	//printMatrix(C, NC, MC);
	//Matrix A(3, 4, 1.0f);
	//printMatrix(A);
	//Matrix B({ 1.0f,2.0f,4.0f,1.2f }, 2, 2);
	//printMatrix(B);


	Matrix A({ 0.0f,-1.0f,1.0f,0.0f }, 2, 2);
	Matrix B({ 0.0f,-1.0f,1.0f,0.0f }, 2, 2);
	auto C = matrixMutiple(A, B);
	printMatrix(C);
	return 0;
}

matrix.h:

#pragma once
#include <tuple>
#include <vector>//尽量减少用指针,因为指针不好操控,所以定义数组就用vector
#include <optional> 


//用类来抽象矩阵这个概念,让这个代码变得简洁好懂
class Matrix
{

//数据访问功能
public:
	//设置矩阵的维度,比如B矩阵用一半,想要改变B的size
	void setsize(unsigned int N, unsigned int M,float val = 0.0f);
	//获得矩阵维度信息
	std::tuple<unsigned int, unsigned int> getsize() const;
	//因为元素的值为private变量,所以要通过public函数来获得矩阵的元素的值 索引号从1开始
	float getElement(unsigned int i, unsigned int j) const;
	//设置矩阵的元素值 索引号从1开始
	void setElement(float val, unsigned int i, unsigned int j);

//构造
public:
	Matrix(unsigned int N, unsigned int M, float val = 0.0f);
	Matrix(std::vector<float> buff, unsigned int N, unsigned int M) ;

//成员数据
private:
	//实际数据
	std::vector<float> m_Data;
	//尺寸
	unsigned int m_N, m_M;// 矩阵维度不可能小于0,所以用unsigned int类型

};

//矩阵乘法1,返回值的类型是tuple的模板类,第一个元素是vector,第二个是行数,第三个是列数
std::tuple<std::vector<float>, int, int> matrixMutiple(const std::vector<float>& A, const std::vector<float>& B, int NA, int MA, int NB, int MB);

void printMatrix(const Matrix& m);


//矩阵乘法2
Matrix matrixMutiple(const Matrix& A, const Matrix& B);

matrix.cpp:

#include <stdio.h>
#include "matrix.h"


//参数缺省值只能出现在函数的声明中,而不能出现在定义体中
Matrix::Matrix(unsigned int N, unsigned int M, float val)
{
	m_Data.resize(N * M, val);
	m_N = N;
	m_M = M;

}
Matrix::Matrix(std::vector<float> buff, unsigned int N, unsigned int M)
{
	//存在隐患就是buff的长度不等于给的N*M大小,构造函数没有返回值 你不好表示他算错了
	//所以有两种方法解决,一是异常抛出,外面如果把异常解决了就没问题了,还有一种是assert直接让程序崩溃
	m_Data = buff;
	m_N = N;
	m_M = M;

}

//设置矩阵的维度,比如B矩阵用一半,想要改变B的size
void Matrix::setsize(unsigned int N, unsigned int M, float val) 
{
	//clear 是为了解决vector resize不会把原有元素的值重置的问题
	m_Data.clear();
	m_Data.resize(N * M, val);
	m_N = N;
	m_M = M;

}
//获得矩阵维度信息
std::tuple<unsigned int, unsigned int> Matrix::getsize() const
{
	return { m_N, m_M };

}
//因为元素的值为private变量,所以要通过public函数来获得矩阵的元素的值 索引号从1开始
float Matrix::getElement(unsigned int i, unsigned int j) const
{
	//越界判定,m_N为行,m_M为列
	if (i >= 1 && i <= m_N && j >= 1 && j <= m_M)
	{
		return m_Data[(i - 1) * m_M + j - 1];

	}
	
}
//设置矩阵的元素值 索引号从1开始
void Matrix::setElement(float val, unsigned int i, unsigned int j)
{
	if (i >= 1 && i <= m_N && j >= 1 && j <= m_M)
	{
		m_Data[(i - 1) * m_M + j - 1] = val;
	}
	
}

std::tuple<std::vector<float>, int, int> matrixMutiple(const std::vector<float>& A, const std::vector<float>& B, int NA, int MA, int NB, int MB)
{
	//定义一个数组C表示最后乘完以后的矩阵 [NA.MA]*[NB*MB】,所以MA=NB
	int NC = NA;
	int MC = MB;
	std::vector<float> C(NC * MC, 0);//定义一个大小为NC * MC的数组初始化为0,用数组来表示矩阵NC表示行数,MC表示列数
	
	
	//判断相乘的两个矩阵维度是否匹配
	if (MA != NB)
	{
		NC = 0;
		MC = 0;
		C = {}; //空的vector
		//应该在这就返回了,外面调用的人怎么知道它接受的数据是有问题的
		std::tuple<std::vector<float>, int, int> result = std::make_tuple(C, NC, MC);
		return result;

	}
	for (int i = 0; i < NC; i++)
	{
		for (int j = 0; j < MC; j++)
		{
			float sum = 0.0f;//整型和浮点型的存储结构不同,所以初始化不用float sum = 0
			for (int k = 0; k < MA; k++)
			{
				sum += A[i * MA + k] * B[k * MB + j];
			}
			C[i* MC + j] = sum;//MC表示行数
		}
	}
 //打包成元组返回

	std::tuple<std::vector<float>, int, int> result = std::make_tuple(C, NC, MC);

	return result;
	//打包成元组返回
	//return {C, NC, MC}; //这是c++ 17的新特性,用了符号重载
}

void printMatrix(const Matrix& m)
{
	auto result = m.getsize();
	int M;
	int N;
	std::tie(N, M) = result;//N是行数

	for (unsigned int i = 1; i <= N; i++)
	{
		for (unsigned int j = 1; j <= M; j++)
		{
			printf("%f ", m.getElement(i,j));
		}
		printf("\n");
	}
}

Matrix matrixMutiple(const Matrix& A, const Matrix& B)
{
	//先获得矩阵A和B的尺寸
	auto resultA = A.getsize();
	auto resultB = B.getsize();
	int MA,MB;
	int NA,NB;
	std::tie(NA, MA) = resultA;//N是行数
	std::tie(NB, MB) = resultB;//N是行数
	Matrix C(NA, MB, 0.0f);
	for (unsigned int i = 1; i <= NA; i++)
	{
		for (unsigned int j = 1; j <= MB; j++)
		{
			float sum = 0.0f;//整型和浮点型的存储结构不同,所以初始化不用float sum = 0
			for (unsigned int k = 1; k <= MA; k++)
			{
				sum += A.getElement(i, k) * B.getElement(k, j);
			}
			C.setElement(sum,i,j);//MC表示行数
		}
	}
	return C;
}

改进版2
改进版1引进了类,但是性能上仍然有不足。
1.在main函数里,做矩阵乘法计算时调用matrixMutiple这个函数,由于这里我们在矩阵类里定义了私有变量,在matrixMutiple这个函数里需要用getElement访问才能得到相应的信息,但每次访问私有变量都要调用getElement函数会比较慢。所以说为了解决这个问题就把matrixMutiple这个函数定义为类的成员函数,并且定义为static静态函数,这样的话调用就是比较科学。
2. 为了做到像MATLAB那样进行矩阵的乘法直接A=BC,所以优化方法进行了运算符的重载。把重载为普通函数,函数名就是operator,调用函数 operator* (B,C)就可以简写为B*C。
3.在Matrix的成员函数里重载了()符号,函数名就是operator(),如A是Matrix类的对象,重载后就可以这样用,A.operator()(2,1) = 77等效于A(2, 1) = 77。

main.cpp

#include <stdio.h>
#include <iostream>
#include "matrix.h"
#include <vector>
#include <tuple>

//main.cpp里面只能有一个函数叫做 main
//写代码的风格:把重复的代码抽象出其内部蕴含的某种逻辑形成积木(模块),写代码本身就是一个搭积木的过程,大的模块通过小的模块构成(积木)
int main(int argc, char* argv[])
{
	Matrix A({ 0.0f,-1.0f,1.0f,0.0f }, 2, 2);
	Matrix B({ 0.0f,-1.0f,1.0f,0.0f }, 2, 2);
	//printf("%f\n",A.element(1,2));
	A(2, 1) = 77;
	Matrix::print(A);

	printf("hello a world\n");
	auto C =operator*(A, B);
	auto D = A*B;
	Matrix::print(C);
	Matrix::print(D);
	return 0;
}

matrix.h:

#pragma once
#include <tuple>
#include <vector>//尽量减少用指针,因为指针不好操控,所以定义数组就用vector
#include <optional> 


//用类来抽象矩阵这个概念,让这个代码变得简洁好懂
class Matrix
{
//静态方法(公有的方法,和独立的对象无关)
public:
	//打印不是矩阵独一无二的属性,所以不定义为对象方法
	static void print(const Matrix& m);
	static Matrix mulptiple1(const Matrix& A, const Matrix& B);
//数据访问功能
public:
	//设置矩阵的维度,比如B矩阵用一半,想要改变B的size
	void setsize(unsigned int N, unsigned int M,float val = 0.0f);
	//获得矩阵维度信息
	std::tuple<unsigned int, unsigned int> getsize() const;
	//因为元素的值为private变量,所以要通过public函数来获得矩阵的元素的值 索引号从1开始
	float getElement(unsigned int i, unsigned int j) const;
	//设置矩阵的元素值 索引号从1开始
	void setElement(float val, unsigned int i, unsigned int j);

	//返回值是float类型的引用,意思是如果对返回值进行更改,引用的那个变量值也会直接跟着变
	float& element(unsigned int i, unsigned int j);
	
	//返回值是float类型的引用,即返回的值是一个左值
	float& operator()(unsigned int i, unsigned int j);


//构造
public:
	Matrix(unsigned int N, unsigned int M, float val = 0.0f);
	Matrix(std::vector<float> buff, unsigned int N, unsigned int M) ;

//成员数据
private:
	//实际数据
	std::vector<float> m_Data;
	//尺寸
	unsigned int m_N, m_M;// 矩阵维度不可能小于0,所以用unsigned int类型
};

Matrix operator*(const Matrix& A, const Matrix& B);

matrix.cpp

#include <stdio.h>
#include "matrix.h"


//参数缺省值只能出现在函数的声明中,而不能出现在定义体中
Matrix::Matrix(unsigned int N, unsigned int M, float val)
{
	m_Data.resize(N * M, val);
	m_N = N;
	m_M = M;

}
Matrix::Matrix(std::vector<float> buff, unsigned int N, unsigned int M)
{
	//存在隐患就是buff的长度不等于给的N*M大小,构造函数没有返回值 你不好表示他算错了
	//所以有两种方法解决,一是异常抛出,外面如果把异常解决了就没问题了,还有一种是assert直接让程序崩溃
	m_Data = buff;
	m_N = N;
	m_M = M;

}

//设置矩阵的维度,比如B矩阵用一半,想要改变B的size
void Matrix::setsize(unsigned int N, unsigned int M, float val) 
{
	//clear 是为了解决vector resize不会把原有元素的值重置的问题
	m_Data.clear();
	m_Data.resize(N * M, val);
	m_N = N;
	m_M = M;

}
//获得矩阵维度信息
std::tuple<unsigned int, unsigned int> Matrix::getsize() const
{
	return { m_N, m_M };

}
//因为元素的值为private变量,所以要通过public函数来获得矩阵的元素的值 索引号从1开始
float Matrix::getElement(unsigned int i, unsigned int j) const
{
	//越界判定,m_N为行,m_M为列
	if (i >= 1 && i <= m_N && j >= 1 && j <= m_M)
	{
		return m_Data[(i - 1) * m_M + j - 1];

	}
	
}
//设置矩阵的元素值 索引号从1开始
void Matrix::setElement(float val, unsigned int i, unsigned int j)
{
	if (i >= 1 && i <= m_N && j >= 1 && j <= m_M)
	{
		m_Data[(i - 1) * m_M + j - 1] = val;
	}
	
}

void Matrix::print(const Matrix& m)
{
	for (unsigned int i = 0; i < m.m_N; i++)
	{
		for (unsigned int j = 0; j < m.m_M; j++)
		{
			printf("%f ", m.m_Data[i* m.m_M+j]);
		}
		printf("\n");
	}
}

Matrix Matrix::mulptiple1(const Matrix& A, const Matrix& B)
{
	
	Matrix C(A.m_N, B.m_M, 0.0f);
	for (unsigned int i = 0; i < A.m_N; i++)
	{
		for (unsigned int j = 0; j < B.m_M; j++)
		{
			float sum = 0.0f;//整型和浮点型的存储结构不同,所以初始化不用float sum = 0
			for (unsigned int k = 0; k < A.m_M; k++)
			{
				//sum += A.getElement(i, k) * B.getElement(k, j);
				sum += A.m_Data[i * A.m_M + k] * B.m_Data[k * B.m_M + j];
			}
			C.m_Data[i * A.m_M + j] = sum;
		
		}
	}
	return C;

}

float& Matrix::element(unsigned int i, unsigned int j)
{
	return m_Data[(i - 1) * m_M + j - 1];

}
float& Matrix::operator()(unsigned int i, unsigned int j)
{
	return m_Data[(i - 1) * m_M + j - 1];

}

Matrix operator*(const Matrix& A, const Matrix& B)
{
	return Matrix::mulptiple1(A, B);
}

对改进版2可以继续改进的方向:
1.改进版2的矩阵乘法是很慢的,相比matlab差了至少1w倍,我们可以采取以下方式提速,cpu的并行指令,gpu的并行计算,但这些技术都太深入了。所以这次只做一个简单的优化,叫做缓存优化。缓存优化的原理是cpu访问存在缓存机制,如有一个100个元素的数组a,当访问到第a[50]的时候,cpu会预先读出50以后的若干个元素放在缓存中。所以读数组时,如果下标是连续的,跳跃的范围很小,就可以读缓存进而会提速。因为改进版2的矩阵乘法有三层for循环,版本1的k在最内层,版本2的i在最内层,版本3的j在最内层,用版本3时就可以实现缓存优化,对程序效率有所提高。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值