【C语言】指针(三)

news2024/11/24 8:52:39

目录

一、字符指针

1.1 ❥ 使用场景

1.2 ❥ 有关字符串笔试题

二、数组指针

2.1 ❥ 数组指针变量

2.2 ❥ 数组指针类型

2.3 ❥ 数组指针的初始化

三、数组指针的使用

3.1 ❥ 二维数组和数组名的理解

3.2 ❥ 二维数组传参

四、函数指针

4.1 ❥ 函数的地址

4.2 ❥ 函数指针变量

4.3 ❥ 函数指针变量的使用

五、函数指针数组

六、转移表


一、字符指针

字符指针:指向字符的指针 

1.1 ❥ 使用场景

【使用场景一】

#include <stdio.h>

int main()
{
	char c = 'w';
	char* pc = &c;
	*pc = 'x';
	printf("%c\n", *pc);

	return 0;
}

【使用场景二】

#include <stdio.h>

int main()
{
	char* p = "abcdef";//这里把首字符a的地址赋给了变量p
	printf("%s\n", p);

	return 0;
}

注意

这里printf中%s的格式逻辑是:从给定的地址处开始,逐个向后输出字符,直到遇见结束标记\0为止。解引用的操作符在printf函数内部造成。

如果由用户解引用,那printf函数将只能拿到单个字符,反而无法实现功能。

易错点1:

场景二中如果修改*p的值,代码就会报错。报错类型如下:

原因:

"abcdef"是个常量字符串。

常量字符串的意思是:这个字符串本身是不能被更改的。

而这个*p没有被限制,它其实是可以去改变后面的字符串的,所以char* p="abcdef"; 报警告是正常的。

防止方法:在char*p 前加const

易错点2:

不能解引用p,解引用打印的是一个字符,一个字符不能用%s打印。

char* p = "abcdef";
printf("%s", *p);//error

以下是将字符串放在数组里面:

char arr[] = "abcdef";

而数组的内容是可变的。

1.2 ❥ 有关字符串笔试题

#include <stdio.h>

int main()
{
	char arr1[] = "hello";
	char arr2[] = "hello";

	const char* p1 = "hello";
	const char* p2 = "hello";

	if (arr1 == arr2)
		printf("arr1=arr2\n");
	else
		printf("arr1!=arr2\n");

	if (p1 == p2)
		printf("p1=p2\n");
	else
		printf("p1!=p2\n");
	
	return 0;
}

运行结果如下:

原因如下:

arr1!=arr2

  1. 我们知道:arr1和arr2是数组首元素的地址,这两个数组是两块独立的内存空间,它们只是存储的内容相同,都是hello字符串。

p1=p2

  1. 这里的p1和p2指向的是同一个常量字符串。c/c++会把常量字符串存储到一个单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存(代码段中)。
  2. arr1,arr2,p1,p2都是放在栈区,指向的hello(常量字符串)是放在代码段。

二、数组指针

整型指针:存放整型变量的地址,能够指向整型数据的指针

浮点型指针:存放浮点型变量的地址,能够指向浮点型数据的指针

数组指针:存放数组的地址,指向数组的指针

2.1 ❥ 数组指针变量

看下面两行代码,p1,p2分别是什么?

int* p1[10];
int(*p2)[10];
  • p1是指针数组

p1是数组名,该数组里存放了10个元素,每个元素是int*类型

  • p2是数组指针

因为p2先和*结合,说明p是一个指针变量,然后指向一个大小为10个整型的数组,所以p是一个指针,指向一个数组,所以叫数组指针。

注意:

  1. [ ]的优先级要高于*号,所以必须加上()来保证p先和*结合。
  2. int(*p2)[10];里面的*不是解引用的意思,这颗星就代表是指针,只有前面不加类型的时候才是解引用。

2.2 ❥ 数组指针类型

去掉指针变量名就是指针(变量)的类型。

看如下代码:

它们跳过的字节不同就是因为他们的类型不同导致。

注意:

  1. 一个指针是否是野指针取决于你是否用它。这个指针虽然指向这里,但是没有产生坏的结果。只要不使用它就没关系。
  2. 虽然它指向的空间不属于“我”,但是它并不危险。

2.3 ❥ 数组指针的初始化

