若是阁下满意的话,可否一键三连呢!
字符指针
顾名思义,就是存储字符的指针,指向字符的地址,但是多用来存储字符串,指针指向的也是字符串第一个字符的地址
我们看写法:
可以看到,第二句有问题,原因是部分编译器不支持这种写法,当然第一种写法是最正确的,记得加上const
我们再知道,加上const修饰后,是不是就相当于把它的内容锁定了(不能被修改),字符指针中的字符串内容(常量)不能被修改
因此我们再来看一个例子 :
我们先来看第一对,这是2个字符指针,我们知道常量字符串内容不能被修改,那么在占用内存的时候是不是只要占用一处是不是就行了,那么它的地址是不是也就一样了,如果两个字符指针的字符串内容相同,那么它们指针指向的地址是相同的(等价)
我们再来看第二对,这是2个数组,它们内容一样,但是常量字符串初始化数组后,是把字符串内容拷贝到数组里面的,然后数组是在栈区开辟的,两个数组,在栈区上的空间是不一样的
指针数组
同样我们猜到,指针数组就是存储指针的数组,数组里面放的是指针(地址)
那么我们刚才了解到字符指针是放的首字符的地址,我们来看字符指针数组:
同样它被const修饰后,也不能改变它的数组内容(个人辨别标志就是 const跟后面存储的常量字符串),关于用法我们后面几篇再详细说,这里只解释这个概念
下面还有存储整型数组地址的数组,整型指针数组:
它存储的是整型的数组名(地址) ,我们通过arr【0】,arr【1】就访问了这两个数组,但是注意整型没有“\0”,因此如果要打印每个数组元素,需要使用循环,比如:
(下面2种打印方法等价)
数组指针
(困难的来了!)数组指针就是存储数组的指针,但是不是指向数组首元素!(重点注意),它指向的是整个数组!整个数组!整个数组!
我们先初始化一个整型数组arr,存放的是10个int类型的元素
第二句就是数组指针: *表示它是指针,p是指向含有10个int类型数组的指针(即指向整个数组)
重点重点重点!我们再来看“&数组名”与“数组名”的区别
&数组名表示数组的地址(整个数组的地址)
数组名表示数组首元素的地址
数组指针指向的是整个数组的地址,如果不加&,编译器也不会通过或者发出警告
为什么要加()跟【】呢?
我们看优先级
() 跟【】优先级相同,从左往右算,()里面的*p是先告诉我们这个是指针和它的指向,后面【】才是数组,即指针数组
下面我们看下它的一个应用:
我们来解释一下:
* 代表它是指针,p指向的是4个int类型数组的指针,如果还不清楚,我们接着看
下面的打印:先对p+i解引用,表示第几行(i表示第几行,那么一行是不是4个元素),后面的 j 控制列
为了方便理解,我们再看几个例子:
我们先看第一个:这是一个数组,存储的是5个int类型的元素
第二个:这是指针数组(存放指针的数组),里面是5个int*类型的元素
第三个:这是数组指针(存放数组的指针),arr是指针指向数组,数组里面有10个元素,每个元素是int类型
第四个:这是一个数组指针数组,我们发现括号()里面的和第三个很相似。括号里面表示arr是指针指向int(*)【5】,这个数组有10个int类型的元素,每个元素是int(*)【5】的数组指针
(一定要牢记数组指针形式,注意括号的位置,这样避免混乱)
数组参数,指针参数
注意:数组参数接收的要么是指针,要么是数组
我们先看一维数组传参的几种方式:
第一种第二种我们都很好理解,传过去的是数组名,我们就用数组接受
第三种,传过去的是数组首元素地址,我们就用指针接收地址
(简单快捷的就是把那个数组等号左边的直接抄一遍,虽然很无脑哈哈哈!但是很实用)
二级数组传参(行可以省,列不能省,切记)
第一种第二种都是上面说的很实用方法,哈哈哈!
第三种就是用数组指针接收,这里的二维数组可以看成3个一维数组,这样就方便理解了,每个一维数组有4个元素
一级指针传参
首先p是一个一级指针,指向数组首元素,我们接收也用指针接收,因为整型没有\0,所以我们如果想打印每个元素,需要用到循环,那么就需要传数组元素个数过去,来控制循环
二级指针传参
传过去pc是二级指针,我们用二级指针接收,我们继续!
函数指针
指向函数的指针
解析:
1:先创建一个函数指针,注意*p要加括号,因为优先级问题
p是指向函数地址的指针变量
2:&可以不加,因为&函数名跟函数名都是函数的地址,但为了方便理解,新手建议不要省略
函数指针数组
概念很简单,就是存放函数指针的数组,相较于函数指针,它可以存放多个函数,我们知道函数指针是指向一个函数的指针,那么函数指针数组就是通过数组下标让函数指针可以指向多个函数
大家可以看到,函数指针数组就是在函数指针里面多加了一个方框,代表它是数组,后面(int,int) 表示2个参数是int类型,可以改变它的参数类型
我们看一个模拟计算器的例子,通过函数指针我们可以更加灵活的调用函数(先看原版):
大家可以看到我们只是先用原来的写法去实现这个功能(没有具体写函数啊),发现很多都是重复的 ,所以我们可以利用函数指针数组去简短一下
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
//四个执行函数
int sum(int x, int y)
{
return x + y;
}
int minus(int x, int y)
{
return x - y;
}
int multiply(int x, int y)
{
return x * y;
}
int excpet(int x, int y)
{
return x / y;
}
void cy_com(int input)
{
int x = 0;
int y = 0;
printf("请输入操作的2个整数\n");
scanf("%d %d", &x, &y);
//函数指针数组集装函数
int (*p[4])(int, int) = { &sum,&minus,&multiply,&excpet };
//input作为变量控制执行函数
int sum = p[input-1](x, y);
printf("%d ", sum);
}
int main()
{
int input = 0;
do
{
printf("\n");
printf("请选择操作\n");
printf("*****1:加法*****\n");
printf("*****2:减法*****\n");
printf("*****3:乘法*****\n");
printf("*****4:除法*****\n");
printf("*****0:退出*****\n");
scanf("%d", &input);
//分支判断
switch (input)
{
case 1:
cy_com(input);
break;
case 2:
cy_com(input);
break;
case 3:
cy_com(input);
break;
case 4:
cy_com(input);
break;
case 0:
printf("退出\n");
break;
}
} while (input);
return 0;
}
这样可以不用频繁输入,我们只需要把函数地址放在函数指针数组里面就可以通过数组下标选择执行的函数
思路:首先选择要执行的计算器方案,然后把那个input传过去,写一个函数指针数组来装填4个函数,用input-1作为下标来控制执行函数类别,大家按照main函数开始位置走一遍就知道了,函数指针数组(比喻成集装箱)相较于函数指针的优点是,更加灵活的选择函数执行,不用频繁的输入
指向函数指针数组的指针
从类别我们知道它是个指针,我们之前了解了数组指针是指向整个数组的指针,这里介绍的也是指向整个函数指针数组的指针
我们知道函数指针数组是个数组,那么把它变成指针,是不是加个解引用符号就行了,我们看写法:(注意括号的位置,对函数指针数组取地址)
函数指针:是一个指针,指针的大小是4/8个字节
函数指针数组:是数组,数组大小是每个元素大小*元素个数,像上面就是4*1
指向函数指针数组的指针:是指针,指向整个数组,大小是4/8个字节(至于为何是4/8个字节,可以看上面提到的数组指针!)
回调函数
我们通过一个例子来分析:
我们写一个计算器(因为视觉效果不能缩太小,我们看主要部分就行了,最上面就是个加减乘除执行函数)
首先,我们根据需要选择计算器,cy_com函数用函数指针接收调过来的函数地址,那么现在我们的函数指针是不是就指向了调过来的那个函数,然后在2(图片上面)那里,我们再次用函数指针调用那个具体执行函数,这么个执行方式就是回调函数(重点我都标颜色了哈)
如果不能理解,我们再看一个例子:
先写一个简单的冒泡排序
我们知道初始版的冒泡排序只能排序整型,那么我们再学个进阶版的,既可以排整型,又可以排名字,身高,大小这些变量
qsort快排函数
首先我们来了解一个库函数:
第一步:进入库函数查询官网
第二步:左上角搜索 qsort 库函数
第三步:了解它的各种信息(学艺不精,用了翻译器啊!)
我这里直接说明它的各种返回值,参数了啊:
1:头文件:stdlib.h
qsort(数组起始位置,数组元素个数,数组一个元素大小,调用函数)
比如:
qsort(arr,sz,sizeof(arr[0]),cy_com)
2:调用函数接收需要有2个万能指针用来指向一对元素:const void * e1,const void * e2
(为何是万能指针:因为void是没有明确传过来的参数类型的,可以接收任意的参数类型)
3:强转化
我们用万能指针接收2个参数后,在接下来怎么移动,一下移动几个字节是不是不确定,那么我们需要强制转换,方便接下来的操作
强制转化,加个括号,里面改为目标类型加上解引用符号(比如我们强制转化为整型指针) :
(int*)e1,(int*)e2
4:比大小(通常强转和比大小是一起的),参考下面:(第二幅图是例子)
下面我们来实现一下用qsort来模拟冒泡排序:(如果看完qsort看不懂的话,看最后一张自写qsort哦!)
为什么要有返回类型int
我们可以看出它是2个指针指向2个元素排的,它排完这2个元素是不是要接着排第二对,第三对,这个函数只是排序工具,它排完需要将值返回去接着下一对
下面我们再用qsort实现对结构体中变量名字的排序:
(注意名字不能进行加减,需要用另外的函数来按照字母顺序排序,同时因为优先级注意加括号)
下面我们再实现一个可以排序结构体,整型,字符类的冒泡排序
我们来梳理思路:
首先我们创建了一个数组arr,接着我们又创建了一个函数qsorty,传了4个参数
传这四个参数原因以及对应接收:
第一个参数:我们需要知道排序对象的首地址吧,既然我们传了首地址,那么我们又不知道它是什么类型,所以我们暂时用void *来接收
第二个参数:排序总要知道排序几个元素个数吧,接收直接用int
第三个参数:我们不知道操作对象的类型,那么我们要怎么控制指针的移动大小?因此需要知道元素占多少字节
第四个参数:cy_com是一个判断函数,我们用函数指针接收,这个函数指针指向这个函数,如果有疑问可以参考上面的函数指针讲解
接着我们来到接收函数qsorty,下面的2个循环我们跟冒泡思想一样,控制交换个数、控制交换一对元素不变,改变的是判断条件,因为我们之前的是整型判断条件,在这里我们不知道元素类型,但是我们可以把交换的2个元素强制转化为char*类型,有2个好处:
1:可以灵活的通过一个元素占多少字节来操控指针移动,比如一个int类型的元素,我们强制转化为char*类型,然后移动一个宽度(一个元素的大小),是不是就把它移动了,然后通过循环变量j来控制移动
2:我们既然不知道元素类型,如果要交换是不是把它转为char类型后,把它宽度个字节全部交换就把这两个元素交换了,比如int类型,我们先转为char类型,然后交换四次,每次交换一个字节
我们在判断那里调用那个函数cy_com,因为函数指针pc是指向这个函数的,进入cy_com函数后,我们把它再转化为int*类型进行解引用拿到具体元素,注意这里的强转是要根据需求变的,比如比较结构体,就需要转化为结构体类型,然后通过返回值判断是否需要进行交换,返回值大于1,说明第一个元素比第二个元素大,需要交换,否则不需要
假如我们需要进行交换,进入函数sub,把每个字节都交换过来就行了
然后一对元素交换完了,我们进行下一对交换,通过2个for循环来控制交换趟数
思路大概和冒泡的思维差不多,我们只是通过强转来实现一个“万能”交换
好了,这篇指针进阶写完了,制作不易,可否一键三连呢!