C语言学习笔记(19) 多维数组和多维指针分析

摘要:总结了指向指针的指针的用法,以及这样使用的意义,分析了二维数组名,二维数组和二级指针的用法,文中用多个相关实例加深理解。


一、指向指针的指针

    我们都知道,指针其实也是一个变量,只是里面存放的是地址而已,所以指针本身也是有地址的,既然有地址,那么就可以用另外一个指针去存放,于是就有了指向指针的指针。

    1.指针变量在内存中会占用一定的内存空间。

    2.可以定义指针来保存指针变量的地址值。

    比如我们看下面一个例子:

<span style="font-size:18px;">#include <stdio.h>
 
int main(void)
{
    int a=10;
    int* p=NULL;
    int** pp=NULL;
   
    p=&a;
    printf("%d\n",*p);
    pp=&p;
    printf("%d\n",**pp);
    *pp=&a;
    printf("%d\n",**pp);
}
结果输出:
10
10
10</span>

    p存放了变量a的地址,然后我们可以通过p打印出a的值,pp存放了指针p的地址,通过两次解引用,我们也可以打印出a的值,红色字体部分是等价的,利用*p存放a的地址,然后再解引用一次,不就可以找到a的值了吗?这其实是一个二维指针的最基本的应用。

   

二、为什么需要指向指针的指针

    因为指针本质上也是变量,既然是变量就可以使用指针去找到,而且,指针之间也存在传值调用与传值调用之间的问题。

    那么复习一下,什么是传值调用,什么是传址调用?其实顾名思义,一个是传的值,一个是传的地址,值就是利用变量直接复制过去,址自然就是指针,比如看下面的例子;

<span style="font-size:18px;">#include <stdio.h>
 
void fun1(int a,int b)
{
   int i;
   i=a;
   a=b;
   b=i;
}
 
void fun2(int* a,int*b)
{
  int i;
  i=*a;
  *a=*b;
  *b=i;
}
 
int main(void)
{
  int i,j,m,n;
  i=1;
  j=2;
  m=1;
  n=2;
  fun1(i,j);
  fun2(&m,&n);
  printf("%d   %d\n",i,j);
  printf("%d   %d\n",m,n);
 
  return 0;
}
输出结果如下:
#1  2
#2  1</span>

    这下可以看出传值和传址的区别了吧,这里需要注意的就是当一个函数体内部需要改变实参的值,则需要使用指针参数,在处理复杂结构参数的时候,一定要使用指针传地址,效率提升的不是一点两点。

    回归正题,下面分析一个指向指针的指针的实例,便于加深理解,例子如下:

<span style="font-size:18px;">#include <stdio.h>
#include <malloc.h>
#include <string.h>
 
int reset(char **p,int size,int new_size)//三个参数,第一个是一个二维指针,第二个参数是原先空间大小,第三个是裁剪后的大小
{
    int ret=1;
    int i=0;
    int len =0;
    char *pt=NULL;//新地址的指针
    char *tmp=NULL;//中间变量指针,目的是为了使用指针自增赋值,效率提高,因为自增后地址改变,所以使用temp
    char *pp=*p;//因为**p=&p,这里pp指向了原来的内存中的内容
   
    if((p!=NULL)&&(new_size>0))//判断指针是否为空,还有新内存大小是否大于零
       {
           pt=(char*)malloc(new_size);//这里新开辟一个内存,赋给pt
           tmp=pt;
           len=(size<new_size)?size:new_size;//利用问好表达式,保证是裁剪还是填充
           for(i=0;i<len;i++)
           {
              *tmp++=*pp++;//将原先内存中的内容拷贝给tmp指向的新的内存中的内容
           }
           free(*p);//释放原来的内存
           *p=pt; //把新的内存的指针给*p,这样在外部使用的时候,不会受到影响
       }
    else
       {
           ret=0;//错误情况的返回值
       }
    return ret;
    }
 
 
int main(void)
{
    char* p=(char*)malloc(5);//使用malloc分配五个字节的空间,并把起始地址赋给指针p
   
    strcpy(p,"hello");
   
    printf("%s\n",p);//打印出p中的内容
   
    if(reset(&p,5,3))//将五个字节裁剪为3个字节,节省不用的空间
    {
       printf("%s\n",p);//在打印裁剪后的内容
    }
      
    return0;
}
便已运行结果如下:
hello
hel</span>

    可以看到得到我们想要的结果,紧要的部分已经备注在程序中了。


