目录
一、什么是指针?
二、指针类型和指针大小
三、指针运算
四、特殊指针
1.野指针
2.字符指针
3.指针数组与数组指针
4.二级指针
5.函数指针与函数指针数组
6.指向函数指针数组的指针
五、回调函数
指针是C语言的重要特点,正是由于指针的存在,C语言才能完成各种对内存的操作。那么,什么是指针呢?
内存被划分成一个个内存单元,每个内存单元的大小是1字节,为了实现对内存的操作,每一个内存单元都有一个编号,就想楼栋的门牌号一样,称为地址。指针就是一种储存地址的变量。如例1:
通过&取出变量的地址赋给指针变量;通过解引用操作符(*)找到指针所指向的内存。
在学习C语言时,我们了解了不同的数据类型:整数型(int)、浮点型(float)、字符型(char)等等,现在如果问例1中指针p的类型,我们根据已有知识,就会猜测指针p的类型是int* ,事实确实如此。
知道了指针类型,我们进而联想指针大小,用sizeof可以计算数据的大小。如例2:
看到输出结果是4,会不会就有人得出结论:指针大小和指针类型有关(X)。先不说这个结论对不对,我们再看几个例子:
例3的结果我们可以看到,全是4,好了这就可以推翻之前的猜测:指针的大小和指针类型无关!所以指针大小究竟怎么算呢?
指针大小和硬件平台有关,在32位平台下,指针大小是4个字节;64位平台下,指针大小是8个字节。由此可知,例3中使用的平台是32位的,大家也可以自己敲敲代码,看看自己的平台是32位还是64位。
指针类型的意义:
- 指针类型决定了解引用操作的权限,如例3中由于pn类型是int*,解引用操作时只能对pn指向的内存向后数4个字节解引用。
- 指针类型决定了指针+/-整数时跳过的步长。
提到运算,我们自然想到加减乘除,不过指针的运算只有三种:指针+/-整数,指针-指针,指针的关系运算。
1.指针+/-整数,如例4:
例4中指针pa一开始指向的是数组首元素的地址,由于指针pa属于int*,(pa+1)就是pa向后跳了4个字节,指向arr[1]的地址,指针类型决定了指针+/-整数时跳过的步长,依托循环,pa可以遍历数组。
2.指针-指针
指针-指针计算的是两个指针之间的元素个数,如例5:
3.指针的关系运算
指针变量之间的关系运算,指的是指向相同数据类型的指针之间进行的关系运算,不同类型的指针之间,或者指针与非0整数之间的比较是没有意义的。0就是空指针(NULL),指针可以和0比较,也可以被初始化成NULL。
指向数组元素的指针可以和指向数组最后一个元素后面的指针比较,但是不能和指向数组首元素前的内存位置的指针比较。
指针的运算发生在同类型的指针之间,多见于数组。
野指针就是指针指向的位置是不可知的、不正确的、随机的。如例6:
例6中的指针没有初始化,指向的位置是未知的,属于野指针。
野指针产生的原因:
从野指针的定义我们可以得出以下:
- 未初始化的指针;
- 访问数组时越界的指针;
- 指针指向的空间释放,没有把对应指针置NULL。
知道野指针产生原因,我们同样可以从这几方面防止野指针,并在使用指针前查看指针有效性。
指针类型中有一种叫字符指针,例如:
char c = 'a';
char* p = &c;
除了指向字符,字符指针还能指向字符串,此时字符指针存储的是字符串首元素的地址。
char *p1 = "Hello world";
char arr[20] = "Hello world";
char* p2 = arr;
上面的代码中,p1和p2是不同的,p1指向的是常量字符串;p2指向的是被常量字符串初始化的变量。
指针数组,顾名思义,它首先是一个数组,不过这个数组存储的元素是指针类型的,例如下:
int* p1[10]; //p1是一个存储了10个int* 类型元素的数组
而数组指针就是指向数组的指针,如下:
int (*p2)[10];
//p2首先是一个指针,所以用括号括起来
//p2是一个指向有10个int元素的数组的指针
数组名和&数组名:
int arr[10] = { 0 };
对于arr,我们知道它既是数组名也是数组首元素地址,通过它,我们可以访问这个数组。那么,&arr呢?
&arr就是整个数组的地址,也是数组指针的赋值。&arr和arr的值是相同的,它们的意义却不同。
指针变量也是变量,也存在地址,指针变量的地址就存储在二级指针中。通过对二级指针的解引用,我们可以实现对指针的改变。
int num = 10;
int* p = #
int** pp = &p; //pp是指向指针p的二级指针
在函数参数的传递过程中,有时我们需要对参数的地址做出改变,这时就需要用到二级指针,就想我们在写一个交换两个数的函数,把指针作为参数一样。
函数指针就是指向函数的指针。如下:
int (*p3)(int ,int);
//p3是一个指向参数是(int,int),返回值也是int的函数的指针
函数名就是函数地址,也是函数指针。
函数指针数组是存储函数指针的数组。可以通过访问函数指针数组的元素,进而使用元素指向的函数。
int (*p4[10])(int, int);
//p4是一个函数指针数组,这个数组有10个元素,
//每个元素都是一个指向参数是(int, int),返回值是int的函数指针
可以先写出函数指针数组,再写指向它的指针。
int (*(*p5)[10])(int, int);
//p5是一个指向函数数组的指针
//p5的类型是int (*(*)[10])(int, int)
有一个指向函数A的指针,当我们把这个指针作为函数B的参数去调用函数A时,函数A就称为回调函数。
#include
void F1(char x, char y)
{
//……
}
//F2的参数是函数指针
void F2(void (*p)(char,char))
{
//……
}
int main()
{
void (*pf)(char, char) = F1;//pf是指向F1的指针
//main函数通过参数将F1的指针传给F2,F2调用F1,F1被称为回调函数
F2(pf);
return 0;
}