C语言二维数组和二重指针详解

news2024/11/24 17:42:20

二维数组

一个二维数组,在本质上,是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组,形式如下:

type arrayName [x][y];

这个表示,有x个一维数组,每个一维数组的元素个数是y个。

声明示例:

/** 定义数组 */
int main()
{
	int		ar[3][4];		// 3 行 4列 未初始化
	char	br[3][4];
	double	cr[3][4];
	return 0;
}

一个二维数组,在本质上是有多个一维数组构成。(每一个一维数的大小必须相同)

例如:定义 int ar[3][4] 的二维数组,它是由3个一维数组组成,每个一维数组的大小是4个整型元素。可以只对部分元素赋值,未赋值的元素自动取 0 值。

可以进行如下赋值:

int x[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};

如果对二维数组的初始化,那么第一维的长度是可以缺省的,但是第二维不可缺省

int main()
{
	int ar[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };		// 3 行 4 列
	int br[][4] = { {1,2},{3,4},{5,6} };				// 3 行 4 列 数字不足自动补 0
	int cr[][4] = { 1,2,3,4,5,6,7,8 };					// 2 行 4 列
	return 0;
}

注:里面的花括号是可以省略的,如果省略,那么按照列的个数来自动分配。

验证如下:

#include <stdio.h>

int main()
{
	int a[][3] = {1, 2, 3, 4, 5};
    /* 我的第一个 C 程序 */
    printf("Hello, World! %d\n", a[0][2]);//Hello, World! 3
	printf("Hello, World! %d\n", a[1][2]);//Hello, World! 0
   
    return 0;
}

二维数组在内存中的存储

二维数组的逻辑表示:

 

二维数组的物理表示(按行优先存储):

 

二维数组的数组名有什么含义

先来梳理一下一维数组的数组名含义。

#include<stdio.h>
int main(void){
	int a[3]={1,2,3};	
	int *p;
	  p=a;
	printf(" a=%d\n",a);
	printf(" &a[0]=%d\n",&a[0]);
	printf(" &a=%d\n",&a);
	printf(" *a=%d\n",*a);
	printf(" *p=%d\n",*p); 
}

我们知道数组是在内存里占据一片连续的内存空间,由此可以看到,数组名a的值为数组a的第一个元素的地址,且数组名a自身的地址也和a指向的地址相同,即 a=&a=&a[0],但是要注意,&a只是在数值上相同,在含义上并不相同。

对于一维数组而言,&a,即数组本身的指针,并没有什么用处,重要的是a,即数组首元素的指针。由main函数中开始的两行代码,我们能知道,如果想要接收数组的数组名,我们需要定义的指针类型是数组元素的类型,而不是定义一个数组指针。当然,如果想要接收&a,那么就需要定义数组指针。

先根据一维数组的函数名含义来猜想下二维数组的函数名含义。

如a[3][4],那么,a就代表着第一个一维数组的地址。也就是说,是个数组的地址。

另外,a[0]、a[1]、a[2]都表示一个一维数组,是否也表示各一维数组的地址。

测试:编辑如下代码:

#include <stdio.h>

int main()
{
	int a[3][4] = {{1, 2 , 3, 4}, {5, 6 , 7, 8}, {9, 10 , 11, 12}};
	
    printf("Hello, World! %x\n", a);
	printf("Hello, World! %x\n", a[0]);
	printf("Hello, World! %x\n", a[1]);
	printf("Hello, World! %x\n", a[2]);
   
    return 0;
}

如果上述猜想是对的,那么第一行和第二行的数值是一样的,且后面的三个地址,依次相差16个字节。

运行:

符合猜想。

为了进一步证实,用他们访问具体的数据。

#include <stdio.h>

int main()
{
	int a[3][4] = {{1, 2 , 3, 4}, {5, 6 , 7, 8}, {9, 10 , 11, 12}};
	
    printf("Hello, World! %d\n", *(int *)(a));
	printf("Hello, World! %d\n", *((int *)a[0] + 1));
	printf("Hello, World! %d\n", *(int *)a[1]);
	printf("Hello, World! %d\n", *((int *)a[2] + 3));
   
    return 0;
}

