C语言二维数组和二重指针详解

二维数组

一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组,形式如下:

type arrayName [x][y];

这个表示,有x个一维数组,每个一维数组的元素个数是y个。

声明示例:

/** 定义数组 */
int main()
{
	int		ar[3][4];		// 3 行 4列 未初始化
	char	br[3][4];
	double	cr[3][4];
	return 0;
}

一个二维数组,在本质上是有多个一维数组构成。(每一个一维数的大小必须相同)

例如:定义 int ar[3][4] 的二维数组,它是由3个一维数组组成,每个一维数组的大小是4个整型元素。可以只对部分元素赋值,未赋值的元素自动取 0 值。

可以进行如下赋值:

int x[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};

如果对二维数组的初始化,那么第一维的长度是可以缺省的,但是第二维不可缺省

int main()
{
	int ar[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };		// 3 行 4 列
	int br[][4] = { {1,2},{3,4},{5,6} };				// 3 行 4 列 数字不足自动补 0
	int cr[][4] = { 1,2,3,4,5,6,7,8 };					// 2 行 4 列
	return 0;
}

注:里面的花括号是可以省略的,如果省略,那么按照列的个数来自动分配。

验证如下:

#include <stdio.h>

int main()
{
	int a[][3] = {1, 2, 3, 4, 5};
    /* 我的第一个 C 程序 */
    printf("Hello, World! %d\n", a[0][2]);//Hello, World! 3
	printf("Hello, World! %d\n", a[1][2]);//Hello, World! 0
   
    return 0;
}

二维数组在内存中的存储

二维数组的逻辑表示:

二维数组的物理表示(按行优先存储):

二维数组的数组名有什么含义

先来梳理一下一维数组的数组名含义。

#include<stdio.h>
int main(void){
	int a[3]={1,2,3};	
	int *p;
	  p=a;
	printf(" a=%d\n",a);
	printf(" &a[0]=%d\n",&a[0]);
	printf(" &a=%d\n",&a);
	printf(" *a=%d\n",*a);
	printf(" *p=%d\n",*p); 
}

我们知道数组是在内存里占据一片连续的内存空间,由此可以看到,数组名a的值为数组a的第一个元素的地址,且数组名a自身的地址也和a指向的地址相同,即 a=&a=&a[0],但是要注意,&a只是在数值上相同,在含义上并不相同。

对于一维数组而言,&a,即数组本身的指针,并没有什么用处,重要的是a,即数组首元素的指针。由main函数中开始的两行代码,我们能知道,如果想要接收数组的数组名,我们需要定义的指针类型是数组元素的类型,而不是定义一个数组指针。当然,如果想要接收&a,那么就需要定义数组指针。

先根据一维数组的函数名含义来猜想下二维数组的函数名含义。

如a[3][4],那么,a就代表着第一个一维数组的地址。也就是说,是个数组的地址。

另外,a[0]、a[1]、a[2]都表示一个一维数组,是否也表示各一维数组的地址。

测试:编辑如下代码:

#include <stdio.h>

int main()
{
	int a[3][4] = {{1, 2 , 3, 4}, {5, 6 , 7, 8}, {9, 10 , 11, 12}};
	
    printf("Hello, World! %x\n", a);
	printf("Hello, World! %x\n", a[0]);
	printf("Hello, World! %x\n", a[1]);
	printf("Hello, World! %x\n", a[2]);
   
    return 0;
}

如果上述猜想是对的,那么第一行和第二行的数值是一样的,且后面的三个地址,依次相差16个字节。

运行:

符合猜想。

为了进一步证实,用他们访问具体的数据。

#include <stdio.h>

int main()
{
	int a[3][4] = {{1, 2 , 3, 4}, {5, 6 , 7, 8}, {9, 10 , 11, 12}};
	
    printf("Hello, World! %d\n", *(int *)(a));
	printf("Hello, World! %d\n", *((int *)a[0] + 1));
	printf("Hello, World! %d\n", *(int *)a[1]);
	printf("Hello, World! %d\n", *((int *)a[2] + 3));
   
    return 0;
}

如果没错的话,应该依次输出1、2、5、12

运行:

以为正确。 

后来,又遇到一个问题,才发现上面的结论并不完全对。

先说进一步发现的结论:

对于二维数组来说,数组名a表示首元素的地址;a[0]相当于一维数组的数组名,表示的是第一个一维数组的首元素的地址。

可以这么理解,对二维数组名进行解引用,能得到一维数组名的效果。

#include <stdio.h>

int main()
{
	int a[][3] = {{1, 2, 3},{4, 5, 6}};
   
	printf("result is %d\n", **a);
	printf("result is %d\n", *a[0]);
	printf("result is %d\n", *a[1]);
	
    return 0;
}

运行结果

想起来一个问题。

如果说a[][]中,a表示的首元素的地址,也就是说,按照常理,*a表示的应该是第一个一维数组的地址吧?也就是说,*a的效果是等同于一维数组中的a还是&a。

做个验证:

经过上述验证,*a得到的就是一维数组名a的效果。

和&a有同样效果的就是二维数组的数组名,表示的是个一维数组的地址。

 

二维字符数组

常规的字符数组按照上述的内容来考虑。
我们这里重点关注字符串形式的二维数组。

char a[][10] = {"be", "happy", "everyday"};

考虑上述代码,其中a是个双重指针,指向首元素;a[0]是“be”字符串,指向'b';a[1]是“happy”字符串,指向'h';a[2]是“everyday”字符串,指向'e'。

#include <stdio.h>

int main()
{
	char a[][10] = {"be", "happy", "everyday"};
   
	printf("result is %s\n", *a);
	printf("result is %s\n", a[0]);
	printf("result is %s\n", a[1]);
	printf("result is %s\n", a[2]);
	
	printf("result is %c\n", **a);
	printf("result is %c\n", *a[0]);
	printf("result is %c\n", *a[1]);
	printf("result is %c\n", *a[2]);
	
	
    return 0;
}

运行结果如下:

注意,直接%s打印字符指针,可以直接输出字符串

#include <stdio.h>

int main()
{
	char *p = "everyday";
   
	printf("result is %s\n", p);
	
    return 0;
}

运行结果:

 从哪里开始打印,就打印其之后的部分字符串

#include <stdio.h>

int main()
{
	char *p = "everyday";
   
	printf("result is %s\n", p + 5);
	
    return 0;
}

运行结果:

补充说明:

二维字符数组和字符指针数组挺像的。

char a[][10] = {"be", "happy", "everyday"};

char *a[] = {"be", "happy", "everyday"};

但是要注意,字符指针数组本质上是个一维数组。

不过,这两者在a、a[0]、a[1]、a[2]上是等效的,比如将上面的一个例子中的二维数组改成一维指针数组,运行结果不变。

#include <stdio.h>

int main()
{
	char *a[] = {"be", "happy", "everyday"};
   
	printf("result is %s\n", *a);
	printf("result is %s\n", a[0]);
	printf("result is %s\n", a[1]);
	printf("result is %s\n", a[2]);
	
	printf("result is %c\n", **a);
	printf("result is %c\n", *a[0]);
	printf("result is %c\n", *a[1]);
	printf("result is %c\n", *a[2]);
	
	
    return 0;
}

运行结果如下:

二维数组作为函数形参

如果想要把二维数组作为函数形参,可以直接用二重指针来接收。




二重指针

二重指针与普通一重指针的区别
本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
一重指针变量和二重指针变量本身都占4字节内存空间,


二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报错。

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

二重指针的用法
二重指针指向一重指针的地址
二重指针指向指针数组

实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去

比如:

void find_max_and_min(int **pmax,int **pmin, int arr[]) {
	*pmax = *pmin = arr;
 
	int i;
	
	for(i=0;i<10;i++) {
		if(**pmax < arr[i]) {
			*pmax = arr+i;
		}
		if(**pmin > arr[i]) {
			*pmin = arr+i;
		}
	}
 
}



补充

看一道题:

还是一样,这种题,画图解决。

注意几个问题:

char s[]中s是个指针,指向首元素,首元素是个普通类型;

char *s[]中s是个二重指针,指向首元素,首元素是个字符串;

char s[][]中s是个二重指针,指向首元素,首元素是个一维数组,s指向一维数组的地址,*可得到一维数组的数组名效果;

char *s[][]中s是个三重指针,指向首元素,首元素是字符串数组,此时,*s能得到字符串数组的地址,**s能得到字符串的地址,***s能得到首字符。

这都是由数组和字符串的特殊设定导致的。

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值