目录
指针就是地址,口语中说的指针通常指的是指针变量
指针的定义以及指针类型
野指针
指针运算
a.指针+/-整数
b.指针只能-指针,不能+指针
指针与数组
二级指针
指针数组
指针的大小是固定的4/8个字节(32位平台/64位平台)
字符指针
指针数组
数组指针
数组指针的使用
数组指针和指针数组的区分
数组名与&数组名的区别
数组传参和指针传参
一维数组传参
二维数组传参
一维指针传参
二维指针传参
函数指针
函数指针数组
回调函数
例如:int* p;
p的类型并不是int,而是int*
当然的,除了int类型的指针还有short*的float*,double*等等的类型的
当然这里值得一提的是,这里的3个printf,都是打印4.
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
那这样指针的类型不一样但都是4个字节,那指针的类型有什么用呢?答案在下面
由图中可以知道,第一个图int类型的a的地址由int*来存储
第二个图int的a的地址由char*来存储。可以看到第一个图更改了4个字节的地址,但第二个图只更改了1个字节的地址。
所以要用相同类型的指针变量来存储相同类型的变量地址,就像第一个图一样。
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
而指针类型的例外一个作用:指针的类型决定了指针向前或者向后走一步有多大(距离)。
定义:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
比较常见的成因是:指针未定义,以及数组越界访问,形参或者其他原因造成的指针指向的空间释放
我们在编写代码时,需要努力的规避野指针,来避免程序出错
例如:
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int *p=arr;
*p+1;
这里*p+1访问的是arr[1];
这里,++的运算级别比*高,所以先进行vp++,再加*,因为是后置++所以*vp++是访问的是*vp,然后再进行++,进入到下一个。
下图红色区域为数组地址的位置。
这里当数组减到values[0]的时候,因为判断条件是vp>=values[0],所以还会进行一次循环判断,这里就会超出数组的范围啦。
这样的话,能不能成功运行取决于编译器,所以最好不要这样写。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
这里我给翻译一下,就是可以跟数组后面的指针作比较,但不可以跟数组前面的的指针作比较。
当数组的首地址(数组名)定义给了指针的话。
.p+i 其实计算的是数组 arr 下标为i的地址。(p为指针)
二级指针其实就是将一个指针的地址再将其放在一个指针中,类似于套娃。.
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
int arr[10]其实就是存有int类型数据的数组
同理可得,指针数组也是一个数组,是一个存有指针的数组.
例如:int* arr[10];
字符指针简单来说就是存放字符变量的指针。
我们来研究一下这个代码:
从这里我们可以看到,p2存放的是一个字符串,所以字符指针不止可以存放字符还可以存放字符串
我们将p2解引用打印出来发现打印出a,所以如果存放的是字符串,字符串存到p2(指针)中存放的是字符串首元素的地址
我们要打印首元素的话需要解引用,也就是*p2
如果我们要打印字符串的时候,我们只要给字符串首元素的地址就好,也就是p2
这里p2存放的是常量字符串,常量字符串不允许修改
而如果我们想要修改的话,就得利用可以修改的空间,用数组来存储的时候就可以修改了
说白了,就是存放指针的数组
存放数组变量的地址的指针
我们看一下这个代码,我们会发现这个代码很别扭
这个代码奇怪的地方就在print传过去的&arr和print接受的指针类型,以及打印的写法。
首先说明不建议这么写。
这里传过的&arr是一个地址,而地址就是指针,这个指针指向数组,所以是数组指针
p是&arr,而p不就是&arr,和&不就相互抵消了嘛,所以p=arr
二维数组传参(写成指针)
arr1为二维数组,而arr1这个数组名表示二维数组的首地址,而二维数组的首地址是第一行的地址。
所以二维数组的数组名是指向二维数组的第一行的,这个第一行相当于一个一维数组
相当二维数组的首地址是一个指针,然后它指向一个一维数组,所以写成数组指针。
而这个我解释一下,p1+i相当于访问第i行的地址,*(p1+i)解引用相当于第i行,然后再加j然后解引用,相当于找第i行第j列的那一个。
我们来看一个例子
这里我们发现arr和&arr是一样,而arr是首元素地址,&arr是整个数组的地址,但整个数组的地址也得从首元素一样啊。
我们看下面两个,这两个的后两位一个是2C一个是50,2C是2x16+12x1=44. 50是5x16=80,这两个的差值是36加上第一个的地址的4刚好是40,跳过一整个数组的大小。
数组传参时,形参可以是数组也可以是指针。
这里比较有困惑的应该是第五个,我们的arr2是一个存放整形指针(地址)的数组,而我们的int**arr是二级指针
二级指针就是存放着一级指针的指针,这两个不就是相同的嘛,所以可以传过去。
一级指针传参只能用一级指针来接收
二级指针传参只能用二级指针来接收
当形参确定为一级指针时,可以将什么变量传过去呢?
一级指针(*p),一级数组(arrp[10]),&a
当形参确定为二级指针时,可以将什么变量传过去呢?
二级指针(**p),对一级指针取地址(&p2)(p2为一级指针),指针数组(int*p[10])
我们先来看一个例子:
我们通过这个例子我们可以知道,原来函数名也等于函数的地址啊
函数指针的定义方法:int(*p)(int x,int y)=Add;
调用函数指针的时候,函数指针的传过去的值可以写名字也可以不写名字
下面这个例子要理解一下
说白了就是存放了函数指针的数组
int(*p[5])(int x, int y) = { 0, add, sub, mul, div };
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。
简单来说就是:一个函数指针(或者地址)作为一个函数参数被另外一个函数调用,这个函数就叫做回调函数
我们先来看一个库函数qsort(只是一个排列函数)(头文件#include)
代码完整实现
void*类型的指针很宽容,什么类型的指针都可以放进去,但应用的时候不好运用
void型时你p+1访问的几个字节的长度不清楚
如果我们利用冒泡排序的方法写一个与qsort类似的函数呢
不同类型的数据,比较方法也不一样,用冒泡排序来举例子就是
如果我们利用冒泡排序的方法的话
我们就需要找到要比较的两个值的地址,但void*不可以进行+/-等操作。所以要下面这样
完整代码如下