【C语言进阶】--- 指针详解 3.0

news2024/11/17 12:39:39

接下来进入指针的进阶部分,准备好大脑

补充:(重点)

数组名是数组首元素地址
数组首元素地址和数组地址,值相同,但本质不同,
区别在于二者的类型不相同
比如数组int arr[10];

  • 数组首元素地址的类型:首先这是一个地址所以要用指针接收,(*),然后是地址指向元素的类型为int,所以这个指针的类型是int*
  • 数组地址的类型:首先这是一个地址所以用指针接收,(*),这个地址表示的是整个数组,这个数组的类型为int [10],所以这个指针的类型是int(*)[10]
  • 额外:数组的类型怎么判断,先判断数组的大小,[10],再判断数组中存储数据的类型,int,所以数组的类型就是int [10]
  • arr是数组首元素地址,&arr是数组地址,那么*(&arr) == arr,给数组地址解引用可以得到数组首元素地址
  • arr[i]的意思是访问数组第i个元素的位置,也可以表示为*(arr+i),数组元素地址+i后解引用,而系统在实现arr[i]这个功能本质就是用*(arr+i),理解到这里,有没有感受到为什么说C语言研究的是底层呢?

一、字符指针

char arr[] = "abcd";
const char* p = "abcd";
因为"abcd"是字符串常量,且*p不能修改这个常量
所以,用const修饰char* p,保证*p不能修改指针变量p指向的值

特别地

  • 这里的指针变量p接收的是字符数组的首元素地址,也就是字符’a’的地址
  • 常量储存在代码区,是不能被修改的

二、.指针数组

字符数组 — 存放字符的数组
整形数组 — 存放整型的数组
指针数组 — 存放指针(地址)的数组

int a = 1;    int b = 2;    int c = 3;
int pa = &a;  int pb = &b;  int pc = &c;
int* arr[3] = {pa, pb, pc};
这里的数组arr存放的是指针变量

int* arr[3]可以这么理解:

  • arr[ 3 ] 代表这是一个大小为3的数组,int* 代表这个数组里存的数据类型为int*
  • 那么int* arr[ 3 ]的意思就是 叫做arr的数组里存放了3个类型为int*的指针变量

特别地:这个数组类型为:int* [ 3 ]

三、数组指针

1.指向一维数组的数组指针

  1. int * p[10]; 指针数组,数组里存放的是指针
    p与[ ]优先级更高,所以p[10]先是个数组,存放的是int* 类型的数据
  2. int (*p)[10]; 数组指针,指向数组的指针
    *p表示p是个指针变量,int [10]是数组类型,p是指向数组类型为int [10]的指针,所以指针变量p的类型为int (*) [10]
    • 这里举个例子方便你理解:
    • int * p; 有*这个符号时,表示这个p是个指针变量,这个指针变量存放数据的类型为int, 所以这个指针变量p的类型是int*

int arr[10];

类型
arr首元素地址int*
&arr[0]首元素地址int*
&arr整个数组的地址int (*) [10]

数组指针:指针变量指向的是整个数组的地址,而不是首元素地址
即使这两个地址在数值上是一样的,但指针对这两个地址进行+/-的操作时,结果是不同的,因为本质上指向数组地址和指向首元素地址的指针类型是有区别的,

  • 指向数组地址的指针类型为int (*) [10]
  • 指向首元素地址的指针类型为int*
  • 当对指向数组地址的指针进行+1,
    • 这时指针指向数组末尾的位置,指针移动的步长是整个数组的大小
  • 当对指向首元素地址的指针进行+1,这时指针指向的是数组中第二个元素的位置,是按照数组中元素所占大小决定步长,
    • 若数组元素为char型,指针+1的意思就是指针移动1个字节
    • 若为int型,指针移动4个字节

综上,这个表达式才是正确的 int (*p) [10] = &arr;
而不是 int (*p) [10] = arr;
p是个变量
—> *p表示p是个指针变量
—> (*p)[10]表示p指向的是个数组
—> int (*p)[10]表示的是指针变量p指向的数组,这个数组存放的是int型的数据
—> p的类型为int(*)[10]

2.指向二维数组的数组指针

首先我们要清楚
int arr[10];

  • arr是数组名,数组名是首元素地址
  • 二维数组的每一行可以理解为二维数组的一个元素
  • 每一行又是一个一维数组
  • 所以,二维数组其实就是存放一维数组的数组
  • 二维数组的数组名,也是数组名,数组名就是首元素的地址

