指针
1.1 简介
通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用 &
运算符访问的地址,它表示了在内存中的一个地址。注意:
用register
修饰的变量是没法取到地址的,没有内存位置
1.2 什么是指针
1.2.1 定义
指针
也就是内存地址
,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为: type *var_name;
type
是指针的基类型,它必须是一个有效的 C 数据类型var_name
是指针变量的名称。用来声明指针的星号*
与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针
。
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
1.2.2 指针与地址
-
将内存抽象成一个很大的一维字符数组
-
编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关)
-
这个内存编号我们称之为内存地址。
内存中的每一个数据都会分配相应的地址:
-
char
:占一个字节分配一个地址 -
int
: 占四个字节分配四个地址 -
float
、struct
、函数、数组等
-
1.指针和变量
内存区的每一个字节都有一个编号,这就是“地址”
如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号)
指针的实质就是内存“地址”。指针就是地址,地址就是指针
指针是内存单元的编号,指针变量是存放地址的变量
通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样
2.直接寻址和间接寻址 --指针对地址操作
- 通过变量名来存取变量值的访问方式称为“ 直接寻址”。
2.指针变量 pa 存储变量 a 的地址, 通过指针变量 pa 间接访问变量 a 称为“ 间接寻址”
3.&运算符和*运算符
- &为取地址运算符,用于取变量的地址 形式:&变量名 int a=20;&a--得到的是a的地址
- *为指针运算符,用于访问指针的目标变量 形式:*指针变量名或目标变量地址
- int a=20; int *sp=&a; -----结果*sp==20; *&a==20;
4.为什么*p=&a不正确
int *p=&a
是对的,但是*p=&a;
却不对?int *p=&a;
这一句定义了一个变量p
,p
是一个指向int型
的指针
,即p
的数据类型是int*
,而&a
的数据类型也是int*
。这条语句可以分写为 int *p;p=&a;
所以这一句没问题。*p=&a;
这一句是对(*p
)进行赋值操作,(*p
)的数据类型是int型
,而&a
的数据类型是int*
,两者类型不同,无法进行赋值,所以语句是错的。
*p=a
和p=&a
是正确的,*p=a
的意思:将a的值
赋给p指针
指向的地址的值;p=&a
的意思是:将a的地址
赋给指针p
;
区别:*p
是一个值
;p
是一个地址
;两者完全不相同,*
代表着p
指向的地址的值,简单来说就是取值;&
是取地址
符号,取的是地址
;p
是指针
,可以理解为所指向的值的地址,*p
就是取p指针
指向的地址的值,&a
就是取a的地址。
1.3 指针的定义和初始化
指针是一种数据类型,基于该类型声明的变量称为指针变量,该变量存放的是内存中的某个地址,和普通的变量一样,在使用指针变量之前应先对指针变量进行声明。
1.3.1指针的定义
形式: 数据类型 * 指针变量名 如: int *pa,*pb; char *sp ; char* pd; //*所在位置跟谁连续都可用
“ * ” 表示语句声明的是一个指针变量,类型指定了指针所指的内存单元的数据类型。
定义什么类型的指针,就要指向什么类型的变量 比如 int *pa int a; pa=&a; char a,*sp=&a
通过sizeof可以计算出指针变量所占用的内存单元始终是4个字节(因为地址都是占用4个字节的大小,指针需要4个字节才能存放地址)。也就是说任何类型的指针变量都只占用4个字节的内存空间。 (64位的位8个字节,但一般默认4个)
1.3.2 指针的初始化
声明指针变量时,C并不会自动对其进行初始化,这时,指针变量的值是随机的,在内存中乱指一气,此时,通过指针间接访问所指的内存区域是十分危险的,因为你完全不知道自己在做些什么。(野指针:int *sp;)
在使用指针前,一般要对其进行初始化(在声明的同时初始化或赋值),使其有一个确定合适的值,对于无处可指的指针变量,也要将其初始化为NULL(即0,空指针)。
形式: 数据类型 *指针变量名=初始化的地址值
#include <stdio.h>
int main (){
int *ptr = NULL;
printf("ptr 的地址是 %p\n", ptr );
return 0;
}
结果:
ptr 的地址是 0x0
1.4指针的运算
C语言中指针所能进行的运算是十分有限的,通常有以下几种:
- 指针与整数的加减(包括指针的自增和自减) //连续地址才有意义
- 同类型指针间的比较
- 同类型的指针相减。
整型指针:假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
ptr++
,在执行完上述的运算之后,ptr
将指向位置1004
,因为 ptr 每增加一次,它都将指向下一个整数位置
,即当前位置往后移 4 字节
。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。字符型指针:如果 ptr 指向一个地址为 1000 的
字符
,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。指针的每一次递增,它其实会指向下一个元素的存储单元。
指针的每一次递减,它都会指向前一个元素的存储单元。指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度
,比如 int 就是 4 个字节。
1.4.1 指针的算数运算
指针与整数的加减:指针与整数的加减表示指针在内存中向下或向上移动整数个单位。该单位是多少个内存字节取决于指针所指变量的类型。
如:short类型的每次移动2个字节,double类型的每次移动8个字节。将指针变量+1,其地址增加的值等于所指向的类型占用的内存字节数。
公式:指针变量新值=指针变量当前值+N*指针所指类型占用的内存字节数。
同类型的指针相减:两个相同类型指针相减,返回值是个整数。
公式:(指针1的值-指针2的值)/ 指针所指向变量的类型所占内存字节数。
代码:
1.4.2 指针的关系比较
指针可以用关系运算符进行比较,如
==、< 和 >
。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
两个指针的比较是两个指针变量保存的地址数值大小的比较。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
1.5 指针数组
- 由于数组的每个元素都相当于一个相应类型的变量,指针变量可以指向一般的变量,当然也可以指向数组中的元素。
- 而且数组中的各个元素是按顺序连续的存放在内存中,因此,我们只要知道一个数组的首地址(即第一个元素的地址),然后依次往下移动,就能找到该数组的所有元素。
1.5.1 建立指针和数组的关系
- 1. int *pa,a[5]; pa=a; 数组名代表首地址。
-
2. int *pa,a[5]; pa=&a[0]; 获取数组首元素的地址。
-
1.5.2 数组元素的引用
- 数组名下标法 a[i]
- 数组名地址法 *(a+i)
- 指针法 *(pa+i)
······指针下标法pa[i],等效: a[i],*(a+i),*(pa+i)
注意:数组名和指针变量本身并不完全等同,数组名代表的是一个地址常量,是数组的首地址,是不能改变的,而指针变量的值是可以改变的,它可以指向数组的任意一个元素的地址。
因此语句p++; ++p; p+=5;等等都是合法的,而数组名a++; ++a; a+=5;等都是非法的。
1.6 指针与二维数组
在C语言中,二维数组是按照行优先的规律转换成一维数组存放在内存中。
例如:int a[4][3]; int *p = &a[0][0]; 或 int *p = a[0] // a[0] 某行的首地址 int *p=a;错误
则二维数组在内存中的存储顺序及地址关系如图:
- 1 a+i:二维数组第i行的首地址,代表第i行的地址。
- 2 *(a+i):二维数组第i行第0列的地址。à&a[i][0] 或 a[i] + 0
- 3 *(a+i)+j:二维数组第i行第j列的地址 à&a[i][j] 或 a[i] + j
- 4 *(*(a+i)):二维数组第i行第0列的值。àa[i][0] 或 *(a[i]+0)
- 5 *(*(a+i)+j):二维数组第i行第j列的值。àa[i][j]或 *(a[i] + j)
如果一个指针*p = a[0];则可以通过*(p + (i*每行列数+j))获得第i行、第j列的值(即a[i][j]),这可以根据上面的对照存储表得出。