不会指针?还不进来看看——进阶指针详解

news2024/11/28 11:54:30

专栏:C语言
每日一句:人贵有自知之明,知道什么可为和不可为。若不可为,怎样做才能可为,那何时可为。

进阶指针

  • 前言
  • 一、字符指针
  • 二、指针数组
    • 1.指针数组的介绍
    • 2.指针数组的使用
  • 三、数组指针
    • 1.数组指针的介绍
    • 2.&数组名和数组名
    • 3.数组指针的使用
  • 四、函数指针
  • 五、函数指针数组
  • 六、回调函数
  • 总结


前言

如若不知道指针是什么,点击传送门
本文是指针的主题,在前面我们已经稍微了解了指针,
1.指针就是个变量,用来存放地址,
2.指针大小在32位中是4字节,在64位中是8字节
3.指针是有类型的,指针的类型决定了指针的±整数的步长。
4.指针的运算

在这里插入图片描述

一、字符指针

字符指针是什么?
C语言的基本类型中有char类型,那么在指针的类型中也有一种指针类型为字符的指针char*,那么字符指针是怎么使用的呢?

int main()
{
	char s = 'a';
	char* ps = &s;
	*ps = 'b';
	return 0;
}

这是一种用法,还有下面一种用法。

#include <stdio.h>
int main()
{
	const char* s = "Hao Hao Xue Xi";
	printf("%s", s);
	return 0;
}

在这里插入图片描述

const char* s = "Hao Hao Xue Xi";为什么通过s可以把这个字符串打印出来?是因为把这个字符串存到s指针变量里面了吗?
其实不是的,这里只是把这个字符串的首元素地址,存在了ps里,由第一个元素的地址可以找到后面元素的地址,直到遇到\0

#include <stdio.h>

int main()
{
	char a1[] = "Hello World";
	char a2[] = "Hello World";

	const char* ps1 = "Hello World";
	const char* ps2 = "Hello World";

	if (a1 == a2)
	{
		puts("a1 == a2");
	}
	else
	{
		puts("a1 != a2");
	}

	if (ps1 == ps2)
	{
		puts("ps1 == ps2");
	}
	else
	{
		puts("ps1 != ps2");
	}

	return 0;
}

那么,这个代码会输出什么结果呢?
在这里插入图片描述
结果为什么不是a1 == a2 和 ps1 == ps2呢?
我们分别把a1,a2,ps1,ps2的地址打印出来观察一下
在这里插入图片描述
看代码结果,ps1和ps2指向的是同一个地址,因为他们指向的是同一个字符串常量,在C语言中会把字符串常量存储到单独的一个内存区域,当n个指针指向同一个字符串常量的时候,他们实际指向的是同一块内存区域,所以,打印出来的地址是相同的。但是用不同的数组初始化为相同的字符串常量,结果就不是这样的,在创建数组的时候,计算机会自动在内存的某一区域开辟一段属于数组的空间,把数组的内容存在这个空间里,所以a1和a2的地址不相同。

二、指针数组

指针数组是指针还是数组?
答案:是数组,但是,指针数组是存放指针的数组

1.指针数组的介绍

我们经常用整型数组,字符数组等

int arr[]
char ch[]

整型数组,数组里面存放的都是整型,
字符数组,数组里面存放的都是字符,
同理:
指针数组,数组里面存放的应该是指针,指针是什么?指针就是地址,也可以这样说,数组里面存放的是地址。

int *arr[]
char *ch[]

在这里插入图片描述

2.指针数组的使用

那么,指针数组是怎么使用的呢?
看代码:

#include <stdio.h>

int main()
{
	const char ch1[4] = "hao";
	const char ch2[4] = "hao";
	const char ch3[4] = "xue";
	const char ch4[4] = "xi";
	char* pch[5] = { ch1, ch2, ch3, ch4 };
	for (int i = 0; i < 4; i++)
	{
		printf("%s\n", pch[i]);
	}
	return 0;
}

在这里插入图片描述
我们把ch[1,4]的地址都存到了pch里面,在通过ch[1,4]的地址,打印出字符串。
在这里插入图片描述
这就是指针数组的用法。

三、数组指针

上面刚刚介绍了指针数组,现在让我们来了解一下什么是数组指针。
数组指针是指针还是数组呢?
答案是:指针。

