指针的初步认识2

上篇博客,我们介绍了指针是什么还有分解的指针变量,这篇博客,我们将介绍指针的运算和野指针。

指针的运算类型

指针也是可以进行运算的,有以下三种运算类型:

·指针+/-整数

·指针-指针

·指针的关系运算

指针+/-整数

我们可以先通过一个代码来观察指针+/-整数的变化

int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("&n = %p\n",&n );
	printf("pc = %p\n",pc );
	printf("pc+1 = %p\n",pc+1 );
	printf("pi = %p\n",pi );
	printf("pi+1 = %p\n",pi+1 );



	return 0;
}

 在这串代码中,我们定义了两种类型的指针变量,在指针解引用的过程中,指针类型决定了解引用时一次能操作多少字节,因此,当int*类型的指针变量加一时,应跳过四个字节,同理,char*类型加一时跳过一个字节,那么,程序运行的结果应该是这样的

我们可以得出结论,指针的类型决定了指针向前或者向后走一步的距离有多大

指针-指针

指针-指针的类型,我们可以通过模拟一个库函数来认识。

strlen函数,这个函数是用来计算字符串长度的,使用方法就像下面这串代码

int main()
{
	char str[] = "abcdef";
	int count = strlen(str);
	printf("%d", count);
	return 0;
}

字符串的长度是6,那么程序打印的结果也应该是6,我们来看看运行的结果

通过strlen函数我们很容易就能得到字符串长度(在使用strlen函数前得包含头文件string.h),现在我们就需要模拟strlen函数

int my_strlen(char* s)
{
	char *p = s;
	while(*p != '\0')
		p++;
	return p - s;
}

int main()
{
	printf("%d", my_strlen("abc"));
	return 0;
}

这串代码中,函数实参传过去的是字符串abc的起始地址,那么形参就用了char*类型来定义s,后又定义了一个char*类型的指针变量p,是为了通过p来改变指针,直到遇到\0停止,这样s保存的地址没有变化,而p的地址已经是字符串\0之前所处的地址了,因此,这时候用指针变量p减去指针变量s,就能得到两个指针之间的距离,以此来计算出字符串的长度。

指针的关系运算

先看一串代码

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)//指针大小的比较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

这段代码里,我们进行了一段指针大小的比较,while循环中,p<arr+sz,这是两个指针的比较,(注意:arr在这里指向的是数组第一项的地址,是一个指针),这样,我们就能打印出数组的所有项了。

野指针的成因与规避方法

在程序中,有很多情况会出现野指针,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

想要规避野指针,我们就得先认识有可能生成野指针的情况

野指针的成因

指针未初始化

如:

int main()
{
	int* p;
	*p = 20;
	return 0;
}

从中可以看出,指针变量p未进行初始化,系统就会默认给个随机值,形成野指针。

指针越界访问

int main()
{
	int arr[10] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;
	}

	return 0;
}

当指针指向的范围超出arr的范围时,就会形成野指针。

指针指向的空间释放

int* test()
{
	int n = 100;
	return &n;
}

int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

如何规避野指针

指针初始化

当我们已经明确指针指向哪里时,就需要直接赋地址,否则会出现野指针的情况,若不明确目标,则可以赋值空指针,即NULL。无论如何,一定要进行初始化,情况如下

int main()
{
	int n = 10;
	int *p1 = &n;
	int* p2 = NULL;
	return 0;
}

其中,p1指针目标是存放n的地址,因此初始化取地址n,p2没有目标地址,则初始化空指针

小心指针越界

注意指针不能超出访问范围,超出了就是越界访问,容易出现野指针

及时置NULL

当我们不需要再用到一个指针时,及时置于空指针,以此来防止野指针的出现。

还需注意的是,每次使用指针之前,我们需要检查的有效性。

这时候,就可以使用到C语言中的一个关键字:assert

assert断言

assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行,常常被称为“断言”。

如:

assert(p != NULL);

assert()宏接受一个表达式作为参数。如果该表达式为真(返回值非零),assert()不会产生任何坐拥,程序继续运行。如果该表达式为假(返回值为零),assert()就会报错。在标准错误流stderr中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

int main()
{
	int n = 20;
	int* p = &n;
	p = NULL;
	assert(p != NULL);
	printf("%d", *p);
	return 0;
}

运行一下上面这条代码,得到这样的结果

可以看到,程序返回了assert所在的行数和文件,很方便得反映出程序的问题所在。

The End

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值