指针的理解(初阶)

一.内存和地址

(1)内存

对于内存的理解,我们先举个生活中的例子——在一个大房子力找人;

就像将小白放在一个有100间没有房号的房间的房子里头,让小黑去寻找小白,最坏的情况,小黑要找100次才可以找到小白,这样的寻找效率非常低,如果房间有了门牌号,小黑便可以根据门牌号找到所对应门牌号的房间,找到小白。 

⽣活中,每个房间有了房间号,就能提⾼效率,能快速的找到房间。

其实生后如此,计算机也是如此。

在计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的 数据也会放回内存中,那我们买电脑的时候,电脑上内存为8GB/16GB/32GB等,那这些内存空间如何⾼效的管理呢?

其实也是把内存划分为⼀个个的内存单元,每个内存单元的大小为一个字节(Byte)

补充:(计算机中常见的单位)

其实,每个内存单元,相当于⼀个学⽣宿舍,⼀个字节空间⾥⾯能放8个⽐特位,就好⽐同学们住的⼋⼈间,每个⼈是⼀个⽐特位。
每个内存单元也都有⼀个编号(这个编号就相当于宿舍房间的⻔牌号),有了这个内存单元的编号,CPU就可以快速找到⼀个内存空间。
⽣活中我们把⻔牌号也叫地址,在计算机中我们把内存单元的编号也称为地址,C语⾔中给地址起了新的名字叫:指针。
所以,在C语言中:内存单元的编号 == 地址 == 指针

 (2)编址的理解

 

我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器。
注:
计算机中的编址,并不是把每个字节的地址记录下来,⽽是通过硬件设计完成的

二.指针变量和地址

 (1)怎么取地址

通过内存的知识,我们了解了其实地址就是指针 ,那么我们该如何取到地址呢?我们需要用到取地址操作符(&),下面,通过代码来看看怎么用:

 (x86)环境下

那如果我们想把a的地址存起来,那么,我们就要为a的地址开辟一个空间p(指针变量),

所以,接下来,我们来说说到底什么才是指针变量。 

(2)指针变量

对于指针变量的理解,我们可以类比整型变量,浮点型变量.......

在指针(地址)中,有整型的地址,有字符的地址,等等,所以说,应该有整型对应的整型指针,字符对应的字符指针........

我们看看下图:

* 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int)类型的对象。
所以,字符指针变量就可以如下表示:

其实:

“*”是一个解引用操作符(也可以称:间接访问操作符),因为创建一个变量就需要开辟一个空间,因此,指针变量也需要开辟一个空间来存放地址,我们可以通过使用解引用操作符来通过地址来找到该地址所存放的变量

一字节=8比特;

//32位平台下地址是32bit位(即4个字节)X86环境小
//64位平台下地址是64bit位(即8个字节)X64环境下
我们可以通过sizeof操作符来观察,在此,就不实操了

注意:指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的

三.指针解引用的注意点

(1)指针类型带来的注意点 

指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节

这也就是为什么在相同的环境下(X86/X64),指针变量都占(4字节/8字节)还要区分他的类型(int*,char*,float*.....)

 我们可以看看对*(解引用)的赋值后,发生的变化:

前:

后:

 那若我们将pa的类型换成char*呢:

(记得与上图进行对比)

前:

char*pa可以发下a,是因为指针本来就是用来存放地址的;

与解引用不要搞混哦 

 后:

发现效果的差异了吧

当pa为int*类型时,*pa=0;改变的时四个字节:(因为他为int,访问的便是int(4字节))

当pa为char*类型时,*pa=0;改变的时1个字节: (因为他为char,访问的便是char(1字节))

 结论:

指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作,访问⼏个字节)

-----此概念就可以用于下面要说明的指针+/-整数了;

所以说,我们若是要该变前2个字节,那么就将pa返回为short*类型;

(2)void*指针(泛型指针)(无具体类型)

void*可以接收任意类型的指针可以到达泛型编程的效果。不过,void*是有局限性的 ,正因为void*是无具体类型的指针,对其指针变量进行*(解引用操作)或+/-整数是无法知道要访问多少字节的,所以:
void*类型的指针变量不能用于解引用操作与指针+-整数操作的;

(3)const修饰

const(常属性)可以对变量进行修饰 ,使所修饰变量不可被修改(被赋值)但本质还是变量,只是变成了常变量(可以用数组这个案例来说明—还是变量(切记在C中实现)):

a被const修饰,不能被改变了,既然是a被const修饰,那么,我们是不是可以不通过a,而是通过访问a的地址来进行解引用操作来改变a的值呢?那我们将const应用到指针:

看,我们通过指针该变了a的值,可是,我们既然要保护a不被改变,那们,我们做事就该做绝:

直接修饰指针:

引入理解:
1.p是指针变量,里面存放了其他变量的地址;

2.*p是指针指向的对象a;

3.指针变量也是变量,所以p也有自己的地址,&p(二级指针);

const修饰指针有3种形式(对int*p来说明)

1. const放在*p的左边

*p不可以被改变(赋值),但p可以被改变(指向另一个变量的地址):

2.const放在*后面 

*p可以被改变(赋值),但p不可以被改变(指向另一个变量的地址):

 

3.const一个放在*前,一个放在*后

 *p不可以被改变(赋值),p也不可以被改变(指向另一个变量的地址):

四.指针的运算 

补充:

1.数组在内存当中是连续存放的,随着数组下标的增长,地址是由低到高变化的;

2.在数组arr中,arr代表的是数组首元素的地址,也就是指针,但有两个例外;

例外1:&arr表示的是取出整个数组的地址;

例外2: 

3.arr[i]等价于*(p+i);

4.&与*结合可视为抵消;

(1)指针+/-整数 

我们还是通过代码的演示来理解

p1与p2的地址是相同的(&a),不过p1+1跳过的是4个字节,p2+1跳过的为1个字节

{与前面的指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)进行联想}:并与下图进行理解: 

结论:

指针类型决定了指针的步长,就是向前/向后走i步的多大距离

type*p:

p+i就是跳过了i * sizeof(type)个字节

EG:int*p:

p+2:就是跳过了2*4=8个字节;

(这对下一篇my CSDN的“指针的应用“个别题目有很大的理解帮助)

(2)指针-指针 

指针1+5=指针2;

指针2-指针1=5;

所以:

 指针-指针得到的是两个指针之间元素的个数,不是字节的个数(是有正负区分的(高地址-低地址)/(低地址-高地址));

也说明了指针+指针无意义;

而且,指针-指针运算的前提是两个指针指向的是同一块空间,而不仅仅是要同类型(比如说是在同一个数组里);

 (3)指针的大小比较

对于指针大小的比较,也就是比较在内存里,地址大小的比较。 

五.野指针的出现与防止

(1)野指针的出现

1. 指针未初始化
eg:
局部变量未初始化指向的是随机值;
2. 指针越界访问
eg:
在数组中,如果越界访问,那将不属于本身数组了
3. 指针指向的空间释放
eg:
在函数调用时,函数里的内容在出函数时会被销毁,回收,指针的到的地址访问时可能得不到想要的内容;

(2)野指针的防止

1.运用空指针NULL;

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址会报错。但是,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.
2.assert断言
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
而且,运用assert需要包含头文件<assert.h>
assert(p!=NULL);
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值