before|正文之前:
c++实验代码及学习笔记(三)
你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。
今天我将试图用沙雕文风解释二维数组的知识;D
1问题
首先我们来看一下问题
FBIwarning:建议在阅读答案前,独立思考,先自行尝试,遇到问题再继续阅读。
怎么样!看起来就很难对吧!实际上!它确实不简单(流下了不学无术的泪水)
特别是,大家都知道作为程序媛每个月总有那么几天很暴躁(乱入的某皮:也就那么二三十天吧)。然后暴躁girl写这道题的时候就被debug折磨得炸毛了,头也快被薅秃了(震惊!程序媛秃头竟为哪般)……所以此题适合心平气和时来做。
好了,进入正题
2思路
这道题涉及的知识比较复杂,关于接口设计简直是一门艺术;而数组的内存存储则需要数据结构的知识(流泪);另外矩阵乘法还需要线性代数的基础知识(再一次流泪);最后是本文的关键,指针。
如果你恨他,请让他用指针,因为足够折磨;如果你爱他,也请让他用指针,因为一旦学会了就会爱上它。
——鲁·我没说过·迅
由于知识庞杂,我们还是看题说话。
题目详解
题目:预设一M*N的矩阵A:
- 计算每行均值
- 计算每列均值
- 移除矩阵的某一行
- 移除矩阵的某一列
- 输入一个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++)