【C语言】— 带你玩转指针(上)

前言


大家好呀,今天将给大家分享关于指针的一系列知识。指针是整个C语言里的非常非常重要的一个概念,并且也是整个C语言学习的难点,涉及非常广泛而且它的用途不言而喻,所以说学好了指针将对我们后续的学习带来很大的帮助。今天这篇文章就将详细介绍指针相关的各种要点带你玩转指针,那我们就一起走进这个篇章吧!

1.什么是指针

什么是指针?

理解指针我们需要知道两点:

1.内存中有一个个小的单元,而计算机会对每一个小单元进行编号叫做内存编号,这个内存编号就是计算机中每个内存单元的地址,内存中每个单元都有唯一确定的地址。用来存放变量的地址需要一个特殊类型的变量,那就是指针。

2.我们常说的指针指的是指针变量,指针变量本质上是一个变量,与其他变量不一样的是,指针变量是专门用来存放变量的地址值的变量。

总的来说,指针就是地址,而我们常说的指针就是指针变量。

举个栗子:

我们在住酒店时候都知道,我们要找到我们的房间需要通过房间的房号来确定,这栋酒店就好比一台计算机,酒店每一个房间都是一个计算机的内存单元,我们要申请一个房间,假设我们的房间号是8304,房间号就是地址,那么指针就指向我们这个房间号,我们可以通过指针指向的房间号找到我们申请的房间。

1.1指针变量

指针变量的形式为: 类型关键字 *指针变量名     

所以说,其中,这里类型关键字代表着指针变量要指向的变量的数据类型。如 int *pa;我们从前往后读就pa是一个指针变量,它指向了一个整型类型的变量。我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址存放到指针变量中。

在32位机器中,假设有32根地址线,那么编址的时候产生高电压和低电压就是由32位1或者0组成的二进制序列地址,那地址就以4个字节来存储,一个指针变量的大小就是4个字节。

而在64位机器中,假设有64根地址线,那么编址的时候产生高电压和低电压就是由64位1或者0组成的二进制序列地址,那地址就以8个字节来存储,一个指针变量的大小就是4个字节。

指针的大小在32位平台是4个字节,在64位平台是8个字节

int main()
{
 int a = 20;
 int *p = &a;//这里我们对变量a,取出它的地址。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
 return 0;
}

1.2指针类型

我们都知道,变量有不同的类型。整型,字符型等,而指针变量存放着这些变量的地址也有着不同的类型。char* 类型的指针是为了存放 char 类型变量的地址;short* 类型的指针是为了存放 short 类型变量的地址;而int* 类型的指针是为了存放 int 类型变量的地址,以此类推。在指针类型中也有一种无具体类型的指针——void*类型,表示可以来接受任意类型的地址,我们在写函数传参时候不知道接收到什么类型的参数所以经常会使用到。这里我们要注意,指针变量所占的大小是与类型无关的,指针变量的大小都是固定的,指针的大小在32位平台是4个字节,在64位平台是8个字节

int main()
{
   char  *pc = &a;
   int   *pi = &b;
   short *p1 = &c;
   long  *p2 = &d;
   float *pf = &f;
   double *pd = NULL;
 
   return 0;
}

1.3指针的解引用

我们这里给一段代码,然后我们调试起来,通过观察内存变化来解释指针的解引用操作,我们这里定义一个16进制的数方便内存观察,int类型占个字节,其地址我们就用int*的指针接收,最后我们把a的值修改为0,调试之后发现,4个字节每一个字节都被修改了,值被该成了0。

cbd2d43a31744444ba254e98212aa655.png

96df5a10c47640b59377b36b4c6fe621.png

接下来定义一个char*的指针,将a的地址强转成char*,char类型占1个字节,调试起来我们发现只有一个字节被修改,其他三个字节不变。

ee2782cfc0024b28b02705a944b8340a.png

e8d2698b88b64209b7f861395bbf2be5.png

因此我们发现,指针的类型决定了对指针解引用的时候有多大的权限,即能访问几个字节。char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字节。

1.4const修饰

我们在创建变量时候都是可以随时修改的,指针变量作为一种特殊的变量在我们解引用之后也可以通过指针修改所指变量的内容。const是一个C语言的关键字,具有着举足轻重的地位。它限定一个变量不允许被改变,产生静态作用。const修饰变量在一定程度上可以提高程序的安全性和可靠性。C语言中 const 修饰指针需要特别注意,共有两种形式,一种是用来限定指向空间的值不可修改;另一种是限定指针不可修改,如下:

int main()
{
	int a = 10;
	int b = 20;
	//1.const在*右边,表示指针指向的变量不可以修改
	//但指针指向的变量的内容可修改
	int *const p = &a;
	//p = &b;//error
	
	//2.const在*左边,表示指针指向的变量的内容不可以修改
	//但指针指向的变量可修改
	int const* p = &a;
	//*p = 15;//error

	//3.const在*右边和左边,表示指针指向的变量的内容不可以修改
	//指针指向的变量也不可修改
	int const*const p = &a;
	//p = &b;//error
	//*p = 15;//error

	//4.const在int*p左边,表示指针指向的变量的内容不可以修改
	//指针指向的变量也不可修改
	const int* p = &a;
	//p = &b;//error
	//*p = 15;//error

	return 0;
}

