指针和数组

目录

一、指针

二、数组

1、数组的内存布局

2、数组名做左值和右值的区别

3、指针和数组的关系

三、指针数组和数组指针

1、指针数组

2、数组指针

四、多维数组&&多级指针

五、数组传参

六、函数指针


一、指针

1、什么是指针?指针就是地址,是一个数据,可以被保存在变量的空间里面

2、什么是指针变量?指针变量是一个变量,用来保存指针,是变量就有地址,有空间,有内容

3、为什么要有指针?提高CPU寻址效率 

如何理解编址?

首先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同工作的。所谓的协同,至少相互之间要能够进行 数据传递 。 但是硬件与硬件之间是互相独立的,那么如何通信呢?答案很简单, 用"线"连起来 CPU和内存之间也是有大量的数据交互 的,所以,两者必须也用线连起来。 不过,我们今天关心一组线,叫做 地址总线 CPU 访问内存中的某个字节空间, 必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址 就如同宿舍很多,需要给宿舍编号一样)计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的(约定而成),无需专门去保存地址以及地址对应的空间,因为两者是由厂商规定好的。
我们可以简单理解, 32 位机器有 32 根地址总线,每根线只有两态,表示 0,1 【电脉冲有无】,那么一根线,就能表示 2 中含义,2 根线就能表示 4 中含义,依次类推。 32根地址线,就能表示2^32中含义,每一种含义都代表一个地址。2^32bit = 4G,故32位机器内存4GB, 地址信息被下达给内存,在内存内部,就可以找到改地址对应的数据,将数据在通过数据总线传入 CPU 内寄存器。

4、所有变量&,取出的永远是地址值最小的字节的地址

5、强制类型转化的本质就是看待方式的变化,从起始位置连续往后读几个字节,以及后续如何解释他:"12345"——>12345——>一定要改变数据本身——>不是强转     int a = 97——>(char)a——>'a',强转

6、对指针解引用,代表指针所指向的目标

7、*p使用的是p的内容,假设p指向的变量的地址为0x00ff40,则*p = *(0x00ff40)

#include <stdio.h>

int main()
{
	int* p = NULL;
	p = (int*)&p;
	//*p = p变量自身,将10作为一个地址付给p
	*p = 10;
	//将20做为一个地址赋给p,这里的*p = p
	p = 20;

	printf("%p", p);//20

	return 0;
}

8、对指针变量+1,实际是加其自身类型的大小(指针自身类型,或强转之后的类型)

9、对指针变量+1的深刻理解:实际是加其自身类型的大小,指针变量的内容发生变化,即指针所指向的变量发生变化,而指针变量自身的地址不受影响

10、因为一级指针指向的目标类型多样化,所以+1后的步长也多样化,但是二级及以上指针+1的步长永远都是4/8

11、int sz = sizeof(arr) / sizeof(arr[0]),为什么一般写arr[0]而不写arr[1]....,因为数组至少有一个元素,arr[0]绝对正确

二、数组

1、数组的内存布局

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", &c);
	
	return 0;
}
我们发现,先定义的变量,地址是比较大的,后续依次减小 这是为什么呢? a,b,c都在main函数中定义,也就是在栈上开辟的临时变量。而a先定义意味着,a先开辟空间,那么a就先入栈,所以a 的地址最高,其他类似。
内存

#include <stdio.h>

int main()
{
	int a[10] = { 0 };
	for (int i = 0; i < 10; i++)
	{
		printf("a[%d] = %p\n", i, &a[i]);
	}

	return 0;
}

发现:在数组a中,a[0]到a[9]地址依次增大,为什么不符合栈区内存开辟顺序规律?

原因:数组内存整体开辟,然后把地址值最低的地址作为首元素的地址,以此类推,最后整体释放

数组空间排布:线性连续且递增

数组int a[10],[ ]中的元素个数也是数组类型的一部分

2、数组名做左值和右值的区别

#include <stdio.h>

int main()
{
	int a[10] = { 0 };
	int* p = a;
	printf("%p %p\n", p, &p[0]);

	return 0;
}

1、数组名做右值,代表数组首元素地址,等价于&a[0]

#include <stdio.h>

int main()
{
	int a[5] = { 0 };
	a = { 1,2,3,4,5 };//此写法错误,数组只能整体初始化,不能整体赋值

	return 0;
}

2、数组名不能做左值,能做左值的一定要有空间,并且可被修改,而数组名代表首元素地址,是一个指针(数据)

3、数组只能进行整体初始化,不能整体赋值,不能做左值

3、指针和数组的关系

1、char* str = "hello bit",真正意义上的不可被修改,由操作系统保护,而char str[] = "hello bit,在栈上开辟,可以被修改

