【C语言-指针】

回忆初阶学习指针的内容:

  1. 指针本质是内存单元的编号。
  2. 指针变量就是个变量,用来存放指针(地址),地址唯一标识一块内存空间。
  3. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  4. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
  5. 指针的运算。

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。

复习一下:

  1. int arr[5]; //整型数组,数组是5个元素;
  2. int * parr1 [10]; //指针数组,数组10个元素,每个元素是 int * 类型的
  3. int ( * parr2 ) [ 10 ]; //parr2是数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型的。
  4. 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个)
在这里插入图片描述

二级指针传参

当函数形参类型为二级指针时,可以传的参数有:

  1. 二级指针变量
  2. 一级指针的地址
  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.回调函数

使用函数指针做 函数的参数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值