【C语言篇】保姆级详解指针|多图解,包懂,让天下没有难懂的指针

在这里插入图片描述

🌱博主简介:是瑶瑶子啦,一名大一计科生,目前在努力学习C进阶,JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛!
📜所属专栏:C/C++
✈往期博文回顾:进入内存,透彻理解数据类型存在的意义,整形在内存中存储,大小端字节序,浮点型在内存中存储
🕵️‍♂️近期目标:成为百粉小博主。持续输出:C进阶、JavaSE,数据结构、算法相关的优质博客,
🙇‍♀️写博客理念:力求用通俗语言去阐述知识知识、技术。喜欢画图、思维导图去描述过程和知识之间的联系。
🎡您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!
🌺:“再牛的程序员也是从小白开始,既然开始了,就全身心投入去学习技术”

🙆‍♀️Write int the front
:也许你是将将学C,而为指针百思不得其解的童鞋,也许你是已经玩转指针的大佬~但不管怎么样,我相信,看完这篇文章,一定会对你对指针的理解有很大很大的帮助!让你使用指针的每一步都有满满的安全感!😉话不多说,咱们即刻启程!🚇

Part1:初识指针–“平平无奇的门牌号罢了”

不知道你学指针的时候有没有这样的疑问,指针变量前面的这个"*"星号是什么,解引用的这个“*”和又是什么,和前者是一样的吗?初次听到“指针”这个“高级名词”是不是也“不寒而栗”(好高级的名字,而且别人都说指针最难学啦–瑶瑶子的亲身感受哈哈)。学校里面老师说“这是行指针,这是列指针,记下来吧,没有别的办法”–(碎碎念:瑶瑶子高程老师说的😅,我内心:???)

(真的没有骗大家,学校里的高程教材!!!老师居然让我把这些全部记下来?????)在这里插入图片描述
🕵️‍♀️这一part,瑶瑶子将带领大家,掀开指针的神秘面纱,通俗移动的解释清楚什么是指针!

1.1引入

  • 👧相信大部分同学都住宿过,住宿的时候,每一间宿舍都有一个门牌号。在这个宿舍楼里,你只要告诉我你的门牌号,我就能找到你的宿舍,并且可以访问你的宿舍(默认我有钥匙),并且可以对你宿舍里的物品进行我想要的处理。
    其实对于计算机的内存来说,你可以把它看作宿舍楼,而指针,其实就是门牌号,通过门牌号,就可以找到对应宿舍,而这个门牌号本质是什么呢?—地址
  • 内存地址是如何产生的?
    answer:我们总是听说,机器有32位,64位?这个32和64指的是什么?其实是地址线,(类似于电线),有两种状态(高低电压),用1,0来表示,在这32/64根地址线运作的过程中,能产生32/64位的0/1序列,每一个序列用来标识内存中最小内存单元(1byte)空间的地址。(32位平台保存一个地址,即0/1序列,需要32bit,4字节,64位需要64bit,8字节)–存储地址所用内存的大小由机器而定。
    在这里插入图片描述

1.2指针&指针变量

🎃虽然上面一直在说指针就是地址,它也确确实实是地址,但是我们更多的时候,说的这个“指针”,指的是指针变量.

  • 什么是指针变量?
    🕋变量,其实就是一个容器,用来存放数据。通过其数据类型来规范变量所占内存空间大小和计算机看待变量的视角以及能对变量进行的操作。⭐(这句话很重要,要好好理解,下面讲的东西其实核心就是这句话!)
    🥙指针变量:固然是存放指针的变量,因为指针其实就是地址,所以,指针变量,就是用来存放地址的变量.

如何理解" * "?

🌟通常," * "会在两种情景下出现:

1.指针变量声明时:
int a=0;
int *p=&a;
 2. 指针被解引用时
int *p=&a;
*p=2;