#include <stdio.h>

void show(int arr[])//实际上已经是int* arr了,所以[ ]内的数组可以忽略(可写可不写,可写任意大于0的数)
{
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
}
void show1(int* p)
{
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	show(arr);//在这里降维
	show1(arr);

	return 0;
}

2、所有数组传参,一定发生降维,降维成指向其内部元素类型的指针,换言之,指针与数组在访问多个连续的元素的时候,既可以采用指针解引用方案,也可以采用[ ]的方案

3、结论:指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表是一个东西或者具有相关性
指针解引用方案,也可以采用[ ]的方案底层区别:*(p + i):先找到p变量的地址,再拿出p变量的内容,然后把内容+i,最后对更新后的内容解引用。p[ i ] : p就是首元素的地址(可以理解为p和首元素共用同一空间),直接地址加i,再访问

4、为什么要将数组和指针的访问方式打通?如果没有将指针和数组元素访问打通,那么在C中(面向过程)如果有大量的函数调用且有大量数组传参,会要求程序员进行各种访问习惯的变化。只要是要求人做的,那么就有提升代码出错的概率和调试的难度。所以干脆,C将指针和数组的访问方式打通,让程序员在函数内,也好像使用数组那样进行元素访问,本质值减少了编程难度,节省人力!

5、c中任何函数传参一定形成临时拷贝

6、如果形参写成int arr[ ],那么 [ ]中的内容是被忽略的

结论:指针和数组没有关系

三、指针数组和数组指针

1、指针数组

1、int *a [10]是指针数组,本质是数组,数组有10个元素,每个元素类型是int*

2、[ ]的优先级大于*

2、数组指针

1、int (*a)[10]是数组指针,本质是指针,指向一个数组,类型是int [10]

四、多维数组&&多级指针

1、二维数组在内存空间排布上也是线性连续且递增的

2、所有数组都可以看作是一维数组,所以所有数组在空间排布上都是线性连续且递增的

几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图,所有的数组(不论几维)都是线性连续且递增

3、二维数组可以看作内部元素是一维数组的一维数组

4、n 维数组可以看作内部元素是n-1维数组的一维数组

5、char p[2][3][4][5],离变量名(p)最近的维度([2])决定一维数组p元素的个数,剩下(char [3][4][5])的是数组内容

指针 - 指针代表指针之间所经历的元素个数

#include <stdio.h>

int main()
{
	int a[5][5] = { 0 };
	int(*p)[4];
	p = a;
	printf("%p %p ", &a[4][2], &p[4][2]);
	printf("%d", &a[4][2] - &p[4][2]);//4

	return 0;
}

6、指针变量也是变量,有地址,地址数据可被保存(多级指针)

五、数组传参

1、所有数组传参,一定发生降维,降维成指向其内部元素类型的指针

2、函数传参,形参和实参的类型必须严格一致(数组传参既可以使用数组接受,也可以使用指针接受,但本质上类型一致都是指针)

3、数组传参,直接传数组名即可

4、数组传参为什么要发生降维?提高函数调用效率,减少内存占用

5、一维数组传参,形参可以忽略[ ]里的数字

6、多维数组传参,形参可以忽略第一个[ ]中的内容,但不可忽略其他[ ]内的数字

7、为什么形参不可忽略其他[ ]里的数字?如果忽略了第二个及之后[ ]里的数字,会导致指针类型不明确,形参int a[5][ ]实际为int(*)[ ]接收类型不明确,而用int a[ ][5]接收实际是int (*)[5],类型很明确(char p[2][3][4][5],离变量名(p)最近的维度([2])决定一维数组p元素的个数,剩下(char [3][4][5])的是数组内容【类型】,数组传参降维成指向其内部元素类型的指针,所以元素个数可以忽略,但内部元素类型不可忽略

二维数组传参的建议:

int a[5][6];print(a, 5);——>print(int a[ ][6], int num)

六、函数指针

1、fun(),()代表函数调用

2、fun和&fun一样,都是函数的地址,建议以后直接用fun来取函数地址

3、为什么函数也有地址?函数也是代码的一部分,程序运行时也要加载到内存,以供CPU寻址访问,代码也有地址

#include <stdio.h>

void show()
{
	printf("hello");
}
void main()
{
	void(*p)() = show;
	p();//推荐使用此写法
	(*p)();

	return 0;
}

4、(*(void (*)())0)()  这是什么:将0强制类型转换成函数指针,并解引用,相当于对0地址出的函数直接调用

是冬至呀-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/2302_81218652?spm=1000.2115.3001.5343

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值