指针进阶
回忆初阶学习指针的内容:
- 指针本质是内存单元的编号。
- 指针变量就是个变量,用来存放指针(地址),地址唯一标识一块内存空间。
- 指针的大小是固定的4/8个字节(32位平台/64位平台)。
- 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
- 指针的运算。
1.字符指针
字符指针:存放字符类型的数据的指针的指针变量。
方法一:
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';//pc就是字符指针。
return 0;
}
方法二:
int main()
{
char* p = "hello";//p中存放的是首元素地址。
return 0;
//注意,这里的字符串是个常量字符串,不可被修改。
//因此,严格的书写方式是:const char* p="hello";
//字符串会一直到访问结束,访问到字符串最后一位(‘ \0 ’结束。)
}
一个小细节:
2.指针数组
首先,它本质还是一个数组。
指针数组是一个存放指针的数组。
整型数组——存放整型的数组
字符数组——存放字符的数组
指针数组——存放指针(地址)的数组
字符指针数组
int mian()
{
char* arr[4]={"aaa","bbb","ccc","ddd"};
//存放字符指针的数组。
//常量字符串,所以注意加const!!!
//const char* arr[4]={"aaa","bbb","ccc","ddd"};
for(int i=0;i<4;i++)
{
printf("%s\n",arr[i]);
}
//逐个打印出字符串。
return 0;
}
将内存以图形的形式展示出来:
关于这8个空间,需要注意的是:
整型指针数组
int mian()
{
int arr1[5]={1,1,1,2,2};
int arr2[5]={3,3,3,4,4};
int arr3[5]={5,5,5,6,6};
int arr4[5]={7,7,7,8,8};
int* arr[4]={arr1,arr2,arr3,arr4};
for(int i=0;i<4;i++)
{
for(int j=0;j<5;j++)
{
printf("%d ",arr[i][j]);//和二维数组相似!!!
//换种写法:printf("%d ", *(arr[i]+j));
}
printf("\n");
}
return 0;
}
将内存以图形的形式展示出来:
其他指针数组
int* arr1[10]; //整型指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3.数组指针
字符指针——存放字符地址的指针,指向字符(char*)
整型指针——存放整型地址的指针,指向整型(int*)
浮点型的指针——指向浮点型(float*)或(double*)
数组指针——存放数组地址的指针,指向数组的指针。
本质上来说,数组指针还是指针。
创建一个数组指针
int main()
{
int arr[5];//整型数组
int (*pa)[5]=&arr;//pa就是整型数组指针
}
对于int (*pa)[5];
解释:pa先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为5个整型的数组。
所以pa是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证pa先和*结合。
&数组名VS数组名
&arr和arr,值是一样的,打印出来的数字,都是首元素地址那个数字。
但是&arr和arr意义是不一样的。
实际上:&arr表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中&arr 的类型是:int(*)[10],是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40.
数组首元素地址+1,仅仅跳过的是一个元素的大小,所以arr+1相对于arr的差值是4。
如图所示:
数组名——数组首元素地址
&数组名——数组地址两者从值的角度来看是一样的。但是意义不一样。
int main()
{
int arr[10]={0};
printf("arr = %p\n", arr); //打印的值是数组首元素地址,类型是int*
printf("arr+1= %p\n\n", arr+1);//4
printf("arr= %p\n", &arr[0]);//打印的值是数组首元素地址,类型是int*
printf("arr+1= %p\n", &arr[0]+1);//4
printf("&arr = %p\n", &arr);//打印的值是数组地址,类型是int (*)[10];
printf("&arr+1 = %p\n", &arr+1);//40
//int (*)[10],任何时候,缺一不可。
//缺一点,比如[10],那类型就变了。
return 0;
}
数组指针的意义是什么呢?
只有数组的地址才能被数组指针接收。
也就是说,它是用来接收数组地址的。
int arr[10]={1,2,3,4,5,6,7,8,9};
arr——类型是int*
&arr——类型是int ( * )[10]
数组指针的使用
在一维数组中的使用:
实际上,在一维数组中基本不会用到数组指针,以下只是举个例子。
这个例子纯属了解其在一维数组中的用法。
在二维数组中的使用:
代码如下:
void print(int(*p)[4], int a, int b)
{
int i = 0;
for (i = 0; i < a; i++)
{
int j = 0;
for ( j = 0; j < b; j++)
{
printf("%d ", (*(p + i))[j]);
//printf("%d ", p[i][j]);这种写法与上面等价!!!
}
printf("\n");
}
}
int main()
{
int arr[3][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } };
print(arr,3,4);
return 0;
}
代码:
(*(p+i))[ j ]
p+i是行
*(p+i)是提取出行
[ j ]是用来访问行中的第几个的元素
对于一个二维数组,数组名是首元素地址(这里的首元素地址,指代的是第一行,整个第一行!!!)
不是第一行第一个元素地址
数组指针的强转
int arr[10];
强转:int(*p)[ 10 ]=(int ( * )[ 10 ] )arr;
注意:
int ( * p)[ 10 ]=&arr
这里,把&arr赋给了p。
*p= *&arr=arr
所以,*p就是arr。
复习一下:
- int arr[5]; //整型数组,数组是5个元素;
- int * parr1 [10]; //指针数组,数组10个元素,每个元素是 int * 类型的
- int ( * parr2 ) [ 10 ]; //parr2是数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型的。
- int ( * parr3 [ 10 ] ) [ 5 ] ; //parr3是数组,数组拥有10个元素,数组的每个元素的类型是:int( * )[ 5 ]
4.数组传参和指针传参
数组传参
一维数组传参
#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 main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
二维数组传参
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);
}
指针传参
一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数 print(p, sz);
print(p, sz);
return 0;
}
当一个函数的参数为一级指针时,可以传的有:(3个)
二级指针传参
当函数形参类型为二级指针时,可以传的参数有:
- 二级指针变量
- 一级指针的地址
- 指针数组的数组名
5.函数指针
认识函数指针
数组指针——指向数组的指针
函数指针——指向函数的指针
补充:
pf是一个存放函数地址的指针变量——函数指针
&函数名和函数名
&函数名和函数名都是函数的地址。
两者没有区别。
使用函数指针
两种方式:
一望而生畏代码:
语句:
(*( void (*)() ) 0 )()
//该代码是一次函数调用
//调用0地址处的一个函数
//首先代码中将0强制类型转换为类型为void(*)()的指针函数
//然后去调用0地址处的函数
语句:
void (* signal ( int , void (*) ( int )))(int)
分为两部分:void(* )(int)和
signal(int ,void(*)(int) )
函数是signal(int ,void(*)(int) )
void(* )(int)是函数的返回类型。
//该代码是一次函数声明
//声明的函数名字叫signal
//函数参数有两个,第一个是int类型,第二个是函数指针类型
(该函数指针能够指向的那个函数的参数的类型是int,返回类型是void)
//signal函数的返回类型是一个函数指针
(该函数指针能够指向的那个函数的参数的类型是int,返回类型是void)
注意:
上面所说的两个函数指针是两种函数指针。
6.函数指针数组
函数指针数组——存放函数指针的数组
或者说:存放函数地址的数组
创建函数指针数组
int main()
{
//指针数组
char ch[5];
//数组指针
int arr[10] = { 0 };
int(*pa)[10] = &arr;
//pa是函数指针
add(5,1.3);
int(*pa)(int, double) = &add;
//函数指针数组(paA)
int(*paA[5])(int, double) = { &arr };
return 0;
}
分辨(函数指针)和(函数指针数组)
通过总结,可以得到规律:
pa先和*结合,那么pa本质上是指针
pa先和[ ]结合,那么pa本质就是数组
函数指针数组的用途:转移表
7.指向函数指针数组的指针
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针
8.回调函数
使用函数指针做 函数的参数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。