C语言指针(指针数组、数组指针、函数指针、传参、回调函数等)超详细

指针

英文名pointer,也叫地址,说白了就是内存块的首地址。

指针变量(地址变量)

  1. 指针变量就是存放指针数据的变量。
  2. int*p;//p只能存放int类型内存块的地址
  3. 所有指针变量都是4字节(32环境)
  4. 未赋初值的指针变量禁止使用(访问未初始化的指针的值程序直接崩溃。访问NULL指针的值也会奔溃)
    如下面这俩种情况:
    int* p;
	printf("%d\n", *p);
    int* p = NULL;
	printf("%d\n", *p);

常见用法

用法含义
*p的含义:根据p里面的地址,找对应类型的内存块。
p+n的含义:p地址向后偏移n个存储单元,得到一个新地址 在一连续空间中俩个指针相减等于间隔的内存空间个数
p[n]的含义:表示p地址第n+1个内存块
指针支持的运算算术运算、自增自减、关系运算、逻辑运算、赋值运算、条件运算(三目)、逗号运算、sizeof关键字、& *

指针的进阶

指针数组

本质是数组,用来存放指针的数组。
例如:int* parr[4]; 存放整型指针的数组
代码如下

	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1, arr2, arr3 };

	for(int i = 0; i < 3; ++i)
	{
		for(int j = 0; j < 5; ++j)
		{
			printf("%d ", *(parr[i] + j));
		}
		printf("\n");
   }

示意图如下
在这里插入图片描述

数组指针

本质是指针,指向数组的指针,用来存放数组的地址。
在这里插入图片描述
思考题:int *p1[10]; int(*p2)[10]; p1,p2分别是什么?
答:
在这里插入图片描述

前者 p先和中括号结合说明p是一个数组,然后每个元素是int存放指针的数组,是指针数组
后者 p先和
结合说明p是一个指针变量,然后指向的是一个大小为10个整型的数组,所以p是一个指针,指向一个数组,叫数组指针 (注:[]的优先级要高于号的,所以必须加上()来保证p先和结合

&数组名 VS 数组名

1、&数组名的时候,表示整个数组
2、sizeof(arr) 整个数组

数组名arr表示数组首元素地址
下图中可以可以看出,&arr加1就相当于加了个数组,所以证明出来,&arr表示的是整个数组
在这里插入图片描述

使用场景(适合于二维数组及其以上)

一维数组使用指针数组,数组指针适用于二维数组及以上。

一维数组遍历

一维数组使用指针数组

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*pa)[10] = &arr;

	// 方法一
	for(int i = 0; i < 10; ++i)
	{
		printf("%d ", (*pa)[i]);
	}

	// 方法二
	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", *(*pa + i));
	}
二维数组遍历(传入数组)

二维数组遍历(传入数组) 参数是数组形式
例如:下面这种遍历一个二维数组。

