C语言—指针进阶(详解篇)

news2025/1/13 15:30:44

目录

1.字符指针

1.1字符指针定义

1.2 字符指针用法

2.指针数组

2.1 指针数组定义及使用

3.数组指针

3.1 数组指针定义

3.2 &数组名和数组名 

 3.3 数组指针的基本用法

4. 数组参数、指针参数 

5. 函数指针 

5.1 函数指针定义既基本使用

5.2 有趣的代码 

6. 函数指针数组 

6.1 函数指针数组定义

6.2 函数指针数组用法 

6.3 指向函数指针数组的指针 

7. 回调函数 

7.1 回调函数定义

7.2 回调函数使用 


在初学指针的时候你是否也常常分不清楚 ’指针数组‘ 和 ’数组指针‘ 呢?

结果是指针数组是数组,数组指针是指针,那快来学习一下这篇好文,更深刻的了解吧

前面初阶指针中学习了一下指针的基本概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算
http://t.csdn.cn/wsHNl​​​​​​

1.字符指针

1.1字符指针定义

在指针类型中我们知道一种指针类型为:char*

一般情况就是存放字符变量地址的指针

int main()
{
	char a = 'w';
	char* pc = &a;
	return 0;
}

除去存放字符变量地址这个用法外还有其他的用法:

首先知道字符串常量:一对双引号括起来的字符序列

int main()
{
	const char* pc = "abcdef";
	//字符串"abcdef"是常量,不可被修改所以加上const修饰
	printf("%c\n", *pc); // 'a'
	printf("%s\n", pc);  // "abcdef"
	return 0;
}

这么一串代码字符指针pc存放的是字符串"abcdef"的首元素地址,所以如果打印一个字符:解引用pc打印结果就是字符串的首元素‘a’,知道了首元素既可通过字符指针pc打印字符串

1.2 字符指针用法

字符指针经典面试题:题源《剑指offer》

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;
}

输出结果:

str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组

2.1 指针数组定义及使用

指针数组是一个存放指针的数组。

int main()
{
	char* pch[5]; // 字符指针数组
	int* parr[5];//  整形指针数组
	char** ppch[5]; //二级字符指针数组
	return 0;
}

指针数组用法:将三个一维数组通过指针变成二维数组并输出打印

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };//定义一个指针数组存放arr1、2、3,的首元素地址
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
//			*(arr[i]+j)=arr[i][j] 通过i分别找出arr1、2、3并通过j找出所对于的数
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

3.数组指针

3.1 数组指针定义

数组指针是指向数组的指针 

数组指针表示形式:

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//取出的arr数组的地址
//  取出arr的地址,元素个数10,每个元素类型为int
	return 0;
}

 解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

区分:指针数组 和 数组指针

3.2 &数组名和数组名 

&数组名:取出的是整个数组的地址

数组名:表示的是首元素的地址 

http://t.csdn.cn/Pm7B9

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

 3.3 数组指针的基本用法

 例:将一个整形二维数组中的元素输出打印

void print1(int arr[3][5],int x,int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print2(int(*pa)[5], int x, int y)
{
//int(*pa)[5] 表示指向一个有5个int类型元素的数组,既arr首地址
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", (*(pa)+i)[j]);//*(pa)+i==pa[i]
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print1(arr,3,5);//arr首元素地址是{1,2,3,4,5}
	print2(arr,3,5);
	return 0;
}

数组名arr,表示首元素的地址
但是二维数组的首元素是二维数组的第一行
所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
可以数组指针来接收

4. 数组参数、指针参数 

一维数组传参

void test1(int arr [])//同样用整形数组接收
{}
void test2(int* str) //用一个同类型指针接收
{}
void test3(int arr[5])//同类型同大小接收(数组大小可忽略)
{}
void test1_1(int* parr[]) //相同整形指针数组接收
{}
void test2_2(int** str) //二级指针接收
{}
int main()
{
	int arr[5] = { 0 };//一维数组
	int* parr[10] = { 0 };//指针数组
	test1(arr);
	test2(arr);
	test3(arr);
	test1_1(parr);
	test2_2(parr);
	return 0;
}

 二维数组传参

void test1(int arr[3][5])//相同类型二维数组
{}
void test2(int arr[][5])//二维数组传参,函数形参的设计只能省略第一个[]的数字。
{}
void test3(int(*pa)[5])//用数组指针接收
{}
int main()
{
	int arr[3][5] = { 0 };
	test1(arr);
	test2(arr);
	test3(arr);
	return 0;
}

一级指针传参

void test(int* p)//同类型指针接收
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p + i));
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* pa = arr;
	test(pa);
	return 0;
}

