指针的奥秘(二):指针与数组的联系+字符指针+二级指针+指针数组+《剑指offer》笔试题

news2024/12/24 9:06:43

指针

  • 一.指针与数组的联系
    • 1.数组名的理解
    • 2.使用指针访问数组
    • 3.一维数组传参的本质
  • 二.字符指针
    • 1.字符指针隐藏秘密
    • 2.常量字符串
    • 3.《剑指offer》笔试题
  • 三.二级指针
  • 四.指针数组
    • 1.指针数组模拟二维数组

一.指针与数组的联系

1.数组名的理解

  也许大部分人认为数组名就是一个名称,没有什么特殊意义,但是这么想的话你就错了,其实数组名有着重要的意义——数组首元素的地址
  你若不信的话可以测试一下:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr     = %p\n", arr);
	return 0;
}

在这里插入图片描述
  我们发现数组名和数组首元素的地址打印出的结果⼀模⼀样,数组名就是数组首元素(第⼀个元素)的地址。

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", sizeof(arr));
	return 0;
}

在这里插入图片描述

  思考:既然数组名是数组首元素的地址,那么输出的不应该是指针变量的大小(4/8)吗?为什么输出的结果又是40呢?

结论:

  • 其实数组名就是数组首元素(第⼀个元素)的地址是对的,但是有两个例外:

  • sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址虽然数值一样,但还是有区别的)

  • 除此之外,任何地方使用数组名,数组名都表示首元素的地址。

  再度思考:那么数组的地址,数组名与数组首元素的地址这三种又存在什么关系呢?以下的代码将使你清晰理解这三者的联系:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0]   = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0] + 1);
	printf("arr       = %p\n", arr);
	printf("arr+1     = %p\n", arr + 1);
	printf("&arr      = %p\n", &arr);
	printf("&arr+1    = %p\n", &arr + 1);
	return 0;
}

在这里插入图片描述
  数组的地址,数组名与数组首元素的地址打印的结果都是相同的,这是为什么呢?

结论:

  • &arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0]和arr都是首元素的地址,+1就是跳过⼀个元素,也就是4个字节,而每个字节都有对应的地址,且地址相差1,所以它们的地址就相差4。
  • &arr和&arr+1相差40个字节,这就是因为&arr是数组的地址,+1操作是跳过整个数组,就是40个字节,地址相差(0x26),到这里大家应该搞清楚数组名的意义了吧。

2.使用指针访问数组

  有了前面知识的支持,再结合数组的特点,我们就可以很方便的使用指针访问数组元素了,如下代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = &arr[0];//等价于int* p = arr;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));//等价于*(arr + i)
		//等价于arr[i],p[i]
	}

	return 0;
}

在这里插入图片描述

  • 将*(p+i)换成p[i]也是能够正常打印的,所以本质上p[i]是等价于*(p+i)。
  • 同理arr[i]应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。
  • 还可以这么写*(i+arr),以及这么写i[arr],是不是很奇妙啊,了解一下就行了,不推荐这么写。

  思考:为什么可以使用指针来访问数组呢?
总结:

  • 数组在内存是一块连续的空间,存放的是相同类型的元素。
  • 指针变量是一个变量,是存放地址的变量,数组和指针不是一回事,但是可以利用指针来访问数组,指针进行不断地+1,解引用可以很方便地遍历数组,取出数组的内容。

3.一维数组传参的本质

  首先从⼀个问题开始,我们之前都是在函数外部计算数组的元素个数,那我们可以把数组传给⼀个函数后,函数内部求数组的元素个数吗?如下:

