指针进阶(超详细讲解)

加油加油,为了美好明天!


前言

`本文主要讲解指针的相关知识


1、字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*
一般使用:
在这里插入图片描述


还有一种指向常量字符串,用法如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0b70e641898c48cf87b852d13e53dc78.png

  • 注意:
    这里不是把字符串 hello bit 放到字符指针pstr 里了,本质是把字符串 hello bit. 首字符的地址:h放到了pstr中。
    在这里插入图片描述



🦄一道有意思的面试题:
在这里插入图片描述

解析:str3str4指向是同一个常量字符串,C/C++会把常量字符串单独存放到一个内存区域(静态内存区),当多个指针同时指向它时,起始指向的是同一块内存空间。但是相同的常量字符串去初始化数组时,不同的数组要开辟不同的内存块。所以str1str2不同,str3str4相同

2、指针数组

从名字上理解,指针数组是一个数组,是存放指针的。
在这里插入图片描述

3、数组指针

3.1 数组指针的定义

从名字上理解,数组指针是一个指针,指向的是一个数组

在这里插入图片描述

  • 1:[]的优先级高于*,p1先和[]结合,故p1首先是一个数组,数组有10个元素,每个元素是int*型,故p1是指针数组!
    2:p2先和*结合,故首先p2是一个指针,指针指向一个数组,数组有10个元素,每个元素是int型。故p2是数组指针!!

3.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr数组名到底是啥?
我们看一段代码:

在这里插入图片描述
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
在这里插入图片描述
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上&arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中&arr的类型是:int(*)[10],是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

3.3 数组指针的使用

例如:打印二维数组

void print(int(*arr)[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 ", (*(arr + i))[j]);
		}
		printf("\n");
	}
}


//使用数组指针打印二维数组
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print(arr,3,5);
	return 0;
}

我们知道数组名表示数组的首元素地址,而是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收

也可以另一种写法:
在这里插入图片描述

🦄分析下列代码的意义:

int arr[5];			//是数组。数组有5个元素,每个元素是int型
int* parr1[10];		//是指针数组。数组有10个元素,每个元素是int*型
int(*parr2)[10];	//是数组指针。指针指向一个数组,数组有10个元素,每个元素是int型。   指针	parr2的类型为int (*)[10]
int(*parr3[10])[5];	//parr3先和[]结合,说明parr3是一个数组。把parr3[10]去掉后剩下的就是数	组里面的元素类型。每个元素的类型为素组指针int (*)[5]
				   //int (*)[5]是一个数组指针,指向的数组有五个元素,每个元素为int型

4、数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参

数组传参的两种形式:
在这里插入图片描述

  • 总结:
    • 一位数组传参

      • 1:一维数组接收(大小可以省略)
        2:一维指针接收
    • 一维指针数组传参

      • 1:一维指针数组接接收
      • 2:二级指针接收

4.2 二维数组传参

在这里插入图片描述

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

4.3 一级指针传参

#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);
	return 0;
}

思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
在这里插入图片描述

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr)  //二级指针接收
{
	printf("num = %d\n", **ptr);
}

int main()
{
	int n = 10;
	int* p = &n;    //p=&n
	int** pp = &p;  //pp=&pp
	test(pp);
	test(&p);
	return 0;
}

思考:
当函数的参数为二级指针的时候,可以接收什么参数?
1:二级指针
2:指针数组

在这里插入图片描述

5、函数指针

指向函数的指针

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

运行结果:
在这里插入图片描述
上面这两个地址都是 test 函数的地址,即test=&test。


那我们的函数的地址要想保存起来,怎么保存?
在这里插入图片描述

🦄分析下列代码的意义:

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

第一个代码
将0当作一个地址强类型转换成函数指针类型(void ()()),再解引用,调用该函数,该函数无参数
第二个代码
signal先和()结合,signal是一个函数。函数有两个参数,一个是int,一个是void(
)(int)函数指针类型,该函数指针指向的函数有一个int型参数,返回值为void。
将signal去掉,剩下的void (*)(int)是一个函数指针,该函数指针指向的函数有一个int型参数,返回值为void型

有一个东西可以让这个函数可以更好的理解有一个函数typedef叫做重命名函数
在这里插入图片描述

6、函数指针数组

6.1 函数指针数组的定义

定义:函数指针数组,每个返回值相同,以及参数类型相同的函数可以放在一个函数指针数组。

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:

int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();

parr1先和[]结合,说明parr1是数组,数组有10个元素。将parr1[10]去掉剩下的就是数组内容
数组内容是int (*)()是一个函数指针。

6.2 函数指针数组的用途

函数指针数组的用途:转移表

例子:计算器(普通版本)

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;
}

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;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);


		switch (input)
		{
		case 0:
			printf("退出计算器\n");
			break;
		case 1:
			printf("请输入要计算的数:>");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret=%d\n", ret);
			break;
		case 2:
			printf("请输入要计算的数:>");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret=%d\n", ret);
			break;
		case 3:
			printf("请输入要计算的数:>");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret=%d\n", ret);
				break;
		case 4:
			printf("请输入要计算的数:>");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret=%d\n", ret);
				break;
		default:
			printf("输入有误,请重新输入\n");
				break;
		}
	} while (input);
	
	return 0;
}

每一个switch case语句都重复输入输出,代码十分冗余

使用函数指针数组

//转移表
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;
}

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;
	int ret = 0;
	int (*p[5])(int, int) = { 0,add,sub,mul,div };  //函数指针数组
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input > 0 && input <= 4)
		{
			printf("请输入两个数:>");
			scanf("%d %d", &x, &y);
			ret = p[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误,请重新选择\n");
		}
	} while (input);
	return 0;
}

7 指向函数指针数组的指针

本质是指针,指向的是一个数组,数组里面的元素是函数指针

//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}

8 回调函数

定义:回调函数就是一个通过函数指针调用的函数。如果你把A函数的地址(指针)通过参数传给B函数,当B函数调用这个指针时候,这个指针就指向了A函数,则A函数就是一个函数指针。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

8.1 qsort()函数的定义

功能简介:可以对任何数据进行指定方式的排序

在这里插入图片描述

🦍qsort()函数的使用:分别按照名字和年龄排序下面的结构体数据

//qsort排序结构体数据
#include <stdlib.h>
#include <string.h>

struct Stu
{
	char name[20];
	int age;
};

//按照名字
int cmp_by_name(const void* s1, const void* s2)
{
	return strcmp(((struct Stu*)s1)->name, ((struct Stu*)s2)->name);
}

//按照年龄
int cmp_by_age(const void* s1, const void* s2)
{
	return ((struct Stu*)s1)->age - ((struct Stu*)s2)->age;
}

int main()
{
	struct Stu s[3] = { {"zhangsan",20},{"lixi",28},{"wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);
	//qsort(s, sz, sizeof(s[0]), cmp_by_name);
	qsort(s, sz, sizeof(s[0]), cmp_by_age);

	return 0;
}

8.2 用冒泡排序的底层逻辑模拟qsort()函数

🦊原本的冒泡排序(只能排序整型)

//冒泡排序(升序)
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//每趟的过程
		for (j = 0; j < sz - i - 1; j++)
		{
			//比较
			if (arr[j] > arr[j + 1])
			{
				//交换
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//打印
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{ 
	int arr[] = { 1,3,5,2,4,8,6,7,9, };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print(arr, sz);
	return 0;
}

🦊使用冒泡排序模拟qsort()

//交换,一个字节一个字节交换
void swap(char* buf1, char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, int sz, int width, int (*cmp)(const void*, const void*))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		//每趟的过程
		for (j = 0; j < sz - i - 1; j++)
		{
			//比较
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

struct Stu
{
	char name[20];
	int age;
};


//按照名字
int cmp_by_name(const void* s1, const void* s2)
{
	return strcmp(((struct Stu*)s1)->name, ((struct Stu*)s2)->name);
}

//按照年龄
int cmp_by_age(const void* s1, const void* s2)
{
	return ((struct Stu*)s1)->age - ((struct Stu*)s2)->age;
}



int main()
{
	struct Stu s[3] = { {"zhangsan",20},{"lixi",28} ,{"wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_by_name);
	return 0;
}

以上就是指针的全部内容了,记得一键三连,兄弟们!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值