拿捏指针(二)

个人主页:秋邱'博客

所属栏目:C语言

(感谢您的光临,您的光临蓬荜生辉)

目录

前言 

数组与指针

数组名的理解

指针数组与数组指针

指针数组

 数组指针

数组传参

一维数组传参的本质

二维数组传参的本质

二维数组模拟 

二级指针

字符指针变量

函数指针变量


前言 

前面我们已经讲了,C语言的第一篇《拿捏指针(一)》,接下里我们继续深入的来了解指针。

1.0 数组与指针

1.1 数组名的理解

我们之前学习了,数组知道了数组arr就是首元素的地址,但却不理解&arr和&arr[0]的区别,脑子还是有点乱,今天我们一次给它讲明白。

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);//&arr[0]表示首元素地址
	printf("&arr    = %p\n", arr);//&arrb表示整个数组的地址
	printf("arr     = %p\n", arr);//arr表示一维数组数组名
	return 0;
}

 输出结果:

&arr[0] = 00AFF754
&arr     = 00AFF754
arr        = 00AFF754

从输出的结果看,&arr[0],&arr和arr这三的地址都是一样的,那么就能得出结论,数组名就是首元素的地址。

那么我们再来看看一下的代码

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %zd字节\n", sizeof(&arr[0]));
	printf("&arr    = %zd字节\n", sizeof(&arr));
	printf("arr     = %zd字节\n", sizeof(arr));
	return 0;
}

输出结果:

&arr[0] = 4字节
&arr     = 4字节
arr       = 40字节

 我们能看出来,&arr[0],&arr和arr尽管都是打印首元素的地址,但还是有所区别的

如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对,为什么打印的确实40?

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

  • sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩, 单位是字节
  •  &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素 的地址是有区别的)

除此之外,任何地⽅使⽤数组名,数组名都表⽰⾸元素的地。 

1.2 指针数组与数组指针

相信看到指针数组和数字指针每个人都很头疼,不知道哪个是数组指针,哪个是指针数字。我带大家来看看吧。

1.2.1 指针数组

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

指针数组是指针还是数组,我们来类比一下,就知道。

 整形数组和字符串数组

整形数组是数组,字符串数组是数组,那么指针数组自然就是指针了。指针数组的每个元素都是⽤来存放地址(指针)的。

 1.2.2 数组指针

数组指针是数组还是指针呢?

int(*arr)[10];

 p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以 p是⼀个指针,指向⼀个数组,叫数组指针

值得注意的是,[]的优先级大于*,所以必须得加括号。

int arr[10] = {0};
int(*p)[10] = &arr;

所以我们知道了,int(*)[10] = &arr这两个的类型其实是相同的。

数组指针类型解析:

int (*p) [10] = &arr;

intp     p指向的数组的元素类型

(*p)p是数组指向变量名

[10]     p指向数组的元素个数


2.0 数组传参

2.1 一维数组传参的本质

一维数组arr表示首元素的地址,那么一维数组传参传的是地址还是一个数值呢?

void  Print(int * arr)
{
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	Print(arr);
	return 0;
}

arr是首元素地址,既然是地址我们就能用指针接收,也可以用 int arr[10]

打印结果:

这里我们预期的打印是1 2 3 4  5 6 7 8 9 10,可是输出的确实1,这就印证了我们的猜想,数组传参传的也是首元素的地址。 

所以arr的元素个数我们还是要在main函数里初始化,在Print函数里是一个指针4个字节(32位下,如果是64位下是8个字节),当然sz=1。

2.2 二维数组传参的本质

我们知道了一维数组传参传的是首元素地址,那么二维数组也是同理的。

void  Print(int(*arr)[5])
{
	for(int j = 0; j < 3;j++)
	{
		for (int i = 0; i < 5; i++)
		{
			printf("%d ", arr[j][i]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7,} };
	Print(arr);
	return 0;
}

 arr[3][5]我们也可以用数组的形式接收,int arr[3][]列数可以省略,而行数却不可以

 输出结果

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

 这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗

⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。

 三行五列,第一行相当于arr[0],第二行相当于arr[2],第三行相当于arr[3];,第⼀⾏的地址是⼀维数组的类型就是数组指针类型 int [5] ,所以第⼀⾏的地址的类 int(*)[5] 那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址。

总结:

  • 数组传参的本质是传递了数组首元素的地址,所以形参访问的数组和实参的数组是同一个数组的。
  • 形参的数组是不会单独再创建数组空间的,所以形参的数组是可以省略掉数组大小的。

实际上还可以用指针的方式打印出来 

void test(int(*p)[5])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}

 因为二维数组在内存中是连续存放的,且arr是一个数组arr[0]里面有五个元素{1,2,3,4,5}; 我们可以把它想像成一维数组。

(arr+1)是首元素的地址,(arr+1)+1是首元素第一个的地址。

