指针进阶(一)

文章目录

  • 1:字符指针变量
  • 2:指针数组
  • 3:数组指针
    • 3.1数组指针的定义
    • 3.2:&数组名vs数组名
  • 4:数组参数,指针参数
    • 4.1:一维数组传参的本质
      • 4.1.1:场景一
      • 4.1.2:场景二
      • 4.1.3:场景三
      • 4.1.4:场景四
      • 4.1.5:场景五
    • 4.2:二维数组传参的本质
      • 4.2.1:场景一
      • 4.2.2:场景二
      • 4.2.3:场景三
      • 4.2.4:场景四
      • 4.2.5:场景五

嘿嘿,家人们,在指针初阶呢,我们学习了指针的概念:
1:指针就是个变量,用来存放地址,地址标识一块唯一的内存空间.
2:指针的大小就是固定的4/8个字节(32位平台/64位平台).
3:指针也分类型,指针的类型决定了指针的±整数的步长和指针解引用操作时的权限.
4:指针的概念
这个章节,我们将继续探讨指针更深入滴主题!好啦,废话不多讲,开干!

1:字符指针变量

在指针的类型中我们知道有一种指针类型为字符指针char *,一般按照下面这种方式来使用.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	char ch = 'a';
	char* pc = &ch;
	*pc = 'w';
	printf("%c\n", ch);
	return 0;
}

在这里插入图片描述

通常呢,我们使用char *指针来存储字符型变量的地址,但除了存储字符型变量的地址以外,char *指针还能这样子使用,我们来看下面这段代码.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	const char* pstr = "hello world";
	printf("%p\n", pstr);
	printf("%p\n", &pstr[0]);
	return 0;
}

代码const char * pstr = “hello world”;是不是很多uu会认为是把字符串hello world放入到字符指针ptr了呢?其实不是滴,这段代码的本质是将字符串hello world的首字符地址放到了字符指针ptr中即将一个常量字符串的首字符h的地址存放到了指针变量ptr中.

在这里插入图片描述
在这里插入图片描述

通过观察,我们能清晰发现,pstr所存储的地址就是常量字符串的首字符h的地址.了解了字符指针后,我们来看下面这样一段代码,同样也是一道面试题.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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

在这里插入图片描述

我们可以看到屏幕上输出的结果,那么为什么会是这样子的结果呢,首先来看str1与str2,这里用的是相同的常量字符串去初始化不同的数组,数组名代表的是首元素地址,当用相同的常量字符串去初始化不同的数组时,这个时候会开辟出不同的内存块,因此str1与str2不同,而str3与str4是指针,指向的是常量字符串,C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,实际上他们会指向同一块内存区域.这里我们通过监视来观察一下.

在这里插入图片描述

通过观察监视,我们可以情绪地发现str1与str2的地址不同,指向的内存区域不同,而str3与str4的地址是相同的,说明这两个指针指向的是同一块内存区域.

2:指针数组

在指针初阶呢,博主详细介绍了指针数组,指针数组是一个存放指针的数组,这里博主带着uu们简单复习一下,如果忘了的uu们可以回过头去看看指针初阶那篇文章.

//整形指针的数组,该数组里头存放了10个元素,每个元素都是整型指针类型.
int * arr1[10]
//字符指针的数组,该数组里头存放了10个元素,每个元素都是字符指针类型.
char * arr2[10]
//二级字符指针的数组,该数组里头存放了10个元素,每个元素都是二级字符指针类型.
char ** arr3[10]

3:数组指针

3.1数组指针的定义

数组指针是指针还是数组呢?答案是指针,是一个指向数组的指针.
在之前我们已经熟悉了
> 整型指针:int * ptr;指向整型数据的指针.
浮点型指针:float * ptr;指向浮点型数据的指针.
那么数组指针即指向数组的指针.

下面我们来看一段代码,看看哪个是数组指针的表达方式.

int * p1[10]
int (*p2)[10]

