比克大魔王--指针 Pointer(二)

  指针进阶:

      Hello,小的们前文我们提到了一些基本的指针类型以及对于各自类型指针指向的数据类型,以及一些关于指针解引用的方式,以及被称之为”特殊指针类型“数组传参的基本方法。大概理解了指针变量是用来存放地址的变量,而对于内存的开辟就会有对应的地址单元即地址。得到某内存的地址你就能访问这个内存了。

     小的们,作为比克大魔王的我,今天为大家带来了好东西哟!!!本期我们会介绍一二级指针关于他们与内存的爱恨情仇,以及爆料一二级指针与一二维数组他们在函数传参之间的卿卿我我,函数指针以及回调函数 这家伙的厉害!!

 一.老大哥的基石-----一二级指针

int main()
{
	//一 二级指针
	char* ps = "hello bike";// 指针ps里面存放的是字符h的地址
	int i = 0;
	while (i<10)
	{
		printf("%c",*(ps+i));
// *(ps+i)  等价于 ps[i]
		// *的顺序优先于 + 故加()
	
		i++;
	}
	printf("\n");
	i = 0;
	while (i < 10)
	{

		printf("%c", *ps++);// ++操作符优先于解引用哦
		i++;
	}

      小的们,看代码以及上面的图解,我们要深刻理解以下几点:

1.指针变量用来存放地址

2.指针解引用访问对应指针指向的内存

3.上面代码一级指针存放了字符串常量的地址,就可以解引用访问该字符串咯

4. 二级指针cha**pps呢存放了一级指针的地址,又一级指针ps存放了字符串首字母'h'的地址

,所以*pps得到ps的地址,在**pps得到字符串首元素的字母咯,进而可以访问整个字符串的地址咯

  tip1:其中对于一些小细节和一些等价关系我在代码注释里面说了哦,这都是很重要的哦。

      tip2: 好了,经过对上面图文的解释你尝试把代码输出结果说说吧!

二.调皮的小屁孩----数组指针

 数组指针一些小细节:
    因为*的优先级低于[] 所以创建数组指针的时候我们会加()
             如 int(*parr)[10];
            该指针的类型是 int(*parr)[10] 该指针指向的内存类型是int[10]
 
 
int arr[5];
 
 看看下面什么意思:
int* parr1[10];    // 表示整型指针数组
 
int(*parr2)[10];    // 数组指针 指向的是int型的数组
难一点的:int (*parr3[10])[5];        // 这是一个parr数组 大小为10个元素 元素类型为
 int(*)[5]    为数组指针的类型 变量parr3是一个指向一个数组,作为指针指向int型数组
 parr[0]     这是该数组中的首元素 即某数组的地址 parr[0][k] :访问里面的内容

   小的们! 对于指针啊我们要知道创建指针*p p成为指针是因为*指向了p使得p位指针

加上类型 如 char*p,p成为指向字符的指针。考虑到优先级*p[] 使得p先成为数组p。

  理解这点:int (*parr3[10])[5]------->parr3先成为数组,类型不过就是int(*)[5] 而已的数组指针数组啦。

三.函数传参-----指针与数组的爱恨情仇

  1.一维数组

	// 传参 
#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)// 不能这是一个二级指针 传递的arr 是首元素地址或者数组地址
	{}

    一维数组传参
      int arr[10] = { 0 };
       int* arr2[20] = { 0 };
       test(arr);
     test2(arr2);

2. 二维数组

void test(int arr[3][5])// 形参arr是一个二维数组
{}
//void test(int arr[][])//这个指针不能使用 因为传二维数组行省列不能省
//{}
void test(int arr[][5])//
{}

   // 二维数组传参
int arr[3][5] = { 0 };
  test(arr);// 实参arr 可以是首元素地址 即某一行的数组(这是一维数组) 
               // arr 也可以是传递一个二维数组 所以接受的形参可以是一个二维数组

提高:二维数组

// 观察主函数的实参看看能不能实现传二维数组 勒
void test(int* arr)// 这就是一个整型指针 显然不行
{}
void test(int* arr[5])//   不能 这是一个指针数组 只能接受实参是指针数组哦
{}
void test(int(*arr)[5])// 这是一个数组指针 接受一维数组
{}
void test(int** arr)//二级指针不行哦
 

先试试你的基础吧!

      总结二维数组传参,函数形参的设计只能省略第一个[]的数字。
           因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
           这样才方便运算。

​void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
void test1(int* p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

   int main{            
// 一级指针传参
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;// p 接受的是一维数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);//指针传递 同级指针传递可以改变指针指向的内容 但是不能改变原指针

// 二级指针传参
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);// 传递二级指针
test(&p); // 一级指针地址

​}

四.实战--动态实现数组进而干翻矩阵!!!

 先了解一些动态数组的实现吧,在实现二维动态数组之前我们尝试实现一维动态数组。

   然后创建一个函数实现添加功能吧!

int  ArrInitAnd_allAdd(int* arr,int n)
{
	int all = 0;
	for (int i = 0; i < n; i++) {
		scanf("%d", &arr[i]);
		all += arr[i];
	}
	return all;
}
int main()
{
	// 开辟一个一维动态数组
	int n;
	scanf("%d",&n);
	int* arr = (int*)malloc(n*sizeof(int));
	if (arr == NULL) {
		perror("malloc");
	}
	else {
	int all = 	ArrInitAnd_allAdd(arr,n);//用指针接受计算结果
	printf("%d\n", all);
	free(arr);



	}


}

     tip:  对malloc不熟悉的家伙们看看下面咯:

1.使用方式呢是 malloc函数返回一个void 接受参数:字节大小 

2.实现返回一个void*的无类型参数我们使用时一般要强转指定指针类型。

3. 开辟了一个动态数组在堆上,就算出了主函数外的函数依然不会被销毁。

4. 开辟之后动态内存记得销毁啊,把申请的内存还给系统哦。

key:本期重点实现NxM的逆置矩阵

 // 动态内存实现动态二维数组
 1. 写一个n*m的矩阵 
    // 功能-- 用函数实现
    // 实现逆置矩阵
 //本质是模仿创建数组指针哦

void MatrixInit(int** ppm, int n, int m)//矩阵初始话
{
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			scanf("%d",&ppm[i][j]);

		}

	}




}
int** ReverseMatrix(int** ppm, int n, int m)
{

	int** newmatrix = (int**)malloc(m * sizeof(int*));
	for (int i = 0; i < m; i++)
	{
		newmatrix[i] = (int*)malloc(n * sizeof(int));

	}
	for (int i = 0; i < n; i++)//1
	{
		for (int j = 0; j < m; j++)
		{
			newmatrix[j][i] = ppm[i][j];

		}

	}
	for (int i = 0; i < n; i++)
	{
		free(ppm[i]);

	}
	free(ppm);
	return newmatrix;

}
void Print(int** ppm, int n, int m)
{
	


	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			printf("%d  ",ppm[i][j]);

		}
		printf("\n");
	}


}

