一、字符指针变量
今天让小白带大家学习新的知识,首先让我们来先了解一下字符指针。
int main()
{
//char arr[] = "abcdef";//a b c d e f \0,数组
//char* p = arr;//数组名是数组首元素地址
char* p = "abcdef";//常量字符串
printf("%s\n", p);
printf("%c\n", p);
return 0;
}
从上述代码不难看出,char* p = arr; char*p = "abcdef";是有区别的,一个是数组,一个是常量字符串,而两者最大的区别就在于常量字符串不能被修改,字符数组是可以被修改的,
由上图可以看出,常量字符串不能被修改,而一旦被修改,就会报警告。
还有一点则是"abcdef"的长度是6,但char*指针在x86环境下是4个字节,显然长度不够,但编译器并没有报警告,所以我们可以得出。常量字符串也是将首字符的地址‘a’赋给p。
由上图的两个printf可以得出,%s只需提供给首字符地址,就可打印字符串,而%c只能打印字符,所以结果会为‘a’。
现在让我们看一道《剑指offer》的题目
首先虽然str1和str2存放的是相同的字符串,但是str1与str2的地址不同, 所以str1与str2不同;
其次str3和str4指向的是同一个常量字符串,C/C++会把常量字符串存放在一块单独的内存区域,多个指针指向同一个字符串时,它们存储的以及指向的都是同一个字符串的地址。所以str3和str4相同。
二、数组指针变量
整型有整型指针,字符串有字符串指针,那数组指针顾名思义就是数组的指针,用来存放整个数组的地址,能够指向整个数组的指针变量。
int (*p)[10] = &arr;
注意:这里一定要加上括号,因为[]的优先级要高于*,所以要保证p与*先结合。否则会变成指针数组。
补充:数组与指针的区别
- 数组就是数组,是一块连续的空间,是可以存放一个或多个数组的。
- 指针变量是一个变量,是可以存放地址的变量
- 数组和指针不是一回事,但是可以是一种指针来访问数组
三、二维数组传参的本质
前面我们学习了一维数组传参的本质,现在让我们来了解一下二维数组传参的本质吧。
首先我们来看一段代码
//void Print(int(*arr)[3][5], int r, int c)
//{
// int i = 0;
// for (i = 0; i < r; i++)
// {
// int j = 0;
// for (j = 0; j < c; j++)
// {
// printf("%d ", arr[0][i][j]);//arr[0]是是第一行元素的数组名,[i]是arr[0]的首元素,[j]是是列数也是第一行的下标
// }
// printf("\n");
// }
//}
//void Print(int(*arr)[5], int r, int c)//*arr是数组指针变量,指向的是数组,又因为数组名是数组的首元素地址,所以这里
// 指的是数组首元素的地址,又因为二维数组的首元素地址为一维数组,这里的一维数组是5个元素,所以是(*arr)[5]
//{
// int i = 0;
// for (i = 0; i < r; i++)
// {
// int j = 0;
// for (j = 0; j < c; j++)
// {
// printf("%d ", *((*arr + i) + j));//arr[i][j]==*((*arr+i)+j)
// //arr[i]==&arr[i][0],arr[i]是数组第i行的数组名,又因为数组名是数组首元素的地址所以为&arr[i][0],
// //这里的[0]为下标,表示数组的首元素。
// }
// printf("\n");
// }
//}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr, 3, 5);
return 0;
}
总结:
- 二维数组的数组名也是数组首元素的地址
- 二维数组的数组名就是第一行的地址
- 第一行是一个一维数组,传过去的就是第一行这个一维数组的地址
- 二维数组可以理解为一维数组的数组,二维数组的每一行可以看做是一个一维数组,所以二维数组其实是一个一维数组的数组,二维数组的首元素也就是他的第一行
四、函数指针变量
指针的世界可真是奇妙,居然连函数都有指针,所以大家应该知道指针的重要性了吧。
那什么叫做函数指针变量呢?
函数指针变量用来存放函数地址的,未来通过地址能够调用函数的。
int (*pf) (int x, inty) = &Add;
//int Add(int x, int y)
//{
// return x + y;
//}
//
//int main()
//{
//
// printf("%p\n", Add);//和数组一样函数名就是函数的地址。
// printf("%p\n", &Add);
// return 0;
//}
该代码的运行打印结果是一样的,所以&函数名和函数名都是函数的地址。
所以我们可以尝试写一个代码来使用函数指针
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pA)(int, int) = ⋐
int ret = (*pA)(4, 5);
printf("%d\n", ret);
return 0;
}
这里还有一个需要注意的点:
printf("%d\n", (*pf)(2,3));//*省略时可以不加括号(*pf),但有*时要加括号
printf("%d\n", pf(2,3));
五、函数指针数组
我们知道,当我们要运行一个复杂的程序时,需要多个函数一起运行,但有时函数多了,在主函数中一个个写出如何调用会显得冗长,所以我们引出了函数指针数组的概念,就是将函数的地址存到一个数组中去。
int (*prr[3]) (int x,int y) = {Add, Sub, Div};
六、转移表
通过对函数指针和函数指针数组的学习和了解,下面我们来做一个程序,实现计算器。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//转移表
//void menu()
//{
// printf("******************************\n");
// printf("*******1:Add 2:Sub*********\n");
// printf("*******3:mul 4:div*********\n");
// printf("*******0:exit*****************\n");
// printf("******************************\n");
//}
//
//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()
//{
// int x = 0;
// int y = 0;
// int input = 0;
// int ret = 0;
// do
// {
// menu();
// printf("请选择: \n");
// scanf("%d", &input);
// switch (input)
// {
// case 1:
// printf("请输入操作数:\n");
// scanf("%d %d", &x, &y);
// ret = Add(x, y);
// printf("%d\n", ret);
// case 2:
// printf("请输入操作数:\n");
// scanf("%d %d", &x, &y);
// ret = Sub(x, y);
// printf("%d\n", ret);
// case 3:
// printf("请输入操作数:\n");
// scanf("%d %d", &x, &y);
// ret = Mul(x, y);
// printf("%d\n", ret);
// case 4:
// printf("请输入操作数:\n");
// scanf("%d %d", &x, &y);
// ret = Div(x, y);
// printf("%d\n", ret);
// case 0:
// printf("退出\n");
// break;
// default:
// printf("输入错误,请重新选择\n");
// break;
// }
// } while (input);
// return 0;
//}
但是这样写过于冗余,繁琐,因为一旦运算法则多了之后,就会无限添加case,所以我们可以试着用函数指针和函数指针数组来简化一下。
//函数指针改造也就是转移表
//void menu()
//{
// printf("******************************\n");
// printf("*******1:Add 2:Sub*********\n");
// printf("*******3:mul 4:div*********\n");
// printf("*******0:exit*****************\n");
// printf("******************************\n");
//}
//
//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()
//{
// int x = 0;
// int y = 0;
// int input = 0;
// int ret = 0;
// int(*pf[5])(int,int) = {0, Add,Sub,Mul,Div};//我们的菜单中其选择从1开始,选0会退出,所以我们通过加上一个0
//使其下标进行了变化 0, 1,2, 3, 4。//转移表
// do
// {
// menu();
// printf("请选择: \n");
// scanf("%d", &input);
// if (input <= 4 && input >= 1)
// {
// printf("请输入操作数\n");
// scanf("%d %d", &x, &y);
// ret = pf[input](x, y);//ret=(*pf[input])(x,y)
// printf("%d\n", ret);
// }
// else
// {
// if (input == 0)
// {
// printf("退出游戏\n");
// }
// else
// {
// printf("选择错误,请重新选择\n");
// }
//
// }
// } while (input);
// return 0;
//}