数组指针存放的是数组的地址。

所以初始化的时候要给整个数组的地址。代码如下:

注意:

[ ]里的元素个数不能省略,不然编译器会自动认为是数组元素个数为0

三、数组指针的使用

3.1 ❥ 二维数组和数组名的理解

首先我们来理解一下二维数组及其数组名:

在c语言中,只有一维数组(N维数组的元素是数组),数组名作为指针时永远指向第一个元素。

如:

  • 数组a[3];  *a=a[0]
  • 数组a[3][4];  *a=a[0]   只不过这时候a[0]又是一个数组。

这时候的a[0]又是指向它自己元素的第一个元素,又有 *a[0]=a[0][0]

  • 这种方式可以推广到N维数组,所有数组直接对数组名取地址(如:&a),得到的指针指向该数组,而不是指向第一个元素。注意这点区别。

举个例子:  int board[3][4];

board:一维数组的地址。

二维数组的数组名,数组名就是首元素地址。我们知道,可以把一维数组看作二维数组的元素。所以,board就是一维数组的地址。

&board:取出的是整个二维数组的地址。

board[0]:第一行第一个元素的地址。

解引用,相当于拿到第一行数组的数组名,也就是首元素地址,即第一行第一个元素的地址。

board[0]=*board=&board[0][0]

&board[0]:第一行的地址。

board=&board[0]

3.2 ❥ 二维数组传参

清楚了上面的概念之后,我们来看下面一段代码:

之前二维数组传参时,形参部分用的是数组接收。

#include <stdio.h>

void test(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[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} };
	test(arr, 3, 5);
	return 0;
}

发现:实参arr是数组名,通过刚才的分析,直到数组名是首元素地址,首元素地址就是第一行的地址,也就是一维数组的地址,那么它的类型就是数组指针类型。

那么实参就可以写成数组指针的形式,代码如下:

#include <stdio.h>

void test(int(*p)[5], int r, int c)//数组指针
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + 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} };
	test(arr, 3, 5);
	return 0;
}

问题:为什么*(p+i) 跳过的是一行数组?

回答:

数组的类型决定了它+1跳过几个字节。

p的类型是:int(*)[5];

p是指向一个整型数组的,数组5个元素 int [5]

p+1 :跳过一个5个int元素的数组

四、函数指针

函数指针:存放函数的地址,指向函数的指针

4.1 ❥ 函数的地址

函数是否有地址呢?我们来测试一下:

由测试结果可知:函数存在地址,取&函数名函数名拿到的都是函数的地址。

4.2 ❥ 函数指针变量

我们通过函数指针来存储函数的地址。

int (*pf) (int x, int y) = &Add;
int (*pf) (int , int ) = Add;//xy可以省略,只写类型
  1. 地址要存起来,放到(指针)变量里去。
  2. pf是变量名,*pf说明是指针,指向的是函数,所以加上括号()。
  3. 函数的参数是int (参数名写不写无所谓,只要类型交代清楚即可),函数的返回值类型也是int。
int (*pf) (int x, int y)
|      |    ------------
|      |        |
|      |        pf指向函数的参数类型和个数的交代
|       函数指针变量名
pf指向函数的返回类型

int (*) (int x, int y) //pf函数指针变量的类型

4.3 ❥ 函数指针变量的使用

通过函数指针调用指针指向的函数。

代码如下:

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int, int) = Add;
	
	printf("%d\n", (*pf)(2, 3));//输出结果为5
	
	printf("%d\n", pf(2, 3));//输出结果为5
	
	return 0;
}

把函数的地址存到pf里,通过解引用pf找到函数,找到这个函数要调用这个函数,调用函数需要传参,所以();(传参),传2,3。这样的话它会把2和3相加,得到5。

问题一:为什么*pf必须带上括号()呢?

回答:因为假如不带上括号,调用返回5,就会对5进行解引用。

问题二:为什么能写成pf(2, 3)这种形式?

回答:在C语言里,pf前的*其实是个摆设,可以不写,也可以写多个。

这是个技术细节问题,不涉及到语法原则,从不同的思考角度出发,观点会略有不同,但不影响C语言实践,初学者也不必过多纠结。

应用:通过函数指针的方式进行调用

#include <stdio.h>

