【C语言初阶】带你玩转C语言中的数组,并逐步实现冒泡排序,三子棋,扫雷

news2025/4/6 18:23:34

在这里插入图片描述

君兮_的个人主页

勤时当勉励 岁月不待人

C/C++ 游戏开发


数组的使用

  • 前言
  • 一维数组
    • 1.一维数组的定义
      • 数组的分类
    • 2.数组的初始化
      • 第一种越界情况
    • 3.数组的使用
      • 数组的下标:
      • 第二种越界情况
    • 4.数组在内存中的存储
  • 二维数组
    • 1.二维数组的创建
    • 2.二维数组的初始化
    • 3.二维数组的使用
    • 4.二维数组在内存中的存储
  • 数组作为函数参数的实际应用
    • 1.冒泡排序
      • 错误设计
        • 数组名是什么?
          • 小小总结一下
      • 正确做法
    • 2.三子棋游戏
    • 3.扫雷游戏
  • 总结

前言

Hello,这里是君兮_,今天为大家带来的是在C语言对数组的详解,废话不多说我们直接开始吧。

一维数组

1.一维数组的定义

  • C语言中给了数组的定义:一组相同类型元素的集合
  • 数组的创建:

type_t arr_name [const_n];
type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小

我们有时可能会看到这种数组:

int count = 10;
int arr2[count];//数组时候可以正常创建吗?
  • 注:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
  • 简单来说就是要看你的编译器,为了防止写出bug,我建议一般创建数组是还是不要这么写了。

数组的分类

  • 根据元素类型的不同,数组大致可以分为三类:
  • 整型数组
                               int arr1[10]
  • 字符数组
                              char arr2[10]
  • 浮点型数组
                            double arr3[10]

2.数组的初始化

  • 数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
  • 话不多说,直接看代码:
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};//完全初始化
int arr2[10] = { 1,2,3 };//不完全初始化,剩余的元素默认都是0
int arr3[10] = { 0 };//不完全初始化,剩余的元素默认都是0
int arr4[] = { 0 };//省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
char arr7[]={"abcdef"}//与arr6一个意思,arr6是它的缩写
  • 数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。

第一种越界情况

但是对于下面的代码要区分,内存中如何分配

int main()
{
	char arr1[] = {"abc"};
	char arr2[] = {'a','b','c'};
	printf("%s\n",arr1);
	printf("%s", arr2);
}
  • 代码结果如图:
    在这里插入图片描述
  • 为啥会出现这种情况?
    注意:当我们以%s形式打印上面数组内容时,遇到’\0’才会停止。第一个数组中的“abc”是一个字符串,在后面有一个我们看不见的’\0’,而第二个数组中是三个字符‘a’ ‘b’ ‘c’,后面是没有’\0’的,此时我想告诉你的是,你的第二个数组越界了!!此时这个数组后面存放的是随机值,打印出来就是随机的乱码,而直至它越界访问的地方出现0即’\0’,才会停止打印。
  • 证明一下:
    在这里插入图片描述
  • 说明我们上面的说法是没错的。

3.数组的使用

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

数组的下标:

1.数组是使用下标来访问的,下标是从0开始。
2. 数组的大小可以通过计算得到

int arr[10];
int sz = sizeof(arr)/sizeof(arr[0]);//用整个数组的大小除以第一个元素的大小得到的就是数组中元素的个数

第二种越界情况

  • 数组的下标是有范围限制的。

  • 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。

  • 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

  • C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,

  • 代码如下:

