【手把手带你入门】数组的创建与使用(超详细解释,包括数组传参以及数组名的理解)

在这里插入图片描述

一维数组的创建和初始化

数组的创建

数组是一组相同类型元素的集合。
在这里插入图片描述

数组的创建方式如下:
在这里插入图片描述
如果直接运行代码,会发现程序会报错或者警告:
在这里插入图片描述

将上面这段代码进行调试:
在这里插入图片描述

注意,这里的数组元素个数必须为常量表达式:
在这里插入图片描述
在这里插入图片描述
在 C99 之后以上代码才允许运行通过,成为变常数组。

数组的初始化

数组的初始化即指:在创建数组的同时,给数组的内容一些合理的初始值。

我们在对数组进行初始化时用 { } 。
在初始化数组时要注意一下两种写法的区别:
在这里插入图片描述
但是当指定了数组的大小时,就不能给出比指定的个数更多的元素了:
在这里插入图片描述

字符数组的初始化

在对字符数组进行初始化时,有以下三种方式:
在这里插入图片描述
前面提到,数组在不进行初始化时,编译器默认放入随机值,但是如果是作为全局变量的数组,在不进行初始化时,数组中的元素默认放的是0。
在这里插入图片描述

一维数组的使用

在了解了数组的创建和初始化之后,以及结合前面在
【手把手带你入门】初识C语言(下)
中的介绍,我们对于一维数组的使用应该已经大致掌握。

对于数组的使用,我们用下标对数组进行访问

即: 数组名[ 下标 ]
例:arr[0] 访问arr数组中的第一个元素

而语法规定,数组的下标是从 0 开始的,如果我们要对数组中的元素进行访问,就要用到的是下标引用操作符:[ ],即对数组进行访问的操作符。

当我们知道数组的访问方式之后,我们可以访问其中的某个元素,也可以利用下标将数组中的元素打印出来。
在这里插入图片描述
在这里插入图片描述
但是这里的下标的数值是固定死的,如果我们对数组arr进行更改,加入了更多的元素,该代码还是只打印前十个元素。

所以这时候,我们可以用一个表示元素个数的变量来完成:
在这里插入图片描述
如果把数组稍作修改,该代码依然能完美打印数组中的每一个元素。
在这里插入图片描述
当然,我们以上的例子都是整型数组,但是实际上数组可以有很多种类型,如浮点型数组(数组元素的类型是浮点型)、结构体数组(数组元素的类型是就结构体类型)等等。

一维数组在内存中的存储

那么,一维数组在内存中到底是如何存储的呢?
接下来,我们通过一段代码来探讨:

首先猜测:数组的元素在内存中是连续存放的。

那么如果我们想知道它在内存中的布局是如何的,我们只需要把数组中的内一个元素的地址打印出来。
在这里插入图片描述
所以由此我们可以画出下图,并得出结论。
在这里插入图片描述
如果你觉得文章有用,记得点赞收藏关注一波哟!你的鼓励将是我巨大的动力!

二维数组的创建和初始化

接下来我们来看看二维数组的创建和初始化,二维数组其实是和一维数组非常相似的,但是它也有一些特别之处,下面我们一起来看看。

二维数组的创建

二维数组的创建其实就是比一维数组多了一维,这里大家可以类比一维坐标系和二维坐标系:一维坐标系的变量只有x,而二维坐标系的变量有x和y。

例如:

int arr[3][5];
char ch[5][6];
double d[4][7];

那么二维数组和一维数组到底有什么区别呢?
对比一维数组,我们可以通过画图来进行理解。
在这里插入图片描述
可以看到,一维数组通过画图表示出来就是一行,而二维数组在内存中则是多行的。

二维数组的初始化

同理,二维数组的初始化也是通过{ }来实现的。

第一种初始化

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

画图如下:
在这里插入图片描述
从上图可以看出,初始化的值将依次一行一行地填入数组中,未进行初始化的部分则默认初始化为0。

第二种初始化

我们知道,一维数组就是只有一行的数组,而二维数组就是有多行的数组,因此,我们可以将二维数组看成是多个一维数组的组成,此时,我们可以这样对二维数组进行初始化:

int arr2[3][5] = { {1,2,3},{4,5},{6} };

我们可以通过调试来看看这些元素在内存中的存放。
在这里插入图片描述
我们依然画个图来看看~
在这里插入图片描述
我们知道,一维数组在初始化之后,元素个数可以省略掉,那么二维数组可省略吗?
在这里插入图片描述
所以对于二维数组,在初始化之后,行可以省略,而列不能省略。如果不对数组进行初始化,则行和列都不能省略。

二维字符数组的初始化

二维字符数字的初始化有一下几种:

char ch1[4][6] = { 'a','b' };
char ch2[4][6] = { {'a'},{'b'} };
char ch3[4][6] = {"abc","def","ghi"};

