C语言:指针(二)

news2024/9/22 21:31:36

目录

  • 1.数组名的理解
  • 2.使用指针访问数组
  • 3.一维数组传参的本质
  • 4.二级指针
  • 5.指针数组
  • 6.字符指针变量
  • 7.数组指针变量
  • 8.二维数组传参的本质
  • 9.函数指针变量
  • 10.函数指针数组
  • 11.回调函数
  • 12.qsort函数
  • 13.使用回调函数模拟实现qsort函数

1.数组名的理解

int main() {
	int arr[] = { 1,2,3 };
	printf("%p\n", &arr[0]);
	printf("%p\n", arr);

	return 0;
}

在这里插入图片描述
从结果可以看出,&arr[0] == arr,这是用为数组名就是地址,而且是首元素的地址
但是,arr作为数组名在两种情况下不表示首元素的地址:

  • sizeof(数组名),sizeof中单独存放数组名表示求整个数组的大小,单位是字节
  • &arr,&arr是&后面直接加上一个数组名,它表示整个数组的地址。(&arr在数值上可能与&arr[0]相同,但是本质上是不一样的,即&arr[0] == arr != &arr )
    除此之外,数组名都表示首元素的地址。
    在这里插入图片描述
    其实arr与&arr的区别不在于直接打印的数值上,而在于运算上,
int main() {
	int arr[] = { 1,2,3 };
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);

	return 0;
}

在这里插入图片描述
可以看出,arr和&arr[0]加一的结果都是跳过4个字节,而&arr则跳过12个字节(E8-F4 =(15 * 16 ^ 1 + 4 * 16 ^ 0)- ( 14*16^1 + 8 *16 ^0) =12).
&arr表示整个数组,加一表示跳过整个数组,数组一共3个int类型的数据,所以一次跳过12个字节。

2.使用指针访问数组

数组可以使用下标引用操作符来访问,比如:

int ret = arr[9];

既然数组名相当于地址,那么还可以这样访问:

int* p = &arr[9];
int ret = *p;

3.一维数组传参的本质

以前将数组作为参数传递给函数时需要将数组的数据个数一起传递给函数,那么,不传递数据个数可以吗?

