1.字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针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", pstr);
return 0;
}
代码const char* pstr="hello bit." ; 特别容易认为是把字符串 hello bit 放到字符指针pstr里了,但是本质上是把hello bit. 的首字符的地址放到了pstr中。
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到了指针变量pstr中。
而printf("%s",pstr) 本质上就是从给定的地址开始打印直到遇到'\0'为止。
《剑指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,str2,str3,str4实质是都是指针,也就是说,str1==str2,str3==str4 比较的是地址,这里str3和str4指向的是一个同一个常量字符串。c/c++ 会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一个内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
cpp
int main()
{
char* str1;
char* str2;
str1= "hello bit.";
str2= "hello bit.";
// 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;
}
2.数组指针变量
2.1数组指针变量是什么?
之前我们学习了指针数组,指针数组是一种数组,数组中存放的是地址(指针)。
那么数组指针变量是指针变量,还是数组?
答案是:指针变量。
- 整形指针变量:int* pint; 存放的是整形变量的地址,能够指向整形数据的指针。
- 浮点型指针变量: float* pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
那我们来看一下数组指针变量的书写形式:
cpp
int(*pa)[10] //这就是pa就是数组指针
解释: p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10类型为整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
2.2 数组指针变量怎么初始化
数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的& 数组名
cpp
int main()
{
int a[10] = { 0 };
int(*pa)[10]=&a;
return 0;
}
这就是数组指针的初始化
我们通过调试也能看到&arr 和p的类型是完全一致的。
数组指针类型解析:
cpp
int(*p)[10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
|
p指向的数组的元素类型
注: 数组指针类型就是去掉数组指针变量名之后剩下的也就是 int(*)[10]。
3.二维数组传参的本质
有了数组指针的理解,我们就能够理解二维数组传参的本质了。
过去我们有一个二维数组的需要传参给一个函数,我们可以这样写:
cpp
#include <stdio.h>
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;
}
这里实参是二维数组,那么时候也可以像一维数组一样,写成指针形式呢?
首先我们再次理解一下二维数组,二维数组其实可以看做是每个元素是一维数组的数组,也就是二维数组的每一个元素是一个一维数组地址。那么二维数组的首元素就是第一行,是个一维数组。
|---|---|---|---|---|---|
| | 0 | 1 | 2 | 3 | 4 |
| 0 | 1 | 2 | 3 | 4 | 5 |
| 1 | 2 | 3 | 4 | 5 | 6 |
| 2 | 3 | 4 | 5 | 6 | 7 |
| | | arr数组 || | |
所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名表示的就是第一行的地址,是一维数组的地址。根据上面的例子,第一行的一维数组的类型就是int [5],所以第一行的地址类型就是数组指针类型int (*)[5].那就意味着二维数组传参本质上传递了地址,传递的是第一行这个一维数组的地址,
那么二维数组函数传递时,形参也是可以写成指针类型的。如下:
cpp
void test(int (*a)[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);
//test(arr[0],3,5); 这里也可以写成这个形式
return 0;
}
总结: 二维数组中每一个元素都是一维数组的地址,所以传递时形参部分可以写成数组,也可以写成指针形式。
注:二维数组数组因为传递的是地址,所以形参位置列不能省略,
数组形式:arr[][5] ,指针形式 (*a)[5]。
4.函数指针变量
4.1函数指针变量的创建
什么是函数指针变量呢?
根据前面学习整形指针,数组指针的时候,我们的类比关系,我们不难得出结论:
函数指针变量应该是用来存放函数地址的,未来可以通过地址能够调用函数的。
那么函数是否有地址呢?
cpp
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
输出结果:
由此,我们可以得知函数是有地址的,且函数名就是函数的地址,当然也可以通过&函数名的方式获得函数的地址。
其实,我们通过之前对于函数栈帧的学习也能分析出,函数是有地址的,在函数栈帧中,每一次调用函数都会在内存中开辟一块空间,那么开辟的这块空间就是函数的栈帧,既然是空间且在内存上,那么它就一定会有地址。
如果我们要将函数的地址存放起来,就都能创建函数指针变量,函数指针变量的写法其实和数组指针非常类似。如:
cpp
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf1)(int, int) = Add;
// int (*pf3)(int x, int y) = Add; x和y 写或省略都是可以的
return 0;
}
函数指针类型解析:
cpp
int (*pf1)(int, int)
| | ---------
| | |
| | pf1指向函数的参数类型和个数的交代
| 函数指针变量名
pf1指向函数的返回类型
4.2函数指针变量的使用
通过函数指针调用指针指向的函数。
cpp
int main()
{
int (*pf1)(int, int) = Add;
printf("%d\n", (*pf1)(2, 3));
printf("%d\n", pf1(3, 5));
return 0;
}
输出结果:
函数指针的使用
- (*函数指针变量名)(参数)
- 函数指针变量名(参数)
4.3两端有趣的代码
代码1:
cpp
(*(void(*)())0)();
代码2:
cpp
void (*signal(int, void(*)(int)))(int);
代码1解析:
代码1 其实就是一次函数调用,它是将0强制类型转换成返回类型为空且空参数的函数指针,然后再调用这个函数指针,如图
代码2解析:
代码2 其实就是一个函数,函数名signal 参数是(int,void(*)(int )), 返回类型是函数指针void(*)(int)类型的,对于它的返回类型为什么这样写,我们可以类比一下, 对于函数指针类型一般是 int(*)(int) 函数指针变量的话就是 int(*a)(int) 正常一个整型指针类型是 int* 整形指针变量是 int* a那么 函数返回类型整型指针是 int* add() 类似于变量定义,所以函数指针变量返回类型书写也就类似于函数定义 也就是 int (*函数名(参数))() 也就是我们代码2的形式
void(* signal(int,void (*)(int)))(int)
4.4 typedef关键字
typedef是用来类型重命名的,可以将复杂的类型,简单化。
比如。你觉得unsigned int 写起来不方便,如果能写成uint就方便多了,那么我们可以使用:
cpp
typedef unsigned int uint;
//将unsigned int 重命名为uint
typedef也是可以对指针类型进行重命名的,但是对于数组指针和数组指针稍微有点区别:
比如我们有数组指针类型 int (*)[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);
5.函数指针数组
数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,
比如:
cpp
int* arr[10];
//数组的每个元素是int*
那要把函数的地址存到一个数组中,那整个数组就叫**函数指针数组,**那函数指针的数组如何定义呢?
cpp
int(*parr1[3])();
parr1先和 [] 结合,说明parr1是数组,数组的内容是说明呢?
是 int(*)()类型的函数指针。
6.转移表
函数指针数组的用途:转移表
通过函数指针数组来达到根据用户的输入调用不同类型,不同功能的函数。
cpp
void menu()
{
puts("*********************************");
puts("**********1.add 2.sub********");
puts("*********************************");
puts("**********3.mul 4.div********");
puts("*********************************");
puts("**************0.exit*************");
puts("*********************************");
}
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()
{
menu();
int x, y;
int (*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
// 用函数指针数组,来实现更具用户的输入,调取不同的函数
int input;
do
{
printf("请选择:");
scanf("%d", &input);
if (input > 0 && input <= 4)
{
scanf("%d %d", &x, &y);
int ret=p[input](x, y);
printf("%d\n", ret);
}
else if (input == 0) break;
else puts("输入错误,请重新输入\n");
} while (input);
}