【C语言】进阶——指针

news2024/11/25 12:33:08

 

目录

①(●'◡'●)前言

1.字符指针 

✌字符指针和数组笔试题 

2.指针数组 和数组指针

👊指针数组 

👊数组指针 

 👊&数组名和数组名

3.数组传参和指针传参 

👊一维数组传参

👊二维数组传参

 👊一级指针传参

👊二级指针传参

4.函数指针 

5.函数指针数组

👊函数指针数组应用 

6.函数指针数组的指针

7.回调函数 

👊qsort() 

冒泡排序通用版


 

①(●'◡'●)前言

在之前【C语言】入门——指针介绍了指针的概念

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2. 指针的大小是固定的4/8个字节(32位平台/64位平台)

3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4. 指针的运算。

这一篇介绍更深度的指针内容 

1.字符指针 

步长最短的字符型指针,字符指针就是用来存放字符变量(或字符串变量)的指针

当存储字符串变量时,会像数组一样只存入首字母的地址,在解引用时可以根据首地址依次往后访问并解引用,直到遇到结束标志 '\0'

由此看来指针貌似和数组有点相似。

int main()
{
	char s1 = 'a';
	char* p = &s1;
	char* p2 = "abcdef";
	printf("%c\n", *p);
	printf("%s\n", p2);
	return 0;
}

 

✌字符指针和数组笔试题 

//字符指针笔试题
int main()
{
	char arr1[] = { "Hello World" };
	char arr2[] = { "Hello World" };
	const char* str1 = "Hello World";
	const char* str2 = "Hello World";
	if (arr1 == arr2)
		printf("arr1 and arr2 are same\n");
	else
		printf("arr1 and arr2 are not same\n");
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");
	return 0;
}

arr1和arr2是两个独立的数组,自然地址不一样,独立空间,所以不相等;

str1和str2是因为指向同一块空间,因为两个相同的常量在内存中只会开辟一块空间; 

 

 

2.指针数组 和数组指针

指针数组是数组,数组内存放的是指针;

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

数组指针与指针数组容易混淆

int arr[5];

arr是一个数组,每个元素是int类型的 ,有5个元素

int* parr1[10];

parr1是一个数组,数组10个元素,每个元素的类型是int*

int (*parr2)[10];

parr2是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是int

int(* parr3[10])[5];

parr3是一个数组,数组有10个元素,每个元素的类型是:int(*)[5]

parr3是存放数组指针的数组 

👊指针数组 

//指针数组
int main()
{
	int a = 1, b = 2, c = 3;
	int* pa = &a;
	int* pb = &b;
	int* pc = &c;
	int* arr[3] = { pa,pb,pc };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("地址:%p\n", arr[i]);
		printf("值为:%d\n", *arr[i]);
	}
	return 0;
}

 

 定义三个变量,取各自他们的地址赋给不同的指针,指针为int*,再将指针放到数组内存放,

&arr的类型为:int* (*arr)[3];

arr的类型为int* arr;

👊数组指针 

//数组指针
#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    int (*p)[10] = &arr;//数组指针,存放整形数组arr的地址
    return 0;
} 

 👊&数组名和数组名

//&数组名与数组名
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int(*pa)[5] = &arr;
	printf("这是起始地址:%p %p\n", arr, pa);
	printf("这是+1后的地址:%p %p\n", arr + 1, pa + 1);
	return 0;
}

 

&arr+1,加一个步长,但是是整个数组的地址+1,所以是跳过整个数组大小;

数组5个元素,0x00fbfe58是首元素地址,58-5c-60-64-68-6c;

跳过了整个数组大小20个字节;

arr+1; 首元素地址+1,是跳过一个元素大小; 

58-5c;整型数组,一个整型4个字节,跳过一个元素即是4个字节;

3.数组传参和指针传参 

👊一维数组传参

//一维数组传参
void test1(int arr[])
{}//一维数组可以省略元素数,也可以写上
void test1(int*pa)
{}//用一级指针接收一维数组
void test2(int*arr2[10])
{}//形参用指针数组接收指针数组传参
void test2(int**ppa)
{}//指针数组本质上是二级指针
int main()
{
	int arr1[10] = { 0 };
	int* arr2[10] = { 0 };
	test1(arr1);
	test2(arr2);
	return 0;
}

👊二维数组传参

三种方式:

完整传参,行和列都不省略;

省略行;

数组指针接受

//二维数组传参
void test(int arr[3][5])
{}//完整接收
void test(int arr[][5])
{}//省略行,是可以的
void test(int(*pa)[5])
{}//用我们前面的数组指针接收
void test(int** pa)
{}//是错误的
int main()
{
	int arr[3][3] = { 0 };
	test(arr);
	return 0;
}

 👊一级指针传参

