c【语言】了解指针,爱上指针(2)

1.野指针

各位读者们,你们想一想,野狗是不是居无定所,到处游走?从人的身边经过搞不好还会来上一口?野指针就好比是一条野狗,也是非常危险的。

野指针的概念

指针指向的位置是不可知的,随机的,不确定的

野指针的成因

  • 指针未初始化
  • 指针越界访问
  • 指针指向的空间被释放

指针未初始化

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

此时,指针p未初始化,值是随机的。

指针的越绝访问

#include<stdio.h>
int main()
{
int arr[5]={0};
int *p=arr;
int i=0;
for(i=0;i<=5;i++)
{
	printf("%d ",*(p+i));
}
return 0;
}

数组下标是从0~4,但是指针越界访问到了下标为5的地方,这就形成了野指针。

指针指向的空间被释放

#include<stdio.h>

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

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

n是创建在test函数下的局部变量,它的生命周期只存在于test函数的范围内,当test函数调用结束后,n的空间就会被释放。在vs下运行这段代码,可以正常运行,并输出n的值,但是会报warning
在这里插入图片描述
将这段代码放到sublime text下运行:
也会报warning,但是程序无法正常运行,不会输出n的值

在这里插入图片描述在这里插入图片描述
指针指向一个被释放的空间,就好比“你某天在123宾馆开了间房,住了一晚,第二天就把房退了,但是你和你朋友说:我在123宾馆开了间房,你可以过来住。你朋友在你退完房后来到了这间房内”这放到现实生活中是不是就属于非法访问?是违法的。

既然野指针这么危险,那我们因该怎么规避呢?

2.如何规避野指针

野指针是非常危险的,为了规避野指针,我们需要注意以下几点:

  • 指针初始化
  • 避免野指针的越界访问
  • 不使用指针时及时赋值NULL
  • 避免返回局部变量的地址

当我们暂时使用不到某个指针时,要及时的赋值NULL:

#include<stdio.h>
int main()
{
int a[5]={0};
int *p=a;
for(int i=0;i<5;i++)
{
	printf("%d ",*(p+i));
}
//此时p已经访问完数组a的空间,要及时置NULL
p=NULL;

//再使用p时,要重新指向一个地址
int arr[1]={1};
p=arr;
return 0;
}

3.assert断言

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

#include<stdio.h>
#include<assert.h>

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

当p为真时,程序正常运行,当p为假时(NULL),报错,显示没有通过的表达式,以及包含这个表达式的文件名与行号
放到vs下运行试一试:
在这里插入图片描述
assert对程序员是非常友好的,使用assert有几个好处

它不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭assert()
的机制。果已经确认程序没有问题,不需要再做断⾔,就在#include <assert.h> 语句的前⾯,定义⼀个宏“NDEBUG”

在这里插入图片描述

假设,我们就是需要p为NULL,此时我们不需要再assert(),就定义一个宏"NDEBUG",运行和后发现assert()不再起效果了。

assert()也是优缺点的:

引入了额外的检查,增加了程序的运行时间

一般我们是在debug版本下使用,在release版本下assert会被直接优化掉。

34指针的使用和传址调用

strlen的模拟实现

库函数strlen的功能是求字符串长度,计算在’\0’前的字符个数
函数原型: size_t strlen ( const char * str );
我们用自己的代码模拟实现:

#include<stdio.h>

size_t my_strlen(const char*s1)
{

	char*start=s1;//用start记录s1的首元素地址
	while(*s1++)
		;
	return s1-start-1;//两个指针相减,求指针之间的元素个数
	//因为在while循环的条件判断下,当*s1为'\0'时不会进入循环,但是有++的存在,s1还是会向后移动一位,所以在返回时需要多减一个1
}

int main()
{
	char s1[]={"abcdef"};
	size_t ret=my_strlen(s1);//将字符数组的首元素地址传入
	printf("%zd",ret);
	return 0;
}

传值调用和传址调用

有的读者想问”为什么要学习指针?是有什么问题非指针不可的吗?“

不急,我们接下去看

我们定义两个变量x与y,通过调用函数swap实现两个值的交换。

#include<stdio.h>

void swap(int x,int y)
{
int tmp=x;
x=y;
y=tmp;
}

int main()
{
int x=10;
int y=20;
swap(x,y);
printf("x=%d y=%d",x,y);
return 0;
}

当我们放到ide下运行时,会发现值根本没有交换:
在这里插入图片描述
其中的原因就在于你向函数传参时,传的只是一个值,在函数内部,由形参接收这个值,但是形参是另外开辟一个空间用于存放传入的值的

我们通过调试,来观察他们的内存地址,我们便于观察,我们把形参改成x1与y1
在这里插入图片描述
虽然实参x、y的值与形参x1、y1的值是相等的,但是他们的地址是不一样的,或者说,我们可以把形参理解为:

形参是实参的一份临时拷贝

因为形参是创建在函数swap内部的,所以它们的生命周期只存在于swap函数内部,值的交换也只对形参有用,当函数调用结束后,形参就会销毁,实参x、y的值不受到影响。
这便是传值调用

那我们有没有什么办法,可以通过swap函数交换实参的值?有,用指针。

我们将代码修改一下,将形参改为int*类型的指针,传入的参数改为传入实参的地址。我们运行后便会发现,两个实参的值被成功交换了。

#include<stdio.h>

void swap(int *x,int *y)
{
int tmp=*x;
*x=*y;
*y=tmp;
}

int main()
{
int x=10;
int y=20;
swap(&x,&y);
printf("x=%d y=%d",x,y);
return 0;
}

在这里插入图片描述
这便是传址调用

所以,想要让函数与主调函数建立真正的联系,就要用传址调用;如果函数只是需要主调函数的值来实现计算,那么就用传值调用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值