#include <stdio.h>
void test(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

在这里插入图片描述
  我们发现在函数内部是没有正确获得数组的元素个数,这又是为什么呢?你也许会想,指针怎么这么…(此处省略一万字),要尝试先接受它,以后学习多了自然都解释地清了。

  • 这就要学习数组传参的本质了,上个小节我们学习了:数组名是数组首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。
  • 那么在函数内部我们写sizeof(arr) 计算的是⼀个地址的大小(单位字节)而不是数组的大小(单位字节)。正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。
  • 那形参为什么可以写成数组的形式呢?这是因为C语言考虑到了学者的感受,在学习数组的时候,如果一来就传地址,形参用指针变量来接收,学者会非常地疑惑的。所以说C语言并不是这么冷若冰霜的。

总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

二.字符指针

1.字符指针隐藏秘密

  在指针的类型中我们知道有⼀种指针类型为字符指针 char* ,存放的是字符的地址,比如:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}

  还有⼀种使用方式如下:

#include<stdio.h>
int main()
{
	const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
	printf("%s\n", pstr);
	return 0;
}

  代码 const char* pstr = “hello bit.”;特别容易让同学以为是把字符串 hello bit 放到字符指针 pstr 里了,但是不妨考虑一下指针存放的是地址,怎么可能会存放字符串呢?其实本质是把常量字符串 hello bit. 首字符(h)的地址放到了指针变量pstr中

2.常量字符串

  常量字符串顾名思义,该字符串不能被修改,接下来看一个代码:

#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	char* p1 = arr;
	*p1 = 'b';
	printf("%s\n", arr);

	char* p2= "abcdef";
	*p2 = 'b';
	printf("%s\n", p2);
	return 0;
}

在这里插入图片描述
可以发现指针p1指向的空间可以修改,而修改指针p2指向的空间则报错:写入访问权限冲突。这是因为p2是常量字符串,它还有更重要的特点,接下来带我慢慢为你分析一二,请看以下的笔试题。

3.《剑指offer》笔试题

  《剑指offer》:中收录了⼀道和字符串相关的笔试题,我们⼀起来学习⼀下:

#include <stdio.h>
int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char* str3 = "hello bit.";
	const char* str4 = "hello bit.";

	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相同。

总结:

  • str1和str2是两个数组,数组的操作方式是将右边常量字符串的内容拷贝进来,所以他们是两个空间,只是内容相同,所以str1 != str2。
  • 而str3和str4是两个指针, 编译器在处理的时候,会将相同的常量字符串做成同一个地址,所以,str3和str4指向的是同一个常量字符串,所以str3 == str4。

三.二级指针

  思考:指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?这就是二级指针,如下代码:

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	int** ppa = &pa;
	*pa = 20;
	printf("%d\n", a);//20
	**ppa = 30;
	printf("%d\n", a);//30
	return 0;
}

在这里插入图片描述

对于⼆级指针的运算有:

  • *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa ,*ppa 其实访问的就是 pa。
int b = 20;
*ppa = &b;//等价于 pa = &b; 
  • **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a。
**ppa = 30;
//等价于*pa = 30; 
//等价于a = 30; 

四.指针数组

  思考:指针数组是指针还是数组?

  • 我们类比⼀下,整型数组,是存放整型的数组,字符数组是存放字符的数组。
  • 那指针数组呢?是存放指针的数组,指针数组的每个元素都是用来存放地址(指针)的。
  • 指针数组的每个元素是地址,又可以指向一块区域。

1.指针数组模拟二维数组

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中 
	int* parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//等价于*(*(parr+i)+j)
		}
		printf("\n");
	}
}

在这里插入图片描述
parr是数组名,表示首元素的地址,也就是数组的地址,这就牵扯到了数组指针,数组指针又是什么呢?
预知后续如何,且听下回分解,指针的奥秘(三)即将来袭!!!你准备好了吗???
创作不易,如果能帮到你的话能赏个三连吗?感谢了!!!
在这里插入图片描述

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

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

相关文章

前端基础知识-ES6扩展运算符(快速实现数组添加新元素、字符串转为数组、对象添加新属性)

前言&#xff1a; 扩展运算符又称为Rest运算符&#xff0c;可以实现数组、对象、字符串在语法层面上的展开&#xff0c;达到简化语法的目的&#xff0c;使得我们提高开发效率 主要用法&#xff1a; 在需要解构的变量前加三个点&#xff08;...xxx&#xff09; 具体示例&…