如果没错的话,应该依次输出1、2、5、12

运行:

以为正确。 

后来,又遇到一个问题,才发现上面的结论并不完全对。

先说进一步发现的结论:

对于二维数组来说,数组名a表示首元素的地址;a[0]相当于一维数组的数组名,表示的是第一个一维数组的首元素的地址。

可以这么理解,对二维数组名进行解引用,能得到一维数组名的效果。

#include <stdio.h>

int main()
{
	int a[][3] = {{1, 2, 3},{4, 5, 6}};
   
	printf("result is %d\n", **a);
	printf("result is %d\n", *a[0]);
	printf("result is %d\n", *a[1]);
	
    return 0;
}

运行结果

 

二维字符数组

常规的字符数组按照上述的内容来考虑。
我们这里重点关注字符串形式的二维数组。

char a[][10] = {"be", "happy", "everyday"};

考虑上述代码,其中a是个双重指针,指向首元素;a[0]是“be”字符串,指向'b';a[1]是“happy”字符串,指向'h';a[2]是“everyday”字符串,指向'e'。

#include <stdio.h>

int main()
{
	char a[][10] = {"be", "happy", "everyday"};
   
	printf("result is %s\n", *a);
	printf("result is %s\n", a[0]);
	printf("result is %s\n", a[1]);
	printf("result is %s\n", a[2]);
	
	printf("result is %c\n", **a);
	printf("result is %c\n", *a[0]);
	printf("result is %c\n", *a[1]);
	printf("result is %c\n", *a[2]);
	
	
    return 0;
}

运行结果如下:

注意,直接%s打印字符指针,可以直接输出字符串

#include <stdio.h>

int main()
{
	char *p = "everyday";
   
	printf("result is %s\n", p);
	
    return 0;
}

运行结果:

 从哪里开始打印,就打印其之后的部分字符串

#include <stdio.h>

int main()
{
	char *p = "everyday";
   
	printf("result is %s\n", p + 5);
	
    return 0;
}

运行结果:

补充说明:

二维字符数组和字符指针数组挺像的。

char a[][10] = {"be", "happy", "everyday"};

char *a[] = {"be", "happy", "everyday"};

但是要注意,字符指针数组本质上是个一维数组。

不过,这两者在a、a[0]、a[0]、a[0]上是等效的,比如将上面的一个例子中的二维数组改成一维指针数组,运行结果不变。

#include <stdio.h>

int main()
{
	char *a[] = {"be", "happy", "everyday"};
   
	printf("result is %s\n", *a);
	printf("result is %s\n", a[0]);
	printf("result is %s\n", a[1]);
	printf("result is %s\n", a[2]);
	
	printf("result is %c\n", **a);
	printf("result is %c\n", *a[0]);
	printf("result is %c\n", *a[1]);
	printf("result is %c\n", *a[2]);
	
	
    return 0;
}

运行结果如下:

 

二维数组作为函数形参

如果想要把二维数组作为函数形参,可以直接用二重指针来接收。




二重指针

二重指针与普通一重指针的区别
本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
一重指针变量和二重指针变量本身都占4字节内存空间,

二重指针的本质
二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报错。
C语言中如果没有二重指针行不行?其实是可以的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西(定义指针时用数据类型来标记,譬如int *p,就表示p要指向int型数据),编译器知道指针类型之后可以帮我们做静态类型检查。编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C语言给程序员提供的一种编译时的查错机制。
为什么C语言需要发明二重指针?原因和发明函数指针、数组指针、结构体指针等一样的。

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:

int **var;

当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:

二重指针的用法
二重指针指向一重指针的地址
二重指针指向指针数组

实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去

比如:

void find_max_and_min(int **pmax,int **pmin, int arr[]) {
	*pmax = *pmin = arr;
 
	int i;
	
	for(i=0;i<10;i++) {
		if(**pmax < arr[i]) {
			*pmax = arr+i;
		}
		if(**pmin > arr[i]) {
			*pmin = arr+i;
		}
	}
 
}

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

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