int Add(x, y)
{
	return x + y;

}
void cale(int(*pf)(int,int))
{
	int a = 3;
	int b = 5;
	int ret = pf(a, b);
	printf("%d\n", ret);
}
int main()
{
	cale(Add);
	return 0;
}

这里的cale没有直接调用Add函数,而是通过函数指针的方式进行调用。

五、函数指针数组

函数指针数组:把一个函数的地址存放到一个数组中。

是个函数指针类型的数组。

去掉 函数名+[ ] 就是该数组的类型。

int (*parr[3])( );

解释:parr先和[ ]结合,说明parr是数组,数组的内容是int(*)()类型的函数指针。

当对函数指针数组进行初始化的时候,后面初始化的可以省略掉数组的大小,它会根据后面初始化的内容来确定数组的大小。

例如:

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

六、转移表

使用了函数指针数组,避免大篇幅地修改内容;也可实现跳转的功能。

所以函数指针数组也叫:转移表。

计算机的一般实现

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

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 x, y;
	int input = 1;
	int ret = 0;

	do
	{
		printf("————----------------------\n");
		printf("1.add        2.sub        \n");
		printf("3.mul        4.div        \n");
		printf("0.exit                    \n");
		printf("————----------------------\n");
		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		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;

		case 0:
			printf("退出程序\n");
			break;

		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;

}

使用函数指针数组的实现

#include <stdio.h>

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 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("0.exit                    \n");
		printf("————----------------------\n");
		printf("请选择:");
		scanf("%d", &input);

		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret = %d\n", ret);
		}

		else if (input == 0)
		{
			printf("退出计算器\n");
		}

		else
		{
			printf("输入有误\n");
		}

	} while (input);

		return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1690892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D瓦片地图组件上线|提供DEM数据接入,全方位呈现三维地图地形!

在用户调研中&#xff0c;我们了解到很多用户自身的可视化项目&#xff0c;需要在垂直空间上表现一些业务&#xff0c;例如&#xff1a;3D地形效果&#xff0c;数据底板建设等&#xff0c;而传统的地图效果不满足此用户需求。瓦片地图能够无限加载大地图&#xff0c;以更三维的…

【免费Web系列】大家好 ,今天是Web课程的第六天点赞收藏关注,持续更新作品 !

这是Web第一天的课程大家可以传送过去学习 http://t.csdnimg.cn/K547r 后端Web实战(IOCDI) 前言 Web开发的基础知识 &#xff0c;包括 Tomcat、Servlet、HTTP协议等&#xff0c;我们都已经学习完毕了&#xff0c;那接下来&#xff0c;我们就要进入Web开发的实战篇。在实战篇中…

正在直播:Microsoft Copilot Studio 新增支持Copilot代理、Copilot扩展等多项功能

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

如何在go语言中调用c语言代码

1.安装c语言编译器 要使用cgo&#xff0c;需要安装c语言编译器 gcc 2.检查CGO_ENABLED时候开启 使用以下命令查看&#xff1a; go env CGO_ENABLED 如果go env CGO_ENABLED被禁用(为0),需要将其设置为开启(为1) 3.编写c语言程序&#xff0c;并用go语言调用c语言程序 1&#xff…

机器学习之支持向量机SVM

支持向量机 概念 是supported vector machine&#xff08;支持向量机&#xff09;&#xff0c;即寻找一个超平面使样本分成两类&#xff0c;且间隔最大分类 分类 硬间隔 若样本线性可分&#xff0c;且所有样本分类正确情况下&#xff0c;寻找最大间隔&#xff0c;即硬间隔 若…

数据库缓存 buffer pool详解

什么是buffer pool buffer pool, 又称之缓存池, 是mysql中为了提升查询性能而引入的缓存, 如果每次查询和修改都去操作磁盘的话, 性能就会很差, 从而引入 Buffer Pool包含多个缓冲页&#xff08;默认大小通常为16KB&#xff09;&#xff0c;每个缓冲页都有对应的控制信息&#…

23种设计模式之一————外观模式详细介绍与讲解

外观模式详细讲解 一、概念二、 外观模式结构核心思想及解释模式的UML类图模式角色应用场景模式优点模式缺点 三、实例演示图示代码展示运行结果 一、概念 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个统一的接口&#xff0c…

