深入理解指针(六)
一、字符指针变量
1.1字符指针变量
在指针的类型中我们知道有一种指针类型为字符指针char*
一般有以下两种使用方式:
#include<stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
#include<stdio.h>
int main()
{
const char* pstr = "hello world.";
printf("%s\n", pstr);
return 0;
}
代码const char* pstr="hello world.";特别容易让同学以为是把字符串 hello world 放
到字符指针 pstr 里了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
接下来给大家分享一串代码,这是《剑指offer》中的一道题:
#include<stdio.h>
int main()
{
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("atr1 and str2 are not same\n");
if(str3==str4)
printf("str3 and str4 are same\n");
else
printf("atr3 and str4 are not same\n");
return 0;}
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
二、数组指针变量
2.1数组指针变量是什么
之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。 数组指针变量是指针变量?还是数组?
答案是:指针变量。
我们已经熟悉:
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量。
下面代码哪个是数组指针变量?
int *p1[10];
int (*p2)[10];
思考一下:p1,p2分别是什么?
int *p1[10],p1是一个数组,每个元素都是一个指向int类型的指针。是一个包含10个指向int类型的指针的数组。它是一个指针数组
int (*p2)[10],p2先和*结合,然后指针指向的是⼀个大小为10个整型的数组。所以p2是 一个指针,指向一个数组,它是一个数组指针。
2.2数组指针变量怎么初始化
数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的:&数组名。如:
int arr[5]={0};
int(*p)[5]=&arr;
int : p 指向的数组的元素类型
p : p 是数组指针变量名
10 : p 指向数组的元素个数
三、二维数组传参的本质
回想一下之前在进行二维数组传参给一个函数时,我们是怎样写的呢?可能是这样的:
#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;
}
这里实参是二维数组,形参也写成二维数组的形式,那还有什么其他的写法吗?
首先我们再次理解⼀下二维数组,二维数组起始可以看做是每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。如下图:
由数组名是数组首元素地址可知,二维数组数组名代表第一行地址。根据上面的例子我们可知。第一行的一维数组类型是int [5],第一行的地址类型就是int(*)[5]。这就意味着二维数组传参的本质是传递地址。形参也可以写成以下形式:
#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;
}
总结:二维数组传参,形参部分可以写成数组,也可以写成指针。
即
void test(int a[3][5], int r, int c)
可以写成
void test(int (*p)[5], int r, int c)
四 、函数指针变量
4.1函数指针变量的创建
根据前面学习的指针,我们可以得出函数指针变量是用来存放函数地址的,那么函数是否真的有地址呢?我们来做个测试:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
结果如下:
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址。如果要将函数的地址存起来,就要创建函数指针变量,它的写法其实和数组指针非常类似。
例如: void test()
函数变量指针可写为: void (*pf1)=&test
也可以写成: void (*pf1)=test
这是没有参数的,那有参数的应该怎么写呢?
例如: int Add(int x, int y)
函数变量指针可写为: int(*pf2)(int, int) = Add
也可以写成:int(*pf2)(int x, int y) = &Add;
int :是pf2指向函数的返回类型
(*pf2) :是函数指针变量名
(int x,int y):是pf2指向函数的参数类型和个数的交代。
总结:函数指针变量的格式为 int (*) (int x,int y)
4.2函数指针变量的使用
它是通过函数指针调用指针指向的函数,例如:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf2)(int, int) = Add;
printf("%d\n", (*pf2)(2, 3));
printf("%d\n", pf2(3, 5));
return 0;
}
输出结果如下:
这里有两段有趣的代码分享给大家,大家可以在评论区里说出自己的分析:
(*(void (*)())0)();
void (*signal(int , void(*)(int)))(int);
4.3typedef关键字
typedef大家应该都有所了解,它是用来类型重命名的,可以将复杂的类型简单化,例如:你觉得unsigned int太长你可以自己起个名字使用,就用uint吧,那应该怎么写呢?
typedef unsigned int uint;
指针类型也可以重命名,比如将int*重命名ptr_r
typedef int* ptr_t;
但是对于数组指针和函数指针,其实是有区别的,比如将int(*)[5]重命名为parr_t,要写为:
typedef int(*parr_t)[5];
函数指针的重命名也是一样的,比如将void(*)(int)类型重命名为pf_t,就要这样写:
typedef void(*pf_t)(int)
还可以这样写:pf_t signal(int, pf_t);
五、函数指针数组
把函数的地址存到一个数组中,那这个数组就叫函数指针数组,下面我给大家写三个代码,大家猜一下哪一个是函数指针数组
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
答案是parr1,parr1先和[]结合,说明parr1是数组,这个数组的内容是int (*)() 类型的函数指针。
六、转移表
函数指针数组的用途:转移表,下面我们来举一个例子:计算器
#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;
}