在这里插入图片描述
二维的字符数组未初始化的部分,编译器也会默认初始化为0,而0在ASCII码表中对应的字符就是‘\0’。
在这里插入图片描述

二维数组的使用

接下来我们看二维数组的使用,数组的访问都是通过下标来进行的,所以如果我们打印一个二维数组的值,同样要通过其下标来依次打印。

所以我们通过两层循环来依次找到二维数组中每一行每一列的下标。代码如下:

#include <stdio.h>
int main()
{
	int arr[3][5] = { {1,2,3},{4,5,6},{7,8,9} };
	int i = 0;
	//二维数组每一行每一列的下标也是从0开始的
	for (i = 0; i < 3; i++)//行 0~2
	{
		int j = 0;
		for (j = 0; j < 5; j++)//列 0~4
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

程序运行结果如下:
在这里插入图片描述

二维数组在内存中的存储

二维数组在我们的想象中是一个有着几行几列的数组,那么它在内存中也是这样存储的吗?我们依然可以通过打印每个元素的地址来观察。

int main()
{
	int arr[3][5] = { {1,2,3},{4,5,6},{7,8,9} };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{   //打印每个元素的地址
			printf("&arr[%d][%d] = %p\n", i,j,&arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

运行程序:
在这里插入图片描述
在这里插入图片描述
所以虽然在我们的想象中,二维数组是有行有列的,但是本质上,二维数组在内存中也是连续存放的,因为我们就可以理解为什么在初始化二维数组的时候,行可以省略而列不能省略的问题了。

因为如果列省略掉了,那在内存中就不知道一行该放几个元素,下一行的元素就不知道该放在那里了,而知道一列放几个元素,我们就可以根据给出的元素来确定一个数组可以放多少行。

这时候我们再来看看一维数组和二维数组,我们是否可以把为数组的每一行理解为一个一维数组呢?

其实是可以的。

我们可以把arr[3][5]理解为一个大的一维数组,包含3个元素,每个元素是1个一维数组。
在这里插入图片描述

数组越界

编译器会根据数组的下标在内存中开辟空间。如果我们访问数组前面或者后面的一块空间,就造成了数组的越界。

所以数组的下标是有范围限制的,语法规定下标从0开始,如数组中有n个元素,则最后一个元素的下标就是n-1.

而如果我们不按套路出牌,访问下标小于0或者下标大于n-1的元素,就会造成数组的越界访问,超出数组访问的合法空间。
在这里插入图片描述
虽然上面的代码越界访问了,但是依然可以正常打印,是因为C语言本身是没有在语法层面上做下标越界的检查的,编译器也不一定会报错,所以为了避免在写出bug,我们在写代码的时候要自己做好数组是否越界的检查。

这里我们可以看到VS2019中是给出了警告的。
在这里插入图片描述
在这里插入图片描述
同样,二维数组的行和列也可能存在越界的问题。

如果你觉得文章有用,记得点赞收藏关注一波哟!你的鼓励将是我巨大的动力!

数组作为函数参数

当我们对一个数组进行操作的时候,我们可能不一定全部的操作都在主函数中完成,有时候我们需要用函数来对数组进行操作,这时候数组就要作为函数参数被传递到某个函数中去。

那么数组在传参时时如何传的呢?
下面我们用一个冒泡排序的函数来说明问题。

冒泡排序函数

用冒泡排序的方法把一个降序排序的数组排成升序。

什么是冒泡排序?

冒泡排序的核心是:将两两相邻的元素进行比较
具体过程:

  1. 将第一个元素和第二个元素进行比较
  2. 如果满足我们想要的顺序,则比较第二个和第三个元素
  3. 如果不满足我们想要的顺序,则交换这两个元素,使其满足我们想要的顺序。然后再比较交换后的第二个和第三个元素。
  4. 依据第2条和第3条依次往下比较,直到比较完最后一个元素。

下面我们给出一个降序的数组:

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

利用冒泡排序将它排成升序的,即:

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

下面我们画图来分析一下:
在这里插入图片描述
我们发现最开始9依次和后面的元素进行比较,最终来到最后的位置,然后我们将8和剩下的元素依次进行比较,最后8来到9的前面,每次冒泡排序到最后,都有一个数字到它最终的位置上,我们把这样的一个过程称为一趟冒泡排序。

函数设计

接下来我们来进行冒泡排序函数的设计。

首先封装函数:

#include <stdio.h>
void Sort(int arr[])//冒泡排序函数
{
	//冒泡排序的实现
}

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

通过上面的分析,我们知道:
一趟冒泡排序可以让一个元素到它最终应该在的位置上,每一趟冒泡排序的内部是相邻两个元素之间的两两比较。

所以这里我们有10个元素,最终应该进行9趟冒泡排序。
如果我们有n个元素,则应该进行n-1趟的冒泡排序。

void Sort(int arr[])//冒泡排序函数
{
	//冒泡排序的实现
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//冒泡排序的趟数:sz-1
	for (i = 0; i < sz-1; i++)
	{
		//实现一趟冒泡排序
	}
}

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

在第一趟冒泡排序中,是10个元素进行排序,则应该进行9次比较。
第二趟冒泡排序中,是剩下的9个元素进行比较,则应该比较8次。
依此类推,每一趟冒泡排序内部两两比较的次数依次递减。

void Sort(int arr[])//冒泡排序函数
{
	//冒泡排序的实现
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//冒泡排序的趟数:sz-1
	for (i = 0; i < sz - 1; i++)
	{
		//实现一趟冒泡排序
		int j = 0;
		//一趟冒泡排序中两两比较的次数:sz-1-i
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两两比较
			if (arr[j] > arr[j + 1])
			{
				//交换
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
			
		}
	}
}

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

这样一个用冒泡排序的程序就写完了,接下来我们来测试一下这段代码。

因为没有在屏幕上对数组进行打印,所以我们在调试中用监视窗口在看arr数组的值。
在这里插入图片描述

函数调用完之后,我们发现数组的排序并没有改变!
在这里插入图片描述
这是为什么呢?

下面我们还是调试程序,用F11进入函数内部看看到底是哪里出了问题。
在这里插入图片描述

问题分析

在这里插入图片描述
当程序走到这里时,我们就发现sz的值是1而不是10,问题已经出现了!
在这里插入图片描述
那么为什么sz的值是1呢?

我们可以看到,在VS2019中,sizeof(arr)这一块下面划了一道波浪线,所以问题难道出现在了这儿?

没错,问题就出在这儿!
在这里插入图片描述
在上图中,我们可以看到arr的类型是int*,说明数组名arr的本质是一个地址,而sizeof(arr)计算的是一个地址的大小,值是4,所以最终计算出来的sz的值是1。

那么我们再来看看数组名arr,arr是一个地址,那它是谁的地址呢?是整个数组的地址吗?
在这里插入图片描述
从上图我们可以得出结论,数组名arr是首元素地址。
但如果数组名是首元素地址,那么我们打印出的sizeof(arr)应该是多少呢?
在这里插入图片描述
在这里打印的sizeof(arr)的值为什么不是4而是40呢?

40说明sizeof(arr)计算的是整个数组的大小,这里的数组名arr表示的是整个数组。

原来,在通常情况下,数组名就是数组首元素的地址。
但是有两个例外:

  1. sizeof(数组名),这里的数组名表示的是整个数组。
  2. &数组名,这里数组名也表示整个数组,取出的是数组的地址。

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

而在上面的代码中,我们传过去的参数arr本质上就是一个地址。

//实际参数的本质是一个地址
void Sort(int* arr)//但是我们为了便于理解,写成了这样:
void Sort(int arr[]);
//数组传参,用数组接收
//但本质上我们传过去的是数组首元素的地址

所以虽然上面我们提到了两个例外,但是当数组名作为参数传给函数Sort时,函数内部就是用一个名为arr的指针接收的,这时候再在函数中用sizeof(arr)就只能是求一个指针的大小,因此我们不能直接在函数内用sizeof来求数组的大小。

一点改动

那么,上面那段实现冒泡排序的代码应该如何更改呢?

在弄清楚了问题的始末之后,一切就变得很简单啦!

我们只需要在函数外部把数组的个数求出来,再把它作为参数传过去就行啦!

最终代码如下:

#include <stdio.h>
void Sort(int arr[],int sz)//冒泡排序函数
{
	//冒泡排序的实现
	int i = 0;
	//冒泡排序的趟数:sz-1
	for (i = 0; i < sz - 1; i++)
	{
		//实现一趟冒泡排序
		int j = 0;
		//一趟冒泡排序中两两比较的次数:sz-1-i
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两两比较
			if (arr[j] > arr[j + 1])
			{
				//交换
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
			
		}
	}
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//在函数外部求数组个数,再把它作为参数传过去
	int sz = sizeof(arr) / sizeof(arr[0]);
	Sort(arr,sz);
	return 0;
}

测试代码:
在这里插入图片描述
数组变为升序,说明冒泡排序成功!~

通过上面代码的实现,相信你对于数组的创建与使用,包括数组传参和数组名的本质一定已经有了更深的理解。正文到这里就暂时告一段落啦~

接下来就要用一些实例把数组真正应用起来~
届时将会写两个简单的小游戏,让我们的代码真正有意思起来!一起期待吧!
在这里插入图片描述

关注我,学习更多实用的C语言知识吧!~
在这里插入图片描述
本文所涉及的源代码均已整理上传至本人的gitee中,欢迎友友们按需自取~
https://gitee.com/fang-qiuhui/my-code/blob/2021.8.22/blog_2021_8_22_array/blog_2021_8_17_array.c

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值