将二维数组中的每一行看成一个元素,等同于,一行就是一个一维数组,二维数组的数组名表示的是首元素的地址,那就是第一个元素的地址,那第一个元素不就是第一行吗,也就是第一个一维数组,因此二维数组的首元素地址,就是第一个一维数组的地址(这个一维数组的地址代表的是整个一维数组的地址,不是一位数组的首元素地址,因此后序如果指针+1时,改变的步长是整个一维数组的大小)
在这里插入图片描述
arr
-------- 首元素地址
-------- 第一行地址
-------- 一维数组的地址(整个一维数组的地址,不是一维数组首元素的地址)

四、数组指针传参

实际应用,这里用二维数组传参举例子

常规的二维数组传参,用二维数组接收
void func1(int arr[3][5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//利用数组指针接收地址,这里接收的实际上是二维数组arr的首元素地址,也就是第一个一维数组的数组地址
//所以需要用一维数组类型的指针接收就行
//*p表示p是个指针,指针指向的是个一维数组数据,这个一维数组的类型为int [5]
void func2(int(*p)[5], int row, int col)
{
	//这里指针指向的是第一个元素的地址,这个元素实际是个一维数组,所以指针指向的是第一个一维数组的数组地址
	for (int i = 0; i < row; i++)
	{
		//要访问一个数组的元素,就要遍历每个位置
		for (int j = 0; j < col; j++)
		{
			//这里的指针p是一维数组的数组地址,
			//p+i是决定p指向第几个一维数组,
			//*(p+i)表示将一维数组地址转化为一维数组的首元素地址,有了首元素的值,才可以用指针访问一维数组中的元素
			//(*(p+i)+j)表示指针指向该一维数组中下标为j的元素 
			//*((*(p + i)) + j)表示访问该元素的内容
			printf("%d ", *((*(p + i)) + j));
			//(*(p + i))相当于一维数组名,那么也可以这样访问数组中的元素(*(p + i))[j]
			//arr是数组名,arr[n]用来访问数组中的元素,arr[n]的底层实现是:*(arr+n)
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = { 1,2,3,4,5,
					  2,3,4,5,6,
				      3,4,5,6,7 };
	//func1(arr, 3, 5);
	func2(arr, 3, 5);

	return 0;
}

五、函数指针

函数指针:指向函数地址的指针

  • 函数名是函数的地址
  • &函数名也是函数的地址

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

int(*p)(int,int) = &Add;
int ---------------> 函数返回类型
(int,int) ---------> 函数参数
&Add --------------> 函数地址
指针变量p的类型 ----> int(*)(int,int)

调用函数:
int ret = Add(3,4);
int ret1 = p(4,5);
int ret2 = (*p)(5,6);
用指针p调用函数时,*可写可不写

六、函数指针数组

存放函数指针的数组

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(* pArr[4])(int,int) = {Add,Sub,Mul,Div};
首先pArr是个数组,数组里存放的数据类型为int(*)(int,int)
这个数组的类型为:int(*)[4](int,int)

下面这张图根据我可以尝试去运行,然后去解释每个地址
在这里插入图片描述
这是完整代码

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(* parr[4])(int,int) = {add,sub,mul,div};
	//首先parr是个数组,数组里存放的数据类型为int(*)(int,int)
	printf("%x\n", parr[0]);
	printf("%x\n", add);
	
	printf("%d\n", sizeof(int(*)(int, int)));
	printf("%x\n", &parr[0]);
	printf("%x\n", (&parr)[0]);
	
	printf("%x\n", (&parr)[1]);
	printf("%x\n", (&parr) + 1);
	printf("%x\n", &(parr[3]) + 1);
	return 0;
}

七、指向函数指针数组的指针

int(*p)(int,int) ------------------ 函数指针
int(*pArr[ ])(int,int) ------------ 函数指针数组
int(*(*p)[ ])(int,int) = &pArr ------------- 函数指针数组的地址
p就是指向函数指针数组的指针
首先要有个指针变量区接收这个函数指针数组,*p,然后再看这个指针指向数据的类型,函数指针数组的类型是,int(*)[ ](int,int)

八、回调函数

通过函数指针调用的函数就是回调函数
如果你把一个函数的地址作为参数传递给给另一个函数,当这个地址被用来调用其所指向的函数时,我们就说这是回调函数
这里我们引入两个知识:

  1. void
  • ①void* 的指针,无具体类型的指针
  • ②void* 类型的指针可以接收任何类型的地址
  • ③这种类型的指针是不能直接用来解引用操作的
  • ④也不能直接进行指针运算的
int a = 10;
float f = 3.14f;
int* pa = &a; ------- 可以
char* pc = &a; ------ 不行 &a的类型是int*
void* pr = &a; ------ 可以②
pr = &f; ------------ 可以②
*pr; ---------------- 不行③
pr++; --------------- 不行④
  1. qosort库函数
void qsort( void* base,   //指向了需要排序的数组的第一个元素
				  size_t num,  //排序的元素个数
				  size_t size,  //一个元素的大小,单位是字节
				  int (*cmp)(const void*, const void*)  //函数指针类型,这个指针指向的函数,能比较base指向数组中的元素
				)
struct stu
{
	char name[20];
	int age;
};

int cmp(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}
qsort库函数默认为排序结果为升序

int main()
{
	struct stu arr[] = {{"猪八戒",30},{"孙悟空",20},{"沙僧",50}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]),cmp);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}
//利用冒泡排序实现qsort函数的功能
struct stu
{
	char name[20];
	int age;
};

int cmp(const void* p1, const void* p2)
{
	return ((struct stu*)p1)->age - ((struct stu*)p2)->age;
}

void Swap(char* p1, char* p2, int size)
{
	for (int i = 0; i < size; i++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}

void bubble_qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 1)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main()
{
	struct stu arr[] = { {"猪八戒",30},{"孙悟空",20},{"沙僧",50} };
	int num = sizeof(arr) / sizeof(arr[0]);
	int size = sizeof(struct stu);
	bubble_qsort(arr, num, size, cmp);
	for (int i = 0; i < 3; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}

	return 0;
}

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

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

相关文章

罗马仕、西圣、绿联充电宝哪个牌子好?热销充电宝实测对比!

在这个快节奏的时代&#xff0c;智能手机已成为我们日常生活中不可或缺的一部分&#xff0c;但电量焦虑也随之而来。无论是忙碌的工作日&#xff0c;还是休闲的周末出行&#xff0c;一款可靠、高效的充电宝成为了许多人的随身必备。在市场上众多充电宝品牌中&#xff0c;罗马仕…

题解web

1.[LitCTF 2023]Follow me and hack me 1&#xff09;进入题目环境&#xff0c;提示get传参&#xff0c;post传参 2&#xff09;看看源码&#xff0c;也没啥 3&#xff09;直接用hackbar&#xff0c;传入对应参数即可得到FLAG 3&#xff09;但是扫描出来它后端还有东西&#x…

java线程变量共享

在Java中&#xff0c;线程变量共享可以通过几种方式实现&#xff1a; 1.实例变量&#xff1a;如果一个实例变量被多个线程共享&#xff0c;你需要确保适当的同步&#xff0c;以避免竞态条件。你可以使用synchronized关键字或者Lock接口来保护共享变量。 2.静态变量&#xff1a;…

InternLM-XComposer2-4KHD开拓性的4K高清视觉-语言模型

大型视觉-语言模型&#xff08;LVLM&#xff09;在图像字幕和视觉问答&#xff08;VQA&#xff09;等任务中表现出色。然而&#xff0c;受限于分辨率&#xff0c;这些模型在处理包含细微视觉内容的图像时面临挑战。 分辨率的限制严重阻碍了模型处理含有丰富细节的图像的能力。…

高级文件操作

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python内置的os模块除了可以对目录进行操作&#xff0c;还可以对文件进行一些高级操作&#xff0c;具体函数如表4所示。 表4 os模块提供的与文件相…

大模型在信用卡行业的应用探索

2022年11月&#xff0c;OpenAI发布ChatGPT3.5&#xff0c;迅速引起各界广泛关注&#xff0c;引发了人工智能领域新一轮发展热潮。ChatGPT作为一款基于人工智能技术的大语言模型&#xff08;LLMs&#xff09;&#xff0c;在文本生成、对话理解、多领域知识覆盖等方面具有卓越表现…

【JVM】从编译后的指令集来再次理解++i和i++的执行顺序

JVM为什么要选用基于栈的指令集架构 与基于寄存器的指令集架构相比&#xff0c;基于栈的指令集架构不依赖于硬件&#xff0c;因此可移植性更好&#xff0c;跨平台性更好因为栈结构的特性&#xff0c;永远都是先处理栈顶的第一条指令&#xff0c;因此大部分指令都是零地址指令&…

走进三态股份,睿观与三态股份的预防商标侵权合作

三态股份是去年上市的大型跨境电商卖家&#xff08;深交所股票代码&#xff1a;301558&#xff09;&#xff0c;致力于通过最新的科技&#xff0c;将国内的优质供应链输送到全球各地。 三态股份每年上新的产品超十万级&#xff0c;可却遇到了侵权违规的巨大挑战&#xff1a;如…

探索k8s集群的配置资源(secret和configmap)

目录 ConfigMap ConfigMap&#xff08;主要是将配置目录或者文件挂载到k8s里面使用&#xff09; 与Secret类似&#xff0c;区别在于ConfigMap保存的是不需要加密配置的信息。&#xff08;例如&#xff1a;配置文件&#xff09; ConfigMap 功能在 Kubernetes1.2 版本中引入&…

谈AI 时代网站的未来趋势

以大语言模型为代表的AI 技术迅速发展&#xff0c;将会影响原有信息网络的方式。其中一个明显的趋势是通过chatGPT 对话代替搜索引擎和浏览器来获取信息。 互联网时代&#xff0c;主要是通过网站&#xff08;website&#xff09;提供信息。网站主要为人类阅读的方式构建的。主要…

鸿蒙轻内核M核源码分析系列十九 Musl LibC

LiteOS-M内核LibC实现有2种&#xff0c;可以根据需求进行二选一&#xff0c;分别是musl libC和newlibc。本文先学习下Musl LibC的实现代码。文中所涉及的源码&#xff0c;均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_m 获取。LiteOS-M内核提供了和内核相关的…

范闲获取到庆帝与神庙的往来信件,用AES进行破解

关注微信公众号 数据分析螺丝钉 免费领取价值万元的python/java/商业分析/数据结构与算法学习资料 在《庆余年2》中&#xff0c;范闲与庆帝和神庙之间的权谋斗争愈演愈烈。一次偶然的机会&#xff0c;范闲从庆帝的密室中获取到几封与神庙往来的密信。然而&#xff0c;这封信件…

jvm学习笔记(二) ----- 垃圾回收

GC 一、判定对象是否是垃圾1.引用计数法2.可达性分析算法 二、垃圾回收算法1.标记清除2.标记整理3. 复制4. 分代垃圾回收1.尝试在伊甸园分配2.大对象直接晋升至老年代3.多次存活的对象4.老年代连续空间不足&#xff0c;触发 Full GC 链接: jvm学习笔记(一) ----- JAVA 内存 链接…

20240607每日通信--------VUE3前端引入scoket-io,后端引入Netty-SocketIO,我成功了,希望一起交流沟通

无语 前置&#xff1a; VUE3 前端集成scoket-io socket.io-client Sringboot 3.0JDK17集成Netty-SocketIO Netty-SocketIO 失败原因一&#xff1a; 前期决定要写demo时候&#xff0c;单独了解了&#xff0c;后端引入Netty-SocketIO注意事项&#xff0c;详见我先头写的博客 前…

别让你的品牌失去声音,品牌策划如何成为你的王牌?

品牌策划可不仅仅是一个简单的概念&#xff0c;它是一门真正的艺术和科学。 它涉及到在确立品牌定位之后&#xff0c;进行一系列精心设计的传播和推广活动&#xff0c;从而塑造和管理品牌&#xff0c;让品牌价值达到最大化。 在这个竞争激烈的市场中&#xff0c;想要让你的品…

一篇文章带你搞懂C++引用(建议收藏)

引用 6.1 引用概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。 比如&#xff1a;李逵&#xff0c;在家称为"铁牛"&#xff0c;江湖上人称&quo…

30、matlab现代滤波:维纳滤波/LMS算法滤波/小波变换滤波

1、信号1和信号2的维纳滤波 实现代码 N 2000; %采样点数 Fs 2000; %采样频率 t 0:1 / Fs:1 - 1 / Fs; %时间序列 Signal1 sin(2*pi*20* t) sin(2*pi*40* t) sin(2*pi*60* t); Signal2[2*ones(1,50),zeros(1,50),-1*ones(1,100),zeros(1,50),-2*ones(1,50),zeros(1,50),1…

删除目录

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 删除目录可以通过使用os模块提供的rmdir()函数实现。通过rmdir()函数删除目录时&#xff0c;只有当要删除的目录为空时才起作用。rmdir()函数的基本语…

升级最新版openssh-9.7p1及openssl-1.1.1h详细步骤及常见问题总结

近期因为openssh相继被漏洞扫描工具扫出存在漏洞&#xff0c;所以考虑升级操作系统中的openssh和openssl为最新版本&#xff0c;来避免漏洞风险。期间的升级过程及遇到的疑难问题&#xff0c;特此记录下来&#xff0c;供有需要的人参考。 本次目标是升级 openssh 为 9.7p1 版本…

算法金 | 不愧是腾讯,问基础巨细节 。。。

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 最近&#xff0c;有读者参加了腾讯算法岗位的面试&#xff0c;面试着重考察了基础知识&#xff0c;并且提问非常详细。 特别是关于Ada…