前言
前面为大家介绍了指针的部分内容,因为指针的内容比较多,需要分为几个部分,今天继续为大家介绍指针的内容;如果你点开了本篇,麻烦各位大佬一键三连,多多支持,谢谢!下面进入正文内容。
1. 字符指针变量
字符指针,顾名思义,是用来存放字符变量的地址的。它也是一种指针类型,关于它的使用,也比较简单,大家请看下面的代码;
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
这里大家可以看到,我们将字符‘w’的地址存入了pc指针指针中,那么当我们对指针解引用时,我们就可以得到字符‘w’。
当然,字符指针不止上面一种用法,下面再为大家介绍一种用法
int main()
{
const char* pstr = "hello bit.";
printf("%s\n", pstr);
return 0;
}
大家发现,我将单个字符改成了一个字符串,那么这个时候pstr指针存放的是什么呢?很多同学可能认为将整个字符串放到pstr指针中;但实际不然,本质上是将字符串hello bit,的首元素地址交给了指针pstr;
上面代码的意思是把⼀个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
为了让大家更好地理解,这里有一道题,出自于《剑指offer》,我们来学习一下
#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;
}
这道题大家可以先思考一下,然后看下面的运行结果
大家可以发现,这个结果可能与你所想的有区别;如果你的想法和答案一样,那么给你一个赞,继续加油;如果不一样,那么可以看下面的解释,相信你也能够理解;关于这道题,str3和str4是字符指针,它们都指向同一个常量字符串,C/C++会把常量字符串存储到单独的⼀个内存区域, 当几个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。然而trs1和str2是两个数组,当使用相同的常量字符串去初始化不同的数组时会在内存中开辟出不同的内存块儿,所以str1和str2是不同的。
2. 数组指针变量
2.1 数组指针变量的定义
关于数组指针变量,其实大家可以进行类比理解,前面我们学过
整型指针:它用来存放整型变量的地址,能够指向整型数据的指针;
浮点型指针:它用来存放浮点型变量的地址,能够指向浮点型数据的指针;
那么数组指针定义就是:存放的是数组的地址,可以指向数组的指针。
int *p1[10];
int (*p2)[10];
大家来看上面的代码,分辨一下哪个是数组指针的表示方法;
很显然,第二种是数组指针的表示方法。而第一种是指针数组的表示方法。这里大家一定要区分清楚;在数组指针的表示方法中,*与p2结合,说明p2是一个指针变量,并且它指向一个大小为10个整型的数组,这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
2.2 数组指针的初始化
数组指针的初始化很简单,我们只需要将数组的地址传给它即可;
int arr[10] = {0};
int(*P)[10] = &arr;
上面的代码就是数组中指针的初始化方法,希望大家可以结合下图进行理解;
3. 二维数组传参的本质
上面我们介绍完了数组指针,那么就不得不来说说二维数组的传参了
过去在我们学习数组的时候,我们有一个二维数组需要传参给一个函数,我们是这样写的
void test(int a[3][5], int m, int b)
{
int i = 0;
int j = 0;
for (i = 0; i < m; i++)
{
for (j = 0; j < b; 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]。
所以通过这个问题,我们可以得到一个结论:二维数组传参本质上是传递了第一行这个一维数组的地址。
有了上面的理论,那我们就可以这样来写代码
void test(int(*p)[5], int m, int b)
{
int i = 0;
int j = 0;
for (i = 0; i < m; i++)
{
for (j = 0; j < b; 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;
}
大家发现,我们可以用数组指针来接受地址,最终也可以实现同样的效果
4. 函数指针变量
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()
{
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;//x和y写上或者省略都是可以的
这里为大家说明一下,函数名就是函数的地址,当然我们也可以使用“&函数名”来表示函数的地址。
4.2 函数指针变量的使用
我们可以通过函数指针来调用其指向的函数
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3));
printf("%d\n", pf3(3, 5));
return 0;
}
上面的代码就运用了函数指针来调用了我们创建的这个加法函数,这里需要说明一点,在使用函数指针调用函数的时候,我们可以直接用函数名来调用;也可以通过解引用的方式来调用。
5. 函数指针数组
前面我们已经学过了指针数组,我们知道指针数组中存放的是地址,那么函数指针数组,顾名思义,存放的就是函数的地址;
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];
大家看上面的三种写法,哪种是函数指针的写法呢?
答案是第一种,parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。
6. 转移表
函数指针数组有什么用呢?转移表就是其中一种用法
我们下面来实现一个计算器
#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("请选择:");
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;
}
大家仔细看上面的代码,我创建了一个函数指针数组,将我们需要的函数的地址全部放到了这个数组中,下面我们就可以通过这个函数指针数组来调用我们需要的函数了;大家可以发现,通过创建函数指针数组可以简化代码,省去了重复的代码,并且提高了我们代码的可读性,所以大家可以在平时的编程中使用函数指针数组来简化自己的代码。
7. 总结
本篇博客为大家介绍了指针部分的剩余内容,到这里。指针的理论内容就告一段落了,这里还是先再次重申一下,指针的内容是C语言中非常重要的内容,也是常考点和难点,所以大家一定要将指针的内容掌握扎实,注意各个概念的区别,合理运用类比思想来进行学习,最后,希望本篇博客的内容可以为大家带来帮助,谢谢!