目录
1. 字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针 char*
一般使用:
char ch='w';
char* pc=&ch;
*pc='w';
还有一种方式:
const char* pstr="hello bit";
printf("%s\r\n",pstr);
上面的代码很容易让人认为是把字符串放到了指针变量里,但本质上是把字符串的首地址放到了pstr里,是 *pstr=‘h’
- 可以把字符串想象为字符数组,但是这个数组不能修改
- 当常量字符串出现在表达式中时,值是第一个字符的地址,存储在只读数据区
可以通过下列方式验证:
const char* str = "abcdef";
//str[3] = 'o'; err,不可修改
printf("%c\r\n", str[2]); //数组方式访问字母
printf("%c\r\n", "abcdef"[3]);
printf("%p\r\n", "abcdef");
printf("%p\r\n", str);
字符指针变量可以像数组一样通过下标访问,也可以在字符串后面添加下标后缀访问。字符串的地址和字符变量的地址相同,都是首字母的地址
下面是《剑指offer》收录的一段代码:
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("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
判断if条件分别会输出哪句话?
str1和str2是字符串首字符的地址,是两个局部变量,会分别申请不同的空间,所以两个的地址也是不一样的,所以输出"not same"
而str3和str4是常量字符串,它们的内容是相同的,就会在只读数据区申请一份空间,因为不能修改,所以只需要存储一份就行
分别输出四个变量地址进行验证:
2. 数组指针变量
2.1 数组指针变量是什么
指针数组是一种数组,那么数组指针就是指针变量
我们已经熟悉:
- 整形指针变量: int* p,存放的是整形变量的地址,能指向整形数据的指针
- 浮点型指针变量: double* p,存放的是浮点型变量的地址,能够指向浮点型数据的指针
定义的区别
int* p1[10]
int (*p2)[10]
p1是数组,p1先和中括号结合,表示数组有10个元素,每个元素都是int*
p2有括号,先和*结合,是指针,指向一个有10个元素的数组,每个元素类型是int
因为[]的优先级大于*号,所以数组指针要加一个括号保证和*先结合
2.2 数组指针变量怎么初始化
数组指针是用来存放数组的地址的,那么怎么获得数组的地址呢?可以用**&数组名**
int arr[10] = {0};
&arr;//得到数组的地址
int (*p)[10] = &arr; //定义数组指针并初始化为arr
在调试里也可以看到&arr和p的类型是一致的
3. 二维数组传参的本质
有了数组指针的理解,就可以讲二维数组传参的本质了
我们在之前一维数组传参的时候,形参部分可以写成数组形式,也可以写成指针形式
- 写成数组形式更加直观,方便理解
- 写成指针形式,因为数组传参,传递的是第一个元素的地址
二维数组也是如此
void Print(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("\r\n");
}
}
void Print(int (*p)[5], int r, int c) {
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++) {
//printf("%d ", p[i][j])
printf("%d ", *(*(p + i) + j));
}
printf("\r\n");
}
}
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr, 3, 5);
总结: 二维数组传参,形参可以写成数组,也可以写成指针形式
4. 函数指针变量
4.1 函数指针变量的创建
函数指针变量应该是用来存放函数地址的,通过地址能够调用函数
那么函数是否有地址呢?
void test(){
}
int main(){
printf("test: %p",test);
printf("test: %p",&test);
}
所以函数是有地址的,函数名就是函数的地址,也可以通过&函数名获得地址,两种是一样的
如果要将函数指针的地址存放起来,就需要创建函数指针变量了,函数指针变量的写法其实和数组指针非常类似
void test(){
}
void (*pfn)() = test;
void (*pfn)() = &test;
函数指针类型解析
4.2 函数指针变量类型的使用
//函数指针调用
int add(int x, int y) {
return x + y;
}
int (*pf)(int, int) = add;
//两种调用方式
printf("%d\r\n",(*pf)(3,5));
printf("%d\r\n", pf(3, 5));
以上两种调用方式都是可以的
4.3 两段代码解读
代码1
(* (void (*) ()) 0 )();
首先回忆一下,(int)5是什么意思,这是将5强制转为int类型
函数指针没有名字,是函数指针类型
将0这个数转换成一个无返回值无参数的函数指针,然后*函数指针()
是对这个0地址函数的调用
代码2
void (signal(int,void()(int)))(int);
- signal是一个函数
- 参数有两个,一个是int,一个是函数指针类型,这个函数指针一个int类型的参数,无返回值
- 函数signal的返回值是无返回值的函数指针类型,参数是int
此代码是一个函数声明
上面的两段代码均出自《c陷阱和缺陷》这本书
5. typedef关键字
用来重命名,将复杂的类型简单化
如果觉得 unsigned int 用起来不方便,可以写成uint就方便多了
typedef unsigned int uint;
//将unsigned int重命名为int
uint n;
对于数组指针和函数指针的重命名稍微有区别:
数组指针类型
typedef int (*par)[5];
函数指针类型
typedef void(*pfun)(int);
有了typedef,就可以简化理解上面的代码2
typedef void(*pfun)(int);
pfun signal(int,pfun);
5. 函数指针数组
数组是一个存放相同类型数据的存储空间,函数指针数组存放的元素类型就是函数指针,定义方法:
int (*parr[3])();
parr先和[]结合,是数组,然后数组的类型是int(*)(),函数指针
6.转移表
函数指针数组的用途: 转移表
转移表是c语言一种常见的数据结构,用于实现条件分支和跳转的功能,它可以将一个输入值映射到相应的输出值,并根据不同的输入值进行不同的处理
就是利用函数指针数组,通过不同的输入值跳转不同的函数处理
实例: 计算器的实现
简单写法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
void menu() {
printf("********************\r\n");
printf("*** 1.add 2.sub ***\r\n");
printf("*** 3.mul 4.div ***\r\n");
printf("*** 0.退出 ***\r\n");
printf("********************\r\n");
}
int main()
{
int input = 1;
int n1, n2;
int ret = 0;
do
{
menu();
printf("请选择功能\r\n");
scanf("%d", &input);
switch (input)
{
case 0:
//退出
printf("退出游戏\r\n");
break;
case 1:
printf("请输入两个数\r\n");
scanf("%d %d", &n1, &n2);
ret = add(n1, n2);
printf("ret=%d\r\n", ret);
break;
case 2:
printf("请输入两个数\r\n");
scanf("%d %d", &n1, &n2);
ret = sub(n1, n2);
printf("ret=%d\r\n", ret);
break;
case 3:
printf("请输入两个数\r\n");
scanf("%d %d", &n1, &n2);
ret = mul(n1, n2);
printf("ret=%d\r\n", ret);
break;
case 4:
printf("请输入两个数\r\n");
scanf("%d %d", &n1, &n2);
ret = div(n1, n2);
printf("ret=%d\r\n", ret);
break;
default:
printf("输入错误\r\n");
break;
}
} while (input);
return 0;
}
运行截图:
上述写法的问题在于switch语句中有很多重复代码,如果计算器再添加新的计算符,需要重写很多冗余的代码,所以用函数指针数组进行优化
优化写法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}
void menu() {
printf("********************\r\n");
printf("*** 1.add 2.sub ***\r\n");
printf("*** 3.mul 4.div ***\r\n");
printf("*** 0.退出 ***\r\n");
printf("********************\r\n");
}
int main()
{
int input = 1;
int n1, n2;
int ret = 0;
//转移表
//为了使下标对应,0下标设置个空
int (*pf[5])(int,int) = {NULL,add,sub,mul,div};
do
{
menu();
printf("请选择功能\r\n");
scanf("%d", &input);
if (input >= 1 && input <= 4) {
printf("请输入两个数\r\n");
scanf("%d %d", &n1, &n2);
ret = pf[input](n1, n2);
printf("ret=%d\r\n", ret);
}
else if (input == 0) {
//退出
printf("退出游戏\r\n");
break;
}
else {
printf("输入错误\r\n");
}
} while (input);
return 0;
}
使用函数指针可以实现跳转的功能,根据输入值找到不同的功能