指针解析(2)

对指针解析(1)总结

上节我们介绍了创建指针变量和指针变量类型及其意义,指针加减整数和指针减指针的内容,还有一个小小的应用。

指针变量是存储地址的变量,需要用到&符号,名叫取地址操作符,创建指针变量时需要给定变量类型变量名,和赋初值,如果不赋初值会导致一个野指针的问题,关于野指针,本节会介绍到。

void fun(int a, int b)
{
	return a - b;
}

int main()
{
	int a = 1;
	int* i = &a;     //整型指针变量

	char* c = NULL;  //字符指针变量,不知道赋值什么时,可以给一个NULL指针

	int arr[5] = { 0 };
	int(*arr)[5] = &arr;   //数组指针,

	void (*pf)(int,int) = fun;   //函数指针
	return 0;
}

对于上面代码,列举了一些简单的指针变量,最后两个指针类型后面会介绍到。

指针变量的类型的意义是:
1,决定指针进行解引用操作访问的字节数目。
2,决定指针进行加减操作跳过的字节数目。

举个例子

int main()
{
	int a = 0x11223344;          
	int* p = &a;                  //p是一个整型指针变量,那么解引用会访问四个字节
	char* p1 = (char*)&a;        //p1是一个字符指针变量,解引用会访问一个字节
	printf("%x\n%x", *p, *p1);   //%x是打印十六进制的数字 打印结果应该是11223344和44
	return 0;                        
}

这个代码可以说明意义一,如果有的小伙伴打印结果是11223344和11的话,说明你的电脑在进行数据的存储时是按照大端字节序的方式存储,关于大小端的介绍大家可以看这一篇[关于大小端的介绍],里面对大小端进行了介绍以及如何测试大小端都有介绍。(https://blog.csdn.net/qq_74245477/article/details/134908862?spm=1001.2014.3001.5501)

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;                 //这里没有加&的原因是:数组名是首元素的地址
	for (int i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));  //p是一个整型指针,加1会跳过四个字节,也就是一个整型
		                          //那么p+i,会跳过i*4个字节,而arr是一个存储整型数据的数组
		                          //那么每个数据就是四个字节,指针加整数可以跳过数据,在使用循环就可以打印数组内容了
	}
	return 0;
}

对意义二也进行了举例

对于指针减指针,这里要纠正一下指针解析(一)里的一个错误,指针相减的结果的绝对值是指针之间元素的个数,因为元素个数总不能有负的吧。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;                  //首元素的地址
	int* p1 = &arr[9];             //尾元素的地址 
	printf("%Id\n", p1-p);         //打印9
	printf("%Id ", p-p1);          //打印-9
	return 0;                      //指针减指针的结果的绝对值是两个指针相差的元素个数
}

对上节内容就复习到这里了,接下来介绍新的内容

一,二级指针

对于变量都有地址,可以创建指针变量存储它们的地址,那么指针变量也是变量,存储指针变量的地址的指针叫什么呢?哎,就是二级指针了

int main()
{
	int a = 1;
	int* p = &a;       //p存储的是整型变量的地址,属于一级指针
	int** pp = &p;     //pp存储的是一级指针变量的地址,属于二级指针

	return 0;
}

同样,我们解释一下代码的含义,int** pp=&p这句代码,第二个*的含义是说明pp是一个指针变量,第一个*和int组合在一起成为int*,说明pp这个指针变量存储的是一级指针变量的地址。

像这样的,存储一级指针变量地址的指针变量称为二级指针。那么存储二级指针变量地址的指针变量就称为三级指针,二级以上的指针称为多级指针。
不过三级及以上的指针用的很少,大家了解下就可以啦。

二,assert断言

assert断言是一种判断错误的一种方式,但是比较暴力,当出现某些不确定或者我们不可掌握的错误,会选择使用这种方式。
函数原型是

void assert (int expression);

括号内部是一个整型表达式,表达式的值为真,则无事发生,如果为假,则会报出错误并中止程序。使用时需要包含一个头文件assert.h

#include<assert.h>
int main()
{
	int* a = NULL;
	assert(a != NULL);
	return 0;
}

上面代码就会报出错误,NULL!=NULL显然为假。
也可以是如下的形式使用

#include<assert.h>
int main()
{
	int* a = NULL;
	assert(a == NULL);    //也可以是下面的方法
	if (a == NULL)
	{
		//.....
		exit();    //代表终止程序
	}

	int i = 5;
	int b = 10;
	assert(i < b);
	printf("%d", i);
	return 0;
}

assert断言和if语句判断效果类似,但是断言会报出详细的错误出处,而if语句判断只会终止程序,上面代码是不会打印i的值的。

三,野指针

野指针是指指向不确定的空间的指针。

野指针的成因:
1.指针没有初始化
2.指针越界访问
3.返回局部变量的地址
4.指向已经释放的空间
接下来对这几种情况进行举例。

int* fun()
{
	int a = 10;
	return &a;
}

int main()
{
	int* a;

	int arr[10] = { 0 };
	int* p = arr;
	for (int i = 0; i <= 10; i++)
	{
		printf("%d ", *p);
		p++;
	}

	int* pf = fun();     
	printf("\n");        //作用是覆盖上一层栈帧
	printf("%d", *pf);   //这里并不会打印10,打印的是随机值
}

a是一个未初始化的指针变量,如果打印a指向的值,vs就会报错,因为不知道a是指向哪一块空间的。

p是一个指向数组的指针,打印后进行p++操作,依次指向数组的每一个元素,依次打印,但是数组只有十个元素,p++了十一次,会访问数组后面的那四个字节的内容,这一块内容是不属于数组的,如果还去访问就会造成越界。

最后一个返回了fun函数内的a的地址,在通过pf接收,打印,这样pf就是一个野指针。局部变量出了作用域就销毁了,内存就还给操作系统了,这样再去访问就构成非法访问。

关于最后的指向已经释放的空间,后期解释动态内存开辟时会介绍。

那么如何规避野指针呢?
首先一定要初始化,如果不知道初始化内容,则可以给NULL指针。二,小心指针越界。三,避免返回局部变量的地址。四,在使用指针之前进行检查,方法就是上面的assert断言和if语句判断了。

本小节就介绍这些了,下节见。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值