概览
先是对指针有入门的理解(然后入土(bushi))
然后观赏一下指针联合数组的组合技
最后和函数指针亲密接触(通过函数指针来理解qsort库函数)
指针的入门理解
一、结合电脑来理解指针
1.内存和地址
电脑把内存中的数据读取至CPU处理,
数据的宿舍就是内存
就像宿舍有房间,内存也有内存单元,一个单元是一个字节的大小
就像宿舍有编号,内存单元也有自己的号码,这,就是地址
在c语言中,又将地址称作指针
总结一下,内存单元的编号就是地址,叫做指针
2、编址的理解
电脑里硬件是咋相互传信号,一起工作的?
用“线”,就是物理意义上的线,地址总线,数据总线,控制总线等等
硬件会用“电线”,来编出超级多的地址,简称,编址(这是一种语言,就好比远古人类结绳记事,或者是摩斯电码)
那,到底咋编址的呢?
线中传递两种信息,0和1,用电脉冲的有无来表示
所有线表示的信息,就是一个地址
举个例子
32位的机器有32根地址线,每根线都可以传递两种信息,它们通力合作来表示一个地址
不妨算一个简单的数学问题,所有线中信号排列组合,一共多少种?
二的三十二次方种,也就代表了二的三十二次方种的地址
这些地址的背后,就是内存里数据的房间号
我们用地址,可以找到数据们
二、指针变量
1、指针变量的创建和使用
创建:
变量创建的本质就是向内存申请空间
举个例子:
int * pn = & n
int *是指针变量的类型,指针变量有很多类型,char *,int * 等
pn是指针变量的名字
n是我所取地址的对象,换句话来说,就是我搞到手了n的地址,不是其他阿猫阿狗的
&取地址操作符,告诉电脑,老子要取地址了
使用:
对于指针变量的使用,就是根据这个地址,返回去找到内存中的数据
* 解引用符就是实现这个操作的工具
*pn 就是根据pn这个地址,找到n
2、指针变量的大小
指针变量的大小和类型无关!!!只和平台有关
32位的机器下(即x86平台)表示有32根线,32个比特位,换算一下,是四个字节
64位的机器,则是八个字节
地址的大小由平台决定
3、指针变量的类型
那大小一样,类型的区分有啥用呢??
指针类型用来决定,在进行计算等操作时,电脑需要访问几个字节
解引用操作:
char* pn=&n
*pn=0 这里在内存里只改变一个字节变成0(毕竟作为char的n就只有这么大)
int *pn=&n
*pn=0 这里改变四个字节
指针加减整数:
char* pn=&n
pn+1 地址跳过一个字节,找到下一个元素
int *pn=&n
pn+1 地址跳过四个字节,找到下一个元素
tips:这里介绍一个无具体类型的指针 void*
它不可以进行解引用和加减整数的操作
4、野指针
指针指向的位置未知
野指针是很危险的东西
所以我们使用指针初始化来紧急避险
举个例子:
char* pn= NULL(空指针)
赋一个空指针就可以
指针和数组
一、一维数组
1、数组名的理解
数组名是一个地址,一个指针,是数组首元素的地址
但是,有两个例外:
1、sizeof(arr)这里计算整个数组的大小,单位是字节
2、&arr这里代表取出了整个数组的地址
所以 &arr+1会跳过整个数组
2、使用指针访问数组元素
法1:我们不妨先搞到数组各元素的地址:
int * p=arr
p+i 就是下标为i的元素的地址
*(p+i )就找到了元素
法2:利用arr数组名也可以
arr[i]和*(arr+i)其实完全等价,也是对元素的访问
3、一维数组传参本质
一维数组传参本质其实是传递数组首元素的地址
举个例子:
创建一个自定义函数: void test(int arr[ ] )
int arr[] 表面上是数组形参,实际上是希望接收一个地址
我们在主函数中写实参写 arr(代表首元素地址)即可
番外:
1、二级指针
这里是一个套娃:
int * * ppn=&pn
一级指针pn存放n的地址,二级指针ppn存放了一级指针pn的地址
int *代表了取地址的对象是一个指针pn
* 代表了新创建的变量ppn是指针类型
以此类推 还有三级 四级指针等等
2、指针数组
注意:指针数组是数组,数组里元素的类型是指针
int * arr[ 7 ] ={&a, &b, &c }
int * 代表数组中存放的类型是整型指针
printf("%d",arr[0]) 这样只能打印地址,
要是想打印a,就要写*arr[0]
3、字符指针变量
存放字符地址的变量就是字符指针变量
char * p = "abcdef"
abcdef是常量字符串,不可以被修改
p代表常量字符串中首元素a的地址
可以把字符串看成数组,里面的元素是字符
二、二维数组
1、数组指针变量
是指针,来存放数组的地址
int arr[10]={0}
int ( * pa ) [ 10 ]=&arr 用于存放arr的地址
紫色的部分(去掉了数组指针的名字)就是数组指针的的类型
tips:去掉变量名,就得到变量类型
再举个例子:char (*pa)[5]=&arr
char (*)[5]就是类型
2、二维数组传参的本质
二维数组传参本质其实是传递数组首元素的地址
举个例子:
形参:用数组名代表地址 int arr[ ] [ 5 ] 这个5不可以省略
或者也可以写成 地址的形式:int ( *p )[ 5 ] 这是一个数组指针变量
这里注意 二维数组的每一行都是一个元素,而这一行,就是一个一维数组
所以 二维数组首元素的地址,是一个数组指针变量
函数指针变量
一、函数指针变量基础
1、函数指针变量的创建
啊?函数也有地址吗?是的!
函数指针变量就是存放函数地址的,便于通过地址,找到函数来引用
举个例子:
假设一个叫add的函数,他的地址有两种表示方法:
add和&add
假设add函数的定义:int add(int x,int y)
那么函数指针变量:int (*pa )( int,int )=add 这里只要写形参的类型
再来一次去掉变量名,就得到变量类型,这里的类型是:int (*)( int,int )
对这个函数进行使用:
int ret=(* pa ) ( 2 , 3 )两个实参
2、函数指针数组
函数指针数组,是数组,这个数组里面的元素是函数指针
在此之前,我们回忆一下我们是如何创建整型数组的:
int arr
int 是数组中元素的类型,arr是数组名
ok 知道了数组名和数组元素类型是最重要的,我们来小试牛刀:
int ( * pfArr [ 4 ] )(int,int )
啊?!为啥完全不一样???
不要紧张 我们一部分一部分看
int ( * pfArr [ 4 ] )(int,int)
黄色是数组名
红色是元素类型,是函数指针的类型,而这个函数会有两个int类型的形参,4代表这个数组可以放四个函数的地址(即四个元素)
二、函数指针的实战
1、回调函数
即通过函数指针调用的函数
就是在一个函数中调用另一个函数的地址来使用该函数
举个例子:回调实现一个计算器
加减乘除四个函数 calc函数调用黄色部分 主函数中调用calc函数
calc函数就像是中间商,通过地址,来使唤加减乘除这四个函数
这样的回调,大大减少了重复书写代码的需要,方便万岁
2、对qsort函数的理解和使用
qsort库函数的大优势是可以实现任意类型的排序,装逼来说就是泛型编程
我们来看一下这个库函数的定义:
void qsort ( void*base, size_t num, size_t size ,int (*compare)(const void * e1,const void *e2) )
void表示函数返回值的类型是void
void*base 存放数组的第一个元素的地址
size_t num数组里元素的个数
size_t size 数组里一个元素的长度,以字节为单位(这是达成泛型编程非常重要的工具)
我们以字节这个较小的基础单位来进行比较排序,就可以应对各种类型
举个例子:这里用冒泡排序实现了qsort函数中排序的模仿:
int (*compare)(const void * e1,const void *e2)一个函数指针,存放一个函数的地址,用于比较两个元素,这个函数有两个类型是const void *的形参,用于字节之间的交换
tips:qsort函数默认排列成升序