上面这段代码p1和p2分别是什么呢?答案是:相信uu们对于p1很熟悉了,p1是指针数组,数组里头存放的数据类型都是整型指针.
p2是数组指针,指向的是一个int类型的数组.那么为什么数组指针要这样子来表达呢?原因是这样子滴.

在操作符优先级里面,[]的优先级是高于 * 的,因此在这里我们要通过()来保证p2先与 * 结合,p2与 * 结合后,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组.因此p2是一个指针,指向一个数组,名为数组指针.

3.2:&数组名vs数组名

int arr[15];

对于上面的数组,我们只知道arr是数组名,数组名表示的首元素的地址,那么&arr是什么呢?我们来看下面一段代码.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main()
{
	int arr[10];
	printf("%p\n",arr);
	printf("%p\n",&arr);
	return 0;
}

在这里插入图片描述

通过观察上面这段代码的结果我们可以发现,数组名和&数组名打印的地址是一样的.那么这两个真的是一样的吗?我们再来看下面这段代码.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

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

在这里插入图片描述

根据上面的代码结果我们可以清晰地发现,&arr和arr,虽然值是一样滴,但是意义是不一样的.
实际上:&arr表示的是整个数组的地址,arr表示的是数组首元素的地址
在上面代码中,&arr的类型是:int (*)[10],是一种数组指针类型.
数组的地址 + 1,跳过的是整个数组的大小,16进制的00EFF780 - 00EFF758 = 16进制的28,转换为10进制就是40,因此&arr + 1与&arr的差值是40.

4:数组参数,指针参数

4.1:一维数组传参的本质

数组之前我们学习过了,数组是可以传递给函数的,那么在这里博主详细讲一下数组传参的本质,首先从一个问题开始,我们之前都是在函数外部计算数组的元素的个数,那么我们可以把数组传给函数后,在这个函数内部计算数组的元素个数吗?我们来看下面这段代码.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[])
{
	int sz2 = 0;
	printf("sz2 = %d\n", sizeof(arr) / sizeof(arr[0]));
}

int main()
{
	int sz1 = 0;
	int arr[10];
	printf("sz1 = %d\n", sizeof(arr) / sizeof(arr[0]));
	test(arr);
	return 0;
}

在这里插入图片描述

我们发现在函数内部没有正确地获得数组的元素个数,这个时候就要涉及到数组传参的本质了,在之前我们学习过:数组名是首元素的地址;那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是首元素的地址. 因此函数形参的部分理论上应该使用指针变量来接收首元素的地址.那么在函数内部我们写sizeof(arr)计算的是一个地址的大小而不是数组的大小.正是因为函数的参数部分本质是指针,所以在函数内部是没办法求得数组元素的个数的.

那了解了一维数组传参的本质后,我们来看看下面这几个数组传参的场景.

4.1.1:场景一

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int arr[10])
{
}
int main()
{
	int arr[10] = { 0 };
	test1(arr);
	return 0;
}

在这种场景下,实参传递过去,将函数的形参部分写成数组的形式可不可以呢?答案是可以滴,并且在这里传参的时候,指定了形参的数组大小与实参的大小是一致的,其实形参的部分写成数组的形式,实际上本质还是指针.

4.1.2:场景二

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test2(int arr[])
{
}


int main()
{
	int arr[10] = { 0 };
	test2(arr);
	return 0;
}

场景二对比第一种场景区别就在于形参部分没有指定数组的范围,那么可不可以呢?答案是可以滴,为什么呢?在之前数组部分我们学习过,数组在没有指定范围的情况下,是根据数组的内容来确定范围的,也就是说数组在进行初始化的时候是可以省略元素个数的,在这里呢,main函数的arr数组是已经确定了范围的,而且数组在内存中的存储是连续的,因此实参在传递过去后,能够确定数组的范围.

4.1.3:场景三

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test3(int* arr)
{}

int main()
{
	int arr[10] = { 0 };
	test3(arr);
	return 0;
}

这里的形参部分和前面两个有所不同,这里的形参部分是用一级指针来进行接收的,之前我们了解过,一维数组传参的本质是传递的是数组首元素的地址,而指针呢是用来存储地址的,因此在这里用指针变量接收是可以的.