二级指针传参

void test(int** ppa)//相同类型二级指针接收
{}
int main()
{
	int a =5;
	int* pa = &a;
	int** ppa = &pa;
	test(ppa);
	return 0;
}

5. 函数指针 

5.1 函数指针定义既基本使用

函数指针:存放函数地址的指针变量

 两个输出的都是test函数的地址

void test()
{}
int main()
{
	test();
	void (*p)() = &test; //函数指针,存放函数地址
	return 0;
}

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

函数指针基本使用

int add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	int(*pa)(int, int) = &add;//存放add函数地址的函数指针
	printf("%d\n", (*pa)(10, 20));//直接通过解引用pa操作函数add
	return 0;
}

5.2 有趣的代码 

下面是两个有趣的代码,简述大概意思。来源:《C陷阱与缺陷》 

代码1:

 首先看到这么一个复杂的代码,我们先将其括号配对,使我们能更明确的看出所代表的含义

 简述:将整形0强制 类型转换成一个参数为无返回类型为void的函数指针地址,既0地址

后调用地址为0的函数。

代码2:

 简述:

signal是一个函数声明
signal函数的参数有2个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void

6. 函数指针数组 

6.1 函数指针数组定义

 函数指针数组:存放函数指针的数组

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 (*pf[4])(int, int) = { add,sub,mul,div };
	return 0;
}

pf 先和 [] 结合,说明 pf是数组,数组内容是:int (*) (int ,int );

6.2 函数指针数组用法 

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

以计算器为例,函数指针数组用法:

void mune()
{
	printf("**********************\n");
	printf("**  1.add    2.sub  **\n");
	printf("**  3.mul    4.div  **\n");
	printf("******* 0.exit *******\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 main()
{
	int n = 0;
	int x = 0;
	int y = 0;
	int(*pf[5])(int, int) = { 0,add,sub,mul,div };//定义5个下标,让数组下标和菜单选项对应
	//函数指针数组:转移表
	do
	{
		mune();
		printf("请选择:>");
		scanf("%d", &n);
		if (n >= 1 && n <= 4)
		{
			printf("请输入两位操作数:>");
			scanf("%d %d", &x, &y);
			printf("%d\n", pf[n](x, y));
		}
		else if (n == 0)
		{
			printf("退出\n");
		}
		else
		{
			printf("输入错误\n");
		}
	} while (n);
	return 0;
}

6.3 指向函数指针数组的指针 

存放函数指针数组地址的指针变量

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

7. 回调函数 

7.1 回调函数定义

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

void print(char* str)
{
	printf("%s\n", str);
}

void test(void)
{
	print("hello world!");
}

int main()
{
	test();
	return 0;
}

7.2 回调函数使用 

设定一个通用冒泡排序函数可以排序任意类型:参考函数qsort() 

qsort ()函数详解:          http://t.csdn.cn/xkfQ8icon-default.png?t=M85Bhttp://t.csdn.cn/xkfQ8代码实现:

//置换
void permute(char* buf1, char* buf2, int width)//类型不同,一个字节一个字节进行置换
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
// base:任意类型数组地址,sz:数组长度,width:数组宽度(数组类型),比较参数函数
void bubble_sort(void* base,int sz,int width,int(*cmp)(void* elem1,void*elem2))
{
	//冒泡排序
	int i = 0;
	//趟数
	for (i = 0; i < sz-1; i++)
	{
		//每躺比较的对数
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			//比较两个参数
			if (cmp((char*)base+width*j, (char*)base+width*(j+1)) > 0)//比较该元素和后一个元素大小
//传参任意类型,可通过一个字节+宽度就等于该数据类型,一个char类型加上一个int类型就等于跳过一个int类型
			{
				//置换
				permute((char*)base + width * j, (char*)base + width * (j + 1),width);
			}
		}
	}
}
int cmp_int(void* elem1, void* elem2)
{
	return *(int*)elem1 - *(int*)elem2;
}
void test(void)
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	test();
	return 0;
}

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

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