整型指针:int* ps:能够指向整型数据的指针
字符指针:char* ps:能够指向字符数据的指针
同理
那数组指针就应该是能够指向数组的指针

1.数组指针的介绍

int *ps[10]:是指针数组
int (*ps)[10]:是数组指针

ps先与结合,说明ps是一个指针变量,可以用来存放地址,在与[]结合,说明数组,所以,ps是一个指针,指向一个数组,叫数组指针
这里要注意:[]的优先级高于
的优先级,所以要加上()

数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

2.&数组名和数组名

&数组名是什么?
数组名又是什么?

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("数组名:%p\n", arr);
	printf("&数组名:%p\n", &arr);

	return 0;
}

在这里插入图片描述
从代码结果中,可以看到数组名的地址和&数组名是一样的,我们知道数组名代表的是首元素的地址,那么&数组名也代表的是数组首元素的地址吗?让我们再看一个代码:

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("数组名:%p\n", arr);
	printf("&数组名:%p\n", &arr);

	printf("数组名+1:%p\n", arr + 1);
	printf("&数组名+1:%p\n", &arr + 1);

	return 0;
}

数组名+1和&数组名+1的结果会是一样的吗?
在这里插入图片描述
很显然,不一样,数组名+1-数组名=4 &数组名+1-&数组名=40,这个40哪里来的呢?是sizeof(arr)为什么&数组名+1跨过了整个数组呢?实际上,&arr代表的是数组的地址,而不是数组首元素的地址,数组的地址+1才能跨过整个数组,而数组某一元素+1跨过的是一个元素的大小,这就是为什么&数组名+1-&数组名=40的原因

3.数组指针的使用

前面简单的说了数组指针,数组指针是怎么使用的呢?

#include <stdio.h>

int main()
{
	int arr[3] = { 6 ,6 ,6 };
	int(*parr)[3] = &arr;//把arr的地址取出来赋给parr,
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", (*parr)[i]);
	}
	return 0;
}

在这里插入图片描述
这样的代码少写,这样写没有任何的实际意义。
看下面的代码:

#include <stdio.h>
//这里的int(*parr)[3]等价于int arr[3][3]
void my_print(int(*parr)[3], int col, int row)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", parr[i][j]);
		}
		puts("");
	}
	puts("");
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", *(*(parr + i) + j));
		}
		puts("");
	}
}

int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	//数组名表示首元素的地址
	//在二维数组中,数组名表示第一行的地址
	//所以这里传arr,传的其实是二维数组第一行的地址,
	//可以拿指针来接收
	my_print(arr, 3, 3);

	return 0;
}

这个写法 *(*(parr + i) + j)表示什么意思呢?在这里,parr表示的是二维数组第一行的地址,第一行的地址+1表示二维数组第二行的地址,+n表示第n-1行的地址,对(parr + i)进行解引用操作*(parr + i),找到的是某一行的第一个元素的地址,在+j,就能找到某一个元素的地址了 (*(parr + i) + j)在进行解引用操作*(*(parr + i) + j),就可以精确到某一元素了。
在这里插入图片描述

四、函数指针

函数指针是什么呢?为什么会有函数指针这一概念呢?
首先看一段代码:

#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	printf("add :%p\n", add);
	printf("&add:%p\n", &add);

	return 0;
}

在这里插入图片描述
可以看出,add和&add是一样的,都是函数add的地址,既然是地址,那就可以用指针进行保存,怎么保存呢?这就引出了函数指针的概念。

#include <stdio.h>

int add(int a, int b)
{
	return a + b;
}

int main()
{
	int (*pf)(int, int) = &add;

	printf("(*pf)(1,2)=%d\n", (*pf)(1, 2));
	printf("add(1,2)=%d\n", add(1, 2));

	return 0;
}

在这里插入图片描述
这是一个很新颖的用法,把函数add的地址传给pf,利用指针把函数的地址存起来,通过这个指针去调用这个函数。

五、函数指针数组

数组是用来存放相同类型数据的存储空间,前面我们介绍过字符数组,整型数组,指针数组,那么也有函数指针数组这一概念。
函数指针数组就是,把函数的地址存到一个数组中,那个数组就叫函数指针数组,函数指针数组该如何定义呢?

