欢迎来到白刘的领域 Miracle_86.-CSDN博客
系列专栏 C语言知识
先赞后看,已成习惯
创作不易,多多支持!
目录
[一、 字符指针变量](#一、 字符指针变量)
[2.1 数组指针变量是什么](#2.1 数组指针变量是什么)
[2.2 数组指针变量如何初始化](#2.2 数组指针变量如何初始化)
[4.1 函数指针变量的创建](#4.1 函数指针变量的创建)
[4.2 函数指针的使用](#4.2 函数指针的使用)
[4.3 两段有意思的代码](#4.3 两段有意思的代码)
[4.3.1 typedef关键字](#4.3.1 typedef关键字)
一、 字符指针变量
指针的类型中有一种为字符指针char* 。
一般我们使用的时候,是这么使用的:
cpp
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
其实还有一种方法:
cpp
int main()
{
const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
printf("%s\n", pstr);
return 0;
}
其实上述方法是将字符串的首字符的地址放在了指针pstr里了,也就是字符h的地址。
《剑指offer》中收录了这样一道题:
cpp
#include <stdio.h>
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");
return 0;
}
来看运行结果:
你猜对结果了嘛,下面我们来简单讲解一下这道题。
首先四个字符串,str1.2.3.4,在3和4的前面用const修饰了,所以这里的str3和str4是常量字符串。
而在C/C++中对于一个常量字符串,是不会额外开辟空间的,所以str3和str4的首地址是一个地址,所以两个字符串也是相同的。而str1和str2不是常量字符串,是可修改操作的,所以分别开辟了两个空间来存放这两个字符串,因此它俩的首字符地址也不相同,所以两个字符串也不相同。
二、数组指针变量
2.1 数组指针变量是什么
前面我们学习了一个类似,非常容易混淆的,叫指针数组,当时也挖坑了,说以后会讲这个。
我们之前说,指针数组,它是数组,那数组指针 呢?是指针!
我们已经熟悉:
整型指针变量:int * pint; 用来存放整型变量的地址,能指向整型数据的指针。
浮点型指针变量:float * pint; 用来存放浮点型变量的地址,能指向浮点型数据的指针。
所以我们能推断出数组指针,它是用来存放数组地址,能指向数组的指针。
来思考下面两行代码,分别是什么:
cpp
int *p1[10];
int (*p2)[10];
第一行是在前面我们学的指针数组,第二行为数组指针。
如何去记忆呢?我们要记住一个点就好,就是[ ]的优先级是比 * 高的,我们去看变量名和谁先结合就好,第一行先和中括号结合,所以是数组,变量名为数组名;第二行加了小括号,所以先和*结合,成了数组指针。
那数组指针的类型是什么呢?我们在学习数组的时候说去掉数组名,剩下的就是类型,那来看看数组指针的类型怎么写呢?
cpp
int (*)[10];
2.2 数组指针变量如何初始化
数组指针用来存放数组的地址,那怎么获取数组的地址呢?没错,就是我们之前说的**&arr。**
cpp
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放整个数组的地址,就存放在数组指针中:
cpp
int(*p)[10] = &arr;
我们调试也能看到&arr和p的地址是一样的。
数组指针类型解析:
cpp
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
三、二维数组传参本质
有了数组指针,我们就能详细了解二维数组传参本质了。
之前,我们将一个二维数组传给一个函数的时候,我们往往会这么写:
cpp
void test(int a[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", a[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} };
test(arr, 3, 5);
return 0;
}
我们学完数组指针之后,还有其它的写法嘛?
我们再次理解一下二维数组,其实它就可以看成一个一维数组,只不过里面元素装的也是一维数组,所以二维数组的每一行都是一个数组。
所以,根据数组名是首元素地址这个规则,二维数组的数组名也就代表着第一行的地址,也就是一个一维数组的地址。根据上面的例子,第一行的数组的类型也就是int [5],所以第一行的地址类型就是int (*)[5]。那就意味着**二维数组传参的本质也是传递了地址,传递的是第一行的一维数组的地址,**那么形参也可以写成指针形式:
cpp
#include <stdio.h>
void test(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(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} };
test(arr, 3, 5);
return 0;
}
**总结:**二维数组传参,形参部分可以写成数组,也可以写成指针。
四、函数指针变量
4.1 函数指针变量的创建
通过类比关系,不难理解函数指针。它应该是存放指针地址的,可以通过函数地址调用函数的。
那函数指针到底是不是这样的呢?我们来做个测试:
cpp
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
运行结果如下:
我们确实打印出来了地址,所以函数确实是有地址的,并且函数名就是地址,所以可以用**&函数名**的方式获得函数的地址。
我们如果想将函数的地址存放起来,也非常简单,这就用到了函数指针变量,写法其实和数组指针的写法非常类似:
cpp
void test()
{
printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
就是后面的改成参数了而已,非常简单。
函数指针类型详解:
cpp
int (*pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
4.2 函数指针的使用
通过函数指针调用指针指向的函数。
cpp
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
运行结果:
我们发现,写(*pf3)(2,3),或者pf3(3,5)都可以。
4.3 两段有意思的代码
代码1
cpp
(*(void (*)())0)();
cpp
//首先void(*)() --这是指针变量
//( void(*)() )0 --强制类型转换
//这就意味着我们假设0地址这个位置放着无参,返回类型为void的函数
代码2
cpp
void (*signal(int , void(*)(int)))(int);
cpp
//void(* signal(int,void(*)(int)))(int);
//声明了一个函数,signal(int,void(*)(int))
//它有两个参数,一个是int,一个是函数地址
//把signal的地址放进了void类型的函数指针,参数为int
这两段代码均出自于《C陷阱与缺陷这本书》。
4.3.1 typedef关键字
我们好久没讲关键字了,今天就新认识一个,叫**typedef。**它是用来干什么的呢?用来给类型重命名的,可以将复杂的类型简单化。
举个栗子:比如说你觉得unsigned int 写起来不方便,你想给它改成uint,代码如下:
cpp
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?当然可以,比如将int*改成ptr_r:
cpp
typedef int* ptr_t;
但是对于数组指针和函数指针,稍微有点不同。
比如我们要将int(*arr)[5]改写成parr_t,我们就需要这么改:
cpp
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
同理,函数指针也是这样,比如我们将void(*)(int)改成pf_t,就要这么写:
cpp
typedef void(*pfun_t)(int);//新的类型名必须在*的右边
练练手,如果我们将刚刚的代码2进行简化,我们可以这么写:
cpp
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
五、函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组。
比如:
cpp
int *arr[10];
//数组的每个元素是int*
如果我们想把函数的地址都存储到一个数组中,那这个数组就叫**函数指针数组,**它是怎么定义的呢?
cpp
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
答案是parr1,
parr1先和[3]结合,说明其是数组,然后数组的内容是什么呢?是int(*)( )类型的函数指针。
六、转移表
讲完了函数指针数组,我们来说一个听起来比较牛逼的东西------转移表。 虽然听起来很牛逼,但是它的本质就是我们刚刚学过的函数指针数组。
举个栗子,假如我们现在要写一个计算器的小程序,正常我们会这么写:
cpp
#include <stdio.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;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
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;
}
很好想,但是这么写稍微有点冗余了(就是switch里有许多重复或者相似的代码片段)。
而我们学完了函数指针数组,我们就可以这么写,我们把加减乘除的函数都装到数组里,代码如下:
cpp
#include <stdio.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;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输⼊有误\n");
}
} while (input);
return 0;
}