c++实验代码及学习笔记(三)
你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。
今天我将试图用沙雕文风解释二维数组的知识;D
首先我们来看一下问题
FBIwarning:建议在阅读答案前,独立思考,先自行尝试,遇到问题再继续阅读。
怎么样!看起来就很难对吧!实际上!它确实不简单(流下了不学无术的泪水)
特别是,大家都知道作为程序媛每个月总有那么几天很暴躁(乱入的某皮:也就那么二三十天吧)。然后暴躁girl写这道题的时候就被debug折磨得炸毛了,头也快被薅秃了(震惊!程序媛秃头竟为哪般)……所以此题适合心平气和时来做。
好了,进入正题
这道题涉及的知识比较复杂,关于接口设计简直是一门艺术;而数组的内存存储则需要数据结构的知识(流泪);另外矩阵乘法还需要线性代数的基础知识(再一次流泪);最后是本文的关键,指针。
如果你恨他,请让他用指针,因为足够折磨;如果你爱他,也请让他用指针,因为一旦学会了就会爱上它。
——鲁·我没说过·迅
由于知识庞杂,我们还是看题说话。
题目:预设一M*N的矩阵A:
预设
本次实验我延续了上次模块化的思想,依旧使用头文件、源文件的形式,看起来或许更为复杂,但是普适性更强。
数据类型:float
//main.cpp
#include
#include "matrix.h" //本次自定义头文件“matrix.h”
#include
using namespace std;
int main()
{
float arr[][2] = { {1,2},{3,4},{5,6} };
...
return 0;
}
我将分为三个部分阐述:
没错,头号任务是在matrix.cpp中定义函数。
一看到这个题目,宝宝们是不是就想到<传入数组参数、行列数-遍历数组计算均值-返回均值> 呢? 没错,思路是正确的。但是用什么样的函数类型呢?
二狗:数组是float型的,返回的均值也应该是float的……那么我函数也float吧!
老师:你要想清楚,矩阵每行均值可不单单是一个值哇!
二狗:糟了,要返回数组!那就……float* 指针型数组?
老师:……指针型数组不是不可以啦,就是用到动态分配malloc,主函数里要free()释放内存,很多人都会忘哦,所以不推荐。(此处动态分配是真的不推荐,二狗以身试法惨遭吐血级bug)
老师:所以呢,我们推荐void型!想不到吧!(二狗:后面这句就不用说了喂!)
之所以是void,是因为我们可以在常量区声明一个数组p,然后传入这个一维数组,直接对其操作,等函数运行完毕局部变量over之后,常量区的数组内存地址还在哦。(在上篇结尾提到了!大家有没有好好阅读呢~)
确定函数类型后,我们来观察一下参数吧!
第一个是预设的二维数组arr,我们如何传入一个二维数组呢?
不要理所当然认为是**arr,可以手动试一试。
代码引用自文章:c++ – 二维数组参数传递
#include
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<
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 ]的我们一定要写成该元素在一维数组的位置。
另外还有计算二维数组的行列数方法,大家可以去查,我会在文末展示。
这道题跟第一题区别最明显在于传入二维数组,返回也返回二维数组(上一题返回一维数组)。
二狗:我在网上查了,可以用
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
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]
运行成功!
(当时还没有搞明白这个)屡战屡败屡败屡战的二狗选择了对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;
}
}
这要求我们对矩阵乘法熟悉。这里默认大家都学过线性代数了。
这道题其实很简单,有一定思维量,跟之前比多了个参数而已。当然这个参数也是个一维数组啦。
同样不需要返回值。
代码示例
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] << " " ;
}
}
为方便阅读,将代码放在一个文件内
#include
//#include "matrix.h"
#include
#include
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;
}
这是皮皮教给二狗的,用来暂停一闪而过的黑窗口。跟system(“pause”)的作用差不多。
那么,为什么不用system(“pause”)呢?
因为
皮皮忘了怎么写了。
但是,二狗后来查了查,发现此中果然大有门道呀!
参考文章:C++第1天:在C和C++里,要尽量避免使用 system(“pause”)
CSDN论坛 > 拜托不要再用system(“pause”)和void main了
后面这个论坛讨论价值还是很高滴,推荐大家点进去看看呀!
将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值, 块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向S的指针。
需要的头文件
在C中
在C++中
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体质了,以后再用指针的时候,要更加小心呀~
谢谢大家!(鞠躬下台)