文章目录
- [深入指针4 - 学习笔记整理](#深入指针4 - 学习笔记整理)
-
- 一、数组指针和指针数组回顾
-
- [验证 `*` 和 `&` 的抵消关系](#验证
*和&的抵消关系)
- [验证 `*` 和 `&` 的抵消关系](#验证
- 二、二维数组传参的本质
- 三、函数指针变量
- 四、两段有趣的代码(提高阅读能力)
-
- [1. 有趣的函数调用](#1. 有趣的函数调用)
- [2. 有趣的函数声明](#2. 有趣的函数声明)
- 函数声明、定义、调用的区别
- 五、typedef关键字
- 六、函数指针数组
-
- [运算符优先级:`*` < `[]`](#运算符优先级:
*<[]) - 函数指针数组实现
- [运算符优先级:`*` < `[]`](#运算符优先级:
- 七、计算器实现
-
- [1. 普通计算器(switch语句)](#1. 普通计算器(switch语句))
- [2. 转移表实现(函数指针数组)](#2. 转移表实现(函数指针数组))
深入指针4 - 学习笔记整理
一、数组指针和指针数组回顾
验证 * 和 & 的抵消关系
c
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%d\n", *p); // *p = *(&a) = a
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int(*parr)[10] = &arr; // 数组指针,存放整个数组的地址
for (int i = 0; i < 10; i++)
{
printf("%d ", (*parr)[i]); // (*parr)[i] = *(&arr)[i] = arr[i] = *(arr+i)
}
return 0;
}
关键点:
[i]:通过下标访问数组元素arr[i][j]:访问二维数组第i行第j列元素*和&互为逆运算:*(&a) = a
二、二维数组传参的本质
二维数组内存布局
二维数组在内存中是连续存储的,按行优先排列。
二维数组名含义
二维数组的数组名是首元素地址 ,而首元素是第一行的一维数组,所以二维数组名就是第一行一维数组的地址。
传参打印实现
c
// 方法1:使用数组接收
void print1(int arr[3][5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", *(*(arr + i) + j)); // 三种写法均可
// printf("%d ", arr[i][j]);
// printf("%d ", (*(arr + i))[j]);
}
printf("\n");
}
}
// 方法2:使用数组指针接收(本质)
void print2(int(*p)[5], int r, int c) // p指向含有5个int元素的数组
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", (*(p + i))[j]); // 或 p[i][j]
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};
print1(arr, 3, 5);
print2(arr, 3, 5);
return 0;
}
核心理解 :二维数组传参,传递的是第一行一维数组的地址,形参要用数组指针接收 int(*p)[5]
三、函数指针变量
基本概念
- 函数也有地址 :
&函数名=函数名,都是函数的地址 - 通过函数地址调用函数:即使不知道函数名,只要知道地址就能调用
- 查看变量类型:去掉变量名,剩余部分就是类型
c
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int* pa = &a; // 整型指针
int arr[5] = {0};
int(*parr)[5] = &arr; // 数组指针
int (*pf)(int, int) = Add; // 函数指针,存放Add函数的地址
// int (*pf)(int, int) = &Add; // 等价
// 三种调用方式,结果相同
int ret1 = (*pf)(4, 5); // 解引用函数指针
int ret2 = pf(4, 5); // 直接使用函数指针(推荐)
int ret3 = Add(4, 5); // 直接使用函数名
printf("ret1 = %d\n", ret1);
printf("ret2 = %d\n", ret2);
printf("ret3 = %d\n", ret3);
return 0;
}
四、两段有趣的代码(提高阅读能力)
1. 有趣的函数调用
c
(*(void (*)())0)();
解析:
(void (*)()):函数指针类型,指向返回void、无参数的函数(void (*)())0:将0强制转换为函数指针类型,即0成为函数地址*(void (*)())0:解引用,找到0地址处的函数(*(void (*)())0)():调用0地址处的函数
含义:调用地址为0处的函数(实际不可调用,仅供理解)
2. 有趣的函数声明
c
void (*signal(int, void(*)(int)))(int);
解析方法:从内向外逐步剖析
signal是一个函数名- 它有两个参数:
int和void(*)(int)(函数指针) signal的返回值类型是void(*)(int)(函数指针)
简化:使用typedef
c
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
函数声明、定义、调用的区别
| 概念 | 说明 | 示例 |
|---|---|---|
| 函数声明 | 告诉编译器函数的存在,包含返回值类型、函数名、参数类型 | int Add(int, int); |
| 函数定义 | 完整实现函数功能,包含函数体 | int Add(int x, int y) { return x + y; } |
| 函数调用 | 使用函数完成特定功能 | int sum = Add(3, 5); |
五、typedef关键字
作用:类型重命名,简化复杂类型
基本用法
c
// 基本类型重命名
typedef unsigned int uint; // unsigned int → uint
typedef int* ptr_t; // int* → ptr_t
// 数组指针重命名
typedef int(*parr_t)[6]; // int(*)[6] → parr_t
// 注意:新类型名必须在*右边
// 函数指针重命名
typedef int(*pf_t)(int, int); // int(*)(int,int) → pf_t
int Add(int x, int y) { return x + y; }
int main()
{
uint n = 10; // 等价于 unsigned int n = 10
ptr_t p = &n; // 等价于 int* p = &n
int arr[6] = {0};
parr_t parr = &arr; // 等价于 int(*parr)[6] = &arr
pf_t padd = Add; // 等价于 int(*padd)(int,int) = Add
return 0;
}
复杂声明简化
c
// 原始复杂声明
void (*signal(int, void(*)(int)))(int);
// 使用typedef简化
typedef void(*pf_t)(int);
pf_t signal(int, pf_t);
六、函数指针数组
概念:存放相同类型函数指针的数组
运算符优先级:* < []
c
int *parr[5]; // 指针数组:parr先与[5]结合,有5个int*元素
int (*parr)[5]; // 数组指针:parr先与*结合,指向有5个int元素的数组
函数指针数组实现
c
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; }
int main()
{
// 函数指针数组:存放4个函数指针
int (*pf[4])(int, int) = {Add, Sub, Mul, Div};
// ↑ pf是数组,有4个元素,每个元素类型是int(*)(int,int)
// 遍历调用
for (int i = 0; i < 4; i++)
{
printf("%d ", pf[i](4, 6)); // 直接通过数组下标调用
// printf("%d ", (*pf)[i](4, 6)); // 错误!*不能解引用数组
}
return 0;
}
注意 :*只能解引用指针,不能解引用数组
七、计算器实现
1. 普通计算器(switch语句)
c
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 x, y, input, ret;
do {
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("结果是:%d", ret);
break;
case 2:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("结果是:%d", ret);
break;
case 3:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Mul(x, y);
printf("结果是:%d", ret);
break;
case 4:
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("结果是:%d", ret);
break;
case 0:
printf("正在退出计算器...");
break;
default:
printf("请重新选择:");
break;
}
printf("\n");
} while (input);
return 0;
}
缺点:添加新功能需要增加case,代码冗长
2. 转移表实现(函数指针数组)
转移表:通过函数指针数组实现函数调用转移
c
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; }
int And(int x, int y) { return x & y; }
int Or(int x, int y) { return x | y; }
int Nor(int x, int y) { return x ^ y; }
int contray(int x) { return ~x; } // 单目运算
void menu()
{
printf("*********************************\n");
printf("**********1.Add 2.Sub***********\n");
printf("**********3.Mul 4.Div***********\n");
printf("**********5.And 6.Or ***********\n");
printf("**********7.Nor 8.contray*******\n");
printf("**********0.exit ***********\n");
printf("*********************************\n");
}
int main()
{
int input, x, y;
// 函数指针数组(转移表)
int (*parr[8])(int, int) = {0, Add, Sub, Mul, Div, And, Or, Nor};
// 下标: 0 1 2 3 4 5 6 7
do {
menu();
printf("请选择:");
scanf("%d", &input);
if (input >= 1 && input <= 7) // 双目运算
{
printf("请输入两个数:");
scanf("%d %d", &x, &y);
int ret = parr[input](x, y); // 通过数组下标调用对应函数
printf("结果是:%d\n", ret);
}
else if (input == 8) // 单目运算(特殊处理)
{
printf("请输入一个数:");
scanf("%d", &x);
int ret = contray(x);
printf("结果是:%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("请重新输入:\n");
}
} while (input);
return 0;
}
优点:
- 添加新功能只需在数组中增加函数指针
- 代码简洁,易于扩展
- 消除了大量重复的switch-case代码