C语言:深入理解指针(1)

何为指针?

计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的
数据也会放回内存中
计算机把内存化分为一个个的内存单元,每个内存单元的大小是一个字节
每个内存单元都有一个编号而:
内存单元编号==地址==指针
因此我们说的指针就是地址。
注:变量创建的本质其实是在内存中申请空间
那我们如何得到某个变量的地址呢?
这时候就需要用到取地址操作符:&

取地址操作符:

刚刚提过,变量的本质实际上是在内存中申请空间,图中int a在内存中申请4个字节,⽤于存放整数10,其中每个字节都有地址,这四个地址如图中红框所示

&a取出的实际上是a所占4个字节中地址较⼩的字节的地址。

我们只要知道了第⼀个字节地址,就可以依次找到剩下的地址。

我们通过取地址操作符(&)拿到的地址是⼀个数值,而这个数值也需要(可以)存储起来,这时就需要指针变量。

指针变量与解引用操作符:

pa就是一个指针变量,指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址。

这⾥pa左边写的是 int* * 是在说明pa是指针变量,⽽前⾯的 int 是在说明pa指向的是整型(int) 类型的对象。再比如,如果创建了一个char类型的变量,存放该变量地址的指针就应该写成:

我们把地址存起来后,以后该如何取出来使用呢?这就需要解引用操作符

解引⽤操作符:

这里的 *pa 中的 * 就是解引用操作符(间接访问操作符),*pa 的意思就是通过pa中存放的地址,找到指向的空间,*pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0.

既然是变量,指针变量也有大小,它的大小是由解决方案平台决定的。

指针变量大小:

x86是32位平台,x64是64位平台

32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。
虽然指针变量的大小与类型无关,但是不同的类型有着不同的含义:
指针的类型决定了对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
如char*类型的指针变量只能访问一个字节,而int*类型就能访问四个字节。

指针+-整数:

我们发现,虽然 char*pc 与 int*pi 指向的是同一个地址但pc+1与pi+1的结果不同,pc+1指针跳过了一个字节,而pi+1则跳过了4个字节

所以说:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

void* 指针:

指针类型中有一种叫void*类型的指针,这种指针为无具体类型的指针(也叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性

void* 类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。

⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以
实现泛型编程的效果。使得⼀个函数来处理多种类型的数据

const:

何为conset?我们知道,变量是可以进行修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。

但是如果我们不想让这个变量进行修改,那么就要用到const:

const修饰变量:

这样子就会报错。

但是,我们还有一种方法去修改,那就是用指针变量修改:

这里我们绕过n,使⽤n的地址,就能去修改n。

但是,我们用const的本意实际上是不愿意要让该变量能进行修改的,那么该如何做,能够做到不能用指针进行修改呢?

const修饰指针:

⼀般来讲const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不⼀样的。

const如果放在*的左边,如int const*p 或者const interesting*p 修饰的是指针指向的内容(限制的是*p),保证指针指向的内容(*p)不能通过指针来改变。 但是指针变量本⾝的内容(p)可变。
const如果放在*的右边,如int* const p 修饰的是指针变量本⾝(限制的是p),保证了指针变量的内容不能修改,但是指针指向的内容(*p)可以通过指针改变。

如果*左右两边都加上const,那么p本身和*p都会被限制。

指针的运算:

指针可以进行三种运算:

1.指针+-整数

2.指针-指针

3.指针的关系运算(这个是指关系比较)

1.指针+-整数:

前面我们提到过,这里直接举例说明:

(我们知道,数组内部存放数据的时候,都是连续存放的)

注:指针类型决定了指针+1的步长,决定了指针解引用的权限。因为关于整型类型的地址,加一,向后移动一个整型大小

2.指针 - 指针:

指针-指针的绝对值是指针与指针之间元素的个数(因为可能为负数)
前提是两个指针是针对同一个空间
例:

3.指针的关系运算:

这个主要是指针间的比较,直接举例说明:

野指针:


定义

何谓野指针?就是指指针指向的位置是不可知的

导致野指针的三种情形:

1:指针未初始化

2:指针越界访问

3:指针指向空间释放

如何规避野指针?

1:指针初始化:如果不知道指针应该指向哪里,可以用NULL对指针赋值

      但是NULL这个地址是无法使用的,直接读写地址会出错

2:注意是否越界

3:指针变量不再使用时,可以及时值为NULL,同时指针使用前要检查是否为NULL

4:不要返回局部变量的地址

这里我们再引入一个概念,叫assert断言:

assert断言:

我们经常用assert来断言指针的有效性(是否为空)

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
例:
使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制:
如果已经确认程序没有问题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG :
#define NDEBUG
#include <assert.h>
assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
注:assert在debug版本中会起作用,但是在release版本中,assert会被优化掉。

传值调⽤和传址调⽤:

为什么要引入指针这个概念,或者说指针有什么特别的作用呢?

我们来举个例子:

写⼀个函数,交换两个整型变量的值:
如果我们这样写:
可是我们最后发现,结果不是我们想要的:
这是为什么呢?
是因为实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实
参。
因此这种方法行不通。
既然传值调用不行,那我们采用传址调用:
在main函数中直接将a和b的地址传递给Swap函数,Swap 函数⾥边通过地址间接的操作main函数中的a和b,并达到交换的效果就可以了
结论:
传址调用,可以让函数和主调函数之间建立真正的关系,在函数内部可以修改主调函数的变量;所以未来函数中只要是需要主调函数中的变量值来实现计算,就可以采用传值调用,如果要修改内部函数主调函数中变量的值,就需要传址调用。

关于指针的介绍还有很多,由于篇幅过长,今天所介绍的是其中一部分,感兴趣的话,关于接下来的介绍请看深入理解指针(2)(♡>𖥦<)/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值