一.指针的概念
指针是编程语言中的一个对象,利用地址,它的值直接指向存在内存中某一的值。由于通过地址能找到所需变量的存储地址,可以说地址指向它所对应的变量单元。因此,将地址被形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元
指针变量也是一个变量,指针存放的内容是一个地址,该地址指向一块内存空间
二.指针变量
指针变量是指针类型的数据,指针类型和基本类型一样,是C语言常用的数据类型之一。
2.1 指针变量的定义:
定义指针变量时要明确指针指向的数据的类型,因为不同类型的数据在内存中的长度是不同的,如:一般的计算机中int型占4个字节,char类型占1个字节,指针要明确自己指向的数据在内存中的长度,这样才能正确的对指向的数据进行操作
int *p; //表示定义一个指针变量
*p; //代表指针所指内存的数据
指针变量只能存放地址,不能将一个int型变量直接赋值给一个指针,如,int *p = 100是错误的
2.2 指针类型的兼容性
指针变量之间赋值比普通数据类型赋值要求更为严格,例如:不可以把double *
赋值给int *
相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向与其不同类型变量的地址
2.3 指针变量常用的运算符:
取地址运算符:&
取地址运算符“&”,对变量(基本类型数据)使用该运算符,可以获得变量在内存当中的地址(一个指针类型的数据)
int p1 = 1; //定义一个int类型的变量p1
int *p = &p1; //将p1的地址赋给p
解引用运算符:*
解引用运算符“*”,对地址(指针类型数据)使用该运算符,可以获得地址对应的内存单元中存储的数据
int p1 = 1; //定义一个int类型的变量p1
int *p = &p1; //将p1的地址赋给p
printf("p1的值为%d",*p); //输出指针p指向的变量中存储的数据
三.指针的分类
3.1 无类型指针
定义一个指针变量,但不指定它指向哪种具体的数据类型,这是这个指针就是无类型指针
void *p; //使用void关键字定义了一个无类型指针
可以通过强制转化将void *
无类型指针转换为其他类型的指针,也可以用(void *)
强制转换将其他类型的指针强制转换为void无类型指针
void无类型指针转换为其他类型指针:
void *p; //定义一个无类型指针
(int*)p; //使用时将p强制转换为int类型的指针
其他类型指针转换为void无类型指针:
int *p; //定义一个int类型指针
(void*)p; //使用时将p强制转换为void无类型的指针
3.2 空指针
指向NULL的指针叫空指针
空指针通常在给指针初始化时使用,让声明的指针指向NULL,当使用时再给指针赋准确的值
C语言类型:NULL
NULL在C语言中的定义为( void * ) 0
3.3 野指针
没有具体指向任何变量地址的指针叫野指针
野指针是没有明确指向的指针,比如声明一个指针却没有对其初始化int * p
,此时该指针的指向不明确,可能会指向系统占用的内存或其他垃圾数据,编程时应注意避免野指针的出现
3.4 指向常量的指针与指针常量
声明指针变量时使用const关键字
指向常量的指针:
const char *p; //定义一个指向常量的指针,该指针可以指向不同的常量
指针常量:
char *const p; //定义一个指针常量,一旦初始化之后其内容不可改变,即该指针指向一个固定的地址,该地址中的内容时可以变化的
3.5 数组指针
数组名是一个指针,即指向数组首元素的地址
若定义一个int类型的数组a[10],则a是一个int类型的指针
int a[10];
int *p = a;
“[]”也是一个运算符,其功能和“*”解引用运算符的功能相同,都可取出地址中存放的数据
3.4 多级指针
指针就是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针,即指向指针的指针(二级指针)
int i = 10;
int *p1 = &i;
int **p2 = &p1;
printf("%d\n", **p2); //输出一个地址
指向多维数组的指针
表达式 | 含义 |
---|---|
int a[3][5] | 定义一个二维数组,a为数组首地址 |
int (*a)[5] | 定义一个指向int [5]类型的指针变量a |
a[0], *(a + 0), *a | 0行,0列元素地址 |
a + 1 | 第1行首地址 |
a[1], *(a + 1) | 第1行,0列元素地址 |
a[1] + 2, *(a + 1) + 2, & a[1][2] | 第1行,2列元素地址 |
*(a[1] + 2), *(*(a + 1) + 2), a[1][2] | 第1行,2列元素的值 |
3.5 函数指针
函数指针是指向函数的指针,指针可以指向变量,数组,也可以指向一个函数。
每一个函数在编译的时候会分配一个入口地址,这个入口地址就是函数指针,函数名称就代表函数的入口地址(和数组类似)。
1)函数指针的定义方式:
函数返回类型 ( * 指针变量名称 ) ( 参数列表 )
int (*p)(int); //定义了一个指向int func(int n)类型函数的指针
2)函数指针的特点:
函数可以通过函数指针调用
使用函数指针可以实现根据条件调用不同的函数:
void man(){
printf("抽烟\n");
printf("喝酒\n");
printf("打牌\n");
}
void woman(){
printf("化妆\n");
printf("逛街\n");
printf("网购\n");
}
int main(){
//声明一个函数指针,此时函数指针没有明确的指向
void(*p)();
int i = 0;
scanf("%d", &i);
if (i == 0)
p = man;
else
p = woman;
//调用函数p
p();
return 0;
}
在回调函数和运行期动态绑定的时候大量的用到了函数指针
回调函数
回调函数就是通过函数指针调用的函数。如果把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这个被调用的函数为回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
如下例中,max函数和add函数均为回调函数,func根据不同的情况,有选择性的调用两个函数中的一个
int max(int a, int b){
if (a > b)
return a;
else
return b;
}
int add(int a, int b){
return a + b;
}
void func(int(*p)(int, int), int a, int b){
int res = p(a, b);
printf("%d\n", res);
}
int main(){
int i = 0;
scanf("%d", &i);
if (i == 0)
func(max, 10, 20);
else
func(add, 10, 20);
return 0;
}
四.指针运算
指针运算不是简单的整数加减法,而是以指针指向的数据类型在内存中占用字节数的倍数的运算,指针运算的实质是指针在内存中指向的移动。
char *p;
p++; //移动sizeof(char)个字节数
int *p1;
p1++ //移动sizeof(int)个字节数
p1+4; //p1移动4个长度单位(4*sizeof(int)的长度)
通过指针使用数组元素
指针的加减运算可以使指针移动,所以指针可以非常方便的操作数组
int a[10];
int *p = a;
p + 1; //代表&a[1]
p + 5; //代表&a[5]