目录
二级指针
字符指针
字符指针使用方法一
字符指针使用方法二
一道面试题
指针数组
指针数组的使用方法
数组指针
&数组名 VS 数组名
数组指针的使用方法一
数组指针的使用方法二
函数指针
两段有趣的代码
函数指针数组
函数指针数组的应用—计算器的实现
回调函数
回调函数的应用
void*指针详解
指针变量也是变量,是变量就有地址,指针变量的地址存放于二级指针;
int main()
{
int a=10;
//a的地址存放于pa中
int*pa=&a;
//pa的地址存放于pa中
int**ppa=&pa;
return 0;
}
此处,pa是一级指针,ppa是二级指针,代码所对应的内存布局如图
通过ppa处存放的pa的地址,即可对ppa进行解引用操作访问pa,即*ppa=pa,然后对pa进行解引用操作即可访问a,即*pa=a,因此 **pa=a;
字符指针指针类型为 char*,字符指针如何应用?
int main()
{
char ch='a';
char* p=&ch;
*p='w';
return 0;
}
int main()
{
const char*p = "abcdef";
printf("%p\n", p);
return 0;
}
''abcdef''为常量字符串,常量字符串不允许被修改,存放于内存的可读区(常量区),用const修饰;
const char*p = "abcdef";
容易误解字符串''abcdef''存放于字符指针p里面,但是指针变量p的大小是4个字节 ,字符串里面有7个字符,每个字符需要用1个字节存放,需要7个字节的空间存放
那么指针变量p里面存放的是什么?
p里面存放的是常量字符串首元素的地址
int main()
{
//数组名代表首元素的地址
char arr1[] = "abcdef";
char arr2[] = "abcdef";
//字符指针存放常量字符串首元素的地址
const char* p1 = "abcdef";
const char* p2 = "abcdef";
if (arr1 == arr2)
{
printf("arr1=arr2\n");
}
else
printf("arr1!=arr2\n");
if (p1 == p2)
{
printf("p1=p2\n");
}
else
printf("p1!=p2\n");
return 0;
}
输出结果:
当创建字符数组接收字符串时,数组会在内存的栈区开辟两块独立的内存空间,分别存放字符串"abcdef",而数组名代表数组首元素的地址,由于是俩块独立的内存空间,所以输出arr1!=arr2,对应的内存布局图如下
由于常量字符串内容相同,而且不允许被修改,故字符串在常量区只保存一份,因为p里面 存放的是常量字符串首元素的地址,所以 p1=p2;对应内存布局如下图
指针数组
整型数组,整型数组用来存放整型
指针数组,指针数组用来存放指针
int main()
{
int a=10;
int b=20;
int c=30;
int* arr[3]={&a,&b,&c};
return 0;
}
arr为指针数组,数组里面有3个元素,每个元素的类型为int*, 上述代码所对应的内存布局如下
使用指针数组模拟实现二维数组,指针数组里面具体元素所对应内存中的位置是不确定的
而二维数组是在内存中连续存放的;
int main()
{
int arr1[] = { 1, 2, 3, 4, 5 };
int arr2[] = { 2, 3, 4, 5, 6 };
int arr3[] = { 3, 4, 5, 6, 7 };
int*parr[3] = { arr1, arr2, arr3 };
//遍历数组的每个元素
int sz = sizeof(parr) / sizeof(parr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
int j = 0;
int k = sizeof(arr1) / sizeof(arr1[0]);
for (j = 0; j < k; j++)
{
//printf("%d ", *(parr[i] + j));
//*(parr[i]+j)=parr[i][j]
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
类比整形指针, 整型指针是能够指向整型数据的指针, 那么数组指针就是能够指向数组的指针
eg: int (*p) [10] p是什么?
根据操作符的优先级和结合性可知,p先和*结合,说明p是一个指针变量,然后指向一个数组,数组里面有10个元素,每个元素的类型为int; 所以p为数组指针
数组名是数组首元素的地址,但是有俩个例外
1. sizeof(数组名)计算的是整个数组的大小,而不是地址的大小;
2. &数组名取出的是整个数组的地址;
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr[0]);
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", arr + 1);//跳过4个字节
printf("%p\n", &arr + 1);//跳过40个字节
return 0;
}
从上述代码可知,arr为数组名,数组名为首元素的地址,首元素的类型为int,所以arr类型为int*,那么&arr的类型是什么?
数组指针
数组指针 指向数组的指针
数组指针的类型是什么 type(*)[size] size为指向数组的大小,type为数组元素的类型
void print1(int(*p)[5], int sz)
{
//p是整个数组的地址
//*p相当于数组名,数组名又是首元素的地址;
//*p=&arr[0]
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(*p + i));
}
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int sz = sizeof(arr) / sizeof(arr[0]);
print1(&arr, sz);
return 0;
}
数组指针经常用于二维数组
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
二维数组是一维数组的数组,二维数组的数组名是什么?
二维数组的数组名表示第一行的地址,二维数组的每个元素是一维数组,那么二维数组的每一行的数组名又是什么?
eg: 第一行的数组名
二维数组第一行的每个元素如下:
int arr[0][0] int arr[0][1] int arr[0][2] int arr[0][3] int arr[0][4]
去掉int [j] j=0,1,2,3,4;剩下arr[0],那么第一行的数组名是arr[0];
void print(int(*p)[5],int row,int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; 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;
}
arr为二维数组的数组名,代表第一行的地址,所以传参时用数组指针接收,p就是数组指针,p+i表示指向第i 行,*(p+i)相当于拿到第i行的数组名,数组名又表示首元素的地址
,所以*(p+i)就是第i行第一个元素的地址;
arr[i]=*(arr+i)
arr[i][j] = *(arr+i)[j] = *(*(arr+i)+j)
所以*(*(p+i)+j)= p[i][j]
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", Add);
printf("%p\n", &Add);
return 0;
}
输出结果:
结论:函数名与&函数名本质相同,都是函数的地址
函数指针
类比数组指针,数组指针能够指向数组的指针, 那么函数指针就是能够指向函数的指针;
函数指针变量的类型是什么?
int (*pf) (int, int) *先和pf结合,说明pf为指针变量,指针指向了一个函数,函数有俩个参数,俩个参数的类型都是int ,函数的返回类型为int
int Add(int x, int y)
{
return x + y;
}
int main()
{
//函数名即为函数的地址,而且&函数名=函数名;
//int(*pf)(int, int) = &Add;
int(*pf)(int, int) = Add;
//int ret = (*pf)(2, 3);
int ret = pf(2, 3);//pf=Add
printf("%d\n", ret);
return 0;
}
因为pf为函数指针变量,pf=&Add,对函数指针变量pf进行解引用操作*pf=Add
然后调用函数Add(2,3)=(*pf)(2,3);
又因为 pf=Add ,所以可直接调用函数pf(2,3)=Add(2,3)
因此 Add(2,3) = (*pf)(2,3) = pf(2,3)
(* ( void(*)() ) 0 )();
代码解读:
void(*)()是一个函数指针类型,(void(*)())0表示对0进行了强制类型转换,将0强转为函数指针,意味着0地址处存放一个参数为空,返回类型为void的函数;(* ( void(*)() ) 0 )()调用0地址处的函数;
void(*signal(int, void(*)(int)))(int);
代码解读:
signal(int,void(*)(int)),signal是一个函数,函数的第一个参数类型是int,函数的第二个参数类型是函数指针,函数指针类型为void (*)(int),去掉signal(int,void(*)(int)),剩下的便是函数的返回类型为void(*)(int);可以这么理解void(*)(int) signal(int, void(*)(int)),但是不可如此书写代码,上述代码太过复杂,如何简化?
typedef void(* pf_t) ()给函数指针类型 void(*)(int)重命名为pf_t
代码简化如下:
pf_t signal (int , pf_t)
首先数组是一组相同元素的集合;
类比指针数组,指针数组存放指针的数组;
那么函数指针数组存放函数指针的数组;
要求函数指针的参数,返回类型必须相同;
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(*p)(int,int)
int(*parr[4])(int, int) = { Add, Sub, Mul, Div };
//调用函数指针数组的每一个函数,来实现对应的操作;
int sz = sizeof(parr) / sizeof(parr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
int ret = parr[i](2, 3);
printf("%d\n", ret);
}
return 0;
}
版本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;
}
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 x = 0;
int y = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
int ret = 0;
switch (input)
{
case 1:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret=Add(x,y);
printf("ret=%d\n", ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret=Sub(x,y);
printf("ret=%d\n", ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret=Mul(x,y);
printf("ret=%d\n", ret);
break;
case 4:
printf("请输入两个操作数\n");
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;
}
当我们采用switch-case语句实现计算器,case语句里面出现很多冗余代码,代码执行的效率不高,当我们采用函数指针数组,代码执行效率高,而且简洁明了
版本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 x = 0;
int y = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
int ret = 0;
int(*parr[5])(int, int) = { 0, Add, Sub, Mul, Div };
//为使数组下标完美匹配菜单选项; 数组里存放五个元素;
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
ret = parr[input](x, y);
printf("ret=%d\n", ret);
}
else if (input==0)
{
printf("退出计算器\n");
}
else
{
printf("选择错误,请重新输入!\n");
}
} while (input);
return 0;
}
回调函数就是一个通过函数指针调用的函数。当我们将函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们称之为回调函数;回调函数不是由函数的实现方直接调用,而是在特定的条件发生时由另一方调用的,用于对该条件进行响应;
回调函数定义的通俗理解:
写一个A函数,并没有直接调用A函数,将A函数的地址传给了B函数,B函数的参数里得有一个函数指针接收A函数的地址;在B函数通过函数指针调用A函数,这个A函数叫做回调函数
//A函数
void test()
{
printf("hehe\n");
}
//B函数
void print(void(*p)())
{
if (1)//特定的条件发生
{
p();
}
}
int main()
{
print(test);
return 0;
}
计算器的实现中,case语句里面代码冗余过多,将差异处封装成函数,采用回调函数处理冗余代码
//将冗余代码封装成一个函数,差异体现在调用的函数有所区别
//将差异处作为函数参数处理
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");
}
//通过函数指针回调Add,Sub,Mul,Div函数;
void calc(int(*p)(int, int))
{
int x = 0;
int y = 0;
printf("请输入两个操作数\n");
scanf("%d%d", &x, &y);
int ret=p(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>\n");
scanf("%d", &input);
int ret = 0;
switch (input)
{
case 1:
calc(Add);
break;
case 2:
calc(Sub);
break;
case 3:
calc(Mul);
break;
case 4:
calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误,请重新输入!\n");
break;
}
}
while (input);
return 0;
}
void* — 无确切类型的指针,可以接收任意指针类型的数据;
int main()
{
int a = 10;
char* p = &a;//出现警告,从int*到char*类型不兼容;
return 0;
}
int main()
{
int a = 10;
void *p = &a;//警告消失,说明void*可以接收任意类型的数据
return 0;
}
void*为无确切类型的指针,不能对void*进行解引用操作,因为没有确切类型,无法得知可以访问几个字节;
同理 不能对void*类型的指针变量进行+-整数的操作