int main()
{
	int n,m;
		scanf("%d %d",&n,&m);
		// 类比 创建一维数组 用int*类型指针 指向in型类型
	   // 用二维数组我们类比指针数组 所以指向的是整型指针类型数据 用二级指针int**
		int** matrix = (int**)malloc(n*sizeof(int*));
		// 把每一行内存开辟出来
		for (int i = 0; i < n; i++)
		{
			matrix[i] = (int*)malloc(m*sizeof(int));

		}
		MatrixInit(matrix, n, m);
		// 因为逆置需要改变矩阵的行列 意味着指针要被改变所以传递指针的地址 指针的内容改变只需要传递
		//指针就好
	matrix =	ReverseMatrix(matrix, n, m);
	// 置换 行列 
	int tmp = m;
	m = n;
	n = tmp;
		Print(matrix, n, m);
		for (int i = 0; i < n; i++)
		{
			free(matrix[i]);

		}
		free(matrix);
	return 0;
}

 key1.1: 解释这里面的动态二维数组的实现吧:

1.因为这里的matrix不就是一个二级指针嘛

2.我们指向改变他们指向的内容而不是改变指针所以函数写同类型指针即可

key 1.2: 其中逆置矩阵的思路

函数实现:

主函数调用部分:

 思路图:

1.图中的代码是一个初步思路错了直接用本身的ppm[i][j] = ppm[j][i];

    思路是对的但是呢:这样会越界哦!因为我们这是模仿二维数组实现的:比如我们开辟了nxm的矩阵是2x1的矩阵 我们首先是创建二级指针指向2个一维数组 然后在让两个一维数组开辟内存1列

    所以只有ppm[0],ppm[1]连行 列只有ppm[][0]

如果逆置就会存在ppm[][1]第二列 你在动态内存开辟时候这样的内存是没有申请的哦所以越界 