4.1.4:场景四

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr[10])
{
}

int main()
{
	int* arr2[10] = { 0 };
	test1(arr2);
	return 0;
}

这里的arr2这个数组是指针数组即里面的每个元素都是整型指针,在上面场景中我们学习到,一维数组传参的时候,形参的部分可以写成数组的形式,而这里的形参恰好也是指针数组的形式,因此是可以传递过去的.

4.1.5:场景五

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int ** arr)
{
}

int main()
{
	int* arr2[10] = { 0 };
	test1(arr2);
	return 0;
}

这里的形参与场景四的形参部分有所不同了,这里形参部分使用的是二级指针来进行接收,那么可不可以呢?答案是可以的,原因在于,我们说数组传参传递的是首元素的地址,对于arr2这个指针数组,里面的每一个元素都是一级整型指针,那么传递首元素的地址就是将一级整型指针的地址传递传递过去了,在指针初阶我们学习过,对于一级指针的地址,我们可以用二级指针来进行存储,因此形参部分写成二级指针的地址是可以的.

4.2:二维数组传参的本质

讲解完一维数组的本质后,接下来我们来探究二维数组传参的本质,那么首先我们来看下面这段代码.

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[3][5])
{
	int row = 3;
	int col = 5;
	for (int i = 0; i < row; i++)
	{
		for(int j = 0; j < col;j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test(arr);
	return 0;
}

在这里插入图片描述

当我们有一个二维数组需要传递给一个函数的时候,我们可以类比一维数组的传参方式,同样形参的部分写成一个二维数组的形式来进行接收.那么除了这种写法,还有什么其他写法吗?
首先我们再次理解一下二维数组,二维数组的每个元素可以看做是一维数组的数组,也就是说,二维数组里面的每个元素都是一个一维数组.那么二维数组的首元素代表了首元素的地址即一维数组的地址.

在这里插入图片描述

所以,根据数组名是数组首元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址.根据上面的例子,第一行的一维数组是int [5],所以第一行的地址的类型就是一个数组指针即int (*)[5].那就意味着二维数组传参本质上也是传递了地址,传递的是第一行这个一维数组的地址.了解了二维数组的传参本质后,我们来看下面这几个场景.

4.2.1:场景一

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int arr[3][5])
{}
void test2(int arr[][])
{}

void test3(int arr[][5])
{}
int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test1(arr);
	test2(arr);
	test3(arr);
	return 0;

test1这里的形参是可以接收形参的,这点是毋庸置疑的,对于test2这里既没有指定二维数组的行又没有指定二维数组的列,在数组那一章节,我们学习到,二维数组在初始化的时候,是能够省略行的但是不能够省略列即知道了多少列,就能够确定一行多少元素,这样子才能够方便运算.因此test2这里是不能够接收的,而test3是可以接收的.

4.2.2:场景二

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr)
{}
int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test1(arr);
	return 0;
}

这里的形参部分通过一个一级整型指针来进行接收,那么能否接收呢?答案是不可以的,因为二维数组传参的本质是传递的是首元素的地址也就是第一行元素的地址即一维数组的地址,因此这里使用整型指针来进行接收是不可以的.

4.2.3:场景三

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int * arr[5])
{}
int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test1(arr);
	return 0;
}

这里的形参能否接收实参呢?答案是不行的,形参arr首先与[]结合,也就是一个数组,这个数组里面的元素类型是int *即指针,因此形参部分是指针数组,而实参传递的是一维数组的地址,很明显不匹配.

4.2.4:场景四

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int (*arr)[5])
{}
int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test1(arr);
	return 0;
}

这里的形参能否接收实参呢?答案是可以的,形参arr首先与*结合,说明arr是个指针,arr指向的是一个int类型的数组即里面存储的是一维数组的地址,因此arr是一个数组指针,而二维数组传参的本质是传递的是一维数组的地址,因此用数组指针来进行接收是可以的.

4.2.5:场景五

