🎈回顾指针基础
👻1.指针的本质:存放地址的变量
👻2.指针大小:4字节(32位机器上);8字节(64位机器上)
👻3.指针的类型:整型指针、字符型指针等(指向什么类型的变量,就是什么类型的指针)
👻4.指针类型决定了指针+1是移动的步长。(变量类型占几个字节,对应的指针+1就移动几个字节
👻5.int* p=&a; *p得到的是a
🎈指针进阶
🌈一.字符指针
👻定义:字符指针是指向字符类型变量的指针
☀️(1)字符指针的两种使用方法
👻1.第一种使用方法:通过解引用指针改变了变量中存放的值
int main()
{
char ch = 'a';
printf("%c\n", ch);
char* pch = &ch;
*pch = 'w';
printf("%c\n", ch);
}
运行结果:
👻2.第二种使用方法:用指针存储常量字符串的首元素地址从而达到对整个常量字符串的访问
(前提:常量字符串,即被类型名char前有const;原理:变量被const修饰后不可被修改,不储存在栈区中,而是储存在特殊区域)
int main()
{
const char* pstr = "hello bit.";
printf("%s", pstr);
return 0;
}
注:const char* pstr = “hello bit./n”;的意思不是将hello bit.字符串存入字符指针pstr中,本质上是将字符串"hello bit."的首元素地址存入指针变量pstr中
☀️(2)对比两种指针的用法:
法一需要在栈区中开辟两个空间,一个存放字符串变量,另一个存放变量的地址;法二中常量字符串储存在单独区域中,此时只用在栈区中开辟空间存放地址就行,该地址指向的是位于静态区的常量字符串首地址
☀️(3)一道相关面试题
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char* str3 = "hello bit.";
const char* str4 = "hello bit.";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
}
运行结果:
分析:字符串变量str1和str2存放在栈区中的两个不同位置,因此这两个字符串首元素地址不同;字符串str3和str4被const修饰,存放在单独的区域上,二者内容相同,无需开辟两个空间分别存储,因此两字符指针指向同一空间
🌈二.指针数组
👻1.定义:存放指针变量的数组,本质上是数组
👻2.表示方法以及含义
int* arr1[10]; 表示存放整形指针的数组
char* arr2[4]; 表示存放一级字符指针的数组
char** arr3[5]; 表示存放二级字符指针的数组
👻3.识别方法
arr后面跟的是[ ] ---->本质是数组
有几个*就代表是几级指针变量
最前面的类型表示指针的类型
🌈三.数组指针
☀️(1)基础概念
👻1.定义:指向整个数组的指针,本质上是指针
👻2.表示方法及其含义
int (*p2)[10]: 表示数组指针变量,变量名为p2,指向一个为int类型且大小为10的数组
int(
∗
*
∗)[10]:表示数组指针类型,只要是该类型的变量,都指向一个为int类型且大小为10的数组
👻3.识别方法
(*P)—>本质是指针
[10]表示大小为10的数组
最前面的类型表示数组的类型
注: 圆括号( )的优先级高于方括号[ ],将p和
∗
*
∗一起放在( )内使二者优先结合,从而表示指针
☀️(2)数组名和&数组名的区别(arr vs &arr)
👻1.数组名—>数组首元素地址—>给地址加1后跳过一个数组内元素(准确来说是跳过该元素对应大小的字节数)
👻2.&数组名—>整个数组的地址—>给地址加1后跳过整个数组(准确来说是跳过整个数组对应大小的字节数)
🌟重点1:&数组名—>①是一个地址 ②指向整个数组—>是数组指针
🌟重点2:该数组指针的类型为int( * )[10]
👻3.用代码来区分二者
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr= %p\n", &arr);
printf("&arr+1= %p\n", &arr + 1);
return 0;
}
运行结果
arr与arr+1的差值为4,即一个int的大小4
&arr与&arr+1的差值为40,即一个int大小4乘数组大小10
☀️(3)数组指针的使用(常在二维数组中用)
#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
int j=0;
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
int j=0;
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
printf("**********\n");
print_arr2(arr, 3, 5);
}
运行结果:
分析:
arr[3][5]表示一个二维数组,该二维数组中有3个元素,每个元素都是大小为5的一维数组。
两次函数调用时都传进去了参数arr,arr是二维数组首元素地址,即一个大小为5的一维数组的地址,即数组指针。print_arr1用arr[ ][ ]数组名arr接收,print_arr2用数组指针int (*arr) [5],二者本质上相同。
练习:看看下面代码的意思
int arr[5] :表示一个大小为5的整型数组arr
int *parr1[10] :表示一个指针数组parr1,该数组大小为10,存放了指针变量
int (*parr2)[10] : 表示一个数组指针parr2,该指针指向的数组是int类型且大小为10的
int (*parr3[10])[5] :int(
∗
*
∗parr3[10])[5]⇔int(**parr3)[5]
🌈四.数组参数、指针参数
原则:传的什么类型,就用什么类型接收
☀️(1)一维数组传参
一维数组传参,传的是指针。(数组名的本质是指针,因此也可以传数组名)
例1:
void test(int arr[])
{}
void test(int arr[10])
{}//arr[],括号内的数字可以不写
void test(int* p)
{}//arr[]本质上是指针*p
int main()
{
int arr[10] = { 0 };
test(arr);
}
arr[ ]本质上是指针*p
例2:
void test2(int* arr[20])
{}
void test2(int** p)
{}//arr[]本质上是指针
//因此*arr[]本质上是**p
int main()
{
int* arr2[20] = { 0 };
test2(arr2);
}
arr[ ]本质上是指针*p,因此 ∗ * ∗arr[ ]本质上是**p
☀️(2)二维数组传参
二维数组传参,传的是一维数组指针。
有以下3种方式:
void test(int arr[3][5])
{}
void test(int arr[][5])
{}//注:可省略第一个数字(表示行数),
//不可以省略第二个数字(表示列数)
void test(int(*p)[5])
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
}
☀️(3)一级指针传参
函数参数为一级指针时,可以接收的参数:&变量名、指针变量、数组名
☀️(4)二级指针传参
函数参数为二级指针时,可以接收的参数:&指针名、二级指针变量、指针数组名
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}
🌈五.函数指针
☀️(1)基础概念
1.函数指针定义:指向函数的指针,即存放函数地址的指针
2.函数名本质上是一个指针
3.函数名本质是指针,&函数名等价于直接写函数名,比如&test⇔test
void test()
{
printf("hehe\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
运行结果:
☀️(2)函数指针类型名的写法:
👻1.举例说明:int (
∗
*
∗pf)(int ,int)=&ADD;(ADD是一个函数)
分析:int (
∗
*
∗)(int,int)是函数指针类型名,这种类型的指针指向的函数的返回值是int类型的、函数参数有2个且为int类型。pf是函数指针变量名。整句意思是一个叫pf的函数指针变量存放了函数ADD的地址
👻2.练习:写出函数指针的类型
void test(char* pc, int arr[10])
{}
int main()
{
void (*pf)(char*, int[10]) = &test;
void (*pf)(char*, int*) = &test;
return 0;
}
图中函数指针变量pf的类型为void (
∗
*
∗)(char
∗
*
∗,int [10]),也可以将参数类型int[10]改成int
∗
*
∗(因为数组名本质上是数组首元素地址即指针类型)。
👻3.错误写法:void
∗
*
∗pf(char
∗
*
∗,int[10])
分析:此时pf优先与(char
∗
*
∗, int [10])结合,就不表示指针了,表示一个返回值类型为void
∗
*
∗、参数类型为char
∗
*
∗和int
∗
*
∗、名称为pf的函数
☀️(3)通过函数指针调用函数
之前学过用函数名调用函数(法1);函数名本质上是地址,可以解引用函数名( ∗ * ∗函数名)来调用函数(法2);今天又学了将函数名即函数地址存放在函数指针中,可以用函数指针变量名来调用函数(法3);函数指针变量本质上是一个指针,可以解引用函数指针名( ∗ * ∗函数指针名)来调用函数(法4)。
int ADD(int x,int y)
{
return x + y;
}
int main()
{
int(*pf)(int, int) = ADD;
//ADD也可以写成&ADD
int ret1 = ADD(2, 3);
printf("ret1=%d\n", ret1);
int ret2 = (*ADD)(2, 3);
printf("ret2=%d\n", ret2);
int ret3 = pf(2, 3);
printf("ret3=%d\n", ret3);
int ret4 = (*pf)(2, 3);
printf("ret4=%d\n", ret4);
}
运行结果:
由此可以看出使用函数指针时,无需对函数指针解引用,
∗
*
∗只是摆设,增加可读性,没有也行。
☀️(4)两句有趣的代码
👻1.代码1:
//代码1
(*(void (*)())0)();
分析:对以上代码拆解:
①(void (
∗
*
∗)( ))0:表示将0强制类型转换成函数指针类型,其中函数返回值类型为void、函数无参数。
②*(void(
∗
*
∗)( ))0:表示解引用0地址,得到0地址处的函数。当然,省略对函数指针的解引用符号
∗
*
∗也可以起到调用0地址处函数的作用。
🌟问题:0为什么可以是地址?
答:同一个数字可以是不同属性的,赋予它int类型它就是整型数字;赋予它char类型它就是字符;赋予它int
∗
*
∗类型它就是指针;同理,赋予它void(
∗
*
∗)( )类型他就是函数指针,该指针指向的函数返回值为void类型、函数无参数
👻2.代码2:
//代码2
void (*signal(int , void(*)(int)))(int);
(1)分析:对以上代码拆解:
①signal (int ,void(
∗
*
∗)(int) ):表示函数signal,有两个参数,第一个参数的类型是int;第二个参数的类型是void(
∗
*
∗)(int)函数指针类型,该函数指针指向的函数的返回值为void型、函数有一个int型的参数。
②signal函数的返回类型也为void(
∗
*
∗)(int)函数指针类型,该函数指针指向的函数的返回值为void型、函数有一个int型的参数。
③整句代码是函数signal的声明。
(2)对函数指针类型重命名后,可直观地理解该句代码
typedef void(*pf_t)(int);
void (*signal(int, void(*)(int)))(int);
pf_t signal(int, pf_t);
☀️(5)对函数指针类型重命名
👻1.回顾对一般类型的重命名
typedef unsigned int uint;
typedef int* ptr_t;
typedef struct id
{
int age;
char name;
char sex;
}stu_id;
👻2.对函数指针类型重命名
例:typedef int(
∗
*
∗pfADD)(int, int):表示将函数指针类型int (
∗
*
∗)(int ,int)重命名为pfADD
注意:
①错误写法:typedef int(
∗
*
∗)(int, int) pfADD;
②新的指针名应该与
∗
*
∗一起放在( )内。
typedef int(*pfADD)(int, int);
int ADD(int x,int y)
{
return x + y;
}
int main()
{
int (*pf1)(int, int) = ADD;
int ret1 = pf1(2, 3);
printf("ret1=%d\n", ret1);
pfADD pf2 = ADD;
int ret2 = pf2(2, 3);
printf("ret2=%d\n", ret2);
}
运行结果:
☀️(6)数组名、&数组名、函数名、&函数名
🌈六.函数指针数组
☀️(1)类比法理解函数指针数组
int* arr[5]:整形指针数组,存放整形指针;
char* arr2[5]:字符指针数组,存放字符指针;
同理,有函数指针数组,存放函数指针。
☀️(2)函数指针数组类型名的写法
举例说明:int (
∗
*
∗pfArr
[
4
]
[4]
[4] )( int,int )表示一个名叫pfArr、类型为
int (
∗
*
∗
[
4
]
[4]
[4] )( int,int )、大小为4的函数指针数组
写法:在函数指针的基础上,给(
∗
*
∗)括号内的
∗
*
∗后面加上
[
n
]
[n]
[n],其中n代表数组内元素个数即数组大小,初始化了数组后也可以省略掉n。
☀️(3)函数指针数组的使用——转移表
任务:实现一个计算器程序(对比用函数指针数组之前VS用了函数指针数组后)
👻用之前,有几个函数就要写几个printf和scanf,重复工作太多,代码冗余:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*****************\n");
printf("***1.Add 2.Sub***\n");
printf("***3.Mul 4.Div***\n");
printf("******0.exit*****\n");
printf("*****************\n");
}
int main()
{
int input = 0;
int x = 0;int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
👻用函数指针数组后(省略menu函数和四个计算函数)只用写一组输入输出函数,不同的函数调用部分用函数指针数组中的不同函数指针来实现:
int main()
{
int input = 0;
int x = 0;int y = 0;
int ret = 0;
int (*pfArr[])(int, int) = { Add,Sub,Mul,Div };
do
{
menu();
printf("请选择:");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = pfArr[input-1](x, y);
//注意:输入的数字对应的是下标为input-1处的函数
printf("ret=%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误,请重新选择\n");
}
} while (input);
return 0;
}
此时,随着运算种类增多,函数增多,只需要补充好菜单部分和函数指针数组内容,调整if()中input的范围就行。
🌈七.指向函数指针数组的指针
☀️该类型指针类型的写法
int(
∗
*
∗pf)(int,int);表示函数指针;
int(
∗
*
∗pfArr
[
4
]
[4]
[4])(int,int);表示函数指针数组;
int (
∗
*
∗(
∗
*
∗p)
[
4
]
[4]
[4])(int,int)=&pfArr;表示指向函数指针数组的指针,指针名为p,p指向的函数指针数组是pfArr,指向的是整个数组。
🌈八.回调函数
☀️(1)回调函数定义
参数为函数指针(函数名),被用来调用该回调函数所指向的函数。
☀️(2)用回调函数实现计算器程序
省略Add、Sub、Mul、Div函数定义和menu函数定义。
通过回调函数Calc,在适当的时机调用适当的函数。
void Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = pf(x, y);
//也可以解引用函数名:ret=*pf(x,y);
printf("ret=%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
具体调用过程及顺序分析:main函数中,进入case语句后——>Calc函数——>Add、Sub、Mul、Div函数中的某一个。