[C语言] 指针初阶

1.指针是什么?

结论:指针是地址,口语中所说的指针通常指指针变量

指针可以理解为内存中最小存储单元的编号,通过指针我们可以完成各种操作,如赋值。我们在编程时,每创建一个变量,完成一次运算,都会被电脑所记录,每个变量都会向内存申请空间,而每字节大小的空间都有相应的编号。

而指针变量就是通过创建一个变量把地址存储起来

int main()
{
	int a = 10;
	int* pa = &a;

	printf("%p\n", &a);
	return 0;
}

2.指针变量和指针类型

如果需要创建一个指针来存储地址,对于不同的数据类型,需要创建不同数据类型的指针

int main()
{
	int* pa;
	char* pb;
	//创建不同类型的指针
	printf("%zu\n", sizeof(pa)); //结果为8
	printf("%zu\n", sizeof(pb)); //结果为8
	return 0;
 }

但是通过 sizeof 这个操作符可以计算出不同数据类型的指针所占的内存一致,那我们何不直接规定一个 type* 来承接所有数据类型的地址呢?

原因有二,1.不同的数据类型的指针在访问数据时每一步所跳过的距离不一样

                  2.指针类型决定了解引用的权限有多大

让我们来先解释第一点

int main()
{
	int a = 3;

	int* pi = &a;
	char* pc = &a; //创建不同的指针类型用于比较不同

	printf("%p\n", &a);

	printf("%p\n", pi);
	printf("%p\n", pc);

	printf("%p\n", pi + 1);
	printf("%p\n", pc + 1);
	return 0;
}

可以看到,在打印地址时,不同的指针类型并没有差异,但将二者都+1 后,char * 的指针的编号只增加了1,而 int* 则加了4,这就是第一点不同。

对于第二点

int main()
{
	int a = 0x11223344;;
	int* pi = &a;
	char* pc = (char*)&a;
	            //a在内存中为 44 33 22 11
	*pc = 0;    //被改为      00 33 22 11  只能修改一个
	*pi = 0;    //            00 00 00 00  可以全部修改  

	return 0;
}

3.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1野指针成因

在写代码时我们应该规避野指针,那么野指针的成因有哪些呢?
1.指针未初始化
2.指针越界
3.指针指向的地址已被回收

1.指针未初始化

int main()
{
	int* p;
	*p = 2; //err
	return 0;
}

对一个未初始化的指针,进行赋值。映射到现实生活中,这件事的离谱程度简直就像你在大街上,随便找了一个酒店房间,进去睡了一晚上

2.指针越界

int main()
{
	int arr[10] = { 0 };

	for (int i = 0; i <= 10; i++) //err
	{
		arr[i] = 0;
	}
	return 0;
}

由于数组的下标是从0开始的,这个代码一共访问了11个元素,这就造成了指针越界,第11个地址为随机值

3.指针指向的地址已被回收

int* test()
{
	int a = 3;
	return &a;
} //出了这个函数以后,a作为局部变量,向内存申请的空间被回收

int main()
{
	int* pa = test();
	*pa = 20; //err
	return 0;
}

上面这个 错误案例就不那么容易被识别,大家在写代码时要特别注意

3.1 野指针规避方案

3.1.1 指针初始化

在写代码时,我们可以养成习惯去初始化指针,就像我们初始化变量一样

#include<stdio.h>
int main()
{
	int* pa = NULL;
	return 0;
}

指针的初始化只需在包含头文件<stdio.h> 的前提下,初始化为NULL即可

3.1.2检查有效性

在无法或难以判断的情况下,我们不妨为我们的代码加上一层保险

#include<stdio.h>
int main()
{
	int* p;
	if (*p != NULL)
	{
		p + 1;
	}
	return 0;
}

4. 指针运算

4.1 指针 - 整数

int main()
{
	int arr[5] = { 0 };
	int* p = arr;
	for (int i = 0; i < 5; i++)
	{
		*p = 1;
		*p++;
	} 
	//for循环完成后 arr[5] = {1,1,1,1,1}
	return 0;
}

通过观察发现指针与整数的运算,并非简单加和,而是让指针指向下一个数据,看到这里,不知道你是否会产生这样的疑惑

指针的本质不是地址吗?为什么让它与整数相加,会产生这样的结果

首先,能产生这样的疑惑非常好,需要注意的是,我们在这里创建了一个指针变量,变量是允许加和的,而在C语言的语法中,指针变量 +1 后,就移向下一个数据。

4.2 指针 - 指针

int my_strlen(char* s)
{
	char* p = s;      //为首元素地址创建副本
	while (*p != '\0')
	{
		p++;          //计算有第多少个元素
	}
	return p - s;
}

int main()
{
	char ch[] = { "abc" };
	int len = my_strlen(ch); //自己实现strlen函数
	printf("%d\n", len);     //结果为 3
	return 0;
}

可以看到我们可以通过指针来计算数组中的元素个数,你可能会感到疑惑,两个地址相减,怎么会得到这样的结果,这就是 王八的屁股 -  龟腚(规定)

4.3 指针的关系运算

int main()
{
	int arr[5] = { 0 };
	int* i;        //不能写&arr,因为arr代表的是这个数组的地址
	for (i = &arr[5]; i > &arr[0];)
	{
		*--i = 1;
	}
	//arr[5] = { 1,1,1,1,1 }
	return 0;
}

你可能对于上面代码的写法感到不习惯,为什么要从最后往前赋值呢?

那下面这个版本就是从前往后,尽管在大多数编译器上都是支持编译的,但是C语言的标准是并不允许这种写法代码

int main()
{
	int arr[5] = { 0 };
	int* i;
	for (i = &arr[0]; i < &arr[5]; *i--)
	{
		*i = 1;
	}
	for (int j = 0; j < 5; j++)
	{
		printf("%d ", arr[j]);
	} //err
	return 0;
}

如果你的电脑上安装了 VS2022 你可以试着运行一下,你就会发现这样的警告

5.指针和数组

读到这里,相信你对指针有了一定的了解,那我们就可以数组用指针来访问数组

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *p + i); //打印arr下标为i的元素
	}
	return 0;
}

这里的 arr 表示的是首元素的地址,详见“C语言数组从入门到精通”我的这篇博客

6.二级指针

指针变量作为一个变量,电脑一定会有相应的地址,那如果我们想读取它的地址,就涉及到了二级指针,二级指针应该怎么表示呢?

int main()
{
	int a = 2;
	int* pa = &a;    //数据类型* 名称
	int** ppa = &pa; 
	return 0;
}

指针的表示方式就是 数据类型* 名称 ,而二级指针需要取出一级指针的地址,一级指针的数据类型为 int* ,所以二级指针的表示形式就是  int*                 * ppa                                                                                                                      (数据类型)(表示该变量为指针)   

 大家有什么不懂的欢迎留言问我😊

                                                          

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值