当前位置: 首页 > news >正文

c语言进阶: 指针的进阶(上)

c语言进阶: 指针的进阶(上)

  • 一. 字符指针
    • 1. 解析
    • 2. 题目巩固
  • 二. 指针数组
  • 三. 数组指针
    • 1. 数组指针的定义
    • 2. &数组名和数组名的区别
    • 3. 数组指针的使用
    • 4. 总结
  • 四. 数组传参 指针传参
    • 1. 一维数组的传参
    • 2. 二维数组的传参
    • 3. 一级指针的传参
    • 4. 二级指针的传参
  • 五. 函数指针
    • 1. 函数指针的创建
    • 2. 函数指针的使用
  • 六. 函数指针数组
    • 1. 函数指针数组的定义
    • 2. 函数指针数组的用途:转移表(以计算器为例)
    • 7. 指向函数指针数组的指针

首先 在学习新知识之前 我们先来回顾下之前的学习的内容

1 指针是个变量 用来存放地址 地址唯一标识的一块内存空间
2 指针的大小是固定的4/8字节(32位平台/64位平台)
3 指针有类型的 指针的类型决定了两点 一个是指针操作的权限以及±整数的步长
4 指针的运算

初识指针

熟悉指针

一. 字符指针

1. 解析

在指针的类型中我们知道有一种指针的类型位字符指针

char *

我们一般这么使用

int main()
{
	char ch = 'w';
	// ch里存放的字符是一个w
	char* pc = &ch;
	// 指针pc里面存放ch的地址
	*pc = 'w';
	return 0;
}

还有一种使用方式如下

int main()
{
	const char* ps = "hello world";
	printf("%s\n", ps);
	return 0;
}

在这里插入图片描述

这里我们存放的ps其实不是一个字符 而是一个字符串的首地址

我们打印字符串的时候只需要把首地址传进去就可以打印出来一个完整的字符串

要验证这个说法我们可以使用数组

int main()
{
	char arr[] = "hello world";
	// arr就是数组的首地址
	printf("%s\n", arr);
}

在这里插入图片描述

我再画图给同学们解析一下

在这里插入图片描述

2. 题目巩固

int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1==str2)
	{
		printf("same\n");
	}
	else
	{
		printf("not same\n");
	}
	printf("----------------------------\n");
	if (str3==str4)
	{
		printf("same\n");
	}
	else
	{
		printf("not same\n");
	}
	return 0;
}

我们来分析下
str1和str2是两个数组 相同的常量字符串去初始化 不同的数组会开辟不同的内存块 所以str1和str2不同
str3和str4是指向同一个常量字符串 会储存在单独的一个内存区域 所以说str3和str4相同
我们打印出来看看

在这里插入图片描述

二. 指针数组

在指针章节中我们也学习了指针数组 指针数组是存放指针的数组

这里我们再复习一下 下面的指针数组是什么意思?

int *arr1[10]
一个指针数组 里面存放的int类型的指针
char *arr2[4]
一个指针数组 里面存放的是char类型的指针
char **arr3[5]
一个二级指针数组 存放的二级指针

三. 数组指针

1. 数组指针的定义

我们先来看两段代码

int* p[10];//1
int(*p)[10];//2

因为1中的变量名先和p结合 所以说它是一个数组
因为2中的解引用标识符先和p结合 所以说它是一个指针

2. &数组名和数组名的区别

还是一样 先看代码

int arr[10];

对于这段代码来说 arr和&arr分别是啥?
(其实这一段已经在我们前面的数组详解中已经介绍过了 我们再来复习下)

我们都知道arr是数组名,数组名表示数组首元素的地址

那么&arr数组名到底是啥?

我们来看下面的一段代码:
在这里插入图片描述我们可以发现地址是一样的

我们再来看下面的代码

在这里插入图片描述
&arr步进的地址实际上是40

arr步进的地址实际上是4

3. 数组指针的使用

老规矩 看代码

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

使用

在这里插入图片描述

