C:数组小结(3)

回顾总结:

前面花了两大篇幅分别说一维和二维数组,始终在围绕着“地址”说数组。再回头想一下:数组在C语言里是一种构造类型,那么C语言是如何构造它的?C语言先在数组定义时候将其元素依序存储在一段内存中,如果没有“构造”的过程,也就没有数组的概念了,因为如果要访问这段连续的内存,只需要知道这段内存的基址、元素的数据类型以及长度,就可以访问、操作任一个元素了,比如:访问第i个元素就可以用*(Base_address+i*sizeof(element type))进行,这是一种直接的地址偏移的访问方式。而通过构造数组,使得我们可以很方便的通过下标方式array_name[i]访问。C语言构造数组的主要工作内容包括就是使得这种下标方式与直接寻址方式去匹配,具体就是构造一些简单便捷的方式描述数组和其元素的地址。

对于一维数组,为了方便对其操作,我们需要描述的地址包括两种:(1)整个数组的首地址:通过&a来描述;(2)数组中元素的地址:通过&a[i]来描述。

对于二维数组,为了方便对其操作,我们需要描述的地址就更多:(1)整个数组的首地址:&a;(2)行数组的首地址:&a[i];(3)数组元素的地址:&a[m][n];

深入了解这些下标及数组名的含义,对理解其和对应指针变量的匹配是非常重要的。

并且在这里,还是想强调一下数组名的含义,我始终在说数组名有两个含义:

(1)第一层含义看上去是句废话:表示这个数组。当对数组名进行取地址运算时候,这句废话就变成金玉良言了,比如数组a,那么&a表示什么?因为&是对a这个整个数组取地址,所以&a表示的就是数组a的首地址(而非数组a的首元素地址)。这里,第一层含义用于:当对数组名取地址运算时。

(2)第二层含义:表示该数组的首元素地址,对于数组int a[],那么当数组名表示一个地址时,其只表示数组首元素地址,即&a[0].这里,第二层含义用于:a本身作为地址进行赋值或加减等运算时。比如x=a;比如(a+i)。而同时a虽然表示地址,但它不是变量,所以不能对a赋值,不能修改。

在准确并且较为深刻理解上述描述之后,就可以很灵活的应用数组。下面就看一下应用较为广泛的数组作为参数的情况。

三、参数传递中的数组

1、一维数组的传参

数组可以作为函数参数进行传递吗?先考虑一个最简单的例子,对于一个整型数组,设计一个函数,打印数组元素。

下面这个例子中的错误,我自己犯过不止一次。

定义好打印子函数,参数使用规范的int a[5]说明是一个数组类型。在调用时候实参使用"a[5]"却报错,error说明为:

'int *' differs in levels of indirection from 'int '。类型不匹配啊,Why?

#include<stdio.h>

void Print_Array(int a[]);

void Print_Array(int a[5])
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d  ",a[i]);
    }
}

int main()
{
    int a[5]={1,2,3,4,5};
    Print_Array(a[5]);   //a[5]实参和形参不匹配

    getch();
    return 0;
}

再试一下实参用“int a[5]"呢?报错就更离奇了~不贴了。

那么对于这样一个函数,到底该怎么样传递实参呢?答案是"a"。

对于我这样的初学者,仍然需要巩固这个印象。对上述函数,如果参数只能通过a来传递,那说明了什么?参数传递无非就是传递数值,a作为数值,表示的是数组首元素的地址;这个函数传递的实质是数组元素首地址,那么函数定义时候的形参"int a[5]"也就是一个假象,其本质形容的也应该是“指向数组元素类型的指针”这个数据类型才对。对于本例,即为int *型。那么推测一下,实参选择&a[0]也应该是正确的,验证也确实正确。

通过上述,可以一定程度上说明“函数无法传递数组参数”(但不是证明,我证明不了=。=),因为本质上传递的是数组首元素地址。了解了“数组传参”背后实质传递的是指针,那么函数定义时候的形参"int a[5]"只能说明一个地址,无法说明数组的长度。所以如果使用"数组的形式"表示参数,参数还可增设数组长度,以加强对数组原型的匹配性:

#include<stdio.h>


void Print_Array(int a[],int n);

void Print_Array(int a[],int n)
{
    int i;
    for(i=0;i<n;i++)
    {
        printf("%d  ",a[i]);
    }
}


int main()
{
    int a[5]={1,2,3,4,5};

    Print_Array(a,5);
    getch();
    return 0;
}

 另外,既然数组传参的本质是传递地址,那其实也可将形参直接设为指针变量,通过指针方式实现。

#include<stdio.h>


void Print_Array(int *a);

void Print_Array(int *a)
{
    int i;
    for(i=0;i<5;i++)
    {
        printf("%d  ",*(a+i));
    }
}


int main()
{
    int a[5]={1,2,3,4,5};

    Print_Array(a);
Print_Array(&a[0]);
getch();
return 0;
}

2、二维数组的传参

通过上述对一维数组传参的了解,可以知道一维数组作为参数时候传递的实质是地址,并且该地址的指向类型就是该一维数组元素的类型。那么:因为二维数组本质也是一维数组,如果二维数组作为参数传递,那么传递的实质是“该等效一维数组的数组元素的指针类型”,有些拗口,多看几遍,并不难理解;接下来,对于二维数组而言,它等效的一维数组的元素的指针类型是什么呢?因为其等效一维数组的元素又是一个一维数组,所以:二维数组传参时传递的实质就是数组指针型啦~看下面这个例子:

#include<stdio.h>
void Print_Array(int a[2][3]);
void Print_Array(int a[2][3])
{
    int i,j;
    for(i=0;i<2;i++)
        for(j=0;j<3;j++)
               printf("%d  ",a[i][j]);
}
int main()
{
    int a[2][3]={1,2,3,4,5,6};
    int (*p)[3];

    p=a;

    Print_Array(p);

    getch();
    return 0;
}

上面这段代码没有警告,没有报错,清楚说明了:如果形参选用“int a[][]“的声明方式,本质传递的是一个“数组指针型”的地址。但对于二维数组的传参,真的必须多增设一个数组指针型变量来传参么?当然不必要。因为C构造了二维数组这个类型,就可以有简便的方式去描述各种所需的地址类型,其中也包括“数组指针型”地址,前面文章(二、二维数组)那部分也详细介绍过:对于二维数组中,与“数组指针型“匹配的地址为二维数组名,在上述代码中即a,(另外,&a[0],&a[i],(a+i)也都是该类型地址)。所以单独对于上面这个简单的例子,可以简化为:

#include<stdio.h>
void Print_Array(int a[2][3]);
void Print_Array(int a[2][3])
{
    int i,j;
    for(i=0;i<2;i++)
        for(j=0;j<3;j++)
               printf("%d  ",a[i][j]);
}
int main()
{
    int a[2][3]={1,2,3,4,5,6};
    Print_Array(a);  //或&a[0]
    getch();
    return 0;
}

也可以通过实参换成:&a&a[0]&a[0][0]试一下,可以发现都可以运行,结果也显示正确,但编译会有警告信息,查看这些警告信息,更好的去理解地址类型的匹配与否。
一个细节:对于一维数组作为形参,参数中数组描述可以不说明数组长度,二维数组是否也可以如此呢?比如形参使用int a[][]?可以自行试一下,不行,而且是报错,不是警告。这里的原因还是要说到:当通过二维数组定义的方式描述形参时,其本质应该描述的是一个指向一维数组的指针。而对于int a[][]仅仅说明了:1、这是一个二维数组,2、二维数组的元素类型为整型。但没有说明:这个二维数组的等效一维数组的长度!这一点没说明的话,就无法知道这个地址指向的数组究竟有多长,它究竟是该跟int (*p)[x],x=几匹配呢?无法得知。所以:二维数组作为形参描述时候,第二维的长度是必须指明的,即int a[][3]。a的第一维长度的说明不是必须的,与一维数组类似,如果需要说明等效一维数组的长度,那就增设第二个参数说明,那可以采用这样的形式:Print_Array(int a[][3],int n)。代码如下:

#include<stdio.h>
void Print_Array(int a[][3],int);
void Print_Array(int a[][3],int n)
{
    int i,j;
    for(i=0;i<n;i++)
        for(j=0;j<3;j++)
               printf("%d  ",a[i][j]);
}
int main()
{
    int a[2][3]={1,2,3,4,5,6};
    Print_Array(a,2);  
    getch();
    return 0;
}

同样的,在理解数组传递本质之后,我们也可以用指针作为参数,如下例:

#include<stdio.h>
void Print_Array(int (*p)[5],int n);
void Print_Array(int (*p)[5],int n)
{
    int i,j;
    for(i=0;i<n;i++)
    {
        for(j=0;j<5;j++)
               printf("%2d  ",*(*(p+i)+j));
        printf("
");
    }
}
int main()
{
    int a[5][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25};
    Print_Array(a,5);  
    getch();
    return 0;
}

通过对下个示例更好的理解数组指针和二维数组之间的联系:

//三个函数均为显示每行首元素功能,注意每一种函数定义时候的形参设置和调用时候的实参选择
#include<stdio.h>
void Print_rowhead1(int (*p)[5],int n);
void Print_rowhead2(int a[],int n);
void Print_rowhead3(int a[][5],int n);

void Print_rowhead1(int (*p)[5],int n)
{
    int i;
    for(i=0;i<n;i++)
    {
        printf("%2d  ",**(p+i));
        printf("
");
    }
}

void Print_rowhead2(int a[],int n)
{
    int i,j;
    for(i=0,j=0;i<n;i++)
    {
        printf("%2d  ",a[j]);
        j+=5;        
        printf("
");
    }
}

void Print_rowhead3(int a[][5],int n)
{
    int i;
    for(i=0;i<n;i++)
    {
        printf("%2d  ",*a[i]);
        printf("
");
    }
}

int main()
{
    int a[3][5]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
    int (*p)[5]=a;
    Print_rowhead1(p,3); 
    Print_rowhead1(a,3);
    Print_rowhead1(&a[0],3); 

    Print_rowhead2(&a[0][0],3); 
    Print_rowhead2(a[0],3);
    Print_rowhead2(*p,3);

    Print_rowhead3(p,3); 
    Print_rowhead3(a,3);
    Print_rowhead3(&a[0],3); 


    getch();
    return 0;
}
原文地址:https://www.cnblogs.com/tsembrace/p/3256252.html