C语言的指针\数组用图解一次搞懂

1.指针

在C语言中有很多不同的数据类型,int、char等等,指针也是一种数据类型,也是变量,int存储整型,char存储字符类型,而指针专门存储一个数据在内存中的地址,需要完全理解指针,必须了解内存地址的概念,C语言编程最重要的是对内存的理解。

以下假设的是虚拟内存,进程的内存布局不做详细介绍了
//不同系统,指针的大小不同(在内存中占的字节数),32位系统下占4字节,大小与指针本身类型,所指向的数据,没有任何关系的!!!

//假设存在以下代码  (可以参照图看1.1)
void main(void)
{
	unsigned char a = 55;  //向系统申请了一块uchar类型大小的内存,内存名为a,内存中存放值55
	short int b = 23;   //申请了2字节大小的内存,内存名为b,小端序存放(参考博主另一篇文章),低地址字节存放值23,高地址字节存放0
	short int *p = &b; //申请了一块4字节大小的内存,内存中存放short int 类型数据的地址,并且是其基地址
	while(1);
}

假设其内存图如下:
在这里插入图片描述

  1. 在内存中,每一字节的内存都有其专属地址,如上图所示,运行时这些变量会有自己专属的位置;
  2. 指针指向的是一大块地址,其值只是这部分地址的基地址,即最低的地址数;
  3. 指针指向的是某个数据的基地址。

1.1指针相关标识符

1.1.1 取地址符号 &

初始化时:
int a = 100;
int *p = &a; //或者int *p = NULL; p = &a;

&为取地址的符号,上述两种初始化的理解:
int *p = &a;  //申请了一块内存,存放int类型数据的地址,这块内存(变量名)名为p,内存中的值为&a

int *p = NULL; //定义了一个int 类型的指针,先指向NULL位置,或者说先存放0x0000 0000这个地址
p = &a;        //将a的地址赋给p

1.1.2 解引用符号 *

只有在定义指针时,* 符号不代表解引用,只代表指针的标识,让编译器明白此变量是个指针
例如:int a = 100;
int *p; //此时 * 只是使p代表指针
p = &a;
*p = 20; //取得该地址所指向内存的使用权,并将该内存的数值置为20

即间接访问内存,通过地址访问内存,操作里面的数据,此过程为解引用。

1.1.3 const 符号

需要了解变量和常量 — 可读可写和只读
const与指针:分清楚 常量指针(指向常量的指针) 和 指针常量(此指针是个常量不可改变他存储的地址)
int i = -1;
1.const int ic = i; 合法,定义了一个常量
2.const int *pic = ⁣ 合法,定义了一个指针指向一个常量 int const *p = const int *p
3.int *const cpi = ⁣ 非法,定义了一个 指针常量 指向一个变量,而ic是常量
4.const int *const cpic = ⁣合法,定义了一个常量指针,指向一个常量

1.1.4 算数运算(偏移概念)

指针算术运算(指针偏移量)
指针支持算术加减: 加-向后偏移,减-向前偏移
偏移量,根据指针本身的类型而定—> int *的指针+1之后,移动了一个int(4字节)
不支持直接的算术乘除(除非强转了)。
如: 都是访问数组的第二个元素
float a[3] = {1.1,1.2,5.5};
1. float mya = *(a+1); 2. float *p = a; float mya2 = *(p+1); 3. a[1]

//在下面配合数组讲更清晰

1.2野指针

野指针:一个指针所指向的地址不确定,此时对它进行赋值会产生不可估计的错误。
一般造成的错误为段错误,即操作了非法数据,非法数据是没有向系统申请的或者是丧失使用权的数据,合法例如int a; 此数据是申请了合法内存的,可以随便使用。
非法数据,例如:

  1. int *p; //只定义不初始化:是个野指针,所存的地址是乱码(随机的),必须初始化,如果直接解引用赋值,操作了个随机地址的数据。
  2. p=(int *)malloc(100); free§; //此时只是释放了内存,使这块内存不能使用,再使用就是非法的,会造成程序错误
    解决:#define NULL ((void *)0x00 ) free§; p = NULL;

1.3多级指针

既然指针是个数据类型,存储在内存中,那么指针也会有一个专属地址。
二级指针:
存储指针地址的指针

int a = 10;
int *p = &a;
int *(*pp) = &p; //申请了一块4字节内存,存储int * 类型的数据的地址(即指向int类型的指针),内存名字为pp,或者说变量名为pp。

//=======================
当然还存在3、4、5、6级指针,也基本用不上

在这里插入图片描述

1.4复杂指针