1,指针变量声明时:

  • * 此时只是一个 标识符,告诉计算机,这是一个存放地址的变量,同时可以被解引用操作符“*”操作(到时候可以通过我来找到我所执行的对象~), 只起说明作用。而把变量名和标识符除去,剩下的这个type,表示的是,p指针变量所指向的那个变量(对象)的类型。

在这里插入图片描述

2,指针变量前加*:

  • 在p变量前单独使用 * 时:此时的 * 是一个 运算符,作用是通过指针变量中的地址,找到所指向对象。间接访问所指向对象,具有 运算功能
    (此时的*p的值就是变量a)

👩‍🏫可以这样理解,比如一个教室里有学生和老师,一个叫小A的同学坐在3排5列的地方,老师想让小A起来回答问题,可以说:小A,你来回答以下问题(喊名字),也可以说:3排5列同学,你来回答一下问题(通过地址,间接找到A)。

Part2:指针变量也有“类型”

🙇‍♀️我们知道,指针变量里面存放的数据是地址,之前文章里我提到过,一个变量的类型同样也表示着所存数据的类型(所以通常讲嘛“XXX变量的数据类型是…")。but ,地址数据有类型吗?答案当然是没有的,地址数据,本身无类型可言,但是指针变量所指向的对象一定有确切的数据类型,由此通常将指针变量所指向对象的数据类型,称为指针变量的数据类型.(说是这么说,但是一般我们还是认为声明变量的时候,去除变量名,剩下的就是变量的数据类型)。

2.1指针变量类型的存在意义:

1,指针的±运算---------指针的运算是基于类型的

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
	int a = 0;
	int *pa = &a;
	printf("%p--> %p\n", pa,pa + 1);
	
	char b = 0;
	char *pb = &b;
	printf("%p--> %p", pb, pb + 1);

	return 0;
}

在这里插入图片描述
🍿:画图理解:
在这里插入图片描述

  • 🌟总结: 指针变量的数据类型,决定了指针变量±整数时,跨过的字节数(内存长度)。

2.2指针±指针

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main() {
	int a = 0;
	int *pa = &a;
	int* pa2 = pa + 1;
	printf("%d", pa2 - pa);//1
	
	return 0;
}

在这里插入图片描述

  • 🌟两个同类型指针相见结果是,两个指针间隔的元素个数。

2.3指针类型对解引用的影响

  • 🌞上面说到了,解引用操作是一种运算,作用是间接访问。但是,计算机是如何知道你要访问和操作多大空间呢?------根据指针的类型来确定。
int a=0;
int *pa=&a;
*a=1;

通过画图,我们来分析:
在这里插入图片描述

  • 🌟指针类型决定了解引用指针的时候,所能间接访问的内存空间,以及看待这份内存空间的视角。

🙆‍♀️在进行Part3之前,请你反问自己几个问题,如果都能回答上来,说明已经对指针有很大掌握了~

  • 普通变量的数据类型的意义是什么?
  • 指针变量类型的意义?

Part3:指针&数组之间的神秘联系

3.1关于数组,你必须知道的:

  • 数组名表示的是?–首元素地址(在main函数中被sizeof()和&操作除外)
  • 二维数组可以看作是一维数组。
  • 🌟arr[i]相当于*(arr+i);
  • 数组名在传参的时候退化成了指针

3.2关于数组,你必须明确的:

  • 数组名指向的是第一个元素的首元素地址(二维数组也看作是一维数组,数组的数组),地址可以用指针变量来接收。
  • 数组存放元素的类型。

3.3指针变量和数组建立联系:

🧙‍♂️有了指针,上面我们提到对于指针变量,有个特有的操作:解引用。所以通过对指针变量解引用,我们可以用指针变量来访问数组元素。

  • 😋举例:

❗注意:由于[ ]的优先级高于*.所以要用括号括起(*p)来表示p是指针。否则p先和[ ]结合,p就代表是个数组了。

