c/c++ 二维数组指针参数传递 矩阵计算实例

before|正文之前:

c++实验代码及学习笔记(三)

你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。

今天我将试图用沙雕文风解释二维数组的知识;D

1问题

首先我们来看一下问题
实验目的
FBIwarning:建议在阅读答案前,独立思考,先自行尝试,遇到问题再继续阅读。

怎么样!看起来就很难对吧!实际上!它确实不简单(流下了不学无术的泪水)
特别是,大家都知道作为程序媛每个月总有那么几天很暴躁(乱入的某皮:也就那么二三十天吧)。然后暴躁girl写这道题的时候就被debug折磨得炸毛了,头也快被薅秃了(震惊!程序媛秃头竟为哪般)……所以此题适合心平气和时来做。

好了,进入正题


2思路

这道题涉及的知识比较复杂,关于接口设计简直是一门艺术;而数组的内存存储则需要数据结构的知识(流泪);另外矩阵乘法还需要线性代数的基础知识(再一次流泪);最后是本文的关键,指针。

如果你恨他,请让他用指针,因为足够折磨;如果你爱他,也请让他用指针,因为一旦学会了就会爱上它。
——鲁·我没说过·迅

由于知识庞杂,我们还是看题说话。

题目详解

题目:预设一M*N的矩阵A:

  1. 计算每行均值
  2. 计算每列均值
  3. 移除矩阵的某一行
  4. 移除矩阵的某一列
  5. 输入一个N维向量X,计算A与X的乘积,结果为一M维向量。

预设

本次实验我延续了上次模块化的思想,依旧使用头文件、源文件的形式,看起来或许更为复杂,但是普适性更强。
数据类型:float

//main.cpp
#include <iostream>
#include "matrix.h"	//本次自定义头文件“matrix.h”
#include <stdlib.h>
using namespace std;