相关文章

手写VITE-MockJS插件

mockJS是什么 mockJS是模拟接口数据&#xff0c;拦截客户端的请求的一个工具。 vite插件编写流程 pnpm init 初始化 pnpm install vite -D 安装Vite 配置package.json文件中的脚本 "scripts": {"dev": "vite","build": "vite…

K8s集群离线安装-kubeadm-详细篇

1、部署k8s的两种方式&#xff1a;kubeadm 和二进制源码安装 #本次实验采用的部署Kubernetes方式&#xff1a; kubeadm Kubeadm是一个K8s部署工具&#xff0c;提供kubeadm init和kubeadm join&#xff0c;用于快速部署Kubernetes集群。2、环境准备 #服务器要求&#xff1a; 建…

JavaSE学习day1_02, JDK安装

1.4 环境变量 1.4.1 为什么配置环境变量 在初次学习编程的时候,环境变量我们经常提到,但是环境变量到底是什么?是干嘛的?弄清楚这些问题很重要. 如果我们想要在CMD的任意路径下都可以打开任意的软件&#xff0c;那么就需要把软件的路径配置到环境变量当中。 为了便于大家…

Zotero | 快速入门

文章目录0. 前言1. Zotero快速入门1.1 下载Zotero和Connector2.2 联动sci-hub实现英文文献批量下载2.3 英文文献翻译2.4 中文文献元数据识别2.5 Zotero其他配置2.5.1 语言切换2.5.2 数据存储位置更改参考0. 前言 Zotero是一款自由及开放源代码的文献管理软件&#xff0c;管理书…

告别2022,喜迎2023

2022只剩下最后几天&#xff0c;新的一年即将拉开序幕。你的2022&#xff0c;是苦尽甘来、柳暗花明&#xff0c;还是安适如常、平安喜乐?你会用什么词来形容你的2022&#xff0c;你期待的2023又是什么样的呢? 时光荏苒&#xff0c;岁月悠悠&#xff0c;转眼间&#xff0c;202…

猿代码超算实习生计划之编程语言分析

编程语言特别多&#xff0c;很多同学都特别纠结以后到底选择哪一种编程语言&#xff0c;才好找高薪实习和工作。 其实编程语言没有好坏之分。很多同学选择未来就业和实习方向时很多也都是参考的薪资和岗位数量。就像前几年很多人选的前端、现在卷到不行的Java、还有今年连实习…

【Cortex-A7核PWM实验】

Cortex-A7核PWM实验 ---蜂鸣器、风扇、震动马达PWN概念PWM硬件电路图如何产生PWM方波捕获/比较寄存器工作原理代码实现PWN概念 PWM是指脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一种常用的模拟信号转换为数字信号的方法。 1.脉冲&#xff1a…

C#WinForm实现多语言切换

因项目需要&#xff0c;所以在网上找了一些方法实现了该功能&#xff0c;本文也是做一个总结和记录。使用resx文件实现Winform多语言切换&#xff0c;以实现简体中文、英文、泰语的切换为例。如果后续需要增加其它语言的切换&#xff0c;只需要按照步骤重复操作即可。 效果图如…

【Kotlin】函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )

文章目录一、闭包概念二、Java 中函数作为参数的替代方案 ( 匿名内部类 )一、闭包概念 匿名函数 就是 Lambda 表达式 , 同时也是 闭包 , 三者的是相同的概念 ; 闭包意义 : 在 Java 中 , 通过 Package 包 , Class 类 , 将作用域区分开 , 将变量 定义在 不同的 包 或 类中 , 可…

阿里云创世纪之盘古传奇

文章目录飞天(Apsara)云计算平台简介面向私有云的Apsara Stack盘古横空出世盘古的架构盘古基本介绍盘古API基于C语言的SDK基于命令行的文件操作接口pu盘古中的目录和文件盘古目录盘古中的文件盘古中的文件类型盘古应用场景盘古的功能特性盘古主要性能盘古的数据安全盘古的边界盘…