int arr[3]={0,1,2};
int *pa=arr;
*(pa+2)=3;
printf("%d",a[2]);
//3

在这里插入图片描述

Part4:二级指针??

🙅‍♀️:碎碎念:这个名字听起来确实还太高级的,但是不要被它吓住了!!!在理清什么是二级指针之前,我们先理清以下概念:

  • 指针变量:(偏正结构),和任何变量一样,本质都是存放数据的容器,在内存中也需要开辟空间。
  • 指针变量的地址:既然指针变量要在内存中开辟空间,上面我们说到过,那么它也有自己的门牌号(地址)!
  • 指针变量的地址谁来存?既然是地址,那肯定有指针变量来存。(逻辑就是这样,一定要理清楚)。
  • 指针变量的定义时,去掉变量名和说明符*之后剩下的这个类型是?上面已经说到过,这是指针变量所指向对象的数据类型。
    在这里插入图片描述

🙆‍♀️弄清楚上面之后,二级指针,其实就是指针变量的地址!

int a=0;
int *pa=&a;
int **ppa=&pa;

在这里插入图片描述

Part5:指针数组:

🙆‍♀️首先,你需要明确以下几点:

  • 指针数组,是数组。
  • 指针数组存放的元素类型是?–指针

类比:char arr[10];arr是字符数组,存放元素类型是字符

int main()
{
	int *arr[5];
	//arr和5结合,说明arr是数组,该数组元素类型是int *
}

在这里插入图片描述

Part6:(常量)字符指针

🤹‍♀️在这之前,你需要知道的:

  • const char * p:常量字符指针,指向的变量类型是const char ,指向对象不能被修改。
  • char * const p:字符常量指针:指针变量p本身不能被修改。

6.1:常量字符串&字符指针之间的关系

🧜‍♂️引入:由于C语言中没有字符串类型,所以,常量字符串用常量字符指针来接收。
所以我们经常能看见:

const char *p="hello";

🧘‍♀️你需要知道的:

  • p存储的是常量字符串首字符的地址。
  • 不能通过指针p对字符串进行修改。
  • C/C++会把常量字符串储存在单独的内存区域中。当两个一模一样的常量字符串存在在程序中时,它们的地址其实是一样的。

Part7:数组指针:

🙆‍♀️首先,你必须明确的:

  • 数组指针,本质是指针。
  • 数组指针变量存放的是数组的地址。
  • 数组指针变量所指向的对象的类型是数组。

7.1数组指针实例及理解:

int main()
{
	char arr[2][3] = { {'a','b','c'},{'d','e','f'} };
	char(*parr)[3] = arr;
}

在这里插入图片描述
👩‍🎓你需要理解的:

  1. 首先arr是一个一维数组的数组。
  2. arr的首元素是arr1,即arr[1]
  3. arr的首元素地址是一维数组arr1的地址
  4. 一维数组的地址需要用一维数组指针变量来接收
  5. ✅parr是一个指针变量,去除说明*和变量名之后,该类型是所指向对象的类型–一维数组。

7.2:数组名 vs &数组名

int arr[3]={1,2,3};
int (*parr)[3]=&arr;
int *p=arr;

相同点

  • 均为指针

不同点

  • arr指向对象的类型是int,&arr指向对象类型是int [3]
  • arr+1跨过的字节数是4,(&arr)+1跨过的字节数是4*3

7.3:数组指针(行指针)怎么用?

💁‍♀️这里讲其中一个用法:使用行指针打印二维数组。

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

🤷‍♀️如何理解*(p+i)[j](对照上方的图,把char转成int就行)

  • *(p+i)找到了arr1(或arr2),即*(p+i)=arr1,p=&arr1(首元素地址)
  • 所以*(p+i)[j]相当于arr1[j]
    在这里插入图片描述

Part8:学会数组传参的全部方式!

