文章目录
✨✨✨学习的道路很枯燥,希望我们能并肩走下来!
编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。
前言
我们之前只是简单了解了指针,为了进一步深入,本篇指针进阶的知识,会让我们更全面了解指针的使用,如有错误,请在评论区指正,让我们一起交流,共同进步! |
文章重点概况
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
我们之前简单了解过指针的内容,本篇就为小伙伴更进一步讲解指针,相信你看完本篇,会对指针有进一步了解!
1.字符指针
字符指针使用:char* ;
[注]:字符指针存储的字符串的首地址(把一个常量字符串的首字符 h 的地址存放到指针变量pa中);
对于字符串“ ”是常量字符串不能被改变.
代码示范:
#include<stdio.h>
int main()
{
//存储字符时用char
//存储字符地址时就需要字符指针char*
char ch = 'w';
char* pc = &ch;
*pc = 'w';
//存储字符串可以用字符数组
//也可以使用字符指针
char arr[] = "hello word";
char* pa = "hello word";
//字符指针存储的字符串的首地址(把一个常量字符串的首字符 h 的地址存放到指针变量pa中)
//对于字符串“”是常量字符串不能被改变
*pa = 'w';//err
return 0;
}
我们来一道题看一下字符指针使用:
#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;
}
上述代码答案是:str1 and str2 are not same ,str3 and str4 are same.(str1,str2,str3,str4都是地址)
原因:这是因为字符数组str1和字符数组str2存储“hello word”都会重新开辟一块空间来储存,它们的地址是不一样的; 而字符指针指向的字符串都是相同的常量字符串不能够被改变,C/C++会把常量字符串存储到单独的一个内存区域,不同指针指向同一字符串时指向同一块内存,所以str3,str4指向同一空间,地址相同.
2.指针数组
指针数组:存放指针的数组.
通过字符指针数组,整型指针数组的代码,让我们了解一下,指针数组的使用.
代码示范:
int main()
{
//字符指针数组
char* arr[5] = { "abcdef","zhang","wang","zhao","qian" };
//这里指针数组存储每个字符串的首字符的地址
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%s\n", arr[i]);
}
//整型指针数组
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int arr4[5] = { 4,5,6,7,8 };
int arr5[5] = { 5,6,7,8,9 };
int* arr[5] = { arr1,arr2,arr3,arr4,arr5 };
int j = 0;
for (i = 0; i < 5; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
//也可以这样写printf("%d ", *(*(arr + i) + j));
//*(arr+i)==arr[i]
//*(*(arr + i) + j==arr[i][j]
}
printf("\n");
}
return 0;
}
3.数组指针
3.1数组指针的定义
整形指针:指向整型数据的指针- int * a;
字符指针:指向字符数据的指针 - char * ch;
数组指=:是指针,指向数组的指针
例如:
int ( * p)[10]: p先和 * 结合,说明p是一个指针变量,指向的是一个大小为10个整型的数组。
【注】:区分数组指针与指针数组
[ ]的优先级要高于 * 号的,所以必须加上()来保证 p 先和 * 结合。
int* p1[10]; // ->指针数组
int (*p2)[10]; // ->数组指针
3.2&数组名和数组名
我们之前讲解过,这里我们再来简单的回忆一下.我们知道数组指针存储的是数组的地址,我们这里需要注意arr与&arr的区别!
我们知道arr与&arr的值是一样的,都指向数组首元素,但代表的意义并不相同.
arr与&arr区别:
&arr 表示的是数组的地址,而不是数组首元素的地址.数组的地址+1(整型数组条件下),跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
arr表示首元素地址,arr+1在整型情况下跳过4个字节.
代码示范:
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%p\n", &arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr+1);
return 0;
}
代码结果:
解释:C:十六进制的12,3C=316^1+12=60,
64=616^1+4=100;
3C与64之间差40个字节==一个arr数组的长度
3.3数组指针的使用
数组指针在一维数组中使用的较少,一般使用在二维数组.
代码1
//不常用
int arr[5] = { 1,2,3,4,5 };
int(*pa)[5] = &arr;
代码2
void text(int(*pa)[10], int s)
{
int i = 0;
for (i = 0; i < s; i++)
{
printf("%d ", (*pa)[i]);
//*pa == *&arr => arr
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
text2(&arr, sz);
return 0;
}
代码3
【注】数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行;
这里传递的arr,相当于第一行的地址,是一维数组的地址,需要数组指针来接收.
void text1(int(*pa)[4], int r,int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(pa + i) + j));
//pa+i找到第i行的地址,解引用指向地址
//*(pa+i)==pa[i],
//+j是找到第i行第j列元素的地址,解引用取值
}
printf("\n");
}
}
int main()
{
int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,0 };
int sz = sizeof(arr1) / sizeof(arr1[0]);
text1(arr1, 3, 4);
return 0;
}
4.数组参数、指针参数
数组和指针都需要传参给函数,那么如何设计正确呢?
4.1一维数组传参
#include <stdio.h>
void test(int arr[])//传入数组首元素地址,数组接收 - 正确
{}
void test(int arr[10])//传入数组首元素地址,数组接收 - 正确,([]中的数写与不写一样)
{}
void test(int *arr)//传入数组首元素地址,指针接收 - 正确
{}
void test2(int *arr[20])//传入数组地址,指针数组接收 - 正确
{}
void test2(int **arr)//传入数组首元素地址,二级指针接收 - 正确(数组每个元素都是int*,每个地址也就是一级指针的地址,需要二级指针接收)
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
4.2二维数组传参
void test(int arr[3][5])//正确
{}
void test(int arr[][])//错误,对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素(必须有列)。
{}
void test(int arr[][5])//正确,原因如上
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。(不写有几行,但要写有几列)
void test(int *arr)//错误,二维数组第一行的地址指针接收不了
{}
void test(int* arr[5])//错误,接收的是地址不是数组
{}
void test(int (*arr)[5])//正确,数组指针接收二维数组第一行的地址
{}
void test(int **arr)//错误,需要数组指针接收,二级指针接收一级指针的地址
{}
int main()
{
int arr[3][5] = {0};
test(arr);
//传入的是第一行的地址
}
4.3一级指针传参
void test1(int *p)
{}
//test1函数能接收什么参数?
//接收一维数组地址
//接收一级指针变量
//接收变量地址
4.4二级指针传参
#include <stdio.h>
void test(int** ptr)
{}
//test函数能接收什么参数?
//接收二级指针变量
//接收一级指针地址&p
//接收指针数组地址,每个元素都是一级指针地址
5.函数指针
函数指针:指向函数的指针
怎么存放函数指针?
我们看一下如下示例来判断一下!
void text()
{
}
int main()
{
void (*p1)();
void *p2();
retunr 0;
}
解释:
p1:p1可以存放函数地址。p1先和*结合,说明p1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
p2:()优先级大于 * 号,p2先与()结合,说明p2是函数,返回值类型为void * .
说了怎么多,函数指针怎么用呢?我们来看两个代码理解一下函数指针.
//代码1
(*(void (*)())0)();//把0当做一个函数的地址
//从0为突破口,0起前面的()为强制类型转换,转换为void (*)()类型的函数指针,再*解引用0的地址,调用函数
//代码2
void (*signal(int , void(*)(int)))(int);
//一步步分析,
//找到signal判断是否为函数调用,定义还是声明
//分析为函数声明
//声明函数叫:signal
//signal与()结合为函数,
//第一个参数类型int,
//signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
//signal函数的返回类型也是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void
//怎么看有可能有点复杂,我们可以重定义
typedef void (*)(int) ptf;
ptf signal(int,ptf);
//这样是不是简洁了一些,
//这里我们不一定要写成复杂的样子,只要求我们在遇见时可以看懂即可!
6.函数指针数组
函数指针数组:存放函数指针的数组
指针数组:int * arr[10];
如何定义函数指针数组呢?我们可以通过函数指针,指针数组来模仿.
int arr[10];
int* arr[10];
int (*p)();
int (*parr[10])();
//parr 先和 [] 结合,说明 parr是数组
//除去分析的parr[10],数组中储存int (*)() 类型的函数指针
函数指针数组又有什么用途呢?我们上代码看一下!
函数指针数组用途:转移表
函数指针数组:简易计算器(计算整数)
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个整数值:");
scanf("%d %d", &x, &y);
ret=Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个整数值:");
scanf("%d %d", &x, &y);
ret=Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个整数值:");
scanf("%d %d", &x, &y);
ret=Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个整数值:");
scanf("%d %d", &x, &y);
ret=Div(x, y);
printf("%d\n", ret);
break;
default :
printf("输入错误\n");
break;
}
} while (input);
return 0;
}
通过上述代码我们看到了使用 switch语句 会有 经常重复的语句 ,当我们增加算数功能时%,移位操作等只能添加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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*parr[5])(int, int) = { 0, Add,Sub,Mul,Div };
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
if (input == 0)
{
printf("退出计算器\n");
}
if (input >= 1 && input <= 4)
{
printf("请输入两个整数值:");
scanf("%d %d", &x, &y);
ret = parr[input](x, y);
printf("%d\n", ret);
}
else
{
printf("输入错误\n");
}
} while (input);
return 0;
}
我们通过函数指针数组简化了复杂的switch语句,通过输入的input可以找到存储的计算函数,将x,y参入函数得到返回值.也可以更好的添加内容,
优点:①函数指针数组定义为5,让输入的1,2,3,4对应+ - * / 更加方便计算.
②可改变函数指针数组的大小,添加% ,<<等操作.
7.指向函数指针数组的指针
函数数组指针:指向函数数组的指针
指针指向一个数组,数组元素为函数指针.
//函数指针
void (*p)(int)
//函数指针数组
void (*parr[10])(int)
//函数指针数组 -> 变为函数指针数组指针
void (* (*str)[10])(int);
这里我们只要见到认识就可以了.
总结
✨✨✨各位读友,本篇分享到内容是否更好的让你理解了指针的知识,对指针的使用更加灵活,如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉一遇挫折就灰心丧气的人,永远是个失败者。而一向努力奋斗,坚韧不拔的人会走向成功。让我们永不放弃,一直向前吧!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!