三、二维数组和二级指针

    1.二维数组在内存中以一维的形式排布。

    2.二维数组中的第一维是一维数组。

    3.二维数组中的第二维才是具体的值。

    4.二维数组的数组名课看做常量指针。

    上面这个理解起来也不难吧,因为我们的内存是一个个排列的,本质上还是线性一维的,所以数组无论一维二维,其实还是一维的形式排布的,这是由于内存的读写机制导致的。二维数组,第一维是一维数组,第二维才是各个数组里面的值。数组名看做常量指针这一点和一维数组是一样的。

    下面用一个例子,以一维数组的方式遍历二维数组

<span style="font-size:18px;">#include <stdio.h>
#include <malloc.h>
 
void printArray(int a[],int size)
{
    int i=0;
   
    printf("printfArray:%d\n",sizeof(a));
   
    for(i=0;i<size;i++)
       {
           printf("%d\n",a[i]);
       }
   
}
 
int main(void)
{
    inta[3][3]={{0,1,2},{3,4,5},{6,7,8}};
    int* p=&a[0][0];
   
    printArray(p,9);
   
    return0;
}
编译运行结果如下;
printf Array:4
0
1
2
3
4
5
6
7
8</span>

    这个程序其实是把二维数组的第一个元素地址赋给了指针p,然后利用指针自增,知道最后,可以看出二维数组也是依次排列的,按照以为数组的方式。


四、二维数组名的问题

    1.inta[2][4]是两行四列还是四行两列?

    答案是两行四列的。

    2.一维数组名代表数组首元素的地址:

    int a[5]->a的类型为int *

    二维数组名同样代表数组首元素的地址,但是它的类型

    int a[5][5]->a的类型为int *[5]

    这个其实很好理解,二维数组第一维是行,然后每行中存放着列中具体元素的值,五列就是五个元素,类型就是type *[line_size],这个不难理解吧。

    下面是一个面试题,和这个知识点相关;

<span style="font-size:18px;">#include <stdio.h>
 
int main(void)
{
    inta[5][5];
    int(*p)[4];
   
    p=a;
   
    printf("%d\n",&p[4][2]-&a[4][2]);
    return0;
}</span>

编译和运行的时候会出现如下输出;

    这里为什么是-4呢?编译的时候,警告又是从何而来的,其实这个知识点主要考察的并不是输出结果,而是应该在意这个警告,任何编译器的警告都不应该被我们忽略,从不兼容的指针类型赋值,表面上,我们把数组首元素的地址给了一个指针,并没有什么不妥,指针给指针,乍一看类型相同,这里p是一个数组指针,但是后面接的是4,而我们的二维数组的类型的int *[5],一个是int *[4],这就是类型的不同。至于为什么会输出-4,&p[4][2],是一次移动四个单元,那就是4*4+2,指向第18个单元。&a[4][2]一次是五个,也就是4*5+2,指向了第22个元素,指针指向同一个数组,相减得到的是下标差,所以等于-4.


五、以指针的方式遍历二维数组

    我们一般遍历二维数组,可以使用下标方式,也可以使用指针方式,这里在一个例子里面给出两种方式的使用方法,具体例子如下:

<span style="font-size:18px;">#include <stdio.h>
 
int main(void)
{
    int a[3][3]={{0,1,2},{3,4,5},{5,6,7}};
    int i;
    int j;
   
    for(i=0;i<3;i++)
       for(j=0;j<3;j++)
           {
              printf("%d\n",a[i][j]);
           }
          
    for(i=0;i<3;i++)
       for(j=0;j<3;j++)
           {
              printf("%d\n",*(*(a+i)+j));
           }         
   
    return 0;
}
输出结果这里就不贴上来了,依次打印0到8。</span>

六、小结

    1.C语言中其实本质上只有一维数组,并且数组大小在编译期就要被确定。

    2.C语言中的数组元素可以是任意类型,也可以是一个数组,这就是二维数组怎么来的。

    3.C语言中只有数组的大小和数组首元素的地址是编译器直接确定的,这一点其实是第一点的重复说明,我们在定义数组的时候,数组类型就包含有大小的信息在里面。

    这篇帖子就总结到这里,如有不正确的地方还请指出,大家共同进步!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值