C语言指针详解

摘要:如果问C语言中最重要、威力最大的概念是什么,答案必将是指针!威力大,意味着使用方便、高效,同时也意味着语法复杂、容易出错。指针用的好,可以极大的提高代码执行效率、节约系统资源;如果用的不好,程序中将会充满陷阱、漏洞。

这篇文章,我们就来聊聊指针。从最底层的内存存储空间开始,一直到应用层的各种指针使用技巧,循序渐进、抽丝剥茧,以最直白的语言进行讲解,让你一次看过瘾。

说明:为了方便讲解和理解,文中配图的内存空间的地址是随便写的,在实际计算机中是要遵循地址对齐方式的。

一、变量和指针的本质

1. 内存地址

我们编写一个程序的源文件之后,编译得到一个二进制可执行文件存放到电脑硬盘上,此时它是一个静态的文件,一般称之为程序。当这个程序被启动时,一般需要做以下三件事:a. 将程序的内容从硬盘拷贝到内存中;b.创建一个数据结构PCB(进程控制块),来描述程序的各种信息;c.在代码段中定位到入口函数的地址,让CPU从这个地址开始执行。

当程序开始被执行时,就变成一个动态的状态,即进程。

在我们的程序中,通过一个变量名来定义变量、使用变量(变量名只要符合规则可以任意定义)。我们定义一个变量之后,这个变量存放在内存的数据区。内存是一个很大的存储区域,被操作系统划分成一个的小空间,操作系统通过地址来管理内存。

内存中最小的存储单元是字节(8bit/字节),一个内存的完整空间就是由一个个字节连续组成的。

2.32位和64位系统

指的是计算机的CPU中寄存器的最大存储长度,如果寄存器中最大的存储64bit,就称之为64为系统。

在计算机中,数据一般是在硬盘、内存、寄存器之间进行来回存取。CPU通过3种总线将各组成部分联系在一起:地址总线、数据总线和控制总线。地址总线的宽度决定了CPU 的寻址能力,也就是CPU 能达到的最大地址范围。

内存是通过地址来管理的,CPU想从内存的某个地址空间上存取一个数据,那么CPU就要在地址总线上输出这个存储单元的地址。

3.变量

在C语言中使用变量来“代表”一个数据,使用函数名来“代表”一个函数。变量和函数最终是放到内存中才能被CPU使用的,二内存中的所有信息(代码和数据)都是以二进制的形式来存储的,所以CPU在访问的时候需要的是地址,而不是变量名和函数名。

编译器:在程序代码中使用变量名来指代变量,而变量名在内存中是根据地址来存放的,需要编译器来进行映射。编译器来安排程序中的各种地址,例如:加载到内存中的地址、代码段的入口地址等等,同时编译器也会把程序中的所有变量名,转成该变量在内存中的存储地址。变量的两个属性:变量的类型和变量的值。

4.指针变量

指针变量可以分两个层次来理解:

指针变量它首先的一个变量,所以它拥有变量的所有属性(类型和值)。它的类型就是指针,它的值是其它变量的地址。既然是一个变量,就要为它分配一个存储空间,存放的是其它变量的地址。

指针变量所指向的数据类型,是在定义指针变量的时候就确定的,例如 int *p,意味着指针指向的是一个 int 型的数据。在 32 位系统中,一个指针变量在内存中占据4个字节的空间。而指针变量中存储的就是地址,所以需要 4 个字节的空间来存储一个指针变量的值。

5.操作指针变量

对指针变量的操作包括3部分(操作指标变量自身的值;获取指针变量所指向的数据;以什么样数据类型来使用/解释指针变量所指向的值)

5.1 指针变量自身的值

int a = 20;这个语句是定义变量a,只要写下 a,就表示要操作变量 a 中存储的值(读 和 写)。

int *pa 语句是用来定义指针变量 pa,只要 操作 pa就表示要操作pa中的值。

二、指针的几个相关概念

1. const 属性

const 用来表示一个对象的不可变性,如 const int b = 20;在后面的代码中,b的值永远是20;同样的,如果用 const 修饰一个指针变量:

int a = 20; int b = 20; int * const p = &a; p在定义为变量 a 的地址之后,就固定了,不能再改变了,也就是说指针变量 p中只能存储 变量 a的地址,不能改变,但是,p 所指向的变量 a 的值是可以变的,即 *p = 21 成立。与 下面的代码进行区分:

int a = 20; int b = 20; const int *p = &a; p = &b;

这里 p 的值可以被改变。因为 此处的 const 是用来修饰 p 所指向的变量的。

2. void 型指针

关键字 void 并不是一个真正的数据类型,它体现的是一种抽象,指明不是任何一种类型。一般使用类型有两种(返回的返回值和形参;定义指针时不明确规定所指数据的类型,即可以指向任意类型)。

指针变量是一种变量,变量之间可以相互赋值,那么指针变量之间也可以相互赋值。

int a = 20; int b = a; int *p1 = &a; int *p2 = p1;变量 a赋值给b, 指针 p1赋值给 指针变量p2,注意他们的类型必须相同,如果不同,应进行强制转换:int a = 20; int *p1 = &a; char *p2 = (char *)p1。如果我们使用 void *来定义指针变量 p2,就不需要强制转换了:int a = 20; int *p1 = &a; void * p2= p1; 意味着可以把任意类型的指针变量赋值给 p2,但是不能反过来操作,也就是不能把 void*型指针赋值给其它确定类型的指针,此时必须强制转换成被赋值指针所指向的数据类型,如以下代码:int a = 20; int *p1 = &a; void *p2 = p1; int *p3 = (int*)p2

看一个系统函数:void *memcpy(void *dest, const void* src, size_t len),第一个参数void *,体现了系统对内操作的真正意义:它不关心用户传来的指针指向什么数据类型,它只把数据挨个存储到这个地址对应的地址空间中;第二个参数同样如此,此外还添加了 const 修饰,说明memcpy只会从src指针处读取数据而不会修改数据。

3.空指针和野指针
3.1 空指针:不指向任何空间

在定义一个指针变量后没有赋值,那么指针变量中存储的就是一个随机值,有可能指向内存中的任何一个空间,此时绝对不能对指针变量进行写操作,因为它有可能指向内存中的代码段区域、也可能指向内存中操作系统所在的区域。

一般会将一个指针变量赋值为NULL来表示一个空指针,在C语言中,NULL = (void*)0, 在 C++中,NULL 为0

3.2 野指针 :地址已经失效的指针

一个指针必须指向一个有意义的地址,才能对这个指针进行操作。如果一个指针中存储的地址是一个随机值,或者是一个已经失效的值,这时候操作起来就非常危险。

三、指向不同数据类型的指针

1.数值型指针: 指向数值型变量的指针;
2.字符串指针:
3.指针数组和数组指针:
3.1 指针数组: int *p1[3]。

[ ]中括号的优先级高,因此与p1先结合,表示一个数组,这个数组中有3 个元素(3个都是指针),他们指向的是 int 型数据。如果有char p[3],则表示有3 个元素都是char 型。但是三个指针指向的地址不一定是连续的;

3.2 数组指针:int (*p2)[3]

()小括号让 p2 和指针结合,表示 p2是一个指针,这个指针是一个数组,这个数组有3 个元素,每个元素的类型都是 int 型。指针指向的是一个数组,此时数组中元素的地址是连续的。

4. 二维数组和指针
5. 结构体指针

C语言中的基本数据类型都是预定义的,结构体是用户定义的,在指针的使用上可以进行类比,唯一的区别就是在结构体指针中,需要使用 -> 箭头 操作符来获取结构体中的成员。

6. 函数指针

每一个函数在经过编译之后,都变成一个包含多条指令的集合,在程序被加载到内存之后,这个指令集合被放在代码区,我们在程序中使用函数名就代表了这个指令集合的开始地址。

函数指针,本质上还是一个指针,只不过这个指针变量中存储的是一个函数的地址。函数最重要的特点是可以被调用,因此当定义了一个函数指针,并把一个函数地址赋值给这个指针时,就可以通过函数指针来调用函数。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值