一、指针简介
1.什么是指针
指针是一个概念,是计算机内存地址的代名词之一。指针变量本身就是变量,存放内存的地址。在大多数情况下,指针变量简称为指针。
2.指针变量的定义
定义指针变量的一般形式为:类型名 * 指针变量名
①指针声明符*表明声明的变量是指针
②类型名表示所指对象的类型
例:
int *pi
定义了一个指针变量pi,指向整型变量。
char *cp
定义了一个指针变量cp,指向字符型变量。
注意:
①无论何种类型的指针变量,他们都是用来存放地址的,因此指针变量自身所占内存的大小和它所指向的变量数据类型无关,尽管不同类型的变量所占内存空间不同,但是不同类型指针变量所占内存空间大小相同。
②指针声明符*不是指针的组成部分,如:int *p;说明p是指针变量,*p不是。
③指针的类型和它所指向变量的类型必须相同
3.指针变量的初始化
指针变量需要先赋值再使用,看下面代码:
int i,*p;
p = &i;
p = 0;
p = NULL;
p = (int*)1732;
①对于赋值的第一条语句:&把 i 的地址取出,赋给指针变量p,这是很常用的赋值方法。
②对于赋值的第二、三条语句:NULL在stdio.h的文件中有定义,其值为0,这两条语句把0赋给指针,代表该指针为空指针,不指向任何单元。
③对于赋值的第四条语句:使用强制类型转换(int*)来避免编译错误,表示p指向的地址为1372的int型变量。但是我们不建议把绝对地址赋值给指针,NULL除外。
注意:
①在指针变量定义或者初始化时变量名前面的*,只表示该变量是一个指针变量,它不是间接访问符。
②不能用数值作为指针变量的值。例: int *p = 100 ;(错误)
③可以用初始化了的指针变量给另一个指针变量作初始值。
④把一个变量的地址作为初始化值赋给指针变量时,该变量必须在此之前已经定义。因为变量只有在定义后才被分配单元,它的地址才能赋给指针变量。
4.指针类型的意义
(1)指针的类型决定了指针向前或者向后走一步有多大。(距离)
地址是按字节编址的,在C语言中,指针加1指的是增加一个储存单元。
(2)指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
5.指针的大小
(1)在32位机器上,地址是32 个 0 或者 1 组成二进制序列,那地址就得用 4 个字节的空间来存储,所以一个指针变量的大小就应该是 4个字节 。
(2)在64位机器上,一个指针变量的大小是8个字节。
6.指针的运算
(1)指针+ -整数:
在指针类型的意义那里我们已经见过啦
(2)指针 - 指针: 结果的绝对值表示它们之间相隔的数组元素的数目,前提:两个指针指向同一块区域。
7.野指针
(1)野指针就是指针指向的位置是不可知的。
(2)野指针的成因:
①指针未初始化
②指针越界访问
③指针指向的空间释放
(3)规避野指针的做法:
①指针初始化
②小心指针越界
③指针指向空间释放即使置NULL
④避免返回局部变量的地址
⑤指针使用之前检查有效性
8.二级指针
(1)二级指针:指向指针的指针(指针变量也是变量,是变量就有地址,指针变量的地址存放在指针里面)
(2)一般定义为:类型名** 变量名
例:int** pa;
pa是一个指针,pa指向的变量是一个int*的变量
(3)二级指针的初始化
int a = 10;
int* pa = &a;//a的地址存在pa中,pa是一级指针
int** ppa = &pa;//pa的地址存在ppa中,ppa是二级指针
9.指针表示法和数组表示法
(1)一维数组
①arr[i] 等价于 *(arr+i),可以认为*(arr+i)的意思是“到内存的arr位置,然后移动i个单位,检索存储在那里的值”。
②arr + i 等价于 &arr[i],前面我们说过数组名是数组首元素的地址,所以arr是该数组首元素的地址,首元素的地址+i得到的是第i个元素的地址,即&arr[i]
注意:不要混淆*(arr+2)和*arr+2;前者的意思是arr第2个元素的值,后者的意思是arr第0个元素的值+2
(2)二维数组
假设有定义:int a[3][2];
①a:数组名是数组首元素的地址。我们可以把二维数组a看成是由a[0],a[1],a[2]组成的一维数组,而a[0],a[1],a[2]各自又是一个一维数组。因此数组名a是a[0](一个内含两个int值的数组)的首元素地址, a[0]是该数组首元素(a[0][0])的地址。所以我们可以知道两个等价关系:a 等价于 &a[0]; a[0] 等价于 &a[0][0]
②a[0]是该数组首元素(a[0][0])的地址,所以,*(a[0])等价于a[0][0]的值;与此类似,*a代表该数组首元素(a[0])的值,而a[0]本身又是一个int类型的地址,所以该值的地址为&a[0][0],换言之,a就是&a[0][0]。*a等价于&(a[0][0]),因为a就是首行首元素的地址,所以再对其进行解引用才能找到首元素的值。我们来捋一下:a即&a[0], a[0]即&a[0][0], 因此,我们可以得出&a[0] 等价于 &&a[0][0]。其实二维数组名相当于一个二级指针,而a[0]相当于一级指针。注意:二级指针和二维数组名是两码事
③由于a[i] 等价于 *(a+i),我们也可以得出a[i][j] 等价于 *( (a+i) + j)或者(a[i] + j)。
二、指针数组和数组指针
1.指针数组
其实C语言中数组可以是任何类型的,如果数组的各个元素都是指针类型,用于存放内存地址,那么这个数组就是指针数组。
(1)一维指针数组定义的一般格式为:类型名* 数组名[数组长度]
例:int* arr[5];
arr是一个数组,有五个元素,每个元素是一个整形指针。该数组的类型是int* [ ]。(去掉变量名就是类型)
(2)指针数组的初始化:
指针数组的各个元素是指针,用于存放地址,因此,我们可以用指针(地址)作为初始化内容,如:
//(1)
int arr[] = {1,2,3};
int* parr[]={arr};
//(2)
char* color[5] = {"red", "blue", "yellow", "green", "black"};
//字符串常量实质上是一个指向该字符串首字符的指针常量
2.数组指针
(1)数组指针:可以指向数组的指针
数组指针的一般形式为:类型名 (*指针变量名)[数组长度]。类型名指的是指针指向的数组的元素的类型。
如:int (*p)[10];
p先和*结合,说明p是一个指针变量,然后指针指向的是一个大小为10的数组,该数组的每个元素是一个整型。所以p是一个指针,指向一个数组,叫数组指针,该数组指针的类型为int(*)[10]。
(2)&数组名和数组名
①第一组的+4的解释:arr是数组首元素的地址,因为arr的类型是int*,所以arr+1跳过4个字节
②第二组的+4的解释:&arr[0]是数组首元素的地址,同样的,&arr[0]的类型也是int*,所以&arr[0]+1也是跳过4个字节
③第三组的+40的解释:&arr是数组的地址,它的类型是int(*)[10],一个指向大小为10的整型数组的指针,所以它+1,应该跳过一个数组的大小10*4=40。
(3)数组指针的使用
数组指针里面存放的是数组的地址。我们来看2个代码:
p指向的是arr整个数组,p[i]等价于*(p+i),当i = 1时,p+1跳过的是整个数组,所以*(p+i)访问的是随机值。
三、函数指针
1.函数指针定义的一般形式为:(类型名)(*变量名)(参数类型表)
类型名:指定函数的返回类型;变量名:指向函数的指针变量的名称
例:
int(*funp)(int,int);
定义了一个函数指针funp,它可以指向有两个整型参数且返回值类型为int的函数,该函数指针的类型为int(*)(int,int).
2.通过函数指针调用函数
(1)在使用函数指针之前,要先对它赋值。赋值时,将一个函数名赋给函数指针,但是该函数必须已经定义或声明,且函数返回值的类型要和函数指针的类型一样。(函数名和&函数名意义相同,都代表函数的地址)(函数指针用来存放函数的地址)
(2)函数调用的一般格式:
(*函数指针名)(参数表)
例:假设fun(x, y)已经有定义,现在要调用fun函数
int(*funp)(int,int) = fun;
fun = (3,5);
(*funp)(3,5);
//两者完全等价
3.函数指针作为函数参数:
C语言的调用中,函数名或已赋值的函数指针也能作为实参,此时,形参就是函数指针,它指向实参所代表函数的入口地址。