2.2.1 二维数组模拟 
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* p[3] = {arr1,arr2,arr3};//数组名是数组⾸元素的地址,类型是int*的,就可以存放在p数组中
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
	return 0;
}

输出结果:

1 2 3 4 5
2 3 4 5 6
3 4 5 6 7 

p[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数 组中的元素。 上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。


3.0 二级指针

二级指针跟一级指针的原理很相似

	int a = 10;
	int* pa = &a;

一级指针是将变量的地址放入一级指针变量里面,二级就是将一级的指针变量的地址放入二级的指针变量。

	int a = 10;
	int* pa = &a;//一级指针
	int** ppa = &pa;//二级指针

这就是一个二级指针,我们画一个图了解。

当我们解引用的时候,*ppa访问达到的指针变量pa的内容(a的地址),*pa继续解引用访问到的是a的内容

这就是二级指针,依次类推三级指针,四级指针,五级指针都是这样的。

4.0 字符指针变量

//代码1
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}

//代码2
int main()
{
	char* pc = "hello world";
	return 0;
}

代码2,常常被人误解,以为 是将“hello world”一整串的地址放入了pc变量中,其实只是将"h"的地址放入了pc变量,这样的字符串我们称为常量字符串。

我们来看你一段代码


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

  打印结果:

str1 and str2 are not same
str3 and str4 are same

 为什么会出现这样的结果呢?

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

5.0 函数指针变量

上面我们已经学习了数组指针变量,字符串指针变量,同样函数指针变量也是类似的。

函数指针变量应该是⽤来存放函数地址的,未来通过地址能够调⽤函数的

void test()
{
	printf("hehe\n");
}
int main()
{
	printf("test:  %p\n", test);
	printf("&test: %p\n", &test);
	return 0;
}

输出结果:

test:  00DE13CA
&test: 00DE13CA

 这里函数的取地址与数组是相似的,都是首元素的地址,&函数名的方法获得函数的地址。

如果我们需要将函数的地址存放起来,,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针 ⾮常类似。

void test()
{
	printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
	return x + y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的

调佣指针指向的函数

int add(int x,int y)
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	int(*pf)(int, int) = add;//存add的地址
	printf("%d\n", (*pf)(2, 3));
	printf("%d\n", pf(5, 5));
	return 0;
}

输出结果:

5

10 

这就是函数变量的用途。

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

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

相关文章

【数据结构与算法】:选择排序与快速排序

&#x1f525;个人主页&#xff1a; Quitecoder &#x1f525;专栏&#xff1a;数据结构与算法 我的博客即将同步至腾讯云开发者社区&#xff0c;邀请大家一同入驻&#xff1a;腾讯云 欢迎来到排序的第二个部分&#xff1a;选择排序与快速排序&#xff01; 目录 1.选择排序1.…

【网站项目】325企业OA管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

SpringBoot启动后出现Please sign in页面

1. 问题 项目启动后&#xff0c;出现莫名其妙的页面&#xff0c;如下 2. 原因 当您启动 Spring Web 应用程序后出现 “Please sign in” 页面时&#xff0c;这通常是由于引用依赖Spring Security默认的身份验证方式导致的。 <dependency><groupId>org.springfr…

Element 选择季度组件

<template><el-dialogtitle"选择季度":show-close"false":close-on-click-modal"false":close-on-press-escape"false":visible"visiable"class"dialog list"append-to-body><div><div>&…

如何本地搭建hMailServer邮件服务

文章目录 前言1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 前言 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpola…

51单片机基础篇系列-定时/计数器的控制工作方式

&#x1f308;个人主页&#xff1a;会编程的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” 定时/计数器的控制 80C51单片机定时/计数器的工作由两个特殊功能寄存器控制&#xff0c;TMOD用于设置其工作方式&#xff1a; 1.工作方式寄存器TMOD 工作方式寄存器TMO…

C++的类和对象(七):友元、内部类

目录 友元 友元函数 友元类 内部类 匿名对象 拷贝对象时的一些编译器优化 再次理解类和对象 友元 基本概念&#xff1a;友元提供了一种突破封装的方式&#xff0c;有时提供了便利&#xff0c;但是友元会增加耦合度&#xff0c;破坏了封装&#xff0c;所以友元不宜多用&…

AXI CANFD MicroBlaze 测试笔记

文章目录 前言测试用的硬件连接Vivado 配置Vitis MicroBlaze CANFD 代码测试代码测试截图Github Link 前言 官网: CAN with Flexible Data Rate (CAN FD) (xilinx.com) 特征: 支持8Mb/s的CANFD多达 3 个数据位发送器延迟补偿(TDC, transmitter delay compensation)32-deep T…

Jenkins 面试题及答案整理,最新面试题