相关文章

BMS 信息资源e分享平台

今天分享的是一款关于医学的企业内部实战系统。当时某药企内部面临现状是医学人力资源有限、信息量需求大、信息资源传递途径受限&#xff0c;覆盖范围小。为解决目前面临的问题&#xff0c;提高信息资源的统一性、准确性和安全性&#xff0c;优化资源获取流程&#xff0c;提高…

[vite.js]按需加载自动注册组件

最近(后知后觉)发现各大ui组件库的按需引入&#xff0c;在使用vite构建项目的时候&#xff0c;都推荐使用unplugin-vue-components插件自动解析ui组件来自动注册&#xff1b;就是说不需要再import { ... } from ..了&#xff0c;该插件会自动帮助解析并注册成组件。其实之前用n…

数字文档管理不能落后的 5 个原因

数字文档管理不能落后的 5 个原因 信息管理对于几乎每个行业的组织都至关重要。从财富 500 强企业到医疗机构&#xff0c;您处理文件的地点和方式都很重要。如果您坚持基于纸张的流程&#xff0c;那么您可能会落后于其他企业而且冒着很大的风险。 在许多组织中&#xff0c;数字…

Linux的Jdk安装教程

liunx下Jdk安装教程 1.创建jdk的安装目录&#xff08;/usr/local/src&#xff09; 确保安装的目录是空的&#xff0c;如果不是空的&#xff0c;删除一下&#xff0c;或者放在自己其他的目录也可以 mkdir -p /usr/local/src/jdk这里可能会出现之前安装过jdk&#xff0c;可以用下…

用HTML+CSS做一个漂亮简单的轻量级图片相册博客网站(web前端期末大作业)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

web表单(详解)

目录 1. 表单的概述 1.1 表单组成 2. 表单标记 2.1 input标记 2.2 select标记 2.3 textarea标记 3.HTML5新增标记 3.1 datalist标记 3.2 date 输入类型 3.3 color输入类型 3.4 button标记 3.5 details标记和summary标记 3.6 progress标记 3.7 meter标记 4 综合…

【pen200-lab】10.11.1.5

pen200-lab 学习笔记 【pen200-lab】10.11.1.5 &#x1f525;系列专栏&#xff1a;pen200-lab &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月29日&#x1f334; &#x1f36d;作者…

阿里资深架构师谈 Java 进阶攻略:7 大技能 +12 份进阶笔记 + 面试 150 题

以下都是阿里大牛推荐的主流技术&#xff0c;当你全部掌握上述的这些技术那么你就已经是 P8 级别&#xff0c;而且你也已经形成了自己的体系&#xff0c;当更加新潮的技术出来时那么你自己稍微花点时间就能吃透&#xff0c;毕竟那时候你已经不是以前的那个你了&#xff0c; 懂底…

竞赛——【蓝桥杯】2022年12月第十四届蓝桥杯模拟赛第二期C/C++

1、最小的2022 问题描述 请找到一个大于 2022 的最小数&#xff0c;这个数转换成二进制之后&#xff0c;最低的 6 个二进制为全为 0 。 请将这个数的十进制形式作为答案提交。 答案提交 这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数…

分享 2022 年最受欢迎的黑科技工具(二)

Hello, everybody &#xff0c;2022 年最受欢迎的黑科技工具&#xff08;二&#xff09;&#xff0c;收藏一波吧&#xff0c;您的在看、转发、点赞就是对tuonioooo最大的支持&#xff01; 1.Sampler 项目地址&#xff1a;https://github.com/sqshq/sampler 官网地址&#xf…

Fmoc-PEG4-NHS酯,1314378-14-7 含有Fmoc保护胺和NHS酯