💁‍♀️你必须明确的,什么类型变量可以接收数组传参

  • ✅数组传参,数组接收–形参为数组
  • ✅数组传参,指针接收(因为数组名表示啥?你自己说🤩)

8.1:一维数组传参:

int main()
{
	char arr[3]={'a','b','c'};
	test(arr);
	return 0;
}

😍3种接收方式:

  • void test (char arr[]);
  • void test (char arr[3]);
  • void test (char *arr);

8.2:二维数组传参:

int main()
{
	char arr[2][3]={'a','b','c','e','f','g'};
	test(arr);
	return 0;
}

😍3种接收方式:

  • void test(char (*arr) [3]);
  • void test(char arr[2][3]);
  • void test(char arr[][3];

🧙‍♂️:注意,二维数组传参,用二维数组来接收时 ,行参数可以省略,列参数不能省略。其实还是用之前的想法,把二维数组看作一维数组。那么数组的一个元素类型就是一维数组,不知道该一维数组长度(就是二维数组列长)就相当于不知道一个一维数组的数组元素的类型一样。

🌊易错!!

  • 为什么不能用char ** arr来接收?
    -答:始终要清楚,用指针来接收,你必须明确所指向的类型是不是一样的,因为指针所指向对象的类型确定了?(你自己说🤗)。而char *和char [3]是完全不同的两种数据类型!(起码所占内存的大小就不同~)。

Part9:函数也有指针!

🐳你需要知道的:

  • 函数名表示函数指针(函数地址)。

9.1函数指针这样声明:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int x, int y) {
	return x + y;
}
int main() {
	int (*p)(int, int) = add;
//返回类型   //形参1,2类型  
	return 0;
}

👼建议:表示指针时,最好都用(*p)这种括号来明确,否则指针变量一不小心就和其他符号结合了哟~

  • 利用函数指针来调用函数
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int x, int y) {
	return x + y;
}
int main() {
	int (*p)(int, int) = add;
//返回类型   //形参1,2类型  
	int ret=p(1,2);
	return 0;
}

😎write in the end
以上就是指针基础知识的全部内容了!!!
理解好了指针,用起来安全感也会up up up!
🙋‍♀️再也不会畏惧指针啦,相信你下次看到指针,内心:区区指针,你占多少字节,指向什么类型的变量,我都看的看的透透的!!
🙆‍♀️原创不易,画图不易,如果有帮助,还请多多点赞,关注,收藏,评论~咱们下期见💦

在这里插入图片描述

  • 54
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 36
    评论
引用\[1\]:C语言字节对齐问题详解中提到了C语言中的字节对齐问题。在结构体中,为了提高内存访问的效率,编译器会对结构体进行字节对齐。这意味着结构体的成员在内存中并不是紧凑排列的,而是按照一定的规则进行对齐。具体的对齐规则取决于编译器和编译选项。\[1\] 引用\[2\]:在C语言中,可以使用宏offsetof来获取结构体成员相对于结构体开头的字节偏移量。这个宏非常有用,可以帮助我们计算出每个结构体成员相对于结构体开头的偏移字节数。通过这个宏,我们可以更好地理解结构体的内存布局。\[2\] 引用\[3\]:在C语言中,指针和结构体的组合常常用于处理复杂的数据结构指针可以指向结构体的成员,通过指针可以方便地对结构体进行操作。指针和结构体的组合可以实现更灵活的数据处理和内存管理。\[3\] 综上所述,C语言中的指针结构体组合可以用于处理复杂的数据结构,而字节对齐问题则是在结构体中为了提高内存访问效率而进行的优化。通过使用宏offsetof,我们可以更好地理解结构体的内存布局。 #### 引用[.reference_title] - *1* *3* [结构体指针C语言结构体指针详解](https://blog.csdn.net/weixin_34069265/article/details/117110735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C语言之结构体详解](https://blog.csdn.net/m0_70749276/article/details/127061692)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是瑶瑶子啦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值