C 指针

·  阅读 406

指针在C语言中的地位是非常高的,可以说是C的核心概念,作为一个Android开发工程师,想要在音视频方向取得一定的成绩,必须要从底层入手,所以C/C++就成了必须熟练使用的技能之一了,作为一个老手,在语言的通用性方面基本可以不用看C/C++的语法部分就可以上手了,那其他重要的技术点还是需要学的。

指针的作用

  • 可以提高程序编译效率、执行速度
  • 可以动态存储分配
  • 可以使程序更加灵活的表示和操作各种数据接结构

数据在内存中的存储和读取

想要弄明白指针就需要知道数据在内存中是怎么存储和读取的

内存的分区是抽象的概念,我们知道Java中JVM的内存会被划分为五大区域,其实都是抽象的概念,在C中,可以这样理解,系统会对物理内存的每个单元进行编号

  • 内存编址

image.png

  1. 每一个单元都表示一个bit,在我们开发中8bit = 1个字节
  2. 在内存中字节是内存寻址的最小单元,所以其实就是对每个子节进行编号,这个编号就是内存的地址

image.png

  • 内存分配

例如,定义一个整型变量i需要4个字节,编译器就会为其分配4个内存单元

int i = 0;

image.png

因为i变量是int型的,所以在内存中对应的内存地址是4个内存单元,即0xff00~0xff03, 存储的值为0,假设在定义一个char 类型的变量,系统会分配一个内存单元的地址。

编译系统会为每个变量都分配一个能满足其类型大小的内存单元地址,访问该地址就能找到对应变量

  • 访问变量的地址

“&” 取地址符,它的作用就是获取变量在内存中的地址,访问时的地址都是变量的首地址,比如&i的表示0xff00. 通过&i就可以访问变量i指向的内存单元

  • 指针

变量的地址成为该变量的指针, 通过它就能访问以它为地址的内存单元,进而操作数据

变量与指针

这里就有一个疑问,变量也可以访问和操作数据,那为什么还要使用指针呢?

  • 通过变量进行操作

在开发视角通用的做法是使用已经定义好的变量名对内存单元进行存取操作。

系统视角 代码编译之后,会将变量名转换为该变量在内存中存放的地址,后续的操作都是对该地址进行的

比如:i+j

i在内存中的地址为1000(指首地址,因为是int 型所以内存地址为1000~1003)

j在内存中的地址为1004

那这个i+j的操作在计算机内部是这样操作的,

  1. 根据变量名与地址的对应关系找到变量i的地址1000
  2. 从1000开始读取4个字节到CPU的寄存器中
  3. 相同的方法找到j的地址1004,并读取4个字节到CPU的另一个寄存器中
  4. 通过CPU的加法指令输出结果

理解CPU进行简单加法

  • 那为什么使用指针呢?

指针就是指向变量地址的变量, 换句话说,变量的地址是变量和指针之间的连接纽带

指向 是通过地址来体现的,指针变量通过指向一个变量的地址,所以一个变量的地址赋值给指针变量后,这个指针就指向了这个变量,例如:将变量i的地址&i存放到指针变量p中,p就指向i image.png

那既然程序是使用内存地址来处理逻辑的,而指针又是指向内存地址的,这就成为了出现指针变量的一个重要因素还有其他原因,后续分析

指针变量

类型* 变量名
复制代码
  • 变量的申明,赋值等简单操作就不记录了

指针变量的引用

引用指针变量是对变量进行间接访问的一种形式。引用指针变量的形式为“*指针变量”,其含义是引用指针变量所指向的值

没有初始化的指针变量俗称“野指针”,使用时容易产生错误,导致不合法的内存空间(良好的编程习惯是在定义指针变量时就将其初始化为NULL,由于NULL处禁止写入,所以一旦有错误,可以将错误造成的危害降到最小)

“&”和“”运算符,“&”和“*&”的区别

运算符“&”和“”都是单目运算符。“&”是取地址运算符,用于返回一个操作数的地址。“”是指针运算符,用于返回指定地址内保存的变量值

int a;
int* p;
p = &a
复制代码

因为“&”和“*”的优先级相同,按自右而左的方向结合,因此“&*p”先进行“*”运算,“*p”相当于变量a;再进行“&”运算,“&*p”就相当于取变量a的地址。“*&a”先进行“&”运算,“&a”就是取变量a的地址,然后执行“*”运算,“*&a”就相当于取变量a所在地址的值,实际就是变量a。

指针可以进行自增、自减运算

指针的自增自减运算不同于普通变量的自增自减运算,也就是说,并非简单地加1减1,而是按照它所指向的数据类型的直接长度进行增减(指针自增自减运算,是按照它所指向的数据类型的直接长度进行增或减)

指针与数组

使用数组时,系统需要提供一段连续的内存来存储数组中的各元素,如果把数组的地址赋给指针变量,就可以通过指针变量来引用数组

  • 一维数组与指针
  1. 定义一维数组时,系统会在内存中为其分配一段存储空间,数组名就是数组在内存中的首地址
  2. 若再定义一个指针变量,并将数组的首地址传给指针变量,则该指针就指向了这个一维数组
int a[10], *p;
p = a;
//也可以写成这样
p = &a[0];
复制代码
  1. 访问时,p+i,和a+i 都表示的是a[i]的地址,即&a[i],比如对共有5个元素的a数组来说,n的取值为0~4,则数组元素的地址就可以表示为p+0~p+4或a+0~a+4
  2. *(p+i)和*(a+i)都用于表示数组元素a[i]。语句“printf("%5d",*(p+i));”和“printf("%5d",*(q+i));”表示输出数组a、b中对应的元素

使用指针指向一维数组及通过指针引用数组元素的过程可以通过下面两个图来表示

指针指向一维数组

指针指向一维数组

通过指针引用数组元素

image.png

  • 指针与二维数组

定义一个3行5列的二维数组

int a[3][5];
复制代码

多种表示二维数组地址的方式:

image.png

对于一个m行n列的二维数组,其元素地址的表示方法如下

  • a表示二维数组的首地址,也表示数组第1行的首地址,a+1表示第2行的首地址,a+m表示第m+1行的首地址。
  • a[0]+n表示数组第1行第n+1个元素的地址,a[m]+n表示第m+1行第n+1个元素的地址。
  • &a[0]表示数组第1行的首地址,&a[m]表示第m+1行的首地址。
  • &a[0][0]既可以表示数组第1行1列的首地址,也可以看作整个数组的首地址。&a[m][n]就是第m+1行n+1列元素的地址
  • *(*(a+m)+n)和*(a[m]+n)含义相同,都表示数组第m+1行第n+1列元素。

利用指针引用二维数组的关键是要记住*(a+i)与a[i]是等价的

后续计划

  1. 指针与字符串
  2. 指针数组
  3. 指向指针的指针
  4. 指针在函数中的使用
  5. 例子
收藏成功!
已添加到「」, 点击更改