深入理解指针(1)

1. 什么是指针

指针英文名pointer,因此通常用p来表示。指针是一种存放内存的东西。

最简单的指针:

其中 在定义指针变量时需要使用 ‘*’这个符号,这个符号叫做解引用操作符。(&)叫做取地址操作符。第二行代码的意思就是将a的地址取出放到指针变量p中去。

1.1 如何理解指针类型

int * pa = &a; pa是指针变量名,* 说明pa是一个指针变量,int 说明 指针变量pa指向的是整型(int)类型的对象。

1.1.3 解引用操作符 ‘*’

我们已经在第二行将 a的地址赋给了pa,通过解引用操作符*找到了pa指向的地址即a,然后将其赋值为10;这便是解引用操作符的作用--通过指针中的地址,找到指针指向的空间。

2. 指针的基本相关概念

2.1 const修饰指针

const 指常量的,不变的。它修饰指针有三种不同的修饰方法。

2.1.1 const 修饰 p

4

const 单独放在p的前面,意味着指针p所指向的地址不能变。怎么更好理解呢? const放在p的前面,p的内容是什么?p的内容是地址,因此p的内容不能改变 ,不然就像最后一行那样,会报错。

2.1.2 const修饰 * p

const 放在 * p的前面,意味着指针p所指向的地址的值不能变。怎么更好理解呢? const放在* p的前面,* p的内容是什么?p的内容是a的值,因此* p的内容不能改变 ,不然就像倒数第二行那样,会报错。

2.1.3 const 修饰全部

const将全部都修饰,通过前面两个说明,我们很容易理解,p保存的地址不能改变,p指向的值也不能改变。倒数两行都报错

2.2 指针的各种类型

指针的类型包括整型指针,字符指针,函数指针,数组指针,void* 等。

2.2.1 指针的运算

2.2.1.1 指针+ / - 整数

指针的类型决定了指针 + /  -整数的地址变化大小。

 从图中可以看出来,char *类型的指针+1之后,地址变化了1个字节,而 int *类型的指针+1后地址变为了4个字节。

一般的指针类型都可以进行+/-整数的运算,但有一种特殊的类型,void* 不可以进行运算。

2.2.1.1.1 void * 指针

void * 指针又称泛型指针,可以接受来自任何类型的地址。我们知道,在给指针给予地址的时候,需要指针的类型与地址指向的数据的类型相同才好赋值,不然可能会出现不兼容的现象。我们的void * 型指针就完美的解决了这个问题,但它的问题就是不能够进行指针计算。这里说的指针计算不止是指针加减整数,还包括指针之间的加减。

2.2.1.2 指针之间的减法运算

指针-指针 :表示两个指针之间的数据的个数

如图所示,p1为首元素的地址,p2为第四个元素的地址,因为地址由小变大,因此用p2减去p1,再输出的结果为3.这里的3是包含了首地址的元素,但是不包含p2地址的元素的

2.2.1.3 指针之间的关系运算

指针之间是可以进行比较的,但仅限于同一个数组的指针进行比较,不然没有意义。如下图的代码:

图中while循环的条件就是p的地址小于arr + size,当满足条件时进行循环体运算。输出的地址是16进制的地址,每一个地址之间差了4,也就是int 的类型大小。

2.3 野指针

野指针就是没有指向一个确切地址的指针。野指针的来源有多个,如指针未初始化,指针越界问题,以及指针指向的了已经被释放的空间。

2.3.1 未初始化的野指针

指针定义时如果没有初始化,将不能使用。

2.3.2 指针越界访问的问题

当指针指向的范围超过一个数组的范围时,p成为野指针。当越界时,系统会报错

2.3.3 指针指向已被释放的内存

当内存被释放之后,指针指向该空间时,如同指向了一个不存在的空间,就会变成野指针。如函数返回局部变量,当函数结束时内存已经被释放。

2.3.4 避免产生野指针

知道了野指针的产生原因,我们需要采用对应的方法去避免产生野指针。

对于未初始化的野指针,我们需要在定义指针时就将其初始化。

对于越界的野指针,我们需要避免其越界,就需要检查我们的代码,避免其越界。

对于指针指向释放内存的野指针,我们需要尽量不返回局部变量的地址。

2.3.5 assert判断

assert:断言,用于判断是否程序满足条件,不满足条件会报错。需要包含头文件<assert.h>。

我们这里便运用assert判断我们的指针是否为空指针。

当assert的条件满足时,便会报错并中止程序。

当assert条件不满足时,便不会报错。

当程序确定没有问题时,再在#include<assert.h>前面加上#define NDEBUG,便可以自动跳过所有的assert语句。节约资源。

assert只能用于debug模式。

2.4 传址调用与传值调用

我们需要知道,实参传递给形参时,传递的只是实参的一份临时拷贝。在形参中改变实参的值并不会影响到实参。因此传参的时候分为传址调用与传值调用,只有当传址调用时才会影响到实参的值。

例如,想要交换两个函数的值,有两种写法。分别是传址和传值。

传值:

这里使用第一种传值调用,可以看到a,b的值实际上并没有交换。

传址:

这里使用了传址调用,将a,b的地址进行调用,就可交换a,b的值了。

  • 51
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值