#define  _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int ** arr)
{}
int main()
{
	int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
	test1(arr);
	return 0;
}

这里的形参能否接收实参呢?答案是不可以滴,这里的形参是二级指针,二级指针是用来接收一级指针的地址,而这里是实参传递过去的是一维数组的地址,因此是不可以接收的.

好啦,uu们,关于指针进阶(一)这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

光影魔术师:Photoshop 2022——你的创意无限可能

在数字艺术的广阔天地中&#xff0c;有一款软件如同魔法师般&#xff0c;以其强大的功能和无尽的可能性&#xff0c;引领着无数创意者探索未知的视觉世界。它&#xff0c;就是Adobe Photoshop 2022。 无论是Mac还是Windows系统&#xff0c;Photoshop 2022都以其卓越的兼容性&a…

mysql数据库操作小寄巧

目录 json字段查询时间相关只有日期只有时间又有时间又有日期时间比较时间运算 某字段同的取最新数据&#xff08;软性的新数据覆盖旧数据查找&#xff09;sql_modeonly_full_group_by的解决办法优化思路 json字段查询 查询某个json字段&#xff08;xx&#xff09;的某个属性下…

从http到websocket

阅读本文之前&#xff0c;你最好已经做过一些websocket的简单应用 从http到websocket HTTP101HTTP 轮询、长轮询和流化其他技术1. 服务器发送事件2. SPDY3. web实时通信 互联网简史web和httpWebsocket协议1. 简介2. 初始握手3. 计算响应健值4. 消息格式5. WebSocket关闭握手 实…

Python复合型数据避坑指南

目录 前言 列表&#xff08;Lists&#xff09; 1. 修改可变对象 2. 浅拷贝和深拷贝 元组&#xff08;Tuples&#xff09; 集合&#xff08;Sets&#xff09; 字典&#xff08;Dictionaries&#xff09; 1. 键值唯一性 2. 键的类型 实际应用场景 1. 数据分析与清洗 2. 网络…

SDR架构 (一)为什么基带有I和Q路?

我之前做过自己的RTL-SDR。一直有一个疑惑。为啥rtl2832u芯片有一对差分I路&#xff0c;还有一对差分Q路。差分很好理解是为了抗干扰&#xff0c;但为啥要I和Q呢&#xff1f;并且我也知道不少人在自己修改的时候&#xff0c;保留I路对接在r820t2&#xff08;跟原版一样&#xf…

CentOS8 同步时间chrony ntpdate已无法使用

CentOS8系统中&#xff0c;原有的时间同步服务 ntp/ntpdate服务已经无法使用&#xff0c;使用yum安装&#xff0c;提示已不存在。 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 8.1.1911 (Core) [rootlocalhost ~]# yum install ntp 上次元数据过期检查…

