数组作为参数传参以及二维数组的动态创建
一 普通一维数组的参数传递
一维数组的创建以及参数传递比较简单,小编在这就直接代码演示,不做过多描述
#include <iostream>
#include <cstring>
using namespace std;
//或者是int demo(int arr[], int n) {}
//但若是二维或多维数组传参,没这么简单
int demo(int* arr, int n) {
printf("demo函数中sizeof(arr) = %d\n", sizeof(arr));
int ans = 0;
for (int i = 0; i < n; i++) {
ans += arr[i];
}
return ans;
}
int main() {
int n;
scanf("%d", &n);
int arr[n];
memset(arr, 0, sizeof(arr)); //将数组初始化为0
printf("main函数中sizeof(arr) = %d\n", sizeof(arr)); //求数组所占的字节数
for (int i = 0; i < n; i++) scanf("%d", &arr[i]); //输入数组元素
printf("the sum of array = %d\n", demo(arr, n)); //求解数组元素总和
return 0;
}
对应结果展示如下:
唯一需要注意的是
-
main函数和demo函数中的sizeof(arr)不一样。
这涉及到数组传参部分,其实不论demo函数的函数头部是demo(int *arr, int n)还是demo(int arr[], int n),本质上传递的都是数组的首地址,也就元素a[0]的地址,因此站在demo函数的角度来看,它得到的只是一个指针,而在64操作系统中一个指针所占的字节数为8,因此这就是为什么在demo函数中sizeof(arr) == 8 !(从侧面来看,这就是为什么数组作为参数进行传参时需要额外传递数组的大小,否则的话,可以sizeof(arr)/ sizeof(int)直接获取数组的长度)
那main函数中呢?sizeof(arr)传进去的不也是一个指针吗?其实这句话是错的!虽然有时我们把数组名称理解为地址,但两者并不等价, 这里的arr应该理解为数组名。
我们的编译器会利用数组名记录数组的相关属性,其中一个属性就是数组包含元素的个数,那有的小伙伴会问:我们应该什么时候把它理解为地址,又什么时候把它看成数组名呢?
若表达式中存在数组名,编译会产生一个指针常量,该指针常量恒指向数组首地址,因此我们在表达式中可以把它理解为指针。但有两种情况除外:
(1) sizeof(数组名); 返回数组对应的字节数,而不是对应指针常量所占的字节数
(2) &数组名; 返回指向数组的指针(数组指针)而不是指向指针常量的指针(二维指针) (这涉及到数组指针的概念,注意区分指针数组以及数组指针,再次强调这里大家有必要追根溯源) -
memset()函数初始化数组
memset()函数初始化数组时,是按照字节进行初始化,因此对于整型数组而言,我们memset()初始化,只可以初始化为0或者是-1(int 占4个字节并且数字在计算机内部都是用补码进行表示的,对0而言每一个字节都是0,所以整体上看自然也是0,-1对应的二进制补码每一个字节每一个比特都是1,因此整体上来说自然是 -1)
其实不仅仅是memset()函数,mem*()一族的函数都是对于单个字节进行操作:比如memcmp()和memcpy()函数等等。
二 二维数组的参数传递
二维数组作为参数进行传递,为了便于描述我们先进行错误演示,代码如下:
#include <iostream>
#include <cstring>
using namespace std;
int demo(int arr[][], int n, int m) {
int sum = 0;
for (i : n) {
for (j : m) sum += arr[i][j];
}
return sum;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
int arr[n][m];
memset(arr, 0 ,sizeof(arr));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) scanf("%d", &arr[i][j]);
}
printf("the sum of array = %d\n", demo(arr, n, m));
return 0;
}
此时代码报错,图片如下:
错误翻译过来大致意思就是:数组除了第一维度的大小可以省略,剩下的维度均不可以省略。
但很显然这不现实,因为数组维度我们预先无法知晓,因此我们只能另想办法,有的小伙伴会说,二维数组数组名不是可以理解为二维指针吗?,我们可以用二维指针来进行,这样就可以躲开维度了!
但是显然还是不行,在这里小编就不再粘贴代码了(冗余太多,仅粘贴错误显示)
正确代码展示如下:
#include <iostream>
#include <cstring>
using namespace std;
//不可以使用demo(int a[][], int n, int m) {}
//若想使用则除了第一维大小可以省略,剩下的不可以省略
//但此处数组维度预先不可知,因此此处只好强制转换为一维指针
int demo(int *a, int n, int m) {
int sum = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) sum += *((a + i * m) + j);
}
return sum;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
int arr[n][m];
memset(arr, 0, sizeof(arr));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) scanf("%d", &arr[i][j]);
}
printf("the sum of array = %d\n", demo((int *)arr, n, m));
return 0;
}
总之:当二维数组作为参数进行传参时,由于数组维度的不可知性,我们不可以用int arr[][]或者int **arr的形式,小编的办法是将其强转为一维指针,通过*运算,完成相应操作;若数组维度预先可知,此时可以使用int arr[][]的形式。
三 动态创建二维数组以及传参
在动态创建二维数组时,我们使用malloc(),free()函数或者是new,delete操作符,动态申请内存空间(是在堆中申请空间,并且申请和释放操作应该成对出现)
话不多说,咱们直接上代码:
#include <iostream>
#include <cstring>
using namespace std;
int demo(int **arr, int n, int m) {
int sum = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) sum += arr[i][j];
}
return sum;
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
printf("开始动态创建二维数组\n");
int **arr = NULL;
arr = (int **)malloc(sizeof(int *) * n);
for (int i = 0; i < n; i++) {
arr[i] = (int *)malloc(sizeof(int ) * m);
memset(arr[i], 0, sizeof(arr[i]));
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) scanf("%d", &arr[i][j]);
}
printf("the sum of array = %d\n", demo(arr, n, m));
for (int i = 0; i < n; i++) free(arr[i]);
free(arr);
printf("释放数组空间完毕\n");
return 0;
}
对应输出为:
二维数组数组名我们可以理解为一个二维指针,数组的每一行,我们都有一个行指针,每一行对应m个元素。细心的小伙伴会发现,采用动态创建的二维数组,demo()函数中可以直接使用arr[][],而不需要使用*运算符,此外动态创建的二维数组(在堆中申请空间)相比较于普通创建的二维数组(在栈中申请内存空间)可以开的更大而不用担心出现segment fault段错误,这也是动态创建的另一个优势。
最后想要提醒大家的一点是,使用完数组后,要free()释放申请的内存空间。
四 题外话
加油,路漫漫其修远兮,吾将上下而求索!