C语言指针的理解五:二重指针与二维数组

1.二重指针

1.1 二重指针与普通一重指针的区别

本质上来说,二重指针和一重指针的本质都是指针变量,占用相同的内存空间,而指针变量的本质就是变量。

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

C语言其实是可以没有二重指针的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(以及函数指针、数组指针),之所以要区分这些指针,就是为了让编译器了解这个指针被定义时,定义它的程序员希望这个指针被用来指向什么东西,定义指针时用数据类型来标记,比如int *p就表示p要指向int型数据。编译器知道指针类型之后可以帮我们做静态类型检查,这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C语言给程序员提供的一种编译时的查错机制。

1.2 二重指针的用法

二重指针必须指向一重指针的地址,例如:

int main(int argc,char**argv)
{
	int a =5;
	int*p1 = &a;	//p1是一重指针,它指向a,保存的是int变量a的地址
	int** p2 = &p1;	//p2是二重指针,它指向p1,保存的是指针变量p1的地址

	return 0;
}

二重指针也可以指向指针数组的地址:

int main(int argc,char**argv)
{
	int*p1[5] = {};	//p1是指针数组,即内部元素都是一重指针的数组
	int** p2 = p1;	//p1作右值的时候是二重指针

	return 0;
}

对于指针数组p1,做右值的时候它是数组首元素的地址,也就是说它指向数组首元素,而这是一个指针数组,数组首元素就是一个一重指针,所以p1是一个指向一重指针的指针,也就是二重指针,所以可以直接做右值赋值给p2

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

1.3 一些概念的实质

首先还是要明确:二重指针、数组指针、结构体指针、一重指针、普通变量,这些的本质都是相同的,它们都是变量,和一段内存空间绑定,空间内会存入数据。

所有的指针变量本质也都是相同的,64位机器中都是8个字节,用来指向别的变量,不同类型的指针变量只是可以指向的,或者说编译器允许指向的变量类型不同。

二重指针大部分情况下就是用来指向指针数组的。

2.二维数组

2.1 二维数组的内存映像

一维数组在内存中是连续分布的多个内存单元组成的,而二维数组在内存中实际上也是由连续分布的多个内存单元组成的。从内存角度来看,一维数组和二维数组没有本质差别。二维数组int a[2][3]和一维数组int b[6]在内存中的存储方式其实没有任何本质差别。

举个栗子:

#include<stdio.h>

int main(int argc,char**argv)
{
	int b[6]={0};
	int a[2][3];
	for(int i=0;i<6;++i)
		printf("%p ",&b[i]);
	printf("\n");
	
	for(int i=0;i<2;++i)
		for(int j=0;j<3;++j)
			printf("%p ",&a[i][j]);
	
	printf("\n");
	return 0;
}

输出:

0x7ffe4a7f1ad0 0x7ffe4a7f1ad4 0x7ffe4a7f1ad8 0x7ffe4a7f1adc 0x7ffe4a7f1ae0 0x7ffe4a7f1ae4 
0x7ffe4a7f1af0 0x7ffe4a7f1af4 0x7ffe4a7f1af8 0x7ffe4a7f1afc 0x7ffe4a7f1b00 0x7ffe4a7f1b04 

既然二维数组都可以用一维数组来表示,那二维数组存在的意义和价值在哪里?二维数组a和一维数组b在内存访问效率上是完全一样的,或者说差异是忽略不计的。在某种情况下用二维数组而不用一维数组,原因在于二维数组好理解,代码好写而且利于组织。

总结:使用C语言提供二维数组并不是必须的,而是一种简化编程的方式。其实一维数组的出现其实也不是必然的,也是为了简化编程。

2.2 二维数组下标理解和访问

二维数组int a[2][3]中,2是第一维,3是第二维。结合内存映像来理解二维数组的第一维和第二维的意义:首先第一维是最外面一层的数组,所以int a[2][3]这个数组有2个元素,其中每一个元素又是一个含有3个元素的一维数组,这个数组就是第二维。

总结:

  • 二维数组的第一维是最外部的那一层,第一维本身是个数组,并且这个数组中存储的元素也是个一维数组。
  • 二维数组的第二维是里面的那一层,第二维本身是个一维数组,数组中存的元素是普通元素,第二维这个一维数组本身作为元素存储在第一维的二维数组中。

一维数组有数组下标和指针偏移运算两种访问方式,以int b[6]为例:

int *p = b;。
b[0]=5//b[0]等同于 *(p+0);   
b[9]=6; //b[9]等同于 *(p+9);  b[i] 等同于 *(p+i)

二维数组同样有两种访问方式,以int a[2][3]为例:

int (*p)[3];
p= a;
a[0][0]=3; //a[0][0]等同于*(*(p+0)+0); 	
a[1][4]=3; //a[1][4]等同于*(*(p+1)+4); a[i][j]等同于 *(*(p+i)+j)

int a[2][3]是一个二维数组,其实它内部也是两个元素,只不过这两个元素也是数组,而数组名a是指向第一个元素:一个大小为3的int型数组的,所以a的类型是一个数组指针,它首先是一个指针,指向一个大小为3的int型数组,根据之前 [C语言指针的理解二:指针与数组、数组指针与指针数组] 中数组指针的定义和理解方法,就可以得到数组指针p的定义语法为:int (*p)[3];

2.3 二维数组的应用和更多维数组

最常用情况:一维数组用来表示一个线性表,二维数组用来表示一张单通道灰度图像,三维数组可以表示三通道的RGB图像等。四维数组也是可以存在的,在数学上有意义,现实空间中没有对应,因为人类生存的宇宙是三维的。

总结:一般常用最多就到三维数组,四维数组除了做一些特殊与数学运算有关的之外基本用不到。

2.4二维数组的运算和指针

1.指针指向二维数组的数组名:二维数组的数组名表示二维数组的第一维数组中首元素,也就是第二维的数的首地址。二维数组的数组名a等同于&a[0],这个和一维数组的符号含义是相符的,用数组指针来指向二维数组的数组名是类型匹配的,通过该指针可以访问整个二维数组元素。

#include<stdio.h>

int main(int argc,char**argv)
{
	int a[2][3]={{1,2,3},{4,5,6}};
	int (*p)[3] = a;
	for(int i=0;i<2;++i)
	{
		for(int j=0;j<3;++j)
			printf("%d ",*(*(p+i)+j));
		printf("\n");
	}
	
	return 0;
}	

输出:

1 2 3
4 5 6

2.指针指向二维数组的第一维:前面的分析可以知道,第一维就是一个数组,例如a[0]实际上就是{1,2,3}a[1]实际上就是{4,5,6}所以可以直接用int *p来指向二维数组第一维的各个元素a[i],通过该指针仅能访问二维数组的某一行元素。

#include<stdio.h>

int main(int argc,char**argv)
{
	int a[2][3]={{1,2,3},{4,5,6}};
	int *p1 = a[0];
	int* p2 = a[1];

	for(int i=0;i<3;++i)
		printf("%d ",p1[i]);
	printf("\n");
	
	for(int i=0;i<3;++i)
		printf("%d ",p2[i]);
	printf("\n");
	
	return 0;
}	

输出:

1 2 3
4 5 6

3.指针指向二维数组的第二维:二维数组的第二维元素其实就是普通变量了,例如a[1][1]其实就是int类型的4,已经不能用指针类型和它相互赋值了。除非int *p = &a[i][j];,类似于指针指向普通int型变量。

2.5 总结

理解二维数组和指针的关键就是2点:

  • 1.数组中各个符号的含义。
  • 2.数组的指针式访问,尤其是二维数组的指针式访问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值