int main()
{
	int arr[10] = { 0 };//数组的不完全初始化
	//计算数组的元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	//对数组内容赋值,数组是使用下标来访问的,下标从0开始
	int i = 0;
	for (i = 0; i < 10; i++)//这里写10,可以吗?
	{
		arr[i] = i;
	}
	//输出数组的内容
	for (i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
  • 这样改可以吗?
for (i = 0; i <=10; i++)
  • 改完结果:
    在这里插入图片描述
  • 这里连编译都走不过去,为什么?
  • 越界访问!
  • 注意:
  • 1 当你写完上面的代码后,我想问问你,你的数组初始化时有几个元素呢?
int arr[10] = { 0 };//最多10个
  • 2 数组的下标是从0开始的,也就是说最后一个元素的下标应该是9,即arr[9]。
  • 3 当你把i的条件改为i<=10后,你会惊奇的发现,你的下标竟然能出现arr[10]!你数组里根本没有这个元素,这不是又越界吗了?
  • 总结:
  • 切记切记,上面两种越界情况是经常出现的,尤其是第二种。
  • 我们经常在循环时因条件设置有误而导致越界,当代码多了之后,这种错误是非常难以发现的。我费这么大篇幅讲这两种越界情况就是为了提醒你们在以后使用数组的时候一定要小心谨慎,要多考虑考虑是否会出现越界情况,避免一个bug要找半天(博主的切身经历)

4.数组在内存中的存储

讲完了应用,我们再来讲讲数组的存储

  • 示例代码如下:
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
  int sz = sizeof(arr)/sizeof(arr[0]);
 
for(i=0; i<sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);//打印每个元素的地址
}
return 0;
}

在这里插入图片描述

  • 由于该数组为int型数组,每个元素占四个字节,即每一元素地址对应+4,实际还是从低地址到高地址连续存放

在这里插入图片描述

  • 仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
  • 由此可以得出结论:数组在内存中是连续存放的。

二维数组

1.二维数组的创建

int arr[3][4];
char arr[3][5];
double arr[2][4];
  • 就像棋盘的行与列一样,我们也可以把二维数组理解为有几行,而每一行又能放多少元素,即多少列。

2.二维数组的初始化

//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
  • 和一维数组的初始化差不多,但是需要注意的是行的大小在初始化时可以省略,但是列的则不行。
  • 就像棋盘,你可以不知道这个棋盘有几行,但是你必须得知道一行中会有几个元素,,否则你怎么确定下一行该从哪开始呢?

在这里插入图片描述

3.二维数组的使用

  • 与一维数组类似,这里不做过多展开,举例说明
int main()
{
	int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{5,6,7,8,9} };
	//printf("%d\n", arr[2][3]);
	int i = 0;
	//行号
	for (i = 0; i < 4; i++)
	{
		//每一行有五列
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);//0 1 2 3 4
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

4.二维数组在内存中的存储

咱们上面拿棋盘比喻二维数组是为了方便大家理解,当然你平时使用时就把它当棋盘想其实也没啥毛病,下面我们来讲讲二维数组在内存中真正的存储方式。

  • 像一维数组一样,这里我们尝试打印一下二维数组的每个元素
  • 代码如下:
int main()
{
	int arr[4][5] = { 0 };
	int i = 0;
	//行号
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
		}
	}
	return 0;
}
  • 结果如下:

在这里插入图片描述

  • 和一维数组类似,但是又和我们刚才所说的棋盘不太一样
  • 来画图理解一下:

在这里插入图片描述

  • 也就是说,二维数组在内存中也是连续存储的。

另外,注意:

  • 二维数组的行与列也是有可能发生越界的,在写代码时千万要注意。

数组作为函数参数的实际应用

1.冒泡排序

  • 什么是冒泡排序?
  • 把一个无序数组的元素从左向右比较,如果左边元素比右边的元素大,就交换这两个元素的位置,继续与下一个右边元素比较直至把该无序数组排成一个元素由小到大的数组(也就是升序数组)。因此冒泡排序也叫升序排序法。

错误设计

void bubble_sort(int arr[])
{
	int sz = sizeof(arr) / sizeof(arr[0]);//这样对吗?
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	bubble_sort(arr);//是否可以正常排序?
	int i = 0;
	for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}


  • 以上代码能实现我们想要的效果吗?
  • 不妨试试:
    在这里插入图片描述
  • 我们从结果上可以发现它只交换了一次元素就停止了,这是为什么?
  • 别急,想明白上面的代码,我们得先看懂下面的这段代码。