Docker - Docker网络

一、Docker网络介绍 Docker是基于Linux Kernel&#xff08;内核&#xff09;的namespace&#xff0c;CGroups,UnionFileSystem等技术封装成的一种自定义容器格式&#xff0c;从而提供了—套虚拟运行环境。 1、namespace&#xff1a;用来做隔离的&#xff0c;比如pid[进程].、…

Java多线程案例之阻塞队列

文章目录一. 认识阻塞队列1. 什么是阻塞队列2. 生产者消费者模型3. 标准库中阻塞队列类二. 基于循环队列实现的简单阻塞队列1. 循环队列的简单实现2. 阻塞队列的简单实现一. 认识阻塞队列 1. 什么是阻塞队列 阻塞队列本质上还是一种队列, 和普通队列一样, 遵循先进先出, 后进…

291. 蒙德里安的梦想(状态压缩dp详解)

求把 NM 的棋盘分割成若干个 12 的长方形&#xff0c;有多少种方案。 例如当 N2&#xff0c;M4 时&#xff0c;共有 5 种方案。当 N2&#xff0c;M3 时&#xff0c;共有 3 种方案。 如下图所示&#xff1a; 输入格式 输入包含多组测试用例。 每组测试用例占一行&#xff0c…

龙芯机器JDK安装和配置

龙芯机器&#xff1a;[rootlocalhost j2sdk-image]# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c4 Loongson-3A R4 (Loongson-3A4000) 1800MHz龙芯机器JDK安装和配置下载地址&#xff1a;http://www.loongnix.cn/zh/api/java/选择JDK8&#xff0c;选择MIPS64进行下…

2-3进程管理-进程同步

文章目录一.进程同步、互斥二.实现临界区互斥的基本方法&#xff08;一&#xff09;软件实现方法&#xff08;二&#xff09;硬件实现方法三.互斥锁四.信号量机制五.经典同步问题&#xff08;一&#xff09;生产者-消费者问题&#xff08;二&#xff09;读者-写者问题&#xff…

流逝的一年

昨天远方的大哥打来了电话&#xff0c;我们聊了下近况。当他问及去年是否有新的著作问世&#xff0c;我不禁有些赧然&#xff0c;解释说还在学习中… 放下电话后&#xff0c;我陷入了思索&#xff1a;又是一年划上了句号&#xff0c;这一年我做了什么&#xff1f;我又有什么收…

数据库的一些基本概念

一、服务器&#xff1a;&#xff08;更正大家头脑中的一个错误认识&#xff09; 1、服务器是一种软件&#xff0c;不是硬件&#xff0c;不是计算机。 2、不同服务器负责调用不同类型的文件。 二、表文件、数据库、数据库服务器以及SQL语句&#xff1a; 1、表文件: …

一条 select 语句的执行过程

MySQL 从大方向来说&#xff0c;可以分为 Server 层和存储引擎层。而 Server 层包括连接器、查询缓存、解析器、预处理器、优化器、执行器等&#xff0c;最后 Server 层再通过 API 接口形式调用对应的存储引擎层提供的接口来执行增删改查操作。 如下即为一个简略的 select 语句…

Android动态运行时权限

android 6.0(API 级别 23)开始&#xff0c;android引入了运行时权限&#xff0c;应用安装时不向其授予权限&#xff0c;应用运行时向其授予权限。如果在运行时该功能没有动态地申请相应的权限&#xff0c;就会抛出SecurityException异常。 android的运行时权限的申请过程主要有…

C语言画一个正方体

程序截图 操作方法 鼠标拖动。左键拖动及滚轮能看到不同角度下正方体的形状&#xff0c;右键拖动能将最近的正方体顶点挪到这个投影面的相应位置。 按键控制。wasd 控制投影面旋转&#xff0c;ws 关于 x 轴旋转&#xff0c;ad 关于 y 轴旋转。 个人思路 首先投影面的确立需…