//一级指针传参
void test1(int* pa, int sz)
{}//传数组名,用指针接收
void test2(int* pa, int sz)
{}//传指针,用指针接收
int main()
{
	int arr[3] = { 1,2,3 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* pa = arr;
	test1(arr, sz);
	test2(pa, sz);
	return 0;
}

👊二级指针传参

//二级指针传参
void test1(int**pa)
{}//接收的是二级指针
void test2(int**pa)
{}//接收的一级指针的地址
int main()
{
	int a = 10;
	int* pa = &a;//一级指针
	int** ppa = &pa;//二级指针
	test1(ppa);//传二级指针
	test2(&pa);//将一级指针的地址取出来
	return 0;
}

4.函数指针 

函数指针是指向函数的指针;函数名就是地址

函数指针由三部分组成:

类型、指针、形参,

类型和形参可以为空,当想要调用函数时,只需要通过指针,并传入参数,就能正常使用函数。 

int Add(int x,int y)
{
	return x + y;
}
int main()
{
	int (*p)(int, int) = &Add;
	printf("%d", p(2, 3));		//5
	return 0;
}

 来个函数指针趣题


	void (*signal(int, void(*)(int)))(int);

1.函数指针: void(*)(int) 

2.函数名: signal

3.signal函数的参数: int,  void(*)(int);   一个整型,一个函数指针

4.返回值:signal函数的返回值是指针 

5.函数指针数组

 将一些函数地址存入数组中,就得到了函数指针数组。

//函数指针数组
int Add(int x, int y)
{
	return x + y;
}
int Sub(const int x, const int y)
{
	return x - y;
}
int main()
{
	int(*pfun[2])(const int x, const int y) = { Add,Sub };
	printf("%d\n", pfun[0](2, 3));	//5
	printf("%d\n", pfun[1](5, 3));	//2
	return 0;
}

函数名就是地址名,不需要&地址,函数形参不能省略 

👊函数指针数组应用 

//简易整型计算器
#include<stdio.h>
void menu()
{
	printf("*****计算器*****\n");
	printf("**1.Add  2.Sub**\n");
	printf("**3.Mul  4.DIV**\n");
	printf("*****0.exit*****\n");

}
int add(const int x, const int y)
{
	return x + y;
}
int sub(const int x, const int y)
{
	return x - y;
}
int mul(const int x, const int y)
{
	return x * y;
}
int div(const int x, const int y)
{
	return x / y;
}
int main()
{
	int input = 1;
	int(*calc[5])(const int x, const int y) = { 0,add,sub,mul,div };
				//放0的原因是和菜单中的序号对应上
	while (input)
	{
		menu();
		printf("请输入:>");
		scanf("%d", &input);
		if (input > 0 && input < 5)
		{
			int x = 0, y = 0;
			printf("请输入两个数:");
			scanf("%d %d", &x, &y);
			printf("计算结果为%d\n", calc[input](x, y));
		}
		else if (input >= 5)
			printf("选择错误,请重新选择!\n");
	}
	printf("退出计算器\n");
	return 0;
}

 

 

6.函数指针数组的指针

 套娃式,本质是指针;

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

 例子2:

//函数指针数组的指针
int add(int x, int y)
{
	return x + y;
}
int main()
{
	//这是函数指针数组
	int (*pa[5])(int x, int y) = { add };
	//这是函数指针数组的指针,需要取出地址
	int(*(*ppa)[5])(int x, int y) = &pa;
	printf("这是函数指针数组的指针%p\n", ppa);
	printf("这是&函数指针数组后的地址%p\n", &pa);
	return 0;
}

7.回调函数 

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,这就是回调函数。

(依赖函数指针,有了函数指针,才能实现回调函数) 

👊qsort() 

qsort():快速排序

库函数,头文件#include<stdlib.h>

qsort函数可以进行各种数据的排序

 

void qsort(void* base,                 //待排序数组的第一个元素的地址

size_t num,                                 //待排序数组的元素个数

size_t size,                                //待排序数组中一个元素的大小

int (* cmp)(const void* e1, const void* e2)        //函数指针,自己定义排序函数

整型实例 

//qsort()
#include<stdio.h>
#include<stdlib.h>
int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = {5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

函数指针-cmp指向了一个函数,这个函数是用来比较两个元素的

e1和e2中存放的是需要比较的两个元素的地址

void* 不能直接进行计算,需要强转成其他类型;

结构体示例 

#include<string.h>
#include<stdlib.h>
struct Stu
{
	char name[20];
	int age;
};

int cmp_by_name(const void* e1,const void* e2)
{
	return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
int main()
{
	struct Stu arr[] = { {"zhangsan",20},{"wangwu",15} };
	//元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_by_name);
	return 0;
}

strcmp()是根据字符字典顺序比较的,后面的大,需要包含头文件#include<stdlib.h> 

qsort函数中就用到了回调函数的知识,使用qsort,它都会去调用比较函数。 

冒泡排序通用版

用冒泡排序的思路去模仿qsort(); 

#include<string.h>
struct Stu
{
	char name[20];
	int age;
};
//结构体年龄比较
int cmp_age(void* e1, void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//字节交换,size是一个数据的字节大小
void swap(char* buf1, char* buf2, size_t size)
{
	int k = 0;
	for (k = 0; k < size; k++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
//整型比较
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//打印
void print(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
//冒泡模拟qsort
void my_qsort(void* base, size_t num, size_t size, int(*cmp_name)(const void* p1, const void* p2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp_name((char*)base + j * size, (char*)base + (j + 1) * size) > 0)		//得到的返回值,如果大于0,就交换
			{
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main()
{
	struct Stu arr[] = { {"zhangsan",15},{"lisi",12},{"wangwu",30} };
	int iarr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//整型数组的元素个数
	int isz = sizeof(iarr) / sizeof(iarr[0]);
	my_qsort(iarr, isz, sizeof(iarr[0]), cmp_int);
	print(iarr, isz);

	//结构体的元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort(arr, sz, sizeof(arr[0]), cmp_age);
	return 0;
}

感谢你看到这里

以上就是我对进阶指针的介绍,身为初学者,自知有很多不足和需要改善的地方,希望大佬们指点一二,感激不急!!!

⭐愿星光照亮每一位赶路人 ⭐

 

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

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

相关文章

Gmail邮箱注册情况及最新动态

在中国大陆地区&#xff0c;对于是否可以注册Gmail邮箱一直存在一定的限制和讨论。准确来说&#xff0c;中国大陆地区的用户目前无法直接访问和注册Gmail邮箱。由于某些政策和技术原因&#xff0c;中国政府对于一些外国的网站和服务实施了网络封锁与限制。因此&#xff0c;中国…

聊聊Go语言的向前兼容性和toolchain规则

Go语言在发展演进过程中一直十分注重向后兼容性(backward compatibility)&#xff0c;在Go 1.0版本发布[1]之初就发布了Go1兼容性承诺[2]&#xff0c;简单来说就是保证使用新版本Go(比如Go 1.21版本[3])可以正常编译和运行老版本的Go代码(比如使用Go 1.18版本[4]语法编写的go代…

Docker实战技巧(一):Kubernetes基础操作实战

Kubernetes定位在Saas层,重点解决了微服务大规模部署时的服务编排问题 1、关闭防火墙并设置开机禁用   systemctl stop firewalld   systemctl disable firewalld 2、配置repo   cd /etc/yum.repos.d/   下载Docker repo   wget https://mirrors.aliyun.com/docker-…

torch.nn.Parameter()函数

引言 在很多经典网络结构中都有nn.Parameter()这个函数&#xff0c;故对其进行了解 pytorch官方介绍&#xff1a; 语法结构&#xff1a; torch.nn.parameter.Parameter(dataNone, requires_gradTrue) """ data (Tensor) – parameter tensor. —— 输入得是…

聊聊Spring事务同步器TransactionSynchronization

在一些业务场景中可能我们需要去对某一个spring事务的生命周期进行监控&#xff0c;比如在这个事务提交&#xff0c;回滚&#xff0c;被挂起的时候&#xff0c;我们想要去执行一些自定义的操作&#xff0c;这怎么去做呢&#xff1f;其实spring作为一个高扩展性的框架&#xff0…

中秋特辑:Java事件监听实现一个猜灯谜小游戏

众所周知&#xff0c;JavaSwing是Java中关于窗口开发的一个工具包&#xff0c;可以开发一些窗口程序&#xff0c;然后由于工具包的一些限制&#xff0c;导致Java在窗口开发商并没有太多优势&#xff08;当然也有一些第三方的工具包也很好用&#xff09;&#xff0c;不过&#x…

使用Python CV2融合人脸到新图片--优化版

优化说明 上一版本人脸跟奥特曼图片合并后边界感很严重&#xff0c;于是查找资料发现CV2还有一个泊松函数很适合融合图像。具体代码如下&#xff1a; import numpy as np import cv2usrFilePath "newpic22.jpg" atmFilePath "atm2.jpg" src cv2.imrea…

java基础-集合-ArrayList(JDK1.8)源码学习

文章目录 类图新增addensureCapacityInternalensureExplicitCapacitygrowhugeCapacity 删除removefastRemove 遍历Iterator 类图 新增 add public boolean add(E e) {// 根据注释可知 Increments modCount!!&#xff0c;modCount下面详解ensureCapacityInternal(size 1); //…

特斯拉Dojo超算:AI训练平台的自动驾驶与通用人工智能之关键

特斯拉公开Dojo超算架构细节&#xff0c;AI训练算力平台成为其自动驾驶与通用人工智能布局的关键一环 在近日举行的Hot Chips 34会议上&#xff0c;特斯拉披露了其自主研发的AI超算Dojo的详细信息。Dojo是一个可定制的超级计算机&#xff0c;从芯片到系统全部由特斯拉自主设计…

如何优化网站SEO(百度SEO优化的6个方案及密度)

一&#xff1a;蘑菇号https://www.mooogu.cn/ SEO优化是提高网站在搜索引擎中排名的关键技术。对于新网站而言&#xff0c;如何快速提高百度排名是每个站长需要关注的问题。下面我们将介绍新网站百度SEO具体方法。 二&#xff1a; 首先&#xff0c;通过网站架构优化来提高页…

解决5053无法安装驱动的故障

用5053连接车机&#xff0c;发现驱动上面有一个问号&#xff0c;看来驱动出问题了&#xff0c;试着用原来的办法无法强行安装&#xff0c;出现如下报错: 主要原因是老旧的设备驱动程序没有及时更新&#xff0c;遭到了新系统的嫌弃&#xff0c;导致数字签名验证失败&#xff0c;…

golang for循环append的数据重复

原因&#xff0c;因为使用了& 需要增加一行&#xff0c;问题解决

华为云云耀云服务器L实例评测| 搭建属于自己的第一个中秋快乐网页

华为云服务器 1 如何快速获得一个华为云服务器1.1 注册华为云账号1.2 选择华为云服务器实例 (云耀L系列)1.3 选择服务器区域1.4 选择实例规格1.5 付款界面确认实例参数&#xff0c;支付即可 2 运行自己的服务器2.1 找到自己的服务器控制面板2.2 了解服务器面板2.3 登录我们的服…

【网络编程】TCP Socket编程

TCP Socket编程 1. ServerSocket2. Socket3. TCP的长短连接4. Socket 通信模型5. 代码示例&#xff1a;TCP 回显服务器 流套接字&#xff1a; 使用传输层TCP协议 TCP: 即Transmission Control Protocol&#xff08;传输控制协议&#xff09;&#xff0c;传输层协议。 TCP的特点…

Pycharm配置环境以及Teminal不能使用问题解决

Pycharm配置环境 配置好环境后点击Terminal Teminal不能使用问题解决 我的报错信息&#xff1a; Import-Module : 无法加载文件 D:\Anaconda\shell\condabin\Conda.psm1&#xff0c;因为在此系统上禁止运行脚本。 解决方案&#xff1a; 第一步.&#xff1a;在 Windows 下用…

K8S名称空间和资源配额

Kubernetes 支持多个虚拟集群&#xff0c;底层依赖于同一个物理集群。 这些虚拟集群被称为名称空间。名称空间namespace是k8s集群级别的资源&#xff0c;可以给不同的用户、租户、环境或项目创建对应的名称空间&#xff0c;例如&#xff0c;可以为test、dev、prod环境分别创建各…

服务器搭建(TCP套接字)-基础版(服务端)

一、socket 1.1、vim man查看socket :!man socket1.2、 依赖的头文件 #include <sys/types.h> #include <sys/socket.h>1.3、原型 int socket(int domain, int type, int protocol);domain说明AF_INETIPV4协议AF_INET6IPV6协议AF_LOCALUnix域协议 type说明S…

JavaScript中的垃圾回收机制

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ JavaScript的垃圾回收机制⭐ 内存管理⭐ 引用计数⭐ 标记-清除算法⭐ 内存泄漏⭐ 性能优化⭐ 使用delete操作符⭐ 注意循环中的变量引用⭐ 使用工具进行内存分析⭐ 使用合适的数据结构⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探…

企业架构LNMP学习笔记54

企业架构NoSQL数据库之MongoDB。 学习目标和内容&#xff1a; 1&#xff09;能够简单描述mongoDB的使用特点&#xff1a; 2&#xff09;能够安装配置启动MongoDB&#xff1b; 3&#xff09;能够使用命令行客户端简单操作MongoDB&#xff1b; 4&#xff09;能够实现基本的数…

vsftp3.0 匿名用户,本地用户,虚拟用户

整体配置介绍&#xff1a; 进入vsftpd配置文件 vim /etc/vsftpd/vsftpd.conf //输入i开始编辑&#xff0c;修改后按esc退出编辑&#xff0c;输入:wq后回车保存并退出anonymous_enableYES #接受匿名用户&#xff0c;默认无密码请求 lo…