4. 总结

我们来逐个分析下 下面代码的意思

int arr[5]//1
int *p[10]//2
int (*p)[10]//3
int (*p[10])[5]//4

第一 是一个数组 数组里面有五个元素 每个元素都是int类型的

第二 是一个数组 数组里面有十个元素 每个元素都是整型指针类型的

第三 是一个指针 指针指向一个数组 数组里面有十个元素

第四 是一个数组 数组里每个元素类型是数组指针 每个数组指针指向的数组有五个元素
这个比较难理解 我画图给大家分析下

在这里插入图片描述

大概就是上图的结构

四. 数组传参 指针传参

1. 一维数组的传参

看代码

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

我们说test传参的时候 arr[10]都是可以传递进去的 因为传递数组本质上是传递一个地址
这一点在我前面的博客有过介绍 大家感兴趣可以参考这两篇文章

函数栈帧上

函数栈帧下

而对于test2来说 arr2传递是本质也是一个数组 数组里面存放的都是指针
所以说*arr[20]是可以传递的
对于最后一个二级指针来说 我们传递进去的是一个一级指针的数组 本质是把一级指针的地址传递进去 用二级指针来接受 当然没有问题

2. 二维数组的传参

看代码


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

分析

第一个指针指向整型 而我们的二维数组的地址实际上表示一行的所有元素 所以显然不行
第二个是一个数组 而我们要传递一个指针进去 显然不可以
第三个是一个数组指针 每一个指针指向五个元素 和我们的数组相符 所以可以传递进去
第四个是一个二级指针 指向的要是一级指针的地址 这里显然不行

3. 一级指针的传参

void print(int* p, int sz) 
{
	for (int 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]);
	print(p, sz);
	return 0;
}

一级指针的传参其实没什么花里胡哨的 直接把指针传进去就可以了
我们主要思考的是 当一个函数的参数部分作为一级指针的时候 函数能接收什么参数呢
我们说 可以传递
1 一维数组的数组名
2 一维数组地址
3 一级指针

4. 二级指针的传参

代码和一级指针差不多 大家可以模仿者写一下 传参其实也没有什么花里胡哨的东西
还是一样的问题
当一个函数的参数部分作为二级指针的时候函数能够接收什么参数呢

1 一级指针的地址
2 二级指针
3 指针数组的数组名 (ex:int *p[5])

因为此时的p是一个数组 数组里面五个元素都是一级指针

当我们传递这个数组名的时候实际上是传递的首元素的地址 也就是说一级指针的地址

当然可以被二级指针接收啦

五. 函数指针

首先 看代码

void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

在这里插入图片描述
我们可以发现 打印的两个地址是一模一样的

我们我们应该怎么设计一个指针来存储函数的地址呢?

void (*pfun1)();//1
void* pfun2();//2

我们想想看 到底是1还是2可以存储呢?

答案是1

这个问题的关键和数组知识是相同的 看变量先和谁结合 所以说1是个函数指针 可以存储函数的地址

1. 函数指针的创建

int (*p)(int x, int y) = Add;// 前面需要加参数 后面不需要

2. 函数指针的使用

int sum = p(3, 5);//解引用操作符可用可不用 对于结果没有影响

完成运行结果

在这里插入图片描述

六. 函数指针数组

我们都知道 数组是一个存放相同类型数据的存储空间 那我们已经学习了指针数组

那么函数有没有对应的指针数组呢? 如果有那应该怎么定义呢?

1. 函数指针数组的定义

我们说 函数指针数组的定义 应该遵循以下格式

int (*p[10])();

首先p和[]结合说明了它是一个数组
我们再将它拿掉可以发现 数组的内容就是
int (*)() 类型的函数指针。

2. 函数指针数组的用途:转移表(以计算器为例)

我们首先来写一段代码实现计算器的功能

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, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("***********************\n");
		printf("1:add               2:sub\n");
		printf("3:mul               4:div\n");
		printf("***********************\n");
		printf("请选择 ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数\n");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("%d\n", ret);
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