五.长的最抽象的哥们------函数指针

  为什么它抽象呢!因为他长得丑的时候是真的抽象!

       好的废话不多说上链接!!!!

        1. 函数指针也就是说函数的地址咯----类型名长这样比如 void(*p)(int )

         2.  跟数组指针类似我们要把*p先括起来

         3. 它的意思是p指向的函数返回void类型 然后接受参数int型 

  看完之后啥感觉:抽象!真tm抽象。。。 

特别是第二个代码居然是常见的信号处理函数。。真离谱还好我脑子够用行吧废话不多说干它!

tip1:用typedef简化你的代码提高可读性

要学会类比:我们把int*这个类型换一个名字叫做p 所以创建int*的变量我们直接p x;

同样的我们讲 int(*)(int,int)类型换个名字 pp 操作是这样哦跟一般不一样

typedef int(*pp)(int ,int);

所以pp x 就是创建了一个x是一个函数指针。

 所以上面那个代码二不就干死了吗:

tip2:函数指针的使用---其实也就这样

   首先要知道吧:创建一个函数指针,我们看上面的代码吧可以直接如 int(*p)(int x,int y)  = 函数名 ||&函数名

效果是一样的都得到了函数名:

   参考下面的代码:原因是使用函数名它进行了隐式转化为了int(*)(int,int)类型的指针。具体的感兴趣你可以去查阅资料哦!

   所以我们以后常用函数指针我们不用解引用同样的会隐式转化

所以int(*p)(int x,int y)  = add;

然后再次调用函数 我们可以直接p(x,y)   而不用先解引用得到函数在使用函数

   即(*p)(x,y)

tip3:实战----使用函数指针数组  实现转移表即计算器

// 使用函数指针数组 
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 x = 0;
	int y = 10;
	int(*p)(int x, int y) = add;// ADD 与&Add效果一样
	printf("%d\n",p(x,y));// 函数指针的使用方法就是指针名加上参数列表 原因是因为函数名被
	// 隐式转化成函数指针类型了 
	// 如果你不理解还可以这样
	printf("%d\n",(*p)(x,y));// 这样不行*p(x,y) ()的优先级高
	

	// 函数指针数组 顾名思义 数组元素类型是函数指针
	//下面实现一下     转移表   ----计算器
	int(*fun[5])(int x, int y) = {NULL ,add,sub,mul,Div };
	int input = 0;
	
	do {
		printf("************************\n");
		printf("*****1.add   2.sub *****\n");
		printf("*****3. mul   4.div******\n");
		printf("*****0.退出************\n");
		printf("******请输入选项:***********\n");
		scanf("%d",&input);
		int x, y;
		printf("请输入操作数\n");
		scanf("%d %d",&x,&y);
		
		if (input > 0 && input < 4)
		{
			int all = fun[input](x, y);
			printf("结果是:%d\n",all);


		}


	
	
	} while (input);

	return 0;

}

key:稍微提一下这里几个小核心以及巧妙的地方

1.函数指针数组呢我们首先让他成为一个数组p[5] 然后写出函数指针的形式

int(*)(int x,int y); 

2. 我们这里使用了一个菜单和选项然后呢多放了一个NULL就是为了对应上选项.

3.依次实现加减乘除函数然后放入函数指针,在用一个菜单。

六.回调函数----大肉在前,小菜而已

回调函数不过就是在函数中通过函数指针调用其他函数而已咯

 有没有听过一种快速排序叫做快排 (quickly sort)

快排呢 形式使用形式:

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

   一个基数组base 然后呢 num个元素,每个元素大小(字节),然后你创建的一个compar函数要求返回一个整数根据正负用来排序。

tip1:快排可以把所有数据类型的进行排序哦,关键看你的compar函数怎么写以及你的基数组

举例使用快排整数组

int compar(const void* p1, const void* p2) {
	return *(int*)p1-*(int*)p2;
}


void Print(int* arr)
{
	for (int i = 0; i < 8; i++)
	{
		printf("%d  ",arr[i]);
	}
	printf("\n");
}



int main()
{
	int arr[] = {1,5,6,2,3,1,1,5};
	qsort(&arr,8,sizeof(int),compar);
	Print(arr);
	return 0;
}

我们这里创建com的时候注意这里要求强转哦 return的时候。

下面是输出结果:

七.结语

小的们,这期先到这里了,本魔王介绍了很多玩意,然后给你们见识了很多我自己见过的想到的用处矩阵等实战,相信到这里你肯定觉得指针而已,面对魔王的包丁解牛也是不过尔尔。

   小的们务必认真掌握指针哦,上面的题解魔王都一字字敲出来了你也不可懈怠哦。同类型指针下棋准备出版指针(三)一些题目放心肯定很easy期待哦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值