深入理解Linux线程(LWP):概念、结构与实现机制(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;会いたい—Naomile 1:12━━━━━━️&#x1f49f;──────── 4:59 &#x1f504; ◀️ ⏸ ▶️ ☰ &a…

2024年经典【自动化面试题】附答案

一、请描述一下自动化测试流程&#xff1f; 自动化测试流程一般可以分为以下七步&#xff1a; 编写自动化测试计划&#xff1b; 设计自动化测试用例&#xff1b; 编写自动化测试框架和脚本&#xff1b; 调试并维护脚本&#xff1b; 无人值守测试&#xff1b; 后期脚本维…

LeetCode 2581.统计可能的树根数目:换根DP(树形DP)

【LetMeFly】2581.统计可能的树根数目&#xff1a;换根DP(树形DP) 力扣题目链接&#xff1a;https://leetcode.cn/problems/count-number-of-possible-root-nodes/ Alice 有一棵 n 个节点的树&#xff0c;节点编号为 0 到 n - 1 。树用一个长度为 n - 1 的二维整数数组 edges…

基于springboot实现图书馆管理系统项目【项目源码+论文说明】

基于springboot实现图书馆管理系统演示 摘要 电脑的出现是一个时代的进步&#xff0c;不仅仅帮助人们解决了一些数学上的难题&#xff0c;如今电脑的出现&#xff0c;更加方便了人们在工作和生活中对于一些事物的处理。应用的越来越广泛&#xff0c;通过互联网我们可以更方便地…

C++用临时对象构造新对象

C用临时对象构造新对象 //用临时对象构造同类型的新对象&#xff0c;该临时对象不产生&#xff1b; // 直接用生成临时对象的方法构造新对象&#xff0c;这是编译器对代码的优化&#xff0c;效率更高 #include<iostream> using namespace std; class MyClass { public:…

2024最新性能测试面试题(带答案)

一、性能测试开展过程&#xff1a; 答&#xff1a;第一步&#xff1a;找产品沟通哪些接口需要压测&#xff0c;需要达到什么样的预期值(TPS和响应时间) 第二步&#xff1a;编写测试计划&#xff0c;人员、时间周期、工具 第三步&#xff1a;环境搭建 第四步&#xff1a;造数…

若依前后端分离版本-自动生成代码

听说若依挺好用的&#xff0c;所以来学习一下。 1.下载项目&#xff0c;配置redis,配置mysql,安装npm&#xff08;版本一定要低于16&#xff09; 2.执行sql脚本数据库相关信息 3.启动后端ruoyi-admin的ruoyiApplication 4启动前端 选择terminal 进入ruoyi-ui&#xff0c;执…

数据结构从入门到精通——算法的时间复杂度和空间复杂度

算法的时间复杂度和空间复杂度 前言一、算法效率1.1 如何衡量一个算法的好坏1.2 算法的复杂度 二、时间复杂度2.1 时间复杂度的概念2.2 大O的渐进表示法2.3常见时间复杂度计算举例2.4等差数列计算公式2.5等比数列计算方法 三、空间复杂度四、 常见复杂度对比五、 复杂度的oj练习…

今日arXiv最热大模型论文:点击即可播放!港中文发布大模型写歌神器!

一首歌&#xff0c;包含作词作曲两个部分。擅长作词or作曲就已经很牛了。比如方文山是周杰伦的御用作词人&#xff0c;而周杰伦写过很多耳熟能详的曲子。而兼具作词作曲才华的全能创作人却是难得一见。 最近港中文发布了一款歌曲创作大模型SongComposer&#xff0c;作词作曲都…

R语言安装和简单入门HelloWorld用法

R语言安装和简单入门HelloWorld用法 #R语言安装地址 https://www.r-project.org/ click->CRAN mirror->选择China下列表&#xff1a; https://mirrors.tuna.tsinghua.edu.cn/CRAN/ 选择Download R for Windows 选择base Download R-4.3.2 for Windows 下载文件R-4.3.2-…

SQL-Labs靶场“26-28”关通关教程

君衍. 一、二十六关 基于GET过滤空格以及注释报错注入1、源码分析2、绕过思路3、updatexml报错注入 二、二十六a关 基于GET过滤空格注释字符型注入1、源码分析2、绕过思路3、时间盲注 三、二十七关 基于union及select的过滤单引号注入1、源码分析2、绕过思路3、联合查询注入4、…

springcloud alibaba组件简介

一、Nacos 服务注册中心/统一配置中心 1、介绍 Nacos是一个配置中心&#xff0c;也是一个服务注册与发现中心。 1.1、配置中心的好处&#xff1a; &#xff08;1&#xff09;配置数据脱敏 &#xff08;2&#xff09;防止出错&#xff0c;方便管理 &#xff08;3&#xff…

精品ssm的社区团购系统购物商城小程序

《[含文档PPT源码等]精品基于ssm的社区团购系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#xff1a;HTML5,CSS3、Jav…

从前端JS逆向到发现后端越权漏洞的渗透测试之旅

前言 本篇文章首发先知社区&#xff0c;作者为本公众号。 前端分析 首先搜索请求接口&#xff0c;未发现关键加密点 根据请求参数进行搜索 在js文件中找到aes加密key、iv eval(function(p, a, c, k, e, r) { e function(c) { return c.toString(36) } ; if…