那么我们再使用函数指针来实现试试

最重要的代码是这两行

int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
ret = (*p[input])(x, y);

整体代码如下:

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, y;
	int input = 1;
	int ret = 0;
	int (*p[5])(int x, int y) = { 0,add,sub,mul,div };
	do
	{
		printf("***********************\n");
		printf("1:add               2:sub\n");
		printf("3:mul               4:div\n");
		printf("***********************\n");
		printf("请选择 ");
		scanf("%d", &input);
		if (input<=4 && input >=1)
		{
			printf("请输入操作数: \n");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("输入有误\n");
		}
	} while (input);
	return 0;
}

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

这个稍微了解下就好


void test(const char* str)
{
	printf("%s\n", str);
}

int main()
{
	void (*pfun)(const char*) = test;
	// 一个函数指针
	void (*pfunarr[5])(const char* str);
	//函数指针数组
	pfunarr[0] = test;
	//数组的第一个元素是test
	void (*(*ppfunarr)[5])(const char*) = &pfunarr;
	//指向函数指针数组的pfunarr的指针ppfunarr
	return 0;
}

以上就是本篇博客的全部内容啦 由于博主才疏学浅 所以难免会出现纰漏 希望大佬们看到错误之后能够

不吝赐教 在评论区或者私信指正 博主一定及时修正

那么大家下期再见咯

相关文章:

  • python使用PIL模块加载图像、通过resize函数改变图像的大小、使用save函数保存处理过的图像
  • Python点云显示:open3d快速上手
  • vue转electron项目以及使用fs报错:Module not found: Error: Can‘t resolve ‘fs‘ in解决办法
  • 【MATLAB教程案例14】基于ACO蚁群优化算法的函数极值计算matlab仿真及其他应用
  • 优化算法 - Adam算法
  • Open3D (C++) 点云变换
  • 黑白照片修复彩色软件免费有哪些?分享这三个实用的软件给你
  • CSS基础入门手册
  • python-- for循环的基础语法
  • _Linux进程控制
  • vue3.0--1.vue3.0环境集成、setup、ref函数、reactive函数、计算属性(computed)
  • 基于Opencv5.x(C++)流媒体视频流实现网页浏览器人脸检测
  • 网络安全——XSS跨站脚本攻击
  • AT24C02存储与读取数据
  • Linux高级编程--gdb调试
  • IE9 : DOM Exception: INVALID_CHARACTER_ERR (5)
  • Angularjs之国际化
  • java2019面试题北京
  • JAVA多线程机制解析-volatilesynchronized
  • leetcode-27. Remove Element
  • Terraform入门 - 1. 安装Terraform
  • XML已死 ?
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 如何优雅地使用 Sublime Text
  • 入口文件开始,分析Vue源码实现
  • 译有关态射的一切
  • ​520就是要宠粉,你的心头书我买单
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • ​学习一下,什么是预包装食品?​
  • #HarmonyOS:Web组件的使用
  • (4)(4.6) Triducer
  • (poj1.2.1)1970(筛选法模拟)
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (二)WCF的Binding模型
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (每日持续更新)jdk api之StringBufferInputStream基础、应用、实战
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (三)Honghu Cloud云架构一定时调度平台
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (十八)三元表达式和列表解析
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (转)拼包函数及网络封包的异常处理(含代码)
  • *ST京蓝入股力合节能 着力绿色智慧城市服务
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET NPOI导出Excel详解
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地中转一个自定义的弱事件(可让任意 CLR 事件成为弱事件)
  • :“Failed to access IIS metabase”解决方法
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • [Android Pro] Notification的使用
  • [ccc3.0][数字钥匙] UWB配置和使用(二)
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包
  • [iOS开发]iOS中TabBar中间按钮凸起的实现
  • [JavaScript] JavaScript事件注册,事件委托,冒泡,捕获,事件流
  • [JavaScript]_[初级]_[关于forin或for...in循环语句的用法]