C 语言中的数组(补充)

本文是对之前文章的一个补充。

首先我们知道,数组一般会存在三个要素:

  • 起始位置
  • 单个元素的字节长
  • 范围

在之前文章中提到的内容这里就不再说明,直接说明补充内容。

一维数组

访问方式

数组名作整体访问

用数组名作整体访问一般出现在两种情况:

  • 求数组大小
  • 取地址

也就是说在上边两种情况中,数组名才能代表整体。

#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);
  1. 这样的形式传递的实际上是两个 int 值
  2. 因此在上边三个 func 中的 sizeof(a) 的值都是 4(一个指针的大小)
  3. 此时函数头中 arr[5] 中的 5 已经失去了含义,而只保留了 []
  4. arr[] = *arr,因此 [] 就转化为了地址
  5. 也就是说,[] 除了起到提示参数是个数组之外,其它都跟指针的作用是一样的

返回堆中的一维数组

我么知道在进行函数调用的时候,在函数中定义的变量需要经过入栈,而函数调用结束的时候,函数中的变量会进行出栈,而你不能够将已经出栈的变量进行返回。换句话说就是栈上的变量是不能够作为返回值的。

而堆上的变量在定义的时候需要主动申请内存,在使用完毕的时候需要主动释放内存,因此在释放内存之前是可以作为返回值进行返回的。以下是两种返回堆中数组的方式。

返回值返回

#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)+ja[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 的使用方式:

  1. 先用已有类型定义一个变量

  2. 在定义语句类型前加 typedef

  3. 将变量名换成自定义名称

  4. 最后一定有分号;

可以定义数组指针的别名:

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] 进行了类型转换,从而能够用数组指针的形式访问一维数组,而一维数组本身并没有发生改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值