了解指针,爱上指针(4)
- 字符指针变量
- 数组指针变量
- 二维数组传参的本质
- 函数指针变量
- typedef关键字
- 函数指针数组
- 转移表
字符指针变量
如整型指针变量一样,它是指针变量的其中一个类型:char*
一般,我们是这样使用字符指针的:
int main()
{
char ch='w';
char* c=&ch;//字符指针变量c指向&ch
*c='z';
return 0;
}
int main()
{
char ch[]="adsd";
char* c=ch;//字符指针变量c指向ch
for(int i=0;i<4;i++)
{
*c='z';
c++;
}
return 0;
}
通过字符指针变量修改字符或者字符数组,这两种情况下,都能够正常的通过字符指针变量来修改、访问并输出指向的地址下的值,但是有一种情况例外:
int main()
{
char*p="abcdef";
return 0;
}
这段代码让指针p指向一段字符串,如果我们通过指针p来输出字符或者字符串,是可以输出的:
但是当我们想要通过指针p来修改字符串的值时,程序将会崩溃:
这是因为:将字符串赋值给指针变量实际上是让指针指向字符串首字符的地址
当我们需要输出字符串的首元素时,只需要解引用指针p就行,如上文输出字符’a’一样
当我们需要输出整个字符的时候,不需要解引用,因为指针p指向的就是字符串首元素的地址,知道了字符串首元素的地址,就能成功访问字符。
为什么用指针指向一个字符串时,不能通过指针变量来修改字符串的值呢?我们回想一下,当我们用数组来存放字符串时,修改字符串是被允许的,这又是为什么呢?
原因是:
- 数组存放字符串,实际上是开辟了一块内存空间,将字符串拷贝了一份到这块空间中
- 指针指向的字符串,实际上是一个常量字符串,对常量字符串进行修改是不被允许的。(在c/c++中,常量字符串是放在只读数据区的)。
下面,给大家来个小测试:
#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");//1
else
printf("str1 and str2 are not same\n");//2
if(str3 ==str4)
printf("str3 and str4 are same\n");//3
else
printf("str3 and str4 are not same\n");//4
return 0;
}
这段代码输出第2、3两句。原因:第一个判断的是两个数组的首地址是否相同,第二个判断的是两个字符指针变量指向的地址是否相等
。
- 数组存储字符串是单独开辟一块连续的空间用来放数据,所以一个数组开辟一块空间,两个数组就开辟两块空间,既然不是同一块空间,那么它们的地址也就不一样。
- 字符指针变量存储的是字符串首字符的地址,被存储的字符串叫“常量字符串”,是不会被修改的,因为它被放在“只读数据区”,既然它不会被修改那么在用到它的时候,只需要将首字符的地址传过去就行了,不需要开辟单独的空间,同时我们回归
指针变量
的本质“用来存放地址的变量
”,所以,str3==str4为真,输出第三句。
数组指针变量
数组指针变量:是指针变量,用来存放的是数组的地址。
声明数组指针变量:
int (*p)[4];
//它的类型是:int(*)[4];
//[4]表示指向的数组中存放四个数据
//int 表示指向的数组是一个整型数组
//因为下标引用符[]的优先级高于*,所以要加括号。否则就变成看指针数组了
数组指针变量的初始化:
int main()
{
int a[4]={1,2,3,4};
int(*p)[4]=&a;
return 0;
}
既然是存放数组地址的指针变量,那么初始化的值就是数组的地址。通过调试我们发现&a与p的类型是一样的
二维数组传参的本质
本质:二维数组传参实际上传递的是第一个元素的地址
二维数组我们可以理解为:“一个存放一维数组的数组”,二维数组的每一个元素是一个一维数组:
int a[3][3]={0};
二维数组中存放了三个一维数组“a[0]”、“a[1]”、“a[2]”,每个一维数组中存放三个元素。
当我们调用函数并传递二维数组:
void Prin(int a[3][3],int r,int c)
{
int i=0;
for(i=0;i<r;i++)
{
int j=0;
for(j=0;j<c;j++)
{
printf("%d ",a[i][j]);
}
}
}
int main()
{
int a[3][3]={0};
Prin(a,3,3);
return 0;
}
在之前,我们学过一维数组的传参,一维数组的传参实际上传递的是首元素的地址,二维数组也是如此,只不二维数组的首元素是一个一维数组,传递的是第一个一维数组的地址。
三个既然是数组,那么就有它的类型,三个一维数组的类型是“int[3]”,一维数组地址的类型是"int(*)[3]",我们又知道,二维数组传参的本质“传递的是首元素的地址”,既然是地址,那就可以写成指针的形式,所以上面的代码还可以写成这样子:
void Prin(int(*a)[3],int r,int c)
{
int i=0;
for(i=0;i<r;i++)
{
int j=0;
for(j=0;j<c;j++)
{
printf("%d ",*(*(a+i)+j));
}
}
}
int main()
{
int a[3][3]={0};
Prin(a,3,3);
return 0;
}
为什么a[3][3]可以写成*(*(a+i)+j)?
我们可以这样理解:
我们知道数组名就是数组首元素的地址,因为是二维数组,所以首元素地址+i可以理解为第几行。
再对a+i解引用就得到了那一行的一维数组的首元素地址,让这个地址+j,就表示,访问这个数组的第j个元素的地址,解引用就得到相应的值
总结:
二维数组传参实际上传递的是首元素的地址
⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
函数指针变量
函数指针:用来存放函数地址的指针。
变量有地址,数组有地址,函数当然也有地址。
如果我们要把函数的地址存放起来,就要用到函数指针。
函数指针变量与数组指针变量非常类似:
void test(int a)
{
printf("%d",a);
}
int main()
{
void(*p)(int a)=test;//也可以写成int(*p)(int a)=&test,因为函数名就是函数的地址
//int(*p)(这个括号里可以只写参数的类型,不写参数)比如int(*p)(int)
p(1);//函数指针的使用,也可以写成(*p)(1)
return 0;
}
函数指针解析:
用上面的代码举例
void(*p)(int a);
void表示指向函数的返回类型
p表示函数指针变量名
(int a)指向函数的参数
关于函数指针两段有趣的代码
代码1:
(*(void (*)())0)();
这段代码运行起来,程序会崩掉。
关于这段代码可以这么理解:将0将转成void(*)()类型,转换后可以看出(*0)(),此时0就是一个函数指针类型,把0用p代替(*p)(),就相当于调用p处的函数,而p又等于0,所以就是调用0地址处的函数。
代码2:
void (*signal(int , void(*)(int)))(int);
关于这段代码可以这么理解:这其实是一个函数指针的声明,函数指针类型是”void()(int)“,变量名也是一个函数signal(int,void(*)(int))
,这里接收两个参数,一个是int类型的,一个是void()(int)类型的。
typedef关键字
typedef关键字是用来为类型重命名的,可以将复杂的类型简单化。
typedef struct student
{
int age;
}stu;
int main()
{
stu s1;
return 0;
}
在这里将struct student
类型重命名为为stu
。
typedef unsigned int unit;
这里将无符号整型重命名成了unit
。
typedef int(*p)(int);
//使用重命名后的类型
假设有一个int test1(int x)函数
p test=test1;
test1(1);
这里将一个int(*)(int)
类型重命名为p
关于数组指针、指针数组、函数指针的重命名,都需要把新的名字放在*号的左边
typedef int*p_t;
这里将int*
类型重命名为p_t
。
这里再提一嘴前面讲到的有趣的代码。
学了typedef
,可以把代码2简写成这样:
typedef void(*pfun_t)(int);
pfun_t signal(int,pfun_t);
函数指针数组
函数指针数组:是数组,用来存放函数的指针(地址)
int Add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x - y;
}
int main()
{
int(*p[2])(int, int) = {Add,sub};//函数指针数组的数组名是p,存放的元素类型是"int(*)(int,int)",存放了两个这样的元素。
for (int i = 0; i < 2; i++)
{
printf("%d",p[i](4,3));
}
return 0;
}
数组我们知道是”里面存放的元素的类型都是相同的“,函数指针数组也是。
函数指针数组存放的是函数的地址,要求:函数指针变量的类型相同。
转移表
void menu()
{
printf("*************************************************\n");
printf("*************************************************\n");
printf("*******1.Add 2.Sub 3.Div 4.Mul********\n");
printf("********************0.退出***********************\n");
printf("*************************************************\n");
}
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int mul(int x, int y)
{
return x * y;
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
int (*p[5])(int x, int y) = { 0,Add,Sub,Div,mul };
do
{
printf("请选择:");
scanf("%d",&input);
if (input>=1&&input<=4)
{
printf("请输入两个操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("请重新输入\n");
}
} while(input);
return 0;
}