Jenkins中如何实现持续集成与持续部署&#xff1f; Jenkins通过自动化构建、测试和部署应用程序来实现持续集成与持续部署&#xff08;CI/CD&#xff09;。这个过程包括以下步骤&#xff1a; 1、源代码管理&#xff1a; Jenkins支持与多种版本控制系统集成&#xff0c;如Git、…

数据结构 之 优先级队列(堆) (PriorityQueue)

&#x1f389;欢迎大家观看AUGENSTERN_dc的文章(o゜▽゜)o☆✨✨ &#x1f389;感谢各位读者在百忙之中抽出时间来垂阅我的文章&#xff0c;我会尽我所能向的大家分享我的知识和经验&#x1f4d6; &#x1f389;希望我们在一篇篇的文章中能够共同进步&#xff01;&#xff01;&…

oops-framework框架 之 启动流程(三)

引擎&#xff1a; CocosCreator 3.8.0 环境&#xff1a; Mac Gitee: oops-game-kit 回顾 上篇博客中我们通过 oops-game-kit 模版构建了基础的项目&#xff0c;另外讲解了下assets目录结构和游戏配置文件的基本使用相关&#xff0c;详情内容可参考&#xff1a; oops-framewo…

mysql5.7离线安装 windows

windows上离线安装mysql5.7 下载安装包 去官网下载对应版本的mysql官网 点击archives,接着选择自己要下载的版本&#xff0c;选择windows系统&#xff0c;并根据自己电脑的位数选择相应的版本【找到“此电脑”&#xff0c;鼠标右击&#xff0c;出来下拉框&#xff0c;选择“属性…

频率响应概述与波特图

频率响应的定义 在放大电路中&#xff0c;存在电抗元件&#xff08;如电容、电感&#xff09;、半导体管&#xff08;存在极间电容&#xff09;。由于电抗元件和极间电容的存在&#xff0c;当输入信号频率过高或过低时&#xff0c;不但放大倍数的数值会减小&#xff0c;而且将…

Python 3.x 快速安装 pip 包管理工具

目录 ℹ️ 1. 查看是否安装 pip1.1 方法一1.2 方法二 &#x1f6e0;️ 2. 安装方法2.1 通过 ensurepip 进行安装2.2 通过 get-pip.py 进行安装 参考文档&#xff1a; pip 官方安装文档&#xff1a;https://pip.pypa.io/en/stable/installation/ ℹ️ 1. 查看是否安装 pip 【…

最详细数据仓库项目实现:从0到1的电商数仓建设(数仓部分)

1、数仓 数据仓库是一个为数据分析而设计的企业级数据管理系统&#xff0c;它是一个系统&#xff0c;不是一个框架。可以独立运行的&#xff0c;不需要你参与&#xff0c;只要运行起来就可以自己运行。 数据仓库不是为了存储&#xff08;但是能存&#xff09;&#xff0c;而是…

hcia复习总结7

1&#xff0c;AR2发送2.0网段的信息给AR1&#xff0c;如果&#xff0c;AR1本身并不存在该网段的路由 信息&#xff0c;则将直接 刷新 到本地的路由表中。 Destination/Mask Proto Pre Cost Flags NextHop Interface 2.2.2.0/24 RIP 100…

Codeql复现CVE-2018-11776学习笔记

基本使用 1、首先下载struts2漏洞版本源码&#xff1a; https://codeload.github.com/apache/struts/zip/refs/tags/STRUTS_2_3_20 2、构建codeql数据库&#xff08;构建失败文末有解决办法&#xff09;&#xff1a; codeql database create ~/CodeQL/databases/struts2-2.3.…

unraid docker.img扩容

unraid 弹Docker image disk utilization of 99%&#xff0c;容器下载/更新失败 我的版本是6.11.5&#xff0c;docker.img满了导致容器不能更新&#xff0c;遇到同样问题的可以先用docker命令清除一下仓库(当然不一定能清理出来&#xff0c;我已经清理过只清理出来1G多点&…

存储卡乱码:原因、恢复与预防全攻略

一、存储卡乱码现象初现 在数字时代&#xff0c;存储卡已成为我们生活中不可或缺的一部分&#xff0c;无论是手机、相机还是其他电子设备&#xff0c;都离不开它的陪伴。然而&#xff0c;当我们在某一天突然发现存储卡上的文件出现了乱码&#xff0c;那种焦虑和困惑感简直无法…

Linux环境(Ubuntu)上的防火墙工具使用方法

目录 概述 1 防火墙工具&#xff08;ufw&#xff09; 1.1 安装防火墙工具&#xff1a; 1.2 操作防火墙相关命令 2 ufw操作命令的范例 2.1 打开/关闭防火墙 2.1.1 打开防火墙 2.1.2 关闭防火墙 2.1.3 查询当前防火墙状态 2.1.4 允许选择的端口访问 2.1.5 允许选择固定…