本文是对之前文章的一个补充。
首先我们知道,数组一般会存在三个要素:
- 起始位置
- 单个元素的字节长
- 范围
在之前文章中提到的内容这里就不再说明,直接说明补充内容。
一维数组
访问方式
数组名作整体访问
用数组名作整体访问一般出现在两种情况:
- 求数组大小
- 取地址
也就是说在上边两种情况中,数组名才能代表整体。
#include <stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};
printf("sizeof(arr) = %d\n",sizeof(arr)); // 求数组大小
printf("arr = %p\n",arr);
printf("arr+1 = %p\n",arr+1);
printf("&arr = %p\n",&arr); // 取地址
printf("&arr+1 = %p\n",&arr+1);
int *p = arr;
printf("sizeof(p) = %d\n",sizeof(p));
printf("p = %p\n",p);
printf("p+1 = %p\n",p+1);
int (*pa)[10] = &arr;
printf("sizeof(pa) = %d\n",sizeof(pa));
printf("pa = %p\n",pa);
printf("pa+1 = %p\n",pa+1);
return 0;
}
结果为:
sizeof(arr) = 20
arr = 0060FE94
arr+1 = 0060FE98
&arr = 0060FE94
&arr+1 = 0060FEA8
sizeof(p) = 4
p = 0060FE94
p+1 = 0060FE98
sizeof(pa) = 4
pa = 0060FE94
pa+1 = 0060FEBC
从上面的结果可以看出:
- 利用 sizeof(arr) 求数组大小时,arr 代表的是数组整体
- 使用 arr+1 实际是偏移了单个数组元素的长度,此种情况可以认为是 &arr[1]
- 使用 &arr+1 实际上是偏移了整个数组的长度
- 使用 *p=arr 时,p 实际上就等于 arr
- 使用 (*pa)[10]=&arr 时,pa+1 实际上偏移的是 10 个数组元素的长度
数组名作为起始地址访问成员
成员访问主要是通过 [] 实现的,需要注意下边的关系:
var[i]=*(var+i)=i[var]
数组作参数传递
之前提到过数组包含有三个元素,起始地址,单个元素的字节长,范围。因此数组作为参数进行传递的时候也需要传递这三部分元素。
#include <stdio.h>
void func1(int a[5],int n)
{
printf("func1:sizeof(a) = %d\n",sizeof(a));
for(int i = 0;i < n; i++)
printf("%d\n",a[i]);
}
void func2(int a[],int n)
{
printf("func2:sizeof(a) = %d\n",sizeof(a));
for(int i = 0;i < n; i++)
printf("%d\n",a[i]);
}
void func3(int *a,int n)
{
printf("func3:sizeof(a) = %d\n",sizeof(a));
for(int i = 0;i < n; i++)
printf("%d\n",a[i]);
}
int main()
{
int arr[5] = {1,2,3,4,5};
func1(arr,5);
func2(arr,5);
func3(arr,5);
return 0;
}
结果为:
func1:sizeof(a) = 4
1
2
3
4
5
func2:sizeof(a) = 4
1
2
3
4
5
func3:sizeof(a) = 4
1
2
3
4
5
在进行参数传递的时候,我们不能够使用:
func(arr[5],5);
- 这样的形式传递的实际上是两个 int 值
- 因此在上边三个 func 中的 sizeof(a) 的值都是 4(一个指针的大小)
- 此时函数头中 arr[5] 中的 5 已经失去了含义,而只保留了 []
- arr[] = *arr,因此 [] 就转化为了地址
- 也就是说,[] 除了起到提示参数是个数组之外,其它都跟指针的作用是一样的
返回堆中的一维数组
我么知道在进行函数调用的时候,在函数中定义的变量需要经过入栈,而函数调用结束的时候,函数中的变量会进行出栈,而你不能够将已经出栈的变量进行返回。换句话说就是栈上的变量是不能够作为返回值的。
而堆上的变量在定义的时候需要主动申请内存,在使用完毕的时候需要主动释放内存,因此在释放内存之前是可以作为返回值进行返回的。以下是两种返回堆中数组的方式。
返回值返回
#include <stdio.h>
#include <stdlib.h>
int *memapp(int n)
{
int *p = (int *)malloc(sizeof(int) * n);
return p;
}
int main()
{
int *p,n = 5;
p = memapp(n);
for(int i=0; i<n; i++)
*(p+i) = i;
for(int i=0; i<n; i++)
printf("%d\n",*(p+i));
free(p);
return 0;
}
结果为:
0
1
2
3
4
在上边的程序中,我们能够看懂其中的逻辑,利用函数 memapp 申请内存,然后返回内存的指针,使用之后对内存进行释放。
参数返回
这里同样可以不使用返回值,也就是传递指针,在函数内部完成返回一维数组的动作。
#include <stdio.h>
#include <stdlib.h>
void memapp(int **p, int n)
{
*p = (int *)malloc(sizeof(int) * n);
}
int main()
{
int *pp,n = 5;
memapp(&pp,n);
for(int i=0; i<n; i++)
*(pp+i) = i;
for(int i=0; i<n; i++)
printf("%d\n",*(pp+i));
free(pp);
return 0;
}
结果为:
0
1
2
3
4
在上边的程序中,函数 memapp 中使用的形参为 **p。
如果形参使用 *p,实参是 pp,两者并不存在指向关系。虽然在函数内部能够使 *p 指向申请的内存,但是函数变量在调用完成之后就会出栈,而此时指针变量 p 本身就被销毁了,申请的内存也就找不到了。
如果形参使用 **p,实参为 &pp,此时形参与实参之间存在指向关系。在函数内部能够使 *p 指向申请的内存,间接地使 pp 指向了申请的内存,函数变量在调用完成之后就会出栈,但此时 pp 本身已经有所指向,存不存在已经无所谓了。
二维数组
访问方式
数组名作整体访问
在一维数组中,我们知道使用求数组大小和对数组名取地址的时候,是以数组整体进行访问的。而对于二维数组也是这样的。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("sizeof(arr) = %d\n",sizeof(arr));
printf("arr = %p\n",arr);
printf("arr+1 = %p\n",arr+1);
printf("&arr = %p\n",&arr);
printf("&arr+1 = %p\n",&arr+1);
return 0;
}
结果为:
sizeof(arr) = 36
arr = 0060FE8C
arr+1 = 0060FE98
&arr = 0060FE8C
&arr+1 = 0060FEB0
不过不同的是 arr+1 代表的是第二行的起始地址。
数组名作为起始地址访问成员
与一维数组相同,需要注意的仍然是 [] 的用法,二维数组与一维数组的关系已经在之前提到的文章中有所描述,在此不再赘述,只列出结论:
*(a+i) | a[i] | &a[i][0] | 第 i 行第 0 个元素的地址 |
*(a+i)+j | a[i]+j | &a[i][j] | 第 i 行第 j 个元素的地址 |
*(*(a+i)+j) | *(a[i]+j) | a[i][j] | 第 i 行第 j 个元素 |
线性存储
二维数组虽然在逻辑上是二维的,但是在计算机中仍然是线性存储的。因此,只要不越界,就可以在单层循环中一直进行访问。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
printf("%d\n",arr[i][j]);
printf("******************\n");
for(int i=0;i<9;i++)
printf("%d\n",(*arr)[i]);
return 0;
}
结果为:
1
2
3
4
5
6
7
8
9
******************
1
2
3
4
5
6
7
8
9
作参数传递
二维数组从根本来说只是一个数组数组指针,因此函数形参也应该是数组指针的形式。
#include <stdio.h>
#include <stdlib.h>
void func(int (*p)[3])
{
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
p[i][j] = i*3+j+1;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
printf("%d\n",p[i][j]);
}
int main()
{
int arr[3][3];
func(arr);
return 0;
}
结果为:
1
2
3
4
5
6
7
8
9
函数形参 int (*p)[3] 就是数组指针的形式,每个数组中有三个元素。
数组指针
形式
上边我们使用二维数组做参数传递时,使用到了数组指针,数组指针的形式为:
datatype (*pointer)[num]
- datatype 表示数组指针的元素类型
- pointer 表示该数组指针的起始地址
- num 表示该数组指针的的步长,为 datatype*num
从上边的定义也可以看出,数组指针与普通指针的区别就在于步长,一个是 datatype,另一个是 datatype*num。
类型别名
按照类型别名 typedef 的使用方式:
-
先用已有类型定义一个变量
-
在定义语句类型前加 typedef
-
将变量名换成自定义名称
-
最后一定有分号;
可以定义数组指针的别名:
typedef int (*TYPE)[num];
应用
二维数组做参数传递
在二维数组部分已经做过介绍,是相同的内容。
一维空间的二维访问
我们可以先对比一下一维数组和二维数组的区别:
一维数组 | 二维数组 | |
形式 | int arr[5] | int arr [3][4] |
本质 | 一级指针 | 数组指针 |
一维数组的二维访问关键就在与如何将一级指针转化为数组指针,而同时数组指针也是一种数据类型,因此我们可以使用类型转化来实现:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arr[9] = {1,2,3,4,5,6,7,8,9};
int (*p)[3] = (int (*)[3])arr;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
printf("%d\n",p[i][j]);
return 0;
}
结果为:
1
2
3
4
5
6
7
8
9
上边我们使用 int (*)[3] 进行了类型转换,从而能够用数组指针的形式访问一维数组,而一维数组本身并没有发生改变。