猿匹配,一款使用环信实现的一个开源聊天应用含服务器

前言 之前写了一篇Android开发集成聊天环信SDK3.x简单开始&#xff0c;然后最近得空开发了一款使用环信实现的实时聊天应用&#xff0c;包含简单的服务器端&#xff0c;并开源给大家&#xff0c;有兴趣的同学可以一起搞一下&#xff0c;详细介绍看下边吧 上代码 服务器&#…

89C52单片机+ESP8266做的物联网+反馈 e4a手机客户端源程序

资料下载地址&#xff1a;89C52单片机ESP8266做的物联网反馈 e4a手机客户端源程序 MCU是89C52单片机 WiFi模块是ESP8266 其他 8路继电器 电源模块 使用贝壳物联做服务器 还有客户端。 也可以用花生壳做内网穿透&#xff0c;8266做服务器&#xff0c;也可以实现物联以及反馈&a…

攻击者正在利用AI,对保险公司发起大规模欺诈

保险欺诈一直是保险行业面临的重要挑战之一&#xff0c;尤其随着技术的进步&#xff0c;欺诈者也在不断更新其手段&#xff0c;利用AI技术&#xff0c;包括生成式模型、机器学习和数据分析工具等欺骗保险公司&#xff0c;而AI技术的应用正成为他们的新工具&#xff0c;使其犯罪…

VSCode(安装)

前言 VSCode&#xff08;全称&#xff1a;Visual Studio Code&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件支持语法高亮、代码自动补全&#xff08;又称 IntelliSense&#xff09;、代码重构、查看定义功能&#xff0c;并且内置了命令行工具和 Git …

韩顺平0基础学Java——第7天

p110-p154 控制结构&#xff08;第四章&#xff09; 多分支 if-elseif-else import java.util.Scanner; public class day7{public static void main(String[] args) {Scanner myscanner new Scanner(System.in);System.out.println("input your score?");int s…

[windows系统安装/重装系统][step-2]BIOS设置UEFI引导、磁盘分区GPT分区、安装系统[含完整操作拍照图片]

背景 先准备U盘启动盘和系统镜像: [windows系统安装/重装系统][step-1]U盘启动盘制作&#xff0c;微软官方纯净系统镜像下载 前言&#xff08;略长&#xff0c;建议可跳过&#xff09; 我的笔记本升级了CPU升级了内存后出现了一个小问题&#xff0c; 每次启动徽标显示后会…

ETL工具kettle(PDI)入门教程,Transform,Mysql->Mysql,Csv->Excel

什么是kettle&#xff0c;kettle的下载&#xff0c;安装和配置&#xff1a;ETL免费工具kettle(PDI)&#xff0c;安装和配置-CSDN博客 mysql安装配置&#xff1a;Linux Centos8 Mysql8.3.0安装_linux安装mysql8.3-CSDN博客 1 mysql -> mysql 1.1 mysql CREATE TABLE user_…

JAVA二叉树相关习题5

1. 二叉树前序非递归遍历实现 。 . - 力扣&#xff08;LeetCode&#xff09; 递归的实现 public List<TreeNode> preOrder1(TreeNode root){List<TreeNode> retnew ArrayList<>();if(root null)return ret;ret.add(root);List<TreeNode> leftTree …

C#获取当前激活网卡的速度 - 开源研究系列文章

以前用C#编写过一个网络速度计&#xff0c;用于监控计算机当前网卡的上传和下载速度。不过当时这个小应用没有完成&#xff0c;主要是那个获取网络速度的类库没有完成。这次趁有空&#xff0c;就把这个小应用进行了编写。其中涉及到的获取网络速度的代码整理出来了&#xff0c;…

使用nvm安装node.js过程

今天Jade尝试安装nvm&#xff0c;并使用命令安装node.js但是碰到了一些问题&#xff0c;在此作为学习记录分享出来。希望可以留下深刻的印象&#xff1a; 1、概念了解 nvm----- (Node.js version manager)是一个命令行应用&#xff0c;可以协助您快速地 更新、安装、使用、卸载…