数组名是什么?

int main()
{
	int arr[10] = { 1,2,3,4,5,6 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);//数组的地址
	printf("%p\n", &arr+1);//+1,跳过整个数组

	printf("%d\n", sizeof(arr));
	return 0;
}
  • 你现在能分别告诉我上面这些打印的数组的含义吗?

  • 这里就不卖关子了,我们来看下结果:
    在这里插入图片描述

  • 先分析上面四行代码的结果

  • 我们可以看到,上面四行代码中打印出来地址的结果竟然一模一样,这是为什么呢?

  • 上面两行可能不太好理解,但是中间的两行我们知道呀。

printf("%p\n", &arr[0]);//打印该数组首元素地址
printf("%p\n", &arr[0]+1);//首元素地址+1
  • 也就是说,在这里的arr表示的是首元素的地址!!!
  • 那这两行呢?
printf("%p\n", &arr);
printf("%p\n", &arr+1);
  • 取arr的地址,那么取的到底是整个数组的地址还是首元素的地址呢?
  • 我们来分析一下:
  • 我们发现&arr与&arr+1只有后两位有差异,那么它们差多少呢?
  • 注意咯,此时在我们屏幕上打印的地址是16进制的,可千万不敢把它们的差值当成60-38了。

正确的算法:
6 * 16^ 1-3 * 16^ 1- 8 * 16 ^0=40
由初始化可知这是一个有10个元素的整型数组,也就是说&arr+1跳过了整个数组。

  • 那么我们就知道了,&arr其实表示的是该数组的地址。
  • 同理,下面的sizeof(arr)=40计算整个数组的大小也可知此时也是整个数组的地址
小小总结一下
  • 1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
  • 2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
  • 除此1,2两种情况之外,所有的数组名都表示数组首元素的地址。

  • 弄明白数组名的含义,我们再回到我们的错误设计中,再来想想,咱们到底错在哪里呢?
int sz = sizeof(arr) / sizeof(arr[0])
  • 把这个放进冒泡函数中,真的对吗?
  • 根据我们上面的分析,我们可以知道此时传进冒泡函数的arr只是首元素的地址,那当我们用sizeof计算该数组中元素时,结果只能是1。因此在冒泡函数眼里,该数组只有一个元素需要交换,因此就出现了上面的那一幕,只改变了1,3的顺序。

正确做法

当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。
所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。
那么,函数内部的 sizeof(arr) 结果是4。
如果第一种方法错了,该怎么设计?

  • 代码如下:
void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
	int i = 0;
		for (i = 0; i < sz - 1; i++)
		{
			int j = 0;
			for (j = 0; j < sz - i - 1; j++)
			{
				if (arr[j] > arr[j + 1])
				{
					int tmp = arr[j];
					arr[j] = arr[j + 1];
					arr[j + 1] = tmp;
				}
			}
		}
	
}
int main()
{
	int i;
	int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);//直接把元素个数传进去
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

  • 成功!!!

2.三子棋游戏

  • 写过一篇相关博客具体介绍该怎么实现,链接如下:
    【C语言】三子棋详解(包教包会的那种)

3.扫雷游戏

  • 也有一篇相关博客具体介绍,感兴趣的可以去看看!
  • 链接如下:
    【C语言】万字教学,带你分步实现扫雷游戏(内含递归函数解析),剑指扫雷,一篇足矣

总结

  • 今天的内容就到此结束啦,数组中我见过以及遇到过的所有知识以及易错点都在里面了。
  • 如果你有任何疑问欢迎在评论区指出或者私信我,咱们下次再见啦!

新人创作不易,如果今天的内容对你有所帮助的话,请点个三连再走吧,你们的支持就是我更新的动力,再次感谢大家的支持!!!
在这里插入图片描述

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

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