void test(int arr[]) {
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz2);
}
int main() {
	int arr[] = {1,2,3,4,5};
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

在这里插入图片描述
从结果来看是不行的,
其实,将数组作为参数是将数组的首元素的地址作为参数传递给函数
所以,在函数内部使用sizeof(arr)实际上计算的是一个地址的大小。正因为参数部分的本质是指针,所以不能不传数据个数

4.二级指针

指针变量也是变量,是变量就有地址,所以二级指针是用来存放一级指针变量的地址的
在这里插入图片描述
画图说明:
在这里插入图片描述
对于二级指针的运算:

  • *ppa通过对ppa中的地址解引用,找到的是pa,*ppa访问的就是p
  • **ppa先对ppa解引用得到pa然后对pa解引用得到a

5.指针数组

存放整型的数组叫做整型数组
存放字符的数组叫做字符数组
那么存放指针的数组就叫做指针数组

在这里插入图片描述

6.字符指针变量

在指针的类型中有一种指针类型叫做字符指针char*
在这里插入图片描述
还有一种使用方法:

int main() {
	const char* p = "abcdef";
	printf("%s\n", p);

	return 0;
}

这种表示方法相当于将字符串看作为一个字符数组,指针变量p存放的不是字符串而是首字符’a‘的地址。
如果两个指针指向同一个字符串,那么不会开辟两个空间来存放,也就是说两个指针指向的是同一个内存

7.数组指针变量

存放整型数据地址的指针叫做整型指针
存放字符型数据的指针叫做字符指针
那么存放数组的地址的指针叫做数组指针

指针数组是数组,而数组指针是变量

int (*P)[5];

p先于*结合说明p是一个指针,再于【】和 int结合说明他是一个数组指针,数组元素类型是int。

数组自指针的初始化:

int main() {
	int arr[10] = { 0 };
	int(*p)[10] = &arr;

	return 0;
}

在这里插入图片描述
&arr == p

8.二维数组传参的本质

当我们将二位数组作为参数传递给函数时,其实传递的是第一行的地址(不是第一个元素的地址)

void test(int(*p)[3], int r, int c) {
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main() {
	int arr[2][3] = { {1,2,3},{2,3,4} };
	test(arr, 2, 3);

	return 0;
}

因为传递的是第一行元素的地址,那么该地址就该用函数指针来接收。
在这里插入图片描述

9.函数指针变量

函数也有地址,那么该地址就可以使用指针变量来接受,那么该指针变量就是函数指针变量。

int (*p)(int, int);

在这里插入图片描述
可以看出函数名就是函数的地址,也可以使用&函数名来获得函数的地址。
要将函数的地址存放起来,就可以使用函数指针变量
在这里插入图片描述
可以使用函数指针变量来调用函数

void test() {
	printf("haha\n");
}
int main() {
	int(*p)() = test;
	p();

	return 0;
}

在这里插入图片描述

10.函数指针数组

函数指针是用来存放函数的地址的,那么有很多函数,要将它们的地址存放在一起,那么就可以使用函数指针数组。

int (*p[10])(int, int);

p先于【】结合说明是一个数组,那么数组的内容就是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;
}
void Menu() {
	printf("**************************\n");
	printf("*****1.加法    2.减法*****\n");
	printf("*****3.乘法    4.除法*****\n");
	printf("*****0.退出          *****\n");
	printf("**************************\n");
}
int main() {
	int n = 0;
	int num1 = 0, num2 = 0;
	int(*p[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do {
		Menu();
		printf("请输入要选择的功能:>");
		scanf("%d", &n);
		if (n >= 1 && n <= 4) {
			printf("\n请输入两个数用于计算:>");
			scanf("%d%d", &num1, &num2);
			printf("\n计算结果:%d\n", (*p[n])(num1, num2));
		}
		else if (n == 0) {
			printf("已退出\n");
			break;
		}
		else {
			printf("选择错误,重新选择:>");
		}
	} while (n);

	return 0;
}

这个代码实现了一个简单的计算器。
int( * p[5])(int, int) = { 0,Add,Sub,Mul,Div };
这段代码就是将四个函数的地址放在一个函数指针数组里面
( * p[n])(num1, num2);
这段代码函数指针数组来调用函数n表示想调用哪个函数,num1和num2就是传递给函数的参数

11.回调函数

回调函数就是一个通过函数指针调用的函数
如果将函数的指针作为一个参数传递给另一个函数,但这个指针被用来调用其所指的函数时,被调用的函数就是回调函数。

void test2() {
	printf("hahaha\n");
}
void test1(void (*test2)()) {
	printf("haha\n");
	test2();
}
int main() {

	test1(test2);

	return 0;
}

在这里插入图片描述
在main函数中,将test2的地址传递给test1,然后test1用函数指针变量来接收,然后调用test2函数,那么test2函数就是回调函数。

12.qsort函数

qsort函数的功能是将任意类型的数据排列,底层原理是快速排序。

#include<stdlib.h>

void com_arr(const void* p1, const void* p2) {
	return *(int*)p1 - *(int*)p2;
}

void print(int arr[], int sz) {
	for (int i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main() {
	int arr[] = { 5,2,1,7,3,4,6,14,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	
	print(arr, sz);
	qsort(arr, sz, 4, com_arr);
	print(arr, sz);

	return 0;
}

在这里插入图片描述
qsort函数需要4个参数,第一个参数是数组首元素的地址,第二个参数是数组中元素的个数,第三个参数是数组中数据类型的大小,第四个参数是一个函数,作用是实现排序这种数据的方法。
在上述代码中排序整型数组中的数据,那么第四个参数就可以让两个整数相减,返回负数表示第一个元素小于第二个元素,反之大于。

13.使用回调函数模拟实现qsort函数

我们可以采用冒泡排序的方式来实现qsort函数

int Method(const void* p1, const void* p2) {//用以判断两个数时候该交换
	return (*(int*)p1 - *(int*)p2);
}

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

	}
}
void Maopao(void* arr, int sz, size_t num, int (*method)(void*, void*)) {
	for (int i = 0; i < sz - 1; i++) {
		for (int j = 0; j < sz - 1 - i; j++) {
			if (method((char*)arr + j * num, (char*)arr + (j+1) * num) > 0){
				Swap((char*)arr + j * num, (char*)arr + (j+1) * num, num);
			}
		}
	}
}
int main() {
	int arr[] = { 4,2,6,1,7,5,9,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}
	printf("\n");

	Maopao(arr, sz, sizeof(arr[0]), Method);

	for (int i = 0; i < sz; i++) {
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

/考研势在必行/

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

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

相关文章

英伟达:通用人工智能最快在五年内通过人类测试能力 | 百能云芯

英伟达&#xff08;NVIDIA&#xff09;的CEO黄仁勋在斯坦福经济政策研究所峰会上发表讲话&#xff0c;表示他预计通用人工智能&#xff08;AGI&#xff09;最快将在五年内问世。通用人工智能是指能够像人类一样学习、推理、解决复杂问题并独立做出决策的人工智能。这一表态引起…

从“茅五泸”到“非茅即五”,泸州老窖是怎么把自己跌出前三的?

文&#xff5c;琥珀食酒社 作者 | 宏一 春节的高端局酒桌上&#xff0c;“非茅即五”是很多人都会做的选择。前者是酱香型白酒的天花板&#xff0c;但后者却是浓香型白酒的“后来者”。 浓香型白酒的“鼻祖”&#xff0c;实际是坐拥国宝级窖池的泸州老窖&#xff0c;其上市时…

开源项目:智能化图像分类技术在新能源发电监控中的应用与实践

一、引言 在当今世界&#xff0c;能源的转型和升级是推动社会可持续发展的关键因素。随着技术的进步&#xff0c;新能源发电逐渐成为能源结构调整的重要力量。在众多发电方式中&#xff0c;新能源发电技术如风力、太阳能等因其清洁、可再生的特性而备受青睐。然而&#xff0c;…

vue入门相关内容

0.vue项目创建 01.vscode创建vue项目以及常见问题汇总 02.项目结构解读 03.启动项目直接访问自定义功能页面非APP.vue 1.事件修饰符 1.1事件修饰符stop 1.2事件修饰符capture 1.3事件修饰符self 1.4事件修…

Vue3中使用ffmpeg.wasm进行转码

一、安装方法 1.1 使用yarn进行安装 yarn add ffmpeg/ffmpeg ffmpeg/core1.2 安装版本 注意安装版本需在0.12.0以上版本才可以使用下面代码&#xff08;目前更新到0.12.10&#xff09;&#xff0c;之前的版本代码使用方法有所不同&#xff08;0.12.10之后的版本也可能会有变动…

【AIGC】如何提高Prompt准确度

前言 随着人工智能的迅猛进展&#xff0c;AIGC&#xff08;通用人工智能聊天工具&#xff09;已成为多个行业中不可或缺的自然语言处理技术。Prompt作为AIGC系统的一项关键功能&#xff0c;在工具的有效运作中发挥了举足轻重的作用。本篇文章将深入探讨Prompt与AIGC之间的紧密…

2024智能遥控器行业市场规模及技术水平分析

智能遥控器&#xff0c;主要是由集成电路板和用来生产不同讯息的按钮所组成&#xff0c;内装有一个中央处理器芯片&#xff0c;芯片在制造时就将设备各种菜单码值信息输入其中&#xff0c;遥控发射器只要发出与之对应的密码就可以实现对设备的控制。无线遥控技术原理就是发射机…

内存飙高问题如何排查?

目录 1、查看日志 2、查看GC情况 3、分析堆内存对象占用情况 4、分析堆内存快照文件 内存飙高如果发生在java进程上&#xff0c;一般情况是因为创建了大量对象导致&#xff0c;持续飙高说明垃圾回收跟不上对象创建的速度&#xff0c;或者内存泄漏导致对象无法被回收&#x…

案例介绍:汽车售后服务网络构建与信息抽取技术应用(开源)

一、引言 在当今竞争激烈的汽车行业中&#xff0c;售后服务的质量已成为品牌成功的关键因素之一。作为一位经验丰富的项目经理&#xff0c;我曾参与构建一个全面的汽车售后服务网络&#xff0c;旨在为客户提供无缝的维修、保养和配件更换服务。这个项目的核心目标是通过高效的…

【CFD小工坊】尝试完成一个简单的溃坝流算例(1)

【CFD小工坊】尝试完成一个简单的溃坝流算例&#xff08;1&#xff09; 前言算例简介网格生成数据的读入与输出模型参数的读入网格数据及结果数据的输出 前言 我们从一个简单的算例开始&#xff0c;从实际建模过程中学习和做代码。我选择的算例是一个矩形区域内的溃坝流&#…

ES入门六:Suggesters Api实践

都是负担在很多app上&#xff0c;当我们输入某些内容时候&#xff0c;它会立即做一些补全操作&#xff0c;如果我想实现上述的需求&#xff0c;我们就可以使用ES提供的Suggesters Api。那Suggesters是如何做到的那&#xff1f;简单来说&#xff0c;Suggesters会将输入的文本拆分…

每日一练:LeeCode-707. 设计链表 【链表+虚拟头结点+设计】

每日一练&#xff1a;LeeCode-707. 设计链表 【链表虚拟头结点设计】 思路设置虚拟头节点 本文是力扣 每日一练&#xff1a;LeeCode-707. 设计链表 【链表虚拟头结点设计】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode-70…

Rabbitmq消息丢失-消费者消息丢失(二)

说明&#xff1a;消费端在处理消息的过程中出现异常&#xff0c;例如&#xff1a;业务逻辑异常&#xff0c;或者消费者被停机&#xff0c;或者网络断开连接等&#xff0c;以上等情况使消息没有得到正确恰当的处理&#xff0c;也会使消息丢失。 分析&#xff1a;分析就是说明中…

中科数安|防止电脑文件资料外泄

#防止电脑文件资料泄漏# 中科数安提供了一系列解决方案来防止电脑文件资料外泄。 www.weaem.com 这些解决方案包括以下几个方面&#xff1a; 访问控制&#xff1a;实施严格的文件访问控制&#xff0c;确保只有授权的人员能够访问和编辑核心文件。使用身份验证和权限管理系统&a…

Android APK包反编译为java文件教程

方法 流程&#xff1a; test.apk -> smali文件 -> dex文件 -> jar文件 ->java 文件 将APK包解压为 smail文件 下载 apktool工具 apktool.jar 将 test.apk 和 apktool.jar放同一目录下&#xff0c;并执行以下命令 java -jar apktool.jar d -f xxx.apk -o xxx(解…

30、类和接口

文章目录 接口概念接口和类之间有何关系&#xff1f; 可以使用接口来约束类接口继承接口接口还可以继承类接口为什么可以继承类内层原因&#xff1a;接口为什么可以继承类 用得出的结论解释最初的demo接口继承类的一些限制 接口概念 接口&#xff08;Interfaces&#xff09;可…

SAP PP学习笔记 - 豆知识07 - 如何查看BOM一览

SAP标准提供了CS03&#xff0c;只能查询单个的BOM&#xff0c;如果想查看一览&#xff0c;只能自己写SQVI 查询。 有其他高招的童鞋&#xff0c;请赐教啊。 1&#xff0c;SQVI 工具 SAP MM学习笔记18- SQVI 工具_sap sqvi-CSDN博客 输入查询名&#xff0c;然后点击 登录 2&a…

C++学习笔记:set和map

set和map set什么是setset的使用 关联式容器键值对 map什么是mapmap的使用map的插入方式常用功能map[] 的灵活使用 set 什么是set set是STL中一个底层为二叉搜索树来实现的容器 若要使用set需要包含头文件 #include<set>set中的元素具有唯一性(因此可以用set去重)若用…

Linux高级编程:进程间的通信(二)、IPC

回顾 共7种方式&#xff1a; 古老的进程间通信方式&#xff1a; 管道&#xff1a; 无名管道 有名管道 信号 系统V IPC进程对象 共享内存 消息队列 信号量集 socket通信 //网络 ------------------------- 无名管道 pipe&#xff08;&#xff09; 特点&#xff1a; 用于…

CSS3笔记

1.相同优先级的样式以写在后面的为主。 2.交集选择器&#xff0c;并且 条件挨在一起 p.rich{...} /*p元素class有rich的元素*/ 3.并集选择器&#xff0c;或者 逗号隔开 .class1,class2{...}/*满足其中一个类名都会使用该样式*/ 4.后代选择器 空格 隔开 所有符合的包括孙子及…