游戏工作室如何利用惯性动作捕捉技术制作动画?

随着动捕设备不断进步和游戏行业的发展&#xff0c;惯性动作捕捉技术在游戏开发领域逐渐普及。惯性动作捕捉技术&#xff0c;可以精准捕捉现实世界中的真人动作&#xff0c;并将其精准应用于虚拟角色上&#xff0c;使游戏中的角色动作可以呈现出更写实、逼真和沉浸感&#xff0…

ESP32引脚入门指南(三):从理论到实践(Touch Pin)

引言 ESP32作为物联网领域的明星微控制器&#xff0c;不仅以其强大的网络通信能力著称&#xff0c;还内置了丰富的外设资源&#xff0c;其中就包括电容式触摸传感&#xff08;Capacitive Touch&#xff09;功能。本文旨在深入浅出地介绍ESP32的Touch引脚&#xff0c;带你了解其…

数据分析处理的步骤是什么?制造业企业如何挑选数据分析处理软件?看这篇就够了

随着工业4.0的深入实施以及国家对制造业高质量发展战略的日益强调&#xff0c;工业数据已经崭露头角&#xff0c;成为生产经营活动中至关重要的核心要素。不仅如此&#xff0c;工业数据还作为优质的生产要素&#xff0c;为新兴生产力的形成提供了强有力的支撑&#xff0c;从而推…

《编译原理》阅读笔记:p4-p17

《编译原理》学习第 2 天&#xff0c;p4-p17总结&#xff0c;总计 14 页。 一、技术总结 1.structure of compiler 编译器组成包括&#xff1a;Lexical Analyzer -> Syntax Analazer -> Semantic tree -> Intermediate Code Generator -> Machine-Independent C…

Linux学习笔记1---Windows上运行Linux

在正点原子的教程中学习linux需要安装虚拟机或者在电脑上安装一个Ubuntu系统&#xff0c;但个人觉得太麻烦了&#xff0c;现在linux之父加入了微软&#xff0c;因此在Windows上也可以运行linux 了。具体方法如下&#xff1a; 一、 在Windows上的设置 在window的搜索框内&#…

Bert 在 OCNLI 训练微调

目录 0 资料1 预训练权重2 wandb3 Bert-OCNLI3.1 目录结构3.2 导入的库3.3 数据集自然语言推断数据集路径读取数据集数据集样例展示数据集类别统计数据集类加载数据 3.4 Bert3.4 训练 4 训练微调结果3k10k50k 0 资料 【数据集微调】 阿里天池比赛 微调BERT的数据集&#xff0…

LeetCode HOT 100刷题总结

文章目录 1 哈希1.1 1-1.两数之和&#x1f7e2;1.2 2-49.字母异位词分组&#x1f7e1;1.3 3-128.最长连续序列&#x1f7e1; 2 双指针2.1 4-283.移动零&#x1f7e2;2.2 6-15.三数之和&#x1f7e1;2.3 7-11.盛最多水的容器&#x1f7e1;2.4 8-42.接雨水&#x1f534; 3 滑动窗…

一、计算机基础(Java零基础一)

&#x1f33b;&#x1f33b;目录 一、&#x1f33b;&#x1f33b;剖析学习Java前的疑问&#x1f33b;&#x1f33b;1.1 零基础学习编程1.2 英语不好能学吗&#xff1f;1.3 理解慢能学好吗&#xff1f;1.4 现在学Java晚吗&#xff1f;1.5 Java 和 Python 还有 Go 的选择1.6 Java…

WINDOWS下zookeeper突然无法启动但是端口未占用的解决办法(用了WSL)

windows下用着用着时候突然zookeeper启动不了了。netstat查也没有找到端口占用&#xff0c;就是起不来。控制台报错 java.lang.reflect.UndeclaredThrowableException: nullat org.springframework.util.ReflectionUtils.rethrowRuntimeException(ReflectionUtils.java:147) ~…