相关文章

vue3新特性与vue2的不同点对比

前端必备工具推荐网站(免费图床、API和ChatAI等实用工具): http://luckycola.com.cn/ 一、vue3是什么? Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c; 并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效…

计算机网络管理-实验4(三) 使用Wireshark 抓取MIB-2中sysUpTime对象的SNMP报文管理信息结构

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

【win11+Visual Studio 2019 配置 PCL 1.12.1 的经验总结分享】

点云pc库的下载与安装参考另外一篇文章&#xff0c;链接&#xff1a; https://blog.csdn.net/weixin_47869094/article/details/131270772?spm1001.2014.3001.5501 各种教程里面这都很好&#xff0c;就不赘述了&#xff0c;当然&#xff0c;这里也给出一个个人认为不错的安装…

移动互联网行业就业率除车载外,还有“该”的岗位在暴涨!

从2013年进入到移动互联网时代&#xff0c;在Android系统广泛应用在手机厂商中&#xff0c;App承载了我们生活工作的方方面面&#xff0c;原来需要在PC或者线下才能做的事情&#xff0c;现在点一点手指就可以做到了。这类方便也带来更多的系统安全危害。正所谓魔高一尺道高一丈…

一.《UE4奥丁》人物最大属性

​寻找突破口 1.续上节课,此时看到标题,有基础的同学第一反应就是,老师用CE搜索血量,通过改变就能找到&#xff01; 2.额,有这么简单吗&#xff01; 3.既然写了这个帖子,肯定是有原因的 4.为了方便学习,我们就先按照同学们的思路来试一试,能不能找到最大属性,比如最大血&am…

mysql索引方面的知识

1. 查看表的索引 show index from 表名 ex: 重点看Key_name这一列的值, 可以看到只有一个主键, 目前该表还没有创建索引, 接下来进行索引的创建 2.给表添加索引 create index idx_time_classtype_sip_sport on event_20230508(time,classtype,sip,sport) 说明: 上面index后…

【论文精读】DELS-MVS

今天读的是发表在WACV2023上的文章&#xff0c;第一作者来自于格拉茨技术大学。 文章链接&#xff1a;DELS-MVS: Deep Epipolar Line Search for Multi-View Stereo 文章目录 Abstract1. Introduction2. Related Work3. Algorithm3.1 Depth estimation via epipolar residual3.…

【换脸详细教程】手把手教你进行AI换脸:换脸流程及源码详解

目录 1. 换脸基本原理2 人脸检测及可视化3. 人脸轮廓点检测及可视化4. 人脸图像变换--仿射变换5. 生成遮罩并直接替换人脸6. 人脸颜色校正 最近AI换脸貌似比较火爆&#xff0c;就稍微研究了一下相关了内容。AI换脸是一个娱乐性比较强的应用&#xff0c;这种错位感让人觉得非常有…

搭建高性能数据库集群之一:主从复制

一、概述 1. 数据库主从概念、优点、用途   主从数据库是什么意思呢&#xff0c;主是主库的意思&#xff0c;从是从库的意思。数据库主库对外提供读写的操作&#xff0c;从库对外提供读的操作。 数据库为什么需要主从架构呢&#xff1f;   高可用&#xff0c;实时灾备&am…

手把手教你撸一个接口自动化测试平台(一)

项目构思&#xff1a;开发一个web版的接口自动化测试平台 功能包括&#xff1a;接口导入、自动化测试用例生成、自动化测试报告、可持续集成 项目框架&#xff1a;django vue mysql 技术要求&#xff1a;熟悉django后台开发、熟悉vue开发 第一节&#xff1a;搭建django 项…

被ChatGPT戏耍的周末

