文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、字符指针
在c语言中,我们把存放字符变量的地址的变量,称为字符指针
1.1字符指针的创建
int main()
{
char ch = 'c';
char* p = &ch;
return 0;
}
代码分析
在这里我们创建一个字符型变量ch,存储字符c,取出字符型变量的地址放入变量p中,此时p的类型就是字符型指针变量。
1.2字符数组的地址和使用
int main()
{
char arr[] = "abcdef";
char* p = arr;
return 0;
}
代码分析
在这里我们创建了一个字符数组,数组中存放一个字符串,通过小编前面所讲述的内容,除了&arr和sizeof(arr),其他时候的数组名都代表数组首元素的地址,因为数组在内存中是连续存放的,取出数组首元素的地址也就是取出数组的地址。
通过上面我们是否可以采取下面方法使用呢
char* p = "abcdef"
;
在这里我们是不是直接将abcdef 赋值给字符指针p,答案是错误的,abcdef也是和数组一样的,它只是把字符a也就是首元素的地址给了字符指针变量p的。与数组相比,它是把字符串的首元素赋值给p。那么两者是否存在不同。接下来我们对他进行解引用打开vs调试一下。
错误分析
在这里出错的原因是p1中存放的是数组的地址,而p2中存放的则是常量字符串的地址。数组的内容是可变的,而常量字符串的内容是不可变的。因此对p2的解引用是错误的。此外数组创建需要开辟一份空间,这份空间是存储在栈区的,而常量字符串是不可变的,就是因为它是不可变的,所以我们把他放在代码段中,想要用的话直接从代码段中去拿。
1.3例题分析
#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++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
二、数组指针
2.1什么是数组指针?
在前面内容我们学习了很多类型的指针,整型指针(存放整型变量的地址),字符指针(存放字符变量的地址)等等,那么什么是数组指针呢?没错数组也有它的地址,存放数组的地址就是数组指针。讲到数组,这里需要区分一下数组指针和指针数组。
定义3个整型数组
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* arr[3] = {arr1,arr2,arr3};
在这里我们把三个整型的数组名放入arr中,数组名是地址,所以arr数组中的三个元素都是指针,指针变量的类型是int*类型的。所以如上面代码所写,[ ]的优先级高于*的优先级,所以arr先与[ ]结合代表arr是一个数组,数组中有三个元素,元素的类型是int*类型的。
数组指针
数组指针是一个指针,存放数组的地址。
int (*p)[5] = &arr1;
在这里我们把整个数组的地址取出来,地址是指针,指针指向的是一个数组,数组元素有5个,元素的类型是int,所以先将p与*结合括起来,代表p是一个指针,然后p再与[ ]结合说明指针变量p指向的是一个数组,里面的5表示指向的这个数组有5个元素,int表示指向的数组内的元素是int类型。
2.2数组指针的初始化
数组指针变量就是存放数组的地址,那么给数组指针进行赋值,那么就要给出数组的地址,数组的地址怎么求呢?也就是我们前面所写的 &数组名。
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果需要存放整个数组的地址,那么就需要将它存入数组指针当中
int(*p)[10] = &arr;
三、二维数组传参的本质
在过去我们学习二维数组进行传参的时候是直接写数组形式的
那么二维数组传参是否可以写成其他形式的呢?在二维数组中,我们讲到过二维数组由许多个一维数组组成,也就是说二维数组可以看成每个元素是一维数组的数组。所以二维数组首元素就是第一行,也就是一个一维数组。根据数组名就是数组首元素的地址,所以二维数组的数组名就是第一行的地址,因为第一行是一维数组,所以二维数组首元素地址的类型是数组指针,
所以二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址。
二维数组传参写成指针的形式
#include <stdio.h>
void print(int (*p)[5], int x, int y)
{
int i = 0;
for (i = 0; i < x; i++)
{
int j = 0;
for (j = 0; j < y; 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} };
print(arr, 3, 5);
return 0;
}
代码分析
在这里二维数组传参也就是把第一行数组传参进去,也就是一维数组的地址,数组元素类型是int,元素个数有5个,所以传参进行接收的形参类型是int (*p)[5],后面*((*p+i)+j)解读是:&和*是可以相互抵消的,p就是&arr的地址,*p则就是arr数组名也就是数组首元素的地址,arr数组首元素就是一维数组,所以通过解引用p+i可以访问1到3行的地址,再通过下标访问1到3行内的元素。
四、函数指针
在前面我们讲解了,整型指针就是存储整型变量地址的变量,那么函数指针就是存储函数地址的变量
4.1函数指针的创建
函数指针变量是用来存储函数地址的,那么函数的地址是什么呢?我们通过一段来代码来分析一下
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 main()
{
int(*pf)(int, int) = &add;
//int(*pf)(int x, int y) = &add; //在这里x和y都可以省略
return 0;
}
代码分析
在这里我们取出加法函数add的地址,我们把他放入函数指针变量pf当中,所以pf是一个指针,又因为他是一个函数,函数调用操作符()的优先级大于*所以代表指针变量pf的*要加上一个括号代表它是一个指针变量。后面接一个括号代表指针变量pf指向的是函数,函数的参数有两个,参数类型都是int类型,最前面的int代表函数的返回参数是int类型。
函数指针类型解析
定义一个变量的时候,我们都是一个类型加上一个变量名进行定义,所以我们想要得到一个变量的类型,我们只要去掉变量名即可,
因此指针变量pf的变量类型就是 int( * )(int , int)
4.2函数指针变量的使用
int add(int x, int y)
{
return x + y;
}
int main()
{
int(*pf)(int, int) = &add;
printf("%d\n", add(3,4));
printf("%d\n", (*pf)(3, 4));
printf("%d\n", pf(3, 4));
return 0;
}
在这里我们之前在函数学习中,调用函数都是直接函数名加上要传递的参数,在这里我们有讲解了函数名就是函数地址,pf指针就是函数地址,所以pf函数指针变量可以直接调用函数,当然对函数指针变量进行解引用,也就是拿到了函数的地址指向的函数,再去使用函数也是可行的。
4.3两端有趣的代码解析
代码段1
( * (void (*)()) 0 ) ();
在这里我们看到这段冗长的代码就感到头疼,首先我们先找到熟悉的0,0前面用括号阔着一个void ()(),这个不就是函数指针变量类型么,变量类型用括号阔着不就是强制类型转换么,所以(void ()()) 0 的意思就是将int类型的0强制类型转化成函数指针类型,函数指针类型中存储的是函数的地址,那么将0转换成函数指针类型,不就是将0的地址放入到函数指针类型么,此时函数的地址就是0所在的地址。
此时通过0地址处放着无参,返回类型是void的函数
所以前面的*也就代表着通过0这个地址调用这个函数。和前面函数指针的使用是一样的(*pf)(3, 4)。只不过这里是通过0地址处访问返回类型是void,无参的函数。
综上0本来是int类型,0也存在地址,后来0被强制转换成void(*)()函数指针类型,解引用0处的地址,但是0被转化为函数指针类型了,所以解引用的也是函数指针类型,函数指针类型0地址处存放的不就是返回void无参的函数么,所以这段代码的意思就是通过0地址处调用这个函数
第二段代码
void (*signal(int , void(*)(int)))(int);
在这里首先要知道signal是一个函数,这是一个函数名,函数里面有两个参数,一个是int类型,一个是void()(int)函数指针类型,有了函数名,函数参数还缺一个什么,肯定是返回类型。我们把分析过的代码取出,得到void ()(int);所以它的返回类型也是函数指针形式的。所以这段代码的含义就是一个函数的声明。
五、typedef关键字
在书写代码中,像一些unsigned int这么长的名字,我们得重复定义,那写下去得多累呀,又或者像上面的第二段有趣代码一样看上去难以分析,于是聪明的编程者们就想到了一个方法,那就是type关键字,type关键字就是将那些复杂的类型名字进行重新命名。
对unsigned int进行重命名
typedef unsigned int un_int;
int main()
{
unsigned int a = 10;
un_int b = 20;
return 0;
}
在这里un_int就等价于unsigned int。
但是对于数组指针和函数指针稍微有点区别:
对数组指针类型进行重命名
数组指针类型:int (*p)[ 5 ]
typedef int(*starr)[5] ;
int main()
{
int arr1[5] = { 1,2,3,4,5 };
starr arr = &arr1 ;
return 0;
}
注意对数组指针进行重命名时,命名的名字放在*右边
对函数指针类型进行重命名
函数指针类型(这里用上述add函数做例):int (*pf)
typedef int(*pf_t)(int,int) ;
int add(int x, int y)
{
return x + y;
}
int main()
{
pf_t pf = &add;
return 0;
}
六、函数指针数组
既然存在指针数组,那么函数指针是否也可以存放到数组中呢?答案是可以的。
6.1函数指针数组的创建
创建加减乘除四个函数,并将它们放入指针数组中
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(*pf)(int,int) = &add; //pf是函数指针变量,存储add函数的地址
//把四个函数指针放进数组中,因为函数名就是地址
int (*pfarr[4])(int, int) = { add,sub,mul,div };
return 0;
}
代码分析
想要学习函数指针数组的创建,我们首先知道函数指针的类型是什么,因为数组中存放都是统一类型的变量,所以我们存放函数指针的时候也要注意必须是相同类型的函数指针类型。根据上面代码,我们创建了四个函数加减乘除,他们去掉变量名就是函数指针类型,所以四个函数的指针变量类型都是int(*)(int,int),因此我们创建函数指针数组的时候是可以将他们放入同一数组中的
,接着我们创建函数指针数组pfarr,pfarr是一个数组,里面有四个元素,所以pfarr后面加上[ 4 ],代表是一个数组,数组里面元素的类型是int(*)(int,int),前面我们又讲述了变量名放在* 旁边,代表这是指针类型。
6.2函数指针数组的使用
让3和4依次完成加减乘除的操作
int main()
{
//想要学习函数指针数组的创建,可以先将函数指针试着写出来
int(*pf)(int,int) = &add; //pf是函数指针变量,存储add函数的地址
//把四个函数指针放进数组中,因为函数名就是地址
int (*pfarr[4])(int, int) = { add,sub,mul,div };
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%d ",pfarr[i](3,4));
}
return 0;
}
6.2函数指针数组的使用 — 转移表
转移表是一种数据结构,他用于根据输入值来确定需要执行的函数或操作,转移表通常是一个包含指针的数组,数组中每个元素包含着指向对应函数或操作的指针,通过查找对应的输入值进行索引,程序可以调用对应位置的函数或操作,从而实现通过输入值动态调度程序执行不同的功能。
接下来我们进行一个小游戏的创建实现计算器的功能
在游戏设计中,经常会根据不同的游戏状态调用不同的函数,我们可以通过函数指针来实现这一功能
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;
}
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 input = 0;
int(*parr[5])(int, int) = { 0,add,sub,mul,div };
int x, y = 0;
do
{
menu();
printf("请选择:");
scanf("%d",&input);
if (input <= 4 && input >= 1)
{
printf("请输入两个数:");
scanf("%d %d", &x, &y);
printf("%d\n",parr[input](x,y));
}
else if (input == 0)
{
printf("退出计算\n");
}
else
{
printf("输入错误,请重新输入\n");
}
} while (input);
return 0;
}
在这里int(*parr[5])(int, int) = { 0,add,sub,mul,div };就被称为转移表,通过转移表我们可以灵活的在各个不同的函数功能之间进行跳转