深入理解指针与数组

Posted RookieStriver

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解指针与数组相关的知识,希望对你有一定的参考价值。

大家好,这里是我时隔许久又一良心巨著,主要是介绍了指针和数组的一些深入的理解,特意整理出来一篇博客供我们一起学习,如果文章中有理解不当的地方,还希望朋友们在评论区指出,我们相互学习,共同进步!

一:指针

1.1什么是指针

重新理解变量:定义一个变量,本质是在内存中根据类型来开辟空间。有了空间,就必须具有地址来标识空间,来方便CPU进行寻址。有了空间,就可以把数据保存起来。

指针就是地址!那么地址的本质是什么呢?地址是数据,数据可以被保存在变量空间中。

1.2为什么要有指针

一句话,为了方便CPU寻址的效率。

1.3指针解引用

int main()

	int a = 10;
	int *p = &a;
	
	*p = 20;
	system("pause");
	return 0;

*p的完整理解是,取出p的地址,访问该地址指向的内存单元(空间或内容)(其实通过指针变量访问,本质是一种间接寻址的方式)
口诀:对指针解引用,就是指针指向的目标

二:数组

2.1概念

数组是具有相同类型的集合

#define N 10
int	main()

	int a[N] =  0 ;
	system("pause");
	return 0;

我们都知道临时变量是在栈区上开辟空间,且地址由高到低,那么数组是如何开辟空间的呢?

int main()

	int a[10] =  0 ;
	for (int i = 0; i < 10; i++)
	
		printf("&a[%d]:%p\\n", i, &a[i]);
	
	system("pause");
	return 0;


&a[0]:003CFAB8
&a[1]:003CFABC
&a[2]:003CFAC0
&a[3]:003CFAC4
&a[4]:003CFAC8
&a[5]:003CFACC
&a[6]:003CFAD0
&a[7]:003CFAD4
&a[8]:003CFAD8
&a[9]:003CFADC
请按任意键继续. . .

有代码结果我们可以看出,数组是整体申请空间的,然后将地址最低的空间,作a【0】元素,以此类推。

引用自比特课件

2.2理解指针+1

int main()

	char * c = NULL;
	short * s = NULL;
	int * i = NULL;
	double * d = NULL;

	printf("%d\\n", c);//0
	printf("%d\\n", c + 1);//1

	printf("%d\\n", s);//0
	printf("%d\\n", s + 1);//2

	printf("%d\\n", i);//0
	printf("%d\\n", i + 1);//4

	printf("%d\\n", d);//0
	printf("%d\\n", d + 1);//8
	system("pause");
	return 0;

口诀:对指针+1,本质加上其所指类型大小

2.3数组名a作为左值和右值的区别

数组名可以做右值,代表数组首元素的地址。

int main()

	int a[10] =  0 ;
	char *p = a;
	system("pause");
	return 0;

数组名不可以做左值!能够当左值的,必须是有空间可以被修改的,数组名不可以整体使用,只能按照元素为单位使用。

三:指针和数组的关系

没关系

3.1以指针的形式和以数组的形式访问

int main()

	char * str = "hello world";
	char arr[] = "hello world";

	int len = strlen(str);
	for (int i = 0; i < len; i++)
	
		printf("%c\\t", *(str + i));
		printf("%c\\n", str[i]);
	

	printf("\\n");

	for (int i = 0; i < len; i++)
	
		printf("%c\\t", *(arr + i));
		printf("%c\\n", arr[i]);
	
	system("pause");
	return 0;

str指针变量在栈上保存,“hello world”在字符常量区,不可修改。
arr整个数组在栈上保存,可以被修改。

代码中,虽然*(str + i)与*(arr + i)的写法一样,但是寻址方法是完全不一样的

指针和数组指向或表示一块空间的时候,访问方式是可以互通的,既有相似性。

那么C语言为什么要这么设计?

说白了就是方便程序员编程,如果没有将指针和数组元素访问打通,那么在C语言中(面向过程)如果有大量的函数调用和大量数组传参,会要求程序员进行各种各种访问习惯的变化,只要是要求人做的,就会提升代码出错的概率和调试i的难度。

四:指针数组和数组指针

4.1数组名与&数组名的区别

int main()

	char arr[5] =  'a', 'b', 'c', 'd', 'e' ;

	char(*p1)[3] = &arr;
	char(*p2)[3]= arr;

	system("pause");
	return 0;


结论:数组元素个数,也是数值指针类型的一部分。

4.2地址的强制转换

struct test

	int NUM;
	char * pcName;
	char ch[2];
*p = (struct test *)0x100000;

int main()

	printf("%p\\n", p + 0x1);
	printf("%p\\n", (unsigned long)p + 0x1);
	printf("%p\\n", (unsigned int *)p + 0x1);
	system("pause");
	return 0;

0010000C
00100001
00100004
请按任意键继续. . .

结论:强制类型转换,改变的是对特定内容的看待方式,对数据本身不发生改变。

五:多维数组和多级指针

5.1 二维数组基本内存布局

int main()

	char arr[3][4] =  0 ;
	for (int i = 0; i < 3; i++)
	
		for (int j = 0; j < 4; j++)
		
			printf("arr[%d][%d] : %p\\n", i, j, &arr[i][j]);
		
	
	system("pause");
	return 0;