自动驾驶---Tesla的自动驾驶技术进化史(PerceptionPlanning)

1 前言 笔者在专栏《自动驾驶Planning模块》中已经详细讲解了传统自动驾驶Planning模块的内容&#xff1a;包括行车的Behavior Planning和Motion Planning&#xff0c;以及低速记忆泊车的Planning&#xff08;最开始有15篇&#xff0c;目前逐渐更新到17篇&#xff09;。读者对整…

【C语言】文件的编译链接和预处理

文件的编译链接和预处理 程序的翻译环境和执行环境翻译环境预处理&#xff08;预编译&#xff09;过程编译过程汇编过程链接过程 运行环境 预处理详解预处理符号预处理指令#define#define定义标识符#define定义宏#define替换规则 #与###的使用##的使用 带有副作用的宏参数宏与函…

C++面向对象的第二大特性:继承

1.继承的介绍 首先容我先向大家举一个列子: 我这里定义了一个Person的类 class Person { protected:string name;int age;string address;}; 在这个基础上&#xff0c;我要定义一个关于Student , Worker 的类 由于Student Worker都具有Person类中的成员变量 &#xff0c…

C语言自定义类型:结构体

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 C语言自定义类型:结构体 收录于专栏【C语言学习】 本专栏旨在分享学习C语言学习的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 结…

EyeMock下载与使用教程

视频眼神修复直视镜头的AI具有极高的灵活性和适应性。它可以根据不同的拍摄环境和主播需求进行个性化设置&#xff0c;确保最佳的视觉呈现效果。在直播互动中&#xff0c;主播可能因为分神或疲劳而失去与观众的直视&#xff0c;这款工具能够迅速识别并修复这一问题&#xff0c;…

苹果M4性能分析:进步神速?还有多少空间?

2024年初&#xff0c;苹果推出了M4处理器&#xff0c;令人意外的是&#xff0c;它的发布距离M3发布仅仅过去了半年时间。更让人惊讶的是&#xff0c;M4首次亮相于iPad Pro。这一新处理器不仅仅是M3的简单升级版本&#xff0c;而是一次全面的架构优化。本文将详细分析M4处理器的…

网络工程师备考1——基础学习

认识设备 1 交换机 一、什么是交换机&#xff1f; 实现不同电脑之间数据的转发 换机是一种用于电(光)信号转发的网络设备。 它可以为接入交换机的任意两个网络节点提供独享的电信号通路。最常见的交换机是以太网交换机。交换机工作于OSI参考模型的第二层&#xff0c;即数据…

sw套合样条曲线

套合样条曲线,可以变成一条曲线,然后可以进行分段

sql select获取mysql所有数据库,指定数据库下的所有表名

介绍一下 MySQL 8.0 中默认安装的几个系统数据库/模式。 当我们安装 MySQL 8.0 并初始化数据库之后&#xff0c;默认会创建以下系统数据库&#xff1a; mysql&#xff0c;存储了 MySQL 服务器正常运行所需的各种信息。 information_schema&#xff0c;提供了访问数据库元数据…

黑马点评1——短信篇(基于session)

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

使用DataGrip连接跳板机后再连接远程服务器的mysql数据库

相比配置本地数据库就是多了一步SSH/SSL配置。 添加新的mysql连接&#xff0c;选择SSH/SSL&#xff0c;勾选Use SSH tunnel&#xff1a; 点击右边的…配置跳板机连接&#xff0c;输入账号密码&#xff0c;然后保存&#xff1a; 接着配置General&#xff0c;里面填上要连接的数…

【蓝桥杯选拔赛真题76】python找出元素 第十四届青少年组蓝桥杯python选拔赛真题 算法思维真题解析

目录 python找出元素 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python找出元素 第十四届蓝桥杯青少年组python比赛选拔赛真题 一、题目要…

谷歌浏览器安装devtools工具

在浏览器中输入极简插件&#xff0c;然后打开如下的网页&#xff0c;在搜素框中输入vue 出现下图 点击推荐下载 &#xff08;地址&#xff1a;https://chrome.zzzmh.cn/info/nhdogjmejiglipccpnnnanhbledajbpd&#xff09; 打开谷歌浏览器如图 选择“扩展程序” 点开之后&…