//指针指向函数,数组等
数组指针:指向数组的指针   运算符优先级,数组指针和指针数组
int (*p)[3]		//指针:指向 int [3] 的数组类型(如int arr[3])

指针数组:存储一系列指针的数组
int *arr[3]		//数组:存储了3个 int *类型的指针
(如字符串数组:char *str[3] = {"hello", "world", "hahaha"};)

函数指针:指向函数的指针
int func_max(int a, int b);
int (*p1)(int, int) = func_max;
printf("%d\n", p1(a, b));

指针函数:返回值为指针的函数
int *func_test(int a, int b)	//函数:形参为 int,int。返回值为 int *

2.数组

数组: 一整块连续的内存,存储了具体长度的相同类型数据。(变量的集合)
任何数组名都被视为一个指向其首元素的指针 — 指针常量特性

//数组的定义
int arr[3];
1.int 元素类型(里面所有元素的数据类型)2.arr 数组名(数组名就是首元素地址)属于指针常量
3.[3] 数组长度(数组中有3个相同的int类型数据)
(数组类型: int [3])
//如:int a[2] [3]  定义了一个数组,有两个元素,每个元素存放的都是int [3]类型的数据
定义的解释:	
通俗解释:定义了一个3个int的数组
内存角度:向系统申请一块连续内存,规定了内存中每个类型为int整型,长度为3。
//使用数组中的元素:arr[0]	使用数组中第1个元素(下标范围:0 ~ 2)

2.1数组与指针(重点)

任何数组名都被视为一个指向其首元素的指针,首元素为即为数组中第一个元素:

int a[3] = {0,1,2};			//首元素为 a[0] = a[0] = *a = 0;
int a[3][2] ={6,7,2,8,3,7,} //首元素为a[0],此a[0]代表的是第一个数组,如下图中的小红圈,*a[0] = a[0][0] = 6;

在此处首元素可以从内存图中看出。
首先了解二维数组:
在C语言中,其实是不存在二维数组这种数据类型的!是为了便于理解交流的一种说法,它在本质上也是一维数组,在内存中也是以一维数组形式存在。
例如: int arr[3][2]
内存的角度:向系统申请一个连续的内存,长度为3个元素,每个元素为 int [2]类型。数组名会和最近的一个[ ]结合。
在这里插入图片描述如上图所示:
int arr[3][2] = {6,7,2,8,3,7};
int (*p)[2] = arr; //定义一个指针指向int [2]类型的数据。
int ***p = &arr; //代表整个数组的地址.

  1. arr :代表首元素的地址,其首元素是上图中的小红圈,所以对其+1,指向的是数字2那个位置。
  2. arr[0]:代表第一个int [2]类型的首元素的地址,即数字6的地址,*a[0] = a[0][0], 对其+1,指向的是数字7. ---- arr[1],指向下一个int[2]数组.
  3. arr[0][0]:代表首元素,就是数字6.
  4. &arr:代表的是整个二维数组的地址,不能对其地址偏移载操作,可能出现段错误
1.数组名也可以当成指针进行运算。
2.指针也可以使用数组下标的形式,来对数组元素进行访问。
3.如指针的下标为负数,则表示指针向左偏移。
4.数组名不能进行自加操作。
5.如果只想对地址进行数值上的算术加减
必须对地址进行强制类型转换,转换为数值形式,才能进行算术加减。
printf("%p \n",arr);   printf("%lx \n",(unsigned int)arr );

2.2复杂数组

2.2.1变长数组

int i = 3;
int a[i]; //这一项在传统C语言中不可初始化,在C99标准和C++中是合法使用的,之前的要求是数组大小必须为常量。

2.2.2柔性数组(零长数组)

int a[0];
柔性数组既数组大小待定的数组,C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度.

它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题用法
在一个结构体的最后,申明一个长度为空的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量,数组名这个符号本身代表了一个不可修改的地址常量,但对于这个数组的大小,我们可以进行动态分配malloc
//============  例子    //同样在C99标准和C++中合法使用
    struct ZeroArrTest
    {
        int a;
        int b;
        int test[0];  //不占内存大小
    };
    //  此时的 8 这个位置,可以方便的自加
	struct ZeroArrTest *p = (struct ZeroArrTest *)malloc(sizeof(struct ZeroArrTest) + 8);
	p->a = 1;
	p->b = 2;
	p->test[0] = 3;
	p->test[1] = 4;  		   //在malloc时,后面的8 为两个int类型数据,一般用sizeof(int)*2
	int nub = *(&p->b + 1);     //nub = 3;
	sizeof(struct ZeroArrTest); //8
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MECHT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值