int (*pf[10])(int,int....);

pf先和[]结合,说明pf是数组,数组的内容就是int (*)()类型的函数指针
看下代码就知道函数指针的用法了:
这是一个简单的计算器,不具有高精度,想了解高精度的点击传送门

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
void menu()
{
	printf("1.Add  2.Sub 3.Mul 4.Div");
}
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 x = 0;
	int y = 0;
	int input = 0;
	int(*parr[5])(int, int) = { 0, Add,Sub,Mul, Div };//这个就是函数指针数组,数组里面存放的是函数,
	//通过下标就能访问函数
	do
	{
		menu();

			printf("选择");
			scanf("%d", &input);
		if (input <= 5 && input >= 1)
		{
			
			printf("请输入数字");
			scanf("%d %d", &x, &y);
			int ret = parr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出");
		}
		else
		{
			printf("出错");
		}
	} while (input);
	 
	return 0;
}

其实还有一个指向函数指针数组的指针的概念,在这里就不多做介绍了。

六、回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应。

给大家看一个代码

//这里面用到了回调函数
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;
#define N 10
int score = 0;
int add(int x, int y)
{
	cout << x << "+" << y << " =";
	return x + y;
}
int sub(int x, int y)
{
	cout << x << "-" << y << " =";
	return x - y;
}
int mul(int x, int y)
{
	cout << x << "*" << y << " =";
	return x * y;
} 
int div_(int x, int y)
{
	cout << x << "/" << y << " =";
	return x / y;
}
void shuru(int (*pf)(int,int))//这里用函数指针来接收函数的地址,然后通过pf就能调动函数
{//shuru函数就是一个回调函数,,如果不用这个函数,那么代码会增加很多
	int x = rand() % 100 + 1;
	int y = rand() % 100 + 1;
	int ret = (*pf)(x,y);//在这里就调用了函数
	int num = 0;
	cin >> num;
	cout << "         ";
	if (ret == num) 
		cout << "对" << endl, score += 10;
	else 
		cout << "错" << endl;
}
int main()
{
	int x, y, num;
	int count = 0;
	srand((unsigned int)time(NULL));
	for (int i = 1;; i++)
	{
		int suiji = rand();
		if (suiji % 10 == 0)
		{
			count++;
			shuru(&add);//传函数的地址
		}
		else if (suiji % 10 == 1)
		{	
			count++;
			shuru(&sub);//传函数的地址
		}
		else if (suiji % 10 == 2)
		{	
			count++;
			shuru(&mul);//传函数的地址
		}
		else if (suiji % 10 == 3)
		{	
			count++;
			shuru(&div_);传函数的地址
		}
		
		if (count == N)
		{	
			break;
		}
	}
	cout << "总分" << score << endl;
	return 0;
}

在这里,给大家展示一个C语言排序函数:qsort
在这里插入图片描述

qsort有四个参数,1.待排序数组的首元素的地址。2.待排序数组的元素个数。3.带排序元素的每个元素的大小,单位是字节。4.函数指针,比较两个元素的所用函数的地址,函数的参数是:带比较的两个元素的地址。qsort的排序机制是快速排序,有不懂的小伙伴可以点击传送门学习一下快速排序。

#include <stdio.h>
#include <stdlib.h>

int cmp(const void* a ,const void* b)
{
	return *(int*)b - *(int*)a;
}

int main()
{
	int arr[9] = { 7,4,1,74,1,747,4741,741,7474741 };

	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp);

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

在这里插入图片描述

这里是降序排序,也可以升序排序,只需要把cmp函数里面的返回值换成*(int*)a - *(int*)b;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct STU
{
	char name[20];
	int length;
}S;

int cmp_name(const void* a, const void* b)
{
	return strcmp(((S*)a)->name, ((S*)b)->name);
}

int main()
{
	S student[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };

	int sz = sizeof(student) / sizeof(student[0]);

	qsort(student, sz, sizeof(student[0]), cmp_name);

	for (int i = 0; i < 3; i++)
	{
		printf("%s %d ", student[i].name, student[i].length);
	}

	return 0;
}

这是给结构体排序,以名字排的序
在这里插入图片描述
再以身高排个序,把排序这个回调函数改变一就行

