浅谈C语言指针
学习指针,我们必须明白什么是指针。在大多数教材上给出的定义为:“一个变量的地址称为该变量的指针”。
由此,首先我们来谈谈计算机内存地址。我们都知道运行一个C语言程序需要将程序从磁盘加载到内存中进行运算。而所谓的运算可以抽象理解为计算机处理器通过地址读取指令、数据的过程。在内存中,每个字节(8位)对应有一个地址,连续存储空间对应连续地址,处理器通过地址可得到存储空间存储的数值。为了方便理解,例举出我们计算机存取变量值的原理:当我们定义一个局部变量例如short a=7时,系统会在栈中随机分配一段字节数为2的连续内存,每个字节都会有地址。假设系统分配给变量a的地址为0x00-0x01,则这2个字节会分别存放00000000与00000111来表示7,当我们访问变量a时,我们会得到变量a的首地址0x00,然后根据已定义的基类型(short型)取出相应字节数据
我们都知道32位处理器不支持4GB+运行内存,因为32位处理器可访问的最大地址由8位十六进制表示,即最大访问地址为0xffffffff,约为4G,超过4G内存,处理器访问不到。
由此,我们明白了计算机地址的相关知识,在C语言中,存在内存空间中的某个数所对应的首地址就是这个数的指针,需要注意,指针均为十六进制表示的常量。因为在栈中内存是由系统随机分配,所以我们不能直接对指针进行操作,由此引入一个变量专门来存放地址,这种变量称为指针变量。我们都知道,C语言对数据进行访问主要是通过两种形式:1.直接通过变量名访问(直接访问)2.先得到指针变量中的值(地址),然后通过地址访问。(其实二者本质上均为通过地址访问,所谓的变量名在内存中不存在,是给编译器看的,根据变量名访问内存空间时,编译器对照符号表(一种用于语言翻译器中的数据结构,在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型,作用域以及内存地址)取出地址,访问内存空间)
我们从以下4个方面深入了解C语言指针
通过指针引用变量(间接访问)
指针变量是存放地址的变量,让我们回顾一下什么是变量。变量:代表一个有名字的,具有特定属性的一个存储单元。它用来存放数据。从定义中我们可以得到两个信息○1变量不是一个具体的值,而是一个存储单元○2变量中存放的值可以改变
指针变量定义:基类型 * 变量名 例如int a.意思为定义a为一个指向int型数据首地址的指针变量,指针变量的值为地址
指针变量赋值:因指针变量值为地址,赋值为p=&a,将变量a地址赋值给p。我们给指针变量值赋地址,其最终目的是想通过地址操作存储在该地址内存中变量的值。在C语言中,我们通常用指针变量名 表示指针变量指向的具体值。如point=1(给point指向的变量赋值为1)。当然,我们要将指针变量定义时的基类型 * 变量名 区分开,在定义中只表明该变量为指向基类型的指针
总所周知,变量除了可以直接操作之外,还可以作为函数参数进行传递,指针变量作为变量的一种自然也具备这一属性,我们可以在其他函数中借助传递过来的变量地址对该变量进行操作
通过指针引用数组
要了解指针怎样引用数组,我们先从最简单的一维数组入手。通过数组来引用数组元素无外乎有两种方式,一种是数组下标法,一种是指针法。在此,我们主要探究怎样用指针法引用一维数组。首先一点我们必须明确,不管我们使用指针引用变量,数组还是函数。我们操作的都是他们的首地址。通过数组引用变量时,我们得到变量首地址,根据其声明的数据类型从内存中取出相应字节。一维数组是一组相同类型、有序数据的集合,其数组名代表首地址,因此我们将数组名赋给指针变量的值,我们就可以通过指针变量引用该数组。举个例子:在int a[]={1,3,5,7,9}中,我们该如何访问数组中元素5.首先我们定义一个指针变量指向该数组int p=a。那么p+2即为a[2]的地址(数组是从a[0]开始),(p+2)即为a[2]的值5.需要注意这里的2,C语言编译系统会自动换算为数组已定义类型再乘2,假设上述数组首地址为0x00,由于定义类型为int型占4个字节,计算机找到0x00+24=0x08中,并取出4个字节即为5
接下来,我们来探讨二维数组,二维数组又称为矩阵,为了形象化地理解二维数组,我们将二维数组写成行列形式(实际为线性结构),前面讲到我们对数组的引用都是通过首地址。那么,我们怎样通过对首地址操作表示行与列。例如我们定义一个3行4列int型二维数组int a[3][4],3表示3行,4表示4列(第0行加1,表示第一行。第0行第0个元素加1,表示0行第一个元素),我们如何表示第二行的首地址呢?前面有说到用数组名来表示首地址,因此第2行首地址,我们可以用a+2来表示。我们如何表示第二行第二个元素的地址呢?是不是用(a+2)+2来表示?显然不是,这种表示在一定程度上会给人误解,因此,我们在指向行的指针前面添加一个,它们就成了指向列的指针,所以,我们很容易表示第2行第二个元素的地址(a+2)+2,进一步我们可以表示第2行第二个元素的值((a+2)+2)。数组在内存中是一段连续的内存空间,我们可以理解为第二行元素比第一行最后一个元素少一个基类型字节,因此第二行第二个元素的值也可以表示为*(a+10)。上面是用指针访问。我们该如何用下标来表示行列的地址呢?其实很简单,二维数组常被称为“数组的数组”,比如上述a[3][4],我们可以将其视为一个以数组名a为首地址的a[3]表示行的数组以及分别以a[0],a[1],a[2],a[3]为数组名的int [4]列数组,故上述数组第2行第2个元素地址可以表示为a[2]+2,其值表示为a[2][2].
通过指针引用字符串
引用字符串有两种方式:1.利用字符数组 2.用字符指针变量
例如1.char string[14]=”I LOVE CHINA!”;
2.char * string=”I LOVE CHINA!”;
第一种方式我们可以这样理解,系统创建了一个14个字节的内存空间,将字符串逐个存放,最后一位存放‘ ’,数组名代表字符数组首地址
第二种方式中,由于C语言对字符串常量是按照字符数组来处理,但存入后,该字符数组没有名字。因此不能通过数组名引用。只能通过指针变量来引用。上述第二条语句实际是将字符串首地址赋给string
通过指针引用函数
什么是函数指针?我们编译一个函数,编译系统为函数代码分配一段存储空间,这段空间起始地址称为函数指针
定义:>int (*p)(int int)
引用:将拥有相同类型参数的函数名(起始地址)赋给p,p就可以调用该函数
最后再总结一下const关键字在指针中的规则:
Const int *p=&i:const在前,表示不能通过指针去修改变变量
Int *const p=&i:const在后,表示p这个指针不能再保存别的地址值