void print(int arr[3][5], int x, int y)
{
	for (int i = 0; i < x; ++i)
	{
		for(int j = 0; j < y; ++j)
		{
			printf("%d ", arr[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);
}

总结:数组名就是首元素地址 把二维数组当成一维数组 第一行就是首元素地址
第一行又是一位数组,有五个元素,每个都是整形

二维数组遍历(用数组指针)

先来看一张图
在这里插入图片描述
从图中可以得出arr[i] == *(arr + i) == *(p + i) == p[i]。那么二维数组也可以拿这四种方式表示。
下面就是传入数组指针的形式

void print(int(*p)[5], int x, int y)
{
	for (int i = 0; i < x; ++i)
	{
		for (int j = 0; j < y; ++j)
		{
			// p + i表示跳到第i行,
			// *(p+i)表示第i行的首地址,相当于数组名
			printf("%d ", (*(p + i)[j]));

			// p + i 找到那一行   *(p + i)那一行首地址
			// (*(p + i) + j) 找到那行那列的地址 然后解析
			printf("%d ", *(*(p + i) + j));

			printf("%d ", *(p[i] + j));

			printf("%d ", p[i][j]);

		}
	}
}
分析 int (*parr3[10])[5];

首先int (* ) [5] 表示这是一个数组指针。再parr3[10]表示一个数组。看下图。
parr3是一个数组,该数组有十个元素,每个元素是一个数组指针,该数组指针指向一个包含5个元素的数组,每个元素是int

在这里插入图片描述

下面列出了常见的数组用法
用法含义
int arr[5];arr是一个整型数组,数组5个元素,每个元素是整型
int *parr1[10];数组指针,数组中存放的元素是int指针
int (*parr2)[10];指针数组,parr与*先结合成指针变量,指向一个大小为10的整型数组
int (*parr3[10])[5];数组指针数组,

动态开辟二维数组

常见有三种方式。

方法一

开辟固定列数,一次释放就好了

int(*p)[10] = NULL;
int row = 10;
p = (int(*)[10]) malloc(sizeof(int) * row * 10);
free(p)
方法二

开辟n列,m行

int m = 10;
int n = 10;
int i = 0;
char** p = (char**)malloc(sizeof(char*) * n);
for (i = 0; i < m; ++i)
{
    p[i] = (char*)malloc(sizeof(char) * m);
}
 
 // 
for (i = 0; i < m; ++i)
{
    free(p[i]);
}
free(p);
方法三
	int n = 0;
	char** p = NULL;
	int* q = NULL;//记录每行的长度
	int i = 0;
	int j = 0;
	scanf("%d", &n);//多少行
	p = (char**)malloc(sizeof(char*) * n);
	q = (int*)malloc(sizeof(int) * n);
	for (i = 0; i < n; ++i)
	{
		scanf("%d", &q[i]);//确定每一行的列数
		p[i] = (char*)malloc(sizeof(char) * q[i]);
	}
	for (i = 0; i < n; ++i)
	{
		for (j = 0; j < q[i]; ++j)
		{
			scanf("%d", &p[i][j]);
		}
	}
	free(q);
	for (i = 0; i < n; ++i)
	{
		free(p[i]);
	}
	free(p);
 
	return 0;

字符指针

在指针的类型中我们知道有一种指针类型为字符指针char*;只是把字符串首地址放到*p中,然后遍历输出直到遇到’\0’字符。
例如

	char* pstr = "hello bit.";
	printf("%s\n", pstr);

错误示范(段错误)

如下这种 试图修改一个字符串常量
在这里插入图片描述

正确做法

	char* p = "asdfg";
	p = "sss";  //修改指向

	printf("%s\n", p);

数组/指针传参

一维数组传参

第一种

如:int arr[10] = {0};

传参方式:

  1. void test(int arr[]){};
  2. void test(int arr[10]){};
  3. void test(int *arr){};
第二种

如:int * arr2[20] = {0};
arr是一个指针,指向一个包含20个元素的数组,数组中存放的是int*,是数组指针

传参方式:

  1. void test(int *arr[]){};
  2. void test(int * arr[20]){};
  3. void test(int **arr){};//取数组指针地址然后本身就是int*类型

二维数组传参

如:int arr[3][5] = {0};

传参方式:

  1. void test(int arr[3][5]){};
  2. void test(int arr[][5]){};//二维数组传参·可以省略行,不能省略列
  3. void test(int (*arr)[5]){};

一维指针传参

如:int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = arr; print(p,10);

传参方式:
void print(int *p, int sz){};

思考:当一个函数的参数部分为一级指针的时候,函数能接受什么参数? void test(int *p);
答:

  1. test(&a); //存放数值的地址
  2. test(\p);//存放地址的变量

二维指针传参

如:int n = 10; int*p = &n; int **pp = &p;

传参方式:
void test(int **ptr){};

思考:当一个函数的参数部分为二级指针的时候,函数能接受什么参数? void test(int **ptr){};
答:

  1. test(pp);//传二级指针
  2. test(&p);/传以及指针地址
  3. test(arr));//int * arr[10]; 传数组,数组中存放的是指针,元素类型是int*。

函数指针

函数指针是指向函数的指针。
从下图可以看出**&函数名 和 函数名是等价的 (都是函数地址)**
在这里插入图片描述

创建及使用

先看下面这个图
在这里插入图片描述

创建

以上面那个Add函数为例。那么函数指针就是int (*p)(int, int) = Add;
格式:返回值 (*函数指针变量名)(参数1,参数2,…)

使用

以上面那个Add函数为例。调用方法(*p)(2,3) 或者(p)(2,3) 。因为函数名就是函数地址 &可加可不加。
格式:(*函数指针变量名)(参数1,参数2,…) 或者**(函数指针变量名)(参数1,参数2,…)**

数组指针和函数指针

int (*p)[10] = &arr; 这是一个指针,指向一个数组,包含10个元素,元素类型为int那么int(* )[10]就是数组指针类型
void (*p)(char*) = Print; 这是一个指针,指向一个函数,函数参数为(char*)返回值为void,那么void (* )(char*) 就是函数指针类型

分析代码

第一题 *(void(* )() 0) ()
分析:
最里面是void(* )() ,这是一个函数指针类型,返回值为void,参数也为void,
(void(* )() 0),前面是一个函数指针类型,后面是0,表示将0强制转换成函数指针类型。想把0当成某函数地址
*(void(* )() 0) (),对函数地址解引用。找函数。然后调用,返回值为void,参数也为void,
总结:调用0地址处那个,参数无参,返回类型void的函数。

第二题 void( signal(int, void(*)(int)))*
分析:
在这里插入图片描述
signal是一个函数,第一个参数是int,第二个参数是函数指针类型,

在这里插入图片描述

去掉函数名和参数类型就是返回值 这又是一个函数指针,参数为void,返回值是void

总:signal是一个函数声明,signal函数参数一为int,参数二为函数指针类型,该函数指针指向的函数返回值为void,参数为int,返回值为函数指针类型,该函数指针指向的函数返回值为void,参数为int

简化代码
在这里插入图片描述

函数指针数组

假如我们现在需要写一个函数指针数组用来存放加减乘除四个函数的地址。那么代码应该是下面这样的。
函数指针:int (*pa)(int, int) = Add;//mul div sub
函数指针数组:int (*parr[4])(int, int) = { Add,Sub,Mul,Div };

int Add(int a, int b)
{
	return a + b;
}
int Sub(int a, int b)
{
	return a - b;
}
int Mul(int a, int b)
{
	return a * b;
}
int Div(int a, int b)
{
	return a / b;
}

int main()
{
	int (*pa)(int, int) = Add;//存放单个函数地址

	// 现在需要一个数组,这个数组可以存放四个函数地址
	int (*parr[4])(int, int) = { Add, Sub, Mul, Div };

	// 使用方法1
	printf("%d\n", (*parr[1])(6, 2));

	// 使用方法2
	printf("%d\n", parr[2](6, 2));
	return 0;
}


使用场景(转移表)

事实上转移表就是一个函数指针数组,声明并初始化一个数组。
上面那个例子目前有4个函数,如果需要扩充,只需要扩充数组,以及在使用的switch中增加分支即可。并不需要其他多余的改动。

回调函数

回调函数就是一个通过【函数指针】调用的函数,如果你把函数的指针(地址)作为函数传递给另一个函数,当这个指针被用来调用其所指的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发送时由另外的一方调用的,用于对该事件或条件进行相应。看概念可能有点懵。那么我们还是举上面那个函数指针数组那块的例子进行改造。

使用函数指针数组

在这里插入图片描述

使用回调函数

在这里插入图片描述

一看就懂的小例子

说白了就是见函数以参数的形式传入,称之为回调函数。
在这里插入图片描述

指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 ,指针指向一个 数组 ,数组的元素都是 函数指针;不需要深挖,仅作了解即可。
在这里插入图片描述

void*类型指针

• 可以接受任意类型的地址
• 不能进行解引用操作,没有具体类型,我们对它操作时候访问几个字节不确定
在这里插入图片描述

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值