int cmp_length(const void* a, const void* b)
{
	return((S*)a)->length - ((S*)b)->length;
}

在这里插入图片描述

注:给字符排序不能直接用-,需要用strcmp,专门用于比较字符串的函数

注:本文不解释qsort的底层实现原理,若感兴趣的话,可以自己模拟实现一下,在这里给点提示:1.要想实现qsort的底层,就得考虑什么类型能够存任意类型的地址,(void*)可以,void可以存任意类型的地址,可以把void比作万能钥匙,什么门都能打开。2.元素的个数,决定了要给多少个元素排序。3.每个元素的大小,每个类型有每个类型的大小,int4个字节,double8字节,那么,怎样才能去访问任意类型呢?可以用char类型,char是一个字节,char*+1访问的是第二个字节,这样就能访问任意类型了,只要知道带排序元素的大小即可。4.写一个比较的函数。

总结

以上就是对指针进阶的讲解,希望对大家有所帮助。

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

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

相关文章

历史大讲堂:真那么好用?Windows前世今生

hello大家好&#xff0c;这里是每天日更哒博主。 还记得我第一次说的Microsoft Dos吗&#xff1f;那期我提到一次Windows并许诺要讲讲&#xff0c;这不来了&#xff01;今天我们就详细的盘一盘最好用的系统Windows真有那么神吗&#xff1f; 注意&#xff01;以下内容包含非常…

人脸识别美颜算法实战-深度学习基础知识

深度学习与机器学习的区别: 机器学习:人类定义输入数据的特征 深度学习:机器自动找到输入数据的特征 在深度学习中,采用多层的神经网络架构来提取图像 信息,越靠近底层的神经网络提取出来的都是点、线等低维度特征, 而高维度的神经网络层则会更多地保留比如耳朵、眼睛…

MySQL事务基础知识

前言 学习/导流&#xff1a; 小林coding - 事务篇 学习意义 理解MySQL如何去处理并发问题&#xff0c;借鉴其思想存储作为应用的关键能力&#xff0c;而事务作为关系型数据库的关键概念&#xff0c;掌握很必要&#xff0c;也为分布式事务学习做奠基 相关说明 该篇博文是个…

快速搭建springboot程序

SpringBoot快速入门 观狂神讲解视频笔记 【狂神说Java】SpringBoot最新教程IDEA版通俗易懂 第一个springboot程序 使用 idea 可以快速构建一个 springboot 的项目&#xff1a; 1.创建新项目&#xff0c;选择 spring initializr&#xff08;会默认通过官网快速构建&#xff09…

【编程经验】如何学习编程语言的秘诀,编程语言选择,按需学习

大家好&#xff0c;欢迎来到停止重构的频道。最近有些朋友问我们如何学习编程、初学软件的问题&#xff0c;我们打算出几期内容聊聊我们的建议。本期聊一下如何学习编程语言。我们将压箱底的诀窍介绍给新手朋友&#xff0c;当然这仅仅是我们的一些经验&#xff0c;并不是绝对的…

连续四年第一!

近日&#xff0c;IDC发布《2022 H1中国AI云服务市场研究报告》&#xff0c;百度智能云连续四年市场份额第一&#xff0c;整体占比28.1%&#xff0c;在"人体人脸"、"图像视频"‍两个规模最大的子市场继续保持第一。‍‍ 在图像视频等多个领域蝉联市场第一 …

PLC算法系列之数值积分器(Integrator)

数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上的应用,请参看下面的文章链接: SMART S7-200PLC流量累计算法实现(梯形图算法详解+优化)_RXXW_Dor的博客-CSDN博客_smart 200 流量积分…

【bootstrap】使用,初学者的总结

官网&#xff1a;bootstrap中文网 首页点击v3文档&#xff0c;然后下载最左边的那个。 解压后把里面的css和js文件复制过去&#xff0c;因为也要用到jQuery&#xff0c;所以要把jQuery.min.js也一起放入js文件夹。 然后&#xff0c;就是导入&#xff1a; <link rel"s…

Java互联网支付系统源码,基于SpringBoot,含支付宝,微信,银联详细代码案例