被ChatGPT戏耍的周末 1. 被ChatGPT戏耍全过程2. 拆穿ChatGPT的把戏3. AIGC与内容安全 1. 被ChatGPT戏耍全过程 电动垂直起降飞行器&#xff08;eVTOL&#xff0c;Electric Vertical Takeoff and Landing&#xff09;技术越来越成熟&#xff0c;为了解下相关产品我周末打开了Cha…

OpenGLES:相机实时滤镜四宫格、九宫格

一.概述 今天继续OpenGLES的学习 今天在之前博文《OpenGLES&#xff1a;GLSurfaceView实现Android Camera预览》 的基础上&#xff0c;使用OpenGLES实现相机 四宫格滤镜 和 九宫格滤镜。 二.四宫格 先定义几个名词&#xff1a; 之前博文中实现的相机普通预览叫&#xff1a;…

【人工智能】“AI + 算力 = 最强龙头”,你怎么看?

文章目录 一、AI 与算力相辅相成1.1 AI 和算力的概念1.2 AI 和算力的应用领域1.3 AI 需要算力的支持1.4 AI 和算力的结合带来的巨大价值1.4.1 人脸识别1.4.2 语音识别1.4.3 自动驾驶1.4.4 医疗诊断1.4.5 自然语言处理 二、AI算力催生“最强龙头”2.1 “最强龙头”的概念2.2 AI …

Unity核心4——SpriteShape

Sprite Shape 是精灵形状的意思&#xff0c;它主要是方便我们以节约美术资源为前提&#xff0c;制作 2D 游戏场景地形或者背景的 ​ 在 Window --> Package Manager 中搜索 2D&#xff0c;选择 2D Sprite Shape&#xff0c;导入项目 一、Sprite Shape Profile 精灵形状概述文…

FPGA基础知识-用户自定义原语

目录 学习目标 学习内容 1.UDP的组成 2.UDP定义规则 3.表示组合逻辑的UDP 4.表示时序逻辑的UDP 5.UDP表中的缩写符号 6.UDP设计指南 学习时间 学习总结 学习目标&#xff1a; 提示&#xff1a;这里可以添加学习目标 理解编写UDP的规则,明白UDP的各个组成部分。 学…

设计师常用的网页设计素材网站大全

设计师不仅需要源源不断的灵感&#xff0c;还需要与时俱进的网页设计素材。 本文推荐4个非常不错的设计素材网站 即时设计资源社区 ​即时设计资源社区是国内优秀的网页设计素材网站&#xff0c;内置阿里、字节、腾讯、京东、谷歌、华为等设计系统&#xff0c;超过3000UI组件…

DDoS攻击导致Azure和Outlook中断

微软已经证实&#xff0c;最近Azure、Outlook和OneDrive门户网站的中断是由于针对该公司服务的第7层DDoS攻击造成的。 这些攻击是由微软追踪到的一个名为Storm-1359的攻击组织造成的&#xff0c;他们自称是匿名苏丹。 故障发生在6月初&#xff0c;Outlook.com的网络门户在6月…

分布式配置中心Apollo中Namespace的类型整理

Namespace的类型 Namespace类型有三种&#xff1a; 【1】私有类型 【2】公共类型 【3】关联类型&#xff08;继承类型&#xff09; &#xff08;1&#xff09;私有类型 私有类型的Namespace具有private权限。例如上文提到的“application” Namespace就是私有类型。 &…

【计算机网络】运输层端口号、复用与分用

1、复用和分用 2.端口号 3.举例 4.详细学习视频 https://www.bilibili.com/video/BV1c4411d7jb?p58&vd_source621b166d35a3636b23f3c4d270272c53

WSL子系统启动报错 Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE

今天琢磨着WindowsLinux子系统研究研究新东西&#xff0c;结果当我启动WSL时却出现了下面的提示&#xff1a; WSL启动报错 由于未安装所需的特性&#xff0c;无法启动操作。 Error code: Wsl/Service/CreateInstance/CreateVm/HCS_E_SERVICE_NOT_AVAILABLE问题排查 于是分析…