int main()
{
    
    
	float arr[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	...
	return 0;
}

我将分为三个部分阐述:

1、计算每行/列均值

函数类型

没错,头号任务是在matrix.cpp中定义函数。
一看到这个题目,宝宝们是不是就想到<传入数组参数、行列数-遍历数组计算均值-返回均值> 呢? 没错,思路是正确的。但是用什么样的函数类型呢?
二狗:数组是float型的,返回的均值也应该是float的……那么我函数也float吧!
老师:你要想清楚,矩阵每行均值可不单单是一个值哇!
二狗:糟了,要返回数组!那就……float* 指针型数组?
老师:……指针型数组不是不可以啦,就是用到动态分配malloc,主函数里要free()释放内存,很多人都会忘哦,所以不推荐。(此处动态分配是真的不推荐,二狗以身试法惨遭吐血级bug)
老师:所以呢,我们推荐void型!想不到吧!(二狗:后面这句就不用说了喂!
之所以是void,是因为我们可以在常量区声明一个数组p,然后传入这个一维数组,直接对其操作,等函数运行完毕局部变量over之后,常量区的数组内存地址还在哦。(在上篇结尾提到了!大家有没有好好阅读呢~)

参数:传入二维数组

确定函数类型后,我们来观察一下参数吧!
第一个是预设的二维数组arr,我们如何传入一个二维数组呢?
不要理所当然认为是**arr,可以手动试一试。
代码引用自文章:c++ – 二维数组参数传递

#include <iostream>
using namespace std;

/*传二维数组*/

//第1种方式:传数组,第二维必须标明
/*void display(int arr[][4])*/ 
void display1(int arr[][4],const int irows)
{
    
    
    for (int i=0;i<irows;++i)
    {
    
    
        for(int j=0;j<4;++j)
        {
    
    
            cout<<arr[i][j]<<" ";     //可以采用parr[i][j]
        }
        cout<<endl;
    }
    cout<<endl;
}

//第2种方式:一重指针,传数组指针,第二维必须标明
/*void display(int (*parr)[4])*/
void display2(int (*parr)[4],const int irows)
{
    
    
    for (int i=0;i<irows;++i)
    {
    
    
        for(int j=0;j<4;++j)
        {
    
    
            cout<<parr[i][j]<<" ";    //可以采用parr[i][j]
        }
        cout<<endl;
    }
    cout<<endl;
}
//注意:parr[i]等价于*(parr+i),一维数组和二维数组都适用

//第3种方式:传指针,不管是几维数组都把他看成是指针
/*void display3(int *arr)*/
void display3(int *arr,const int irows,const int icols)
{
    
    
    for(int i=0;i<irows;++i)
    {
    
    
        for(int j=0;j<icols;++j)
        {
    
    
            cout<<*(arr+i*icols+j)<<" ";   //注意:(arr+i*icols+j),不是(arr+i*irows+j)
        }
        cout<<endl;
    }
    cout<<endl;
}

/***************************************************************************/
/*
//第2种方式:一重指针,传数组指针void display(int (*parr)[4])
//缺陷:需要指出第二维大小
typedef int parr[4];
void display(parr *p)
{
    int *q=*p;        //q指向arr的首元素
    cout<<*q<<endl;   //输出0
}

typedef int (*parr1)[4];
void display1(parr1 p)
{
    cout<<(*p)[1]<<endl;  //输出1
    cout<<*p[1]<<endl;    //输出4,[]运算符优先级高
}
//第3种方式:
void display2(int **p)
{
    cout<<*p<<endl;           //输出0
    cout<<*((int*)p+1+1)<<endl; //输出2
}
*/

int main()
{
    
    
    int arr[][4]={
    
    0,1,2,3,4,5,6,7,8,9,10,11};
    int irows=3;
    int icols=4;
    display1(arr,irows);
    display2(arr,irows);

    //注意(int*)强制转换.个人理解:相当于将a拉成了一维数组处理。
    display3((int*)arr,irows,icols);
    return 0;
}

可以看出,无论第一种还是第二种,都需要指明二维数组的列数。所以为了对任意M*N数组适用(普适性:我的函数不止可以用于一个文件、一类数组),我们只能选用第三种。
重要,请仔细阅读哦↓

第三种传参形式是(float*)arr,定义是*a
简单解释就是,传入一维数组指针是可行的,其实二维数组可以视为一维数组套一维数组,我们把二维数组拍扁,就可以假扮一维数组混入函数啦!
例:
arr[3][2]:{ {1,2},{3,4},{5,6}}
拍扁:
arr’[6]:{1,2,3,4,5,6}

那么我们可以写出函数了!

代码实现
void avgRows(float *a, int rows, int cols, float p[])
{
    
    	
	for (int i = 0; i < rows; i++)
	{
    
    
		p[i] = 0;
		for (int j = 0; j < cols; j++)
		{
    
    
			p[i] = p[i] + a[ i * cols + j];	//这里比较难理解,不是a[i][j],
											//它变为了一维数组,应该是a[i乘以列数+j]
		}
	}
	for (int i = 0; i < rows; i++) {
    
    
		p[i] = p[i] / cols;
	}
}

同理,我们可以写出求列的均值函数

void avgCols(float *a, int rows, int cols, float p[])
{
    
    
	for (int i = 0; i < cols; i++)//列数
	{
    
    
		p[i] = 0;
		for (int j = 0; j < rows; j++)//行数
		{
    
    
			p[i] = p[i] + a[j*cols + i];//等效于二维的a[j][i]
		}
	}

	for (int i = 0; i < cols; i++)
	{
    
    
		p[i] = p[i] / rows;
	}
}
int main()
{
    
    
	float arr[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	float arr1[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	float arr2[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	int rows = sizeof(arr) / sizeof(arr[0]);
	int cols = sizeof(arr[0]) / sizeof(arr[0][0]);
	float p[100];


	avgRows((float*)arr, rows, cols, p);
	cout << "每行均值为:" << endl;
	print(p, rows);		//源文件写了个打印函数
	cout << endl;

	avgCols((float*)arr, rows, cols, p);
	cout << "每列均值为:" << endl;
	print(p, cols);
	cout << endl;

	return 0;
}

TIPS
这里始终要注意的是,由于我们传入的二维数组已经被拍扁了,所以习惯a[ i ][ j ]的我们一定要写成该元素在一维数组的位置。
另外还有计算二维数组的行列数方法,大家可以去查,我会在文末展示。

2、移除矩阵某一行/列

这道题跟第一题区别最明显在于传入二维数组,返回也返回二维数组(上一题返回一维数组)。
二狗:我在网上查了,可以用

float **

函数类型!然后我们再float一个*p指针!然后动态分配!最后返回p

//此代码有bug,仅供参考
float **remove(float *a, int rows, int cols, int value)
{
    
    
	float **p;
	p = (float**)malloc(100 * sizeof(float*));
	value = value - 1;
	if (value > rows)
		return 0;
	for (int i = 0; i < rows; i++)
	{
    
    
		p[i] = (float*)malloc(100 * sizeof(float));
		
		memset(p, 0, 100 * sizeof(float));

		if (i < value)
		{
    
    
			for (int j = 0; j < cols; j++)
				p[i][j] = a[i*cols + j];	//调试,每到这就报错!
		}
		else if(i > value){
    
    
			for (int j = 0; j < cols; j++)
				p[i-1][j] = a[i*cols + j];
		}
	}
	return p;
}

看上去无懈可击,后面也释放了内存,为什么会报错呢?
此时查也查不到标准代码的二狗陷入了深深的绝望,她做这个题已经一天了,还没有解决,她甚至开始怀疑人生……

乱入的皮皮出现!
皮皮:我们用断点测试吧!啊,你这里数组a里面都是0了呀!难道没有传进来?
噼里啪啦在函数开头令数组a输出。
输出正常。
显然是malloc动态分配出了问题呀。应该是给p指针指针君分配了本来属于arr的地址。
真是一个悲伤的故事呢。

(电闪雷鸣多云转晴)二狗:呜呜呜皮皮你真是太牛批了!打死不用动态分配了!

二狗:那么我们沿用第一题的思路,也void,这回传进去两个拍扁的二维数组
皮皮:听上去可行,试试吧。这回我用第二种传二维数组的方法试试。

代码示例:

//matrix.cpp
#include <string.h>

void removeRows(float *a, int rows, int cols, int value,float *p)
{
    
    
	value = value - 1;
	if (value > rows)
		return ;
	memset(p,0,10);
	for (int i = 0; i < rows; i++)
	{
    
    
		if (i < value)
		{
    
    
			for (int j = 0; j < cols; j++)
				p[i*cols + j] = a[i*cols + j];
		}
		else if(i > value){
    
    
			for (int j = 0; j < cols; j++)
				p[(i-1)*cols + j] = a[i*cols + j];
		}
	}
}

//main.cpp
int main()
{
    
    
   ...
   removeRows((float*)arr, rows, cols, x,(float*)p);
   cout << "删去第" << x << "行后的数组:" << endl;
   for (int i = 0; i < rows - 1; i++)
   {
    
    
   	for (int j = 0; j < cols; j++)
   	{
    
    
   		cout << p[i][j] << " " ;
   	}
   	cout << endl;
   }
   ...
}

(#@%¥#%)
二狗(哭喊):皮皮!为什么我的又报错了!是不是我思路有什么问题呀55555网上也查不到5555
在这里插入图片描述
皮皮:emmmm我用arr[ ][2]的方法没毛病呀
二狗:那我只能对arr本身操作了……
(后来想通了的)皮皮:问题出在主函数的【输出】上
皮皮说法
原来,二维数组拍扁了虽然还能勉强回去,但是形状已经变了!!
本该排在a[1][0] a[1][1],被拍扁了放在一排上,也就是a’[0][2] a’[0][3]
真的太神奇了!

所以,在输出的地方,我们把p[i][j]改为p[0][i*(rows-1)+j]
皮皮大讲堂
运行成功!
运行1成功
(当时还没有搞明白这个)屡战屡败屡败屡战的二狗选择了对arr本身进行操作……

(¥%…¥%&)

终于成功。虽然这种方法会改变原来arr数组,但是最简单。
(至于arr[i][j]为什么能行?这,就是玄学了(被打飞))

void removeRows(float *a, int rows, int cols, int value)
{
    
    
	value = value - 1;
	if (value > rows)
		return ;
	for (int i = 0; i < rows; i++)
	{
    
    
		if (i < value)
		{
    
    
			for (int j = 0; j < cols; j++)
				a[i*cols + j] = a[i*cols + j];
		}
		else if(i > value){
    
    
			for (int j = 0; j < cols; j++)
				a[(i-1)*cols + j] = a[i*cols + j];
		}
	}
}

同理我们可以写出移除列的函数

void removeCols(float *a, int rows, int cols, int value)
{
    
    
	value = value - 1;
	if (value > cols)
		return;
	for (int i = 0; i < cols; i++)
	{
    
    
		if (i < value)
		{
    
    
			for (int j = 0; j < rows; j++)
			{
    
    
				a[j*cols + i] = a[j*cols + i];
			}
		}
		else if (i > value)
		{
    
    
			for (int j = 0; j < rows; j++)
			{
    
    
				a[j*cols + (i-1)] = a[j*cols + i];
			}
		}
	}
}

//main.cpp
int main()
{
    
    
	...
	float arr1[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	float arr2[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	int x;
	cout << "请输入想删去的行数" << endl;
	cin >> x;

	removeRows((float*)arr1, rows, cols, x);
	cout << "删去第" << x << "行后的数组:" << endl;
	for (int i = 0; i < rows - 1; i++)
	{
    
    
		for (int j = 0; j < cols; j++)
		{
    
    
			cout << arr1[i][j] << " " ;
		}
		cout << endl;
	}
	
	int y;
	cout << "请输入想删去的列数" << endl;
	cin >> y;

	removeCols((float*)arr2, rows, cols, y);
	cout << "删去第" << y << "列后的数组:" << endl;
	for (int i = 0; i < rows; i++)
	{
    
    
		for (int j = 0; j < cols-1; j++)
		{
    
    
			cout << arr2[i][j] << " ";
		}
		cout << endl;
	}
}

3、矩阵与向量相乘

这要求我们对矩阵乘法熟悉。这里默认大家都学过线性代数了。
这道题其实很简单,有一定思维量,跟之前比多了个参数而已。当然这个参数也是个一维数组啦。
同样不需要返回值。

代码示例

void multiply(float *a, float p1[], int rows, int cols, float p[])
{
    
    
	for (int i = 0; i < rows; i++)
	{
    
    
		p[i] = 0;
		for(int j=0;j<1;j++)
			for (int k = 0; k < cols; k++)
			{
    
    
				p[i] = p[i] + a[i*cols + k] * p1[i];
			}
	}
}

这里的p1[ ]是新输入的N维向量。p呢就是我们要输出的乘积M维向量啦。

//main.cpp
int main()
{
    
    
	...
	float p1[100];
	cout << "请输入一个" << cols << "维向量:" << endl;
	for (int i = 0; i < cols; i++) {
    
    
		cin >> p1[i];
	}
	multiply((float*)arr, p1, rows, cols, p);
	cout << "相乘结果是:" << endl;
	print(p,cols);
	...
}

最后,为了方便起见,我在源文件中写了一个print函数,用来打印结果。

//matrix.cpp
void print(float *a,int n)
{
    
    
	for (int i = 0; i < n; i++)
	{
    
    
		cout << a[i] << " " ;
	}
}

3代码实现

为方便阅读,将代码放在一个文件内

#include <iostream>
//#include "matrix.h"
#include <stdlib.h>
#include <string.h>
using namespace std;

void avgRows(float *a, int rows, int cols, float p[])
{
    
    	
	for (int i = 0; i < rows; i++)
	{
    
    
		p[i] = 0;
		for (int j = 0; j < cols; j++)
		{
    
    
			p[i] = p[i] + a[ i * cols + j];
		}
	}
	for (int i = 0; i < rows; i++) {
    
    
		p[i] = p[i] / cols;
	}
	
}

void avgCols(float *a, int rows, int cols, float p[])
{
    
    
	for (int i = 0; i < cols; i++)//列数
	{
    
    
		p[i] = 0;
		for (int j = 0; j < rows; j++)//行数
		{
    
    
			p[i] = p[i] + a[j*cols + i];//a[j][i]
		}
	}

	for (int i = 0; i < cols; i++)
	{
    
    
		p[i] = p[i] / rows;
	}
}

void removeRows(float *a, int rows, int cols, int value)
{
    
    
	value = value - 1;
	if (value > rows)
		return ;
	for (int i = 0; i < rows; i++)
	{
    
    
		if (i < value)
		{
    
    
			for (int j = 0; j < cols; j++)
				a[i*cols + j] = a[i*cols + j];
		}
		else if(i > value){
    
    
			for (int j = 0; j < cols; j++)
				a[(i-1)*cols + j] = a[i*cols + j];
		}
	}
}

void removeCols(float *a, int rows, int cols, int value)
{
    
    
	value = value - 1;
	if (value > cols)
		return;
	for (int i = 0; i < cols; i++)
	{
    
    
		if (i < value)
		{
    
    
			for (int j = 0; j < rows; j++)
			{
    
    
				a[j*cols + i] = a[j*cols + i];
			}
		}
		else if (i > value)
		{
    
    
			for (int j = 0; j < rows; j++)
			{
    
    
				a[j*cols + (i-1)] = a[j*cols + i];
			}
		}
	}
}

void multiply(float *a, float p1[], int rows, int cols, float p[])
{
    
    
	for (int i = 0; i < rows; i++)
	{
    
    
		p[i] = 0;
		for(int j=0;j<1;j++)
			for (int k = 0; k < cols; k++)
			{
    
    
				p[i] = p[i] + a[i*cols + k] * p1[i];
			}
	}
}

void print(float *a,int n)
{
    
    
	for (int i = 0; i < n; i++)
	{
    
    
		cout << a[i] << " " ;
	}
}
int main()
{
    
    
	float arr[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	float arr1[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	float arr2[][2] = {
    
     {
    
    1,2},{
    
    3,4},{
    
    5,6} };
	int rows = sizeof(arr) / sizeof(arr[0]);
	int cols = sizeof(arr[0]) / sizeof(arr[0][0]);
	float p[100];


	avgRows((float*)arr, rows, cols, p);
	cout << "每行均值为:" << endl;
	print(p, rows);
	cout << endl;


	avgCols((float*)arr, rows, cols, p);
	cout << "每列均值为:" << endl;
	print(p, cols);
	cout << endl;


	int x;
	cout << "请输入想删去的行数" << endl;
	cin >> x;

	removeRows((float*)arr1, rows, cols, x);
	cout << "删去第" << x << "行后的数组:" << endl;
	for (int i = 0; i < rows - 1; i++)
	{
    
    
		for (int j = 0; j < cols; j++)
		{
    
    
			cout << arr1[i][j] << " " ;
		}
		cout << endl;
	}
	
	int y;
	cout << "请输入想删去的列数" << endl;
	cin >> y;

	removeCols((float*)arr2, rows, cols, y);
	cout << "删去第" << y << "列后的数组:" << endl;
	for (int i = 0; i < rows; i++)
	{
    
    
		for (int j = 0; j < cols-1; j++)
		{
    
    
			cout << arr2[i][j] << " ";
		}
		cout << endl;
	}

	
	float p1[100];
	cout << "请输入一个" << cols << "维向量:" << endl;
	for (int i = 0; i < cols; i++) {
    
    
		cin >> p1[i];
	}
	multiply((float*)arr, p1, rows, cols, p);
	cout << "相乘结果是:" << endl;
	print(p,cols);

	getchar();//用于显示最终结果
	getchar();

	return 0;
}

4最终效果

最终效果

5补充知识

getchar( )

这是皮皮教给二狗的,用来暂停一闪而过的黑窗口。跟system(“pause”)的作用差不多。
那么,为什么不用system(“pause”)呢?
因为
皮皮忘了怎么写了。
但是,二狗后来查了查,发现此中果然大有门道呀!
参考文章:C++第1天:在C和C++里,要尽量避免使用 system(“pause”)
CSDN论坛 > 拜托不要再用system(“pause”)和void main了
后面这个论坛讨论价值还是很高滴,推荐大家点进去看看呀!

memset(p,0,size)

将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值, 块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向S的指针。
需要的头文件
在C中 <string.h>
在C++中 <cstring`>
memset() 函数常用于内存空间初始化。如:

char str[100];
memset(str,0,100);

计算矩阵行列数

对于type array[A][B];形式的二维数组,可以通过计算sizeof获取行列数。
sizeof(array[0][0])为一个元素占用的空间,
sizeof(array[0])为一行元素占用的空间,
sizeof(array)为整个数组占用的空间,
于是:
行数 = sizeof(array)/sizeof(array[0]);
列数 = sizeof(array[0])/sizeof(array[0][0]);

关于指针的进一步研究

推荐文章
深度好文:c++之指针作为函数参数传递的问题
详解C++中指针()、取地址(&)、解引用()与引用(&)的区别 (完整代码)

二狗:好了,总算结束了,这期做的可谓是身心俱疲啊!可以说深切认识到数组指针的bug体质了,以后再用指针的时候,要更加小心呀~
谢谢大家!(鞠躬下台)

猜你喜欢

转载自blog.csdn.net/duoluka/article/details/88894863