●英文&#xff1a;Fmoc-PEG4-NHS酯 ●外观以及性质&#xff1a;粘性液体或固体粉末&#xff0c;一般取决于分子量&#xff0c;是一种含有Fmoc保护胺和NHS酯的PEG连接剂。亲水性PEG间隔物增加了在水介质中的溶解度。Fmoc基团可在碱性条件下脱保护以获得游离胺&#xff0c;其可…

springboot+java+vue大学生求职招聘就业岗位匹配推荐系统

目 使用人职匹配推荐系统分为管理员和用户、企业三个权限子模块。 管理员所能使用的功能主要有&#xff1a;首页、个人中心、用户管理、企业管理、岗位信息管理、岗位类型管理、应聘信息管理、应聘状况管理、平台费用管理、系统管理等。 用户可以实现&#xff1b;首页、个人中…

JavaScript基础语法(运算符)

JavaScript基础语法&#xff08;运算符&#xff09; 概述 相比于Java,JS多了一个&#xff0c;其他的下面的运算符都一样。 一元运算符&#xff1a;&#xff0c;– 算术运算符&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;% 赋值运算符&#xff1a;&…

我要涨知识——TypeScript 经典高频面试题(一)

又是一个年底来了&#xff0c;好大一批人可能又准备跑路了&#xff0c;翻了翻掘金和 CSDN 发现好多大佬都有大厂 Offer &#xff0c;看着看着我心动了&#xff01; 话不多说&#xff0c;赶紧开干&#xff0c;给自己整了一个前端面试小助手——微信小程序内搜索 “WEB学习学习加…

鲲鹏devkit性能分析工具介绍(二)

鲲鹏devkit性能分析工具介绍&#xff08;二&#xff09; 上一篇笔记录性能分析工具的全景分析模式的基本原理和重点参数的解读&#xff0c;在这片文章里将会讲解其他的分析功能特点和重点参数 进程/线程性能分析 进程/线程性能分析借鉴业界的USE方法&#xff0c;采集进程/线…

Kubernetes之Pod初始化容器

Kubernetes之Pod初始化容器 概述 ​ 初始化是很多编程语言普遍关注的问题&#xff0c;甚至有些编程语言直接支持模式构造来生成初始化程序&#xff0c;这些用于进行初始化的程序结构称为初始化器或初始化列表。初始化代码要首先运行&#xff0c;且只能运行一次&#xff0c;它们…

Java.Integer.bitCount(int)源码解析

bitCount前言一、由易到难&#xff0c;头脑热身二、简单优化&#xff0c;一题多解三、分治优化四、bitCount(int)源码优化总结参考文献前言 如何求解一个二进制中1的个数&#xff1f;有常规的O(N)法&#xff0c;还有基于分治的O(logN)&#xff0c;即Java的bitCount(int)方法。…

CockroachDB-备份与恢复(1)备份架构

本文知识点来源于官网地址https://www.cockroachlabs.com/docs/stable/backup-architecture.html CockachDB备份以作业的方式操作&#xff0c;作业是可能跨越多个SQL会话的长期运行操作。与常规SQL语句不同&#xff0c;BACKUP语句在作业工作流中执行。备份任务有四个主要阶段:…

CDMP考试时间与报名方式

CDMP“数据管理专业人士认证”证书国际通用&#xff0c;行业认可度极高&#xff0c;是一项涵盖学历教育、工作经验和专业知识考试在内的综合资格认证&#xff0c;也是 目前全球唯一数据管理方面权威性认证 。CDMP考试时间是什么时候&#xff1f;怎样报名&#xff1f;今天小编来…

C语言 =(按位与后赋值)^=(按位异或后赋值) |=(按位或后赋值)

&(按位与后赋值&#xff09; x 0x02; x & 0x01; 按位与后的结果为&#xff1a;0x00 x 0x02; x & 0x01; 字符 & 的最早历史可以追溯到公元1世纪&#xff0c;最早是拉丁语et (意为and)的连写。最早的 & 很像 E 和 T 的组合&#xff0c;随着印刷技术的发展&…