深入了解指针(1)

练习1:多个字符从两端移动,向中间汇聚。

练习2:⼆分查找 在⼀个升序的数组中查找指定的数字n,很容易想到的⽅法就是遍历数组,但是这种⽅法效率⽐较低。 ⽐如我买了⼀双鞋,你好奇问我多少钱,我说不超过300元。你还是好奇,你想知道到底多少,我就让 你猜,你会怎么猜?你会1,2,3,4...这样猜吗?显然很慢;⼀般你都会猜中间数字,⽐如:150,然 后看⼤了还是⼩了,这就是⼆分查找,也叫折半查找。

在指针知识的讲解之前,先让我们进行一些知识铺垫

内存和地址:
CPU在处理数据时,需要的数据是在内存中读取的,处理后的数据也会放回内存中,而内存空间是将内存划分为一个个内存单元进行管理的,每个内存单元的大小取1个字节。

补充知识:计算机常见的单位:

一个比特位可以存储一个2进制的0或1

内存单元(1个字节)相对于每个人的宿舍(假设是8人间),而里面的8个学生就相对于8个比特位;

那么,指针就相当于宿舍的门牌号,有了指针(内存空间编号),CPU就能迅速找到对应的内存空间

所以我们可以这样理解:内存单元编号 == 地址 == 指针

指针变量和地址:

取地址操作符(&)

&a取出的是a所占4个字节(int类型占4个字节)中地址较小的字节

当我们知道了第一个字节地址,就能顺藤摸瓜访问4个字节的数据。

指针变量和解引用操作符(*)

指针变量:我们可以通过取地址符&获得数据的地址;数据的地址也是一种数据,存储在指针变量中。

指针变量也是一种变量,这种变量是用来存储地址的,存放在指针变量的值都会被理解为地址。

指针变量的类型:

解引用操作符(*)

我们使用指针变量存储地址,当我们需要使用时,我们使用解引用操作符(*)来获取指针所指向的数据

那为什么不直接写成  a = 0;为何要使用指针?他们的区别我们之后会讲解

 指针变量的大小:取决于地址的存放需要多大的空间,与指针的类型无关。

指针变量的⼤⼩和类型⽆关,只要是指针变量,在同⼀个平台下,⼤⼩都是⼀样的,为什么还要有各 种各样的指针类型呢?让我们来探究

指针的解引用:

从结果中可以看出,char*类型的指针+1每次跳过一个字节,而int*类型的指针+1每次跳过4个字节,由此可知:指针的类型决定了指针向前或者向后的一步有多大。

void*指针(泛型指针):无具体类型指针,可以接收任意类型地址,但是也有缺点,void*类型指针不能直接进行指针的+-整数和解引用的运算。

通常的使用的场景:void*是使用在函数参数的部分,用来接收不同类型数据的地址。使得一个函数可以处理多种类型的数据。

const修饰指针:

变量是允许被修改的,将变量的地址交给一个指针,通过这个指针也可以修改这个变量。当我们希望一个变量加上一些限制(不被随意修改时),不能被修改,我们可以使用const

在上述代码中,m是可以被修改的;n因为被const修饰,所以不能通过直接赋值方式修改

注意:虽然我们不能通过直接赋值的方式去修改n,但是我们可以使用指针,使用n的地址,去修改n就能做到修改n的操作,虽然这样是在打破语法规则(这种写法很危险,正式项目不推荐使用喔)。

我们来思考一下:为什么当变量n被const修饰后,就不能通过直接赋值来修改了;为什么通过指针,使用地址却还可以改变变量n的值?

因为const仅仅在语法上加了限制,但是变量还是变量,可以被修改的属性没有改变(变量就像是家里的东西,const是给门上了锁,但是小偷还是有可能通过其他方式(指针等)进入家里偷走东西)

被const修饰的变量,我们叫做常变量;

接下来,我们来看两个代码案例,来深入了解const修饰指针的情况

注意:p里面存放的是变量的地址;

p本身也是变量,有自己的地址;

*p是p指向的空间,即变量的值。

指针的运算:

指针 +- 整数:

上文已经提过,不再展开

指针 - 指针:

就是地址减去地址

指针的关系运算:指针比较大小(地址比较大小)

野指针:指向的位置不可知的指针(随机的、不正确的、没有明确限制的)

出现野指针的原因:

局部变量如果不初始化,变量的值是随机的;

全局变量和静态变量不初始化,变量的值默认值是0;

注意:在gcc编译器中代码不会报错,也还能跑(狗头),但是这种写法非常危险,指针的指向无法确定!!

规避野指针的方法:

1.指针初始化

2.小心指针越界

3.不使用指针时及时置NULL

4.避免返回局部变量的地址:如造成野指针的第三种情况

assert断言

上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提示。

assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣ 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误 流 stderr 中写⼊⼀条错误信息,显

⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

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

然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。如果程序⼜出现问题,可以移 除这条 #define NDBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语句。

assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。 ⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响⽤⼾使⽤时程序的效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值