指针详解(3)
字符指针变量
字符指针变量,使用来存放字符数据的地址,常用于存放单个字符或字符串。
int main()
{
char str = 'h';
char* ptr = &str;
//*ptr 就等于 ‘h’
return 0;
}
更常用的是使用字符指针存放字符串.
int main()
{
char* pstr1 = "hello hehe";
char str[] = "hello haha";
return 0;
}
使用数组来存放字符串与使用字符指针存放字符串的区别:
- 使用字符指针存放字符串实际上存放的是首元素的地址。
int main()
{
char* pstr = "hello haha";
printf("%c\n", *pstr);
printf("%s\n", pstr);
return 0;
}
在使用上,由于pstr存放的是字符串第一个字符的地址所以 对pstr解引用就可以打印第一个字符,打印整个操作符只需提供首元素的地址,使用%s就可以打印出来。
- 使用字符指针存放字符串,又名常量字符串,是不可以被修改的。所以在使用时一般会加上const进行修饰,这样当试图对常量字符串进行修改系统就会直接报错,否则不加上const修饰时,当试图对常量字符串内容进行修改时只有在程序运行起来才会报错。
int main()
{
const char* pstr = "hellow hehe";
*pstr = 'a';//这里再编译器里直接报错了
return 0;
}
一到剑指offer里关于字符串的题目:
分析程序运行后的结果为什么?
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指向的是同一个常量字符串,C/C++会把常量字符串存放在一块单独的内存区域,多个指针指向同一个字符串时,它们存储的都是同一个字符串的地址。所以str3 == str3 而 str1 != str2
数组指针变量
理解数组指针变量:之前学过整形指针、字符指针 int* p; char* ps;
,整形指针是用来存放整形变量的地址,字符指针是用来存放字符的地址,而数组指针同理,数组指针是用来存放数组的地址。
int* p1[5];
int (*p2)[5];
这里p1,p2分别是什么呢?
p2先和*结合,说明p2是一个指针变量,然后指针指向一个大小为10十个整形的数组,所以p2是一个指针,指向一个数组,为数组指针。是来存放数组的地址。
[]的优先级高于,这里必须加上(),来保证 p先和 * 结合。*
数组指针变量的使用
数组指针变量使用来存放数组地址的,通过& + 数组名获得数组的地址,而不是直接通过数组名赋值。
//变量声明
int (*p)[10];
//初始化
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int (*p)[10] = &arr;//取出整个数组的地址
//使用
(*p)[i];//访问数组里的元素
- 变量声明,变量声明很容易理解,类型加变量名,数组指针变量的类型是
int (*)[5];
去掉变量名剩余的就是它的类型,这是编译器给出的答案,相互应证。
-
初始化,将数组指针初始化为一个已经存在的数组
-
使用,数组指针存放的是数组地址,
(*p)
,这步对变量进行解引用,就通过数组地址找到了数组arr,(*P) 等价 arr
,然后使用索引就可以访问数组的元素了。
二维数组传参本质
之前说过,一维数组名是首元素地址,那二维数组的数组名又是什么呢?
二维数组的数组名也是数组首元素的地址,二维数组可以理解为,一维数组拼接在一起的数组。
void Print(int arr[3][5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", arr[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}};
Print(arr, 3, 5);
return 0;
}
这是一个打印二维数组的函数,在函数参数上,使用了数组的形式来接受,二维数组。我们说在一维数组传递参数时,实际上传递的是一维数组首元素的地址,所以可以使用指针来接受。同理,二维数组也可以这样操作。
而二维数组的数组名表示什么意思,又该使用什么类型的参数接受?
-
二维数组可以怎么理解,它是一维数组拼接在一起的数组
-
二维数组名表示的是首元素的地址,更准确的说,这里首元素指的是第一行数组的地址,也就是一个一维数组的地址。
可以借助指针数组理解,这里使用三个一维数组模拟了二维数组,将每个一维数组的首元素的地址放在指针数组里,一共有三个一维数组,没个一维数组有五个元素,等价于
int arr[3][5]
。int arr1[5] = { 0 }; int arr2[5] = { 0 }; int arr3[5] = { 0 }; int* parr[3] = {arr1, arr2, arr3};
这里,函数参数就可以使用,数组指针来接收
int arr[3][5] = {0};
int (*parr)[5] = arr;//二维数组首元素的地址,每一行有5个元素,所以数组指针中括号里的值为5
使用指针的形式接收二维数组就可以写为:
void Print(int (*parr)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", *(*(parr + 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}};
Print(arr, 3, 5);
return 0;
}
这里将二维数组写成指针的形式,*(*(parr + i) + j)
,数组的下标访问操作符,和括号内的数值可以这么理解,数值用来计算元素在内存中偏移量获取对应的地址,然后解引用,获得对应的值。
二维数组arr:
函数指针变量
函数也是有它对应的地址吗?我们不妨可以通过代码进行测试。
void test()
{
printf("haha\n");
}
int main()
{
test();
printf("test = %p\n", test);
printf("&test = %p\n", &test);
return 0;
}
这里可以发现,打印函数名以及取函数名的值打印,两者均能打印出地址来,不用想别的,这就是函数的地址。而函数名与对函数名取地址二者是等价的,都用来表示地址。
如果将函数的地址存放起来这就是函数指针变量。
函数指针变量在声明和定义时与数组指针类似,在声名函数指针变量它涉及了函数的返回类型,函数名,函数参数。
具体写法:int (*Pf)(int x, int y);
。
int add(int x, int y)
{
return x+y;
}
int main()
{
int (*padd)(int, int) = add;//等价int (*add)(int x, int y);
printf("%d", (*padd)(3, 2));
printf("%d", padd(3, 2));
}
上述printf函数里展现了使用函数指针变量的方法,其中padd是需要加上 (*)
,还是不需要呢~,前文说明了,函数名等价于对函数名进行取地址(&add),而 (*padd)
对变量进行解引用通过函数地址访问函数,所以(*padd)等价于add
,函数指针变量名padd存放的是函数的地址 它于 &add是等价的,所以 padd 与 *padd 是等价的
,在写法上就可以忽略 (*)
。
四者等价 |
---|
add |
&add |
padd |
*padd |
函数指针数组
int* arr[5];//整形指针数组
,用来存放整形指针的地址
char* arr2[5];//字符指针数组
,用来存放字符指针的地址
而函数指针数组,则是用来存放函数指针的数组。
int (*pf1)(int, int) = Add;
int (*pf1)(int, int) = Sub;
int (*pf1)(int, int) = Mul;
int (*pf1)(int, int) = Div;
当需要使用的函数指针过多,我们可以想既然有用来存放整形指针的地址的数组,那也可以有存放函数指针的地址的数组,这样更方便对它们进行维护。
函数指针数组首先得是一个数组,然后才是一个函数指针类型。
int (*pf[4])(int, int) = {Add, Sub, Mul, Div};
下标0,对应加法函数Add;下标1,对应着减法函数Sub;下标2,对应着乘法函数;下标3,对应着除法函数。
#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 (*pf[4])(int, int) = {Add, Sub, Mul, Div};
int i = 0;
for(i = 0; i < 4; i++)
{
int ret = pf[i](4, 2);
printf("%d\n", ret);
}
return 0;
}
数组下标访问 | 函数名 |
---|---|
pf[0] | Add |
pf[1] | Sub |
pf[2] | Mul |
pf[3] | Div |
以上表格说明这左右两者均是等价得,pf[i](4, 2)
,后面的括号是为函数传递参数。
运行结果:
转移表
函数指针数组的用途:转移表
使用函数指针数组实现:整数的加法、减法、乘法、除法。
当不是使用函数指针数组时,写法如下:
#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;
}
void menu()
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
menu();
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;
}
如何,这种写法是不是发现还可以进行优化,因为在每条case语句里的许多代码都是重复的,重复使用printf函数、scanf函数、对函数的调用。这里就可以借助函数指针数组来帮助我们完成简化的目的。
使用函数指针数组将四个函数存入数组里,通过下标就可以完成对这个函数的调用,这样通过输入菜单里的数字就可以完成对不同函数的调用,这样一来就不需要使用switch语句,重复敲许多代码。
这种使用转移表实现的功能,更加灵活,便于维护,可以更具我们的需求来不断完事计算器,不局限于 加、减、乘、除,而实现更多的计算机功能在主函数上并不会做出太大的改动。
int main()
{
int x, y;
int ret = 0;
int input = 1
int (*pf[5])(int, int) = {0, add, sub, mul, div};//转移表
do
{
menu();
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;
}
();
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;
}