spring-boot-pay 支付服务&#xff1a;支付宝&#xff0c;微信&#xff0c;银联详细 代码案例 (支付宝和微信支付测试均需要企业认证&#xff0c;如果没有企业推荐使用 [服务商模式] 申请开通个人商户 也可以测试 )&#xff0c;项目启动前请仔细阅读 注意事项 :fa-hand-o-left…

快速上手 BearPi-HM Micro 一个带显示屏的开发板

一、前言 为什么要写这篇文章呢? 华为官方给OpenHarmony分:南向开发 和 北向开发 北向开发:应用APP开发 南向开发:设备开发 带显示屏的设备,可以更好的使用可视化界面,能更直接的利用烧录到设备里面的代码。 例如:冰箱上面的小的显示屏,可以更好的操控和观察当前冰箱…

一个 web 开发者眼中的技术美术(TA—Technical Artist)

Techical Artist 的中文翻译是技术美术&#xff0c;相比于直译为技术艺术家&#xff0c;技术美术这个称谓让我感觉更加亲切&#xff0c;当然艺术家这个称谓也很好&#xff0c;很高级 :p 。在游戏行业里我们常常能听到美术这个职业&#xff0c;而技术美术&#xff0c;从字面意思…

微信公众号活动策划方案怎么写?看完你也可以直接套用

一场成功的活动策划可以给企业带来相当可观的用户流量和曝光&#xff0c;前提是你的这份公众号活动策划方案是可执行和有回报的&#xff01; 毫不夸张的说&#xff0c;我们在写公众号活动策划方案之前&#xff0c;只要你搞清楚以下这几点&#xff0c;这个方案就可以说是成功了…

介绍一个MySQL参数检索工具

碰巧看到徐老师的这篇文章《MySQL的参数工具》&#xff0c;其中介绍了一个讲解MySQL不同版本参数的小工具&#xff0c;网站的作者是MySQL日本用户组的负责人Tomita。该网站能够提供不同版本MySQL的参数&#xff0c;包括不同版本之间的对比。当用户需要对MySQL进行升级、需要确认…

用javascript分类刷leetcode13.单调栈(图文视频讲解)

84. 柱状图中最大的矩形 (hard) 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 …

2022年总结与2023年展望

又是疫情的一年&#xff0c;所幸年尾的政策看来是要全面开放了&#xff0c;也算是一件好事了。但是明年会不会就要强制线下上课导致实习不方便&#xff0c;这也是个问题。 技术学习 确定了自己的方向是走C/CLinux后端开发方向&#xff0c;跟着课程学到了一个图床后端的项目&a…

前端页面插件集成-Markdown编辑器

页面插件集成-Markdown下载MarkDown的插件包引入css、js、Jquary文件定义一个textarea区域作为MarkDown的载体初始化Markdown编辑器最终效果下载MarkDown的插件包 下载地址如下&#xff1a; 下载传送门 点击Github下载&#xff0c;然后解压 解压之后将如下文件夹和 js 文件cop…

贷后联动管控指标与差异化案件的分配逻辑

在风控精细化运营的当下&#xff0c;贷后工作的开展&#xff0c;越来越需要精细化管理。如何做好相关的精细化管理工作&#xff0c;首先我们从这些贷后相关的名词如下开始熟悉&#xff1a; 贷后基本催收名词解释 Flow Rate 迁移率就是在贷后资产评估里最重要的报表了&#xf…

【C语言进阶】还说不会?一文带你全面掌握计算机预处理操作

目录 &#x1f34a;前言&#x1f34a;&#xff1a; &#x1f348;一、宏与函数&#x1f348;&#xff1a; 1.宏与函数对比&#xff1a; 2.宏与函数的命名约定&#xff1a; &#x1f353;二、预处理操作符&#x1f353;&#xff1a; 1.预处理操作符 " # "&#xff…

C语言学生成绩管理系统

程序示例精选 C语言学生成绩管理系统 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<C语言学生成绩管理系统>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 …

DFS算法-leetcode java题解

DFS算法-leetcode java题解 本文目录DFS算法-leetcode java题解leetcode 547. 省份数量leetcode 463. 岛屿的周长leetcode 200. 岛屿数量leetcode 130. 被围绕的区域leetcode 417. 太平洋大西洋水流问题leetcode 17. 电话号码的字母组合leetcode 22. 括号生成leetcode 39. 组合…