字符指针变量
char*一般有两种写法
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(){
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
还有一种是
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(){
const char* pstr = "hello world";//这是把⼀个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}
虽然容易被这样以为, 但本质上是把字符串hello world的首字符的地址放到了pstr
来看这道练习
#define _CRT_SECURE_NO_WARNINGS 1
#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("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;
}
想想答案再看运行结果
第一个不同是因为数组是在栈上开辟的是不同空间,按照这个形式往里面存入数据虽然是一样的,但是数组下标空间的地址是不一样的,所以元素地址肯定是不一样。数组的内容不是在字符常量区上而是在栈上,所以可以不加const。
第二个一样的是因为字符串存放在字符常量区中不会额外在开辟相同的字符串,指针变量指向的都是字符串首元素的地址。
数组指针变量
我们从前面知道了指针数组是一种数组,数组中存放的是指针
那数组指针变量是指针变量还是数组呢
整形指针是指向整形的指针,整形指针变量存放的就是整形数据的地址
浮点型指针是指向浮点型的指针,浮点型指针变量存放的就是浮点型数据的地址
那么数组指针就是指向数组的指针,数组指针变量存放的就是数组的地址
int *p1[10];
int (*p2)[10];
上面哪个是数组指针变量?
int*p1[10]中,p1先和[]结合,先说明自己是一个数组,那前面的int*就是数组指向的元素的类型,表明这是一个指针数组
int(*p2)[10]中,p2先在括号中与*结合,说明自己是一个指针,指向的是一个int类型的数组,表明这是一个数组指针
注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合
那怎么获取数组的地址呢
如果要存放个数组的地址,就得存放在数组指针变量中
我们可以看到&aar和p的类型是完全一致的
int (*p) [10] = &arr;
| | |
| | |
| | p指向数组的元素个数
| p是数组指针变量名
p指向的数组的元素类型
二维数组传参的本质
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(int q[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 ", q[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;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(int(*p)[5], int x, int y){
int i = 0;
int j = 0;
for (i = 0; i < x; i++){
for (j = 0; j < y; 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} };
test(arr, 3, 5);
return 0;
}
函数指针变量
通过前面的类比,我们不难得出函数指针变量是存放函数地址的,是能通过地址调用函数的,那函数是否有地址呢
做个测试
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void test(){
printf("hehe\n");
}
int main(){
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
我们确实发现函数是有地址的,函数名是函数的地址,取地址(&)函数名也是函数的地址,函数的地址要保存到函数指针变量里面去,那函数指针变量要怎么写呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
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;
函数类型解析:
int (*pf3) (int x, int y)
| | ------------
| | |
| | pf3指向函数的参数类型和个数的交代
| 函数指针变量名
pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
(*(void (*)())0)();
void (*signal(int, void(*)(int)))(int);
分析这两句代码
(*(void (*)())0)();
首先我们分析0是什么类型?0是个int类型的
0前面是(void (*) () )0,(void (*) () )这个是函数指针类型,这个指针指向的函数没有参数,返回类型是void,所以(void (*) () )0 我们是把0强制转换成函数指针的类型,所以我们可以把0当做一个函数的地址,(*0)()这样就是去调用0地址处的函数
void (*signal(int, void(*)(int)))(int);
(*signal ( int, void(*)(int)) )首先*signal没有单独用括号扩起来,所以signal是先跟后面的结合,所以signal是一个函数,这个函数第一个函数时int,第二个参数是void(*)(int),
void(*)(int)是函数指针,该指针指向函数参数是int,返回类型是void,
我们把void (*signal(int, void(*)(int)))(int) 去掉signal ( int, void(*)(int)) 我们可以得到这个函数的返回类型是也是void(*)(int)这个函数指针,该指针指向函数参数是int,返回类似是void
所以这段代码是一段函数的声明
去掉函数名和参数列表剩下的就是函数的返回类型
typedef关键字
typedef是可以把复杂类型重命名的,使其简单化
如果你觉得unsigned int这个无符号整形类型太复杂了,那么我们就可以使用typedef来重命名类型
int main(){
typedef unsigned int uint;
//所以unsigned int就等价于uint
unsigned int a = 10;
uint b = 20; //a和b的类型是一样的
return 0;
}
但是对数组指针和函数指针类型重命名还是稍微有区别的
我们把数组指针int(*)[5],需要重命名为parr_t
int main(){
//typedef int(*)[5] parrt_t 这个写法是错误的
typedef int(*parrt_t)[5];
int arr[5] = { 0 };
parrt_t p1 = &arr;
int(*p2)[5] = &arr;
//p1和p2类型是等价的
return 0;
}
函数指针重命名也是一样,我们想将int(*)(int)重命名为pf_t
int test(int a){
return a + 1;
}
int main(){
//typedef int(*)(int) pf_t;这个写法是错误的
typedef int(*pf_t)(int) ;
pf_t p1 = test;
int(*p2)(int) = test;//p1和p2类型是一样
return 0;
函数指针数组
数组是用来存放相同类型数据的存储空间,如果我们要把函数的地址存放在数组中,我们就需要用到函数指针数组
函数指针数组就是在函数指针的基础上做一点点改变
函数指针void(*p)(int, int),这颗*说明p是个指针,指向函数参数是2个int类型的,函数的返回类型是void
函数指针数void(*p[4])(int, int),这里p是首先和[]先结合的,说明的p是个数组,数组有4个元素,每个元素的类型都是void(*)(int, 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;
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;
}
但这段代码有点过于冗余,使用函数指针数组进行改进
#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;
}