arr[0][0] : 012FFAFC
arr[0][1] : 012FFAFD
arr[0][2] : 012FFAFE
arr[0][3] : 012FFAFF
arr[1][0] : 012FFB00
arr[1][1] : 012FFB01
arr[1][2] : 012FFB02
arr[1][3] : 012FFB03
arr[2][0] : 012FFB04
arr[2][1] : 012FFB05
arr[2][2] : 012FFB06
arr[2][3] : 012FFB07
请按任意键继续.

结论:二维数组在内存地址空间排布上,也是连续递且递增的。

5.2二维数组如何画图

只有正确画出二维数组的布局图,才能算真正深刻理解二维数组的空间布局。
以 char【3】【4】 = 0 ;为例

理解链:数组的定义是既有相同元素类型的集合,特征是数组中可以保存任意类型。那么可以保存数组吗,答案是可以的!
在理解上,我们甚至可以认为所有的数组都可以当作一维数组,就二维数组而言,可以被当作“一维数组”, 只不过内部“元素”也是一维数组。

那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体也是线性且连续递增的。这也就解释了为何上述二维数组的地址是连续递增的。

如果愿意的话,我们也可采用如下代码遍历二维数组:

int main()

	char arr[3][4] =  0 ;
	char *p = (char *)arr;
	for (int i = 0; i < 3 * 4; i++)
	
		printf("%p\\n", p + i);
	
	system("pause");
	return 0;


00FDFCCC
00FDFCCD
00FDFCCE
00FDFCCF
00FDFCD0
00FDFCD1
00FDFCD2
00FDFCD3
00FDFCD4
00FDFCD5
00FDFCD6
00FDFCD7
请按任意键继续. . .

下面我给出一组题目,看朋友们能否明白答案的由来:

int main()

	int arr[3][4] =  0 ;
	printf("%d\\n", sizeof(arr));
	printf("%d\\n", sizeof(arr[0][0]));
	printf("%d\\n", sizeof(arr[0]));
	printf("%d\\n", sizeof(arr[0] + 1));
	printf("%d\\n", sizeof(*(arr[0] + 1)));
	printf("%d\\n", sizeof(arr + 1));
	printf("%d\\n", sizeof(*(arr + 1)));
	printf("%d\\n", sizeof(&arr[0] + 1));
	printf("%d\\n", sizeof(*(&arr[0] + 1)));
	printf("%d\\n", sizeof(*arr));
	system("pause");
	return 0;


48
4
16
4
4
4
16
4
16
16
请按任意键继续. . .

欢迎评论区讨论

5.3二级指针

同样的,二级指针是变量,变量有地址,地址是数据,数据可以被保存。

int main()

	int a = 10;
	int * p = &a;
	int *pp = &p;

	p = 100;
	*p = 100;
	pp = 100;
	*pp = 100;
	** pp = 100;
	return 0;

六:数组参数和指针参数

6.1一维数组传参

数组传参是要发生降维的。

void show(int * arr)

	printf("main: %d\\n", sizeof(arr));

int main()

	int arr[10];
	printf("main: %d\\n", sizeof(arr));

	show(arr);

	system("pause");
	return 0;


main: 40
main: 4
请按任意键继续. . .

为什么要降维?

在C语言中,只要函数调用,必定发生拷贝,形参是实参的一份临时拷贝,所以如果不发生降维,那么会导致成本高,效率低。

降维成什么?

降维成指向其内部元素的指针

6.2一级指针传参

发生函数调用,指针作为参数,要不要发生临时拷贝?

void test(char * p)

	printf("test: &p = %p\\n", &p);

int main()

	char * p = "hello world";
	printf("main: &p = %p\\n", &p);
	test(p);

	system("pause");
	return 0;


main: &p = 012FFB50
test: &p = 012FFA7C
请按任意键继续. . .

需要! 因为指针变量,也是变量,在传参上,它也必须符合变量的要求,进行临时拷贝.所以上述代码形参与实参地址不一样!

6.3二维数组参数和二级指针参数

二维数组传参也要发生降维
降维成什么?

指向其内部元素的指针

void show(char arr[][4])

	printf("hello world!");

int main()

	char arr[3][4] =  0 ;
	printf("main: %d\\n", sizeof(arr));

	show(arr);

	system("pause");
	return 0;


数组名arr代表首元素地址,此时首元素是一个一维数组,其地址是一个数组指针,须知所有的数组传参,都会发生降维,这里则是降维成一个一级数组指针来传参

思考为什么在形参接收是行标可以省略,而列标不可以呢?

这里就涉及到前面所说的数组指针的类型与元素个数有关,如上述代码在形参列表也可表示成降维成:
void show(char (*arr)[4])
所以列标不可以省

综上:任何维度的数组,在传参是时候都要发生降维,降维成指向其内部元素类型的指针.
那么二维数组内部元素是"一维数组",就应该降维成指向一维数组的指针.
同理三维数组,四维数组一样的道理!

谢谢大家的支持,还希望看到这里的能够给一个三连支持,谢谢大家!

以上是关于深入理解指针与数组的主要内容,如果未能解决你的问题,请参考以下文章

深入理解C语言的指针

深入理解一维数组与二维数组

《深入理解C指针》第四章 指针和数组

C++|深入理解智能指针

深入理解C指针经典笔试题——指针和数组

深入理解C语言的指针