2.指针运算

我们知道了指针的类型,那我们能进行什么操作呢?

2.1指针加(减)整数

我们通过一个代码来讲解:

 90eab13a4c7543b4bc12724a085dc05a.png

因此,指针的类型决定了指针能向前或者向后跳过几个字节(距离)。跳过的是n*sizeof(type)这么多个字节。

2.2指针减指针

指针就是地址,指针和指针之间也是能进行运算的,而指针-指针就表示两个指针之间元素的个数,我们以my_strlen这个函数为例:

bee72abc665a4a0fb519c5c771cef976.png

注意:

指针-指针的前提条件是两个指针必须指向同一块空间,因此指针-指针操作只能是在同类型指针变量相减,不同类型之间不可以相减,如上代码都是char*类型,我们可以算出元素个数,不同类型之间无法实现。

2.3指针的关系运算

我们说指针就是地址,地址也是有大小之分的,而指针和指针之间可以比较大小关系,就是指针的关系运算。比如代码:当p1的值小于p2的值时,就打印p1所指向的值。

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p1 = &arr;
	int* p2 = &arr[4];
	while (p1 <= p2)//指针大小的比较
	{
		printf("%d ", *p1);
		p1++;
	}
	return 0;
}

 注意:C语言规定允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

3.野指针

3.1什么是野指针

野指针,是不是感觉听名字就知道像野孩子一样,野指针顾名思义就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),也是个非常危险的存在。

 为什么会造成野指针呢?主要有两点原因:

1.指针未初始化

(初始化内容在函数栈帧那篇文章中详细讲过,不初始化全是0xCCCCC.....)

int main()
{ 
 int *pa;//局部变量指针未初始化,默认为随机值
    *pa = 10;
 return 0;
}

2.指针越界访问

int main()
{
    int arr[10] = {0};
    int *p = arr; //数组名表示首元素地址
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0;
}

3.指针指向的空间被释放

int* test()
{
	int a = 10; //a是局部变量
	//a的空间由函数创建,出了函数空间还给操作系统
	return &a;
}
int main()
{
	int* p = test();//此时p保存了带回的地址,但地址不属于我们当前程序
	printf("%d\n", *p);//若想访问就是非法操作,p为野指针

	return 0;
}

此外,还可能使动态内存释放时未合理置空NULL造成野指针。

3.2如何避免野指针

1. 一定要初始化指针,不知道指针初始化为何值也可以置空NULL;
2. 小心指针越界访问,合理设置;
3. 指针指向空间释放,及时置NULL;
4. 避免返回局部变量的地址,如上代码;
5. 指针使用之前检查有效性;

4.指针和数组

4.1指针和数组的关系

这里我们思考,指针和数组之间有什么关系呢?

他们之间有着很大区别,我们前面讲过指针就是地址,而我们常说的指针就是指针变量。指针变量的大小是4/8个字节,大小与类型无关。

数组是存放一些相同或不同类型元素的集合,内存中会为数组开辟一块连续的空间,数组大小可以根据需要来定义,但它不是指针。

他们之间也有着联系,数组名就是数组首元素的地址,就是arr[0]的地址,所以我们可以得到一个关系,数组名就是地址,地址又是指针,可以通过指针访问数组,所以如下代码这里通过数组名访问数组和通过指针访问数组是一样的。

b60416ce504441f0b6dccb7e4460f5c6.png

注意:数组名就是数组首元素的地址,但是由两个特例:

1.sizeof(数组名),计算出的是整个数组的大小,单位是字节。

2.&数组名,取出的是整个数组的地址,数组名表示整个数组。

除此2种情况之外,其余的地方见到了数组名都表示数组首元素的地址。

4.2二级指针

既然知道了指针变量也是变量,那创建变量就会有地址,指针变量的地址存放在哪里? 我们调试观察&p,发现p这个指针变量有自己的地址,我们这里假设用pp来接收&p,pp也是指针变量,这就是二级指针,类型是int**,用户来接受一级指针的地址。因此我们可以推断如果&pp的话,也会有自己的地址,就是三级指针,来接受二级指针地址,以此类推。

ac9766d9053241ce9524f5b3b4a890bb.png

c83a515640084973a54a6200c61e60df.png

同样的,当我们对pp解引用时候,我们就可以得到p,对p解引用,同样可以访问变量a。

5.结语

今天的分享到这里就要结束了,本篇给大家初步讲解了指针的相关内容,知道了什么是指针,指针可以进行什么样的操作,也讲了野指针的成因,介绍了指针和数组之间的关系。其实指针的知识体系非常庞大,内容复杂,后续还会接着为大家带来新的内容,如果你觉得本篇内容对你有所帮助,那就多多支持我吧!

9f5601f342cc4b88aa69bb52100ea3a7.gif

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值