引言:
在前几篇文章中,我们深入探讨了函数的奥秘。在讲述函数知识的过程中,我们邂逅了一个新的概念,你或许还记得在演示 strcpy函数时,出现的这行代码:char1[20]={0};。当时,你是否感到好奇,心中是否升起了疑问:这是什么呢?没错,这正是我们本篇文章的主角——数组!
在数字的世界里,数组就像是一座有序排列的宝库,它以一种简洁而高效的方式存储和管理着大量的数据。但你是否真正了解它的奥秘呢?让我们一起踏上探索数组的旅程吧!
1、一维数组
1.1 数组的创建
数组是一组相同类型元素的集合。
你可以这么理解什么是数组:想象一个书架,上面整齐地排列着同一类书籍,这就类似于一个数组。每本书的位置都是固定的,而且它们都属于同一类型(比如都是小说或者都是传记)。
在了解了什么是数组的情况下,我们又产生一个疑问,为什么要引入数组这个概念呢?
我们拿一个例子来解释一下:
存放一串整数,我们用代码可以怎么表示?
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
//如果我们要存1~100个数字呢?
//数组时一组相同类型的元素的集合
return 0;
}
如果我们要存1~100的数字,难道我们就这样写下去吗?那也太麻烦了,这时数组就应运而生了!
当我们了解了为什么需要数组后,我们还需要知道该怎么使用它。
数组的创建方式:
type_t arr_name [const_n];
// type_t 是指数组的元素类型// arr_name是指数组的名
// const_n 是一个常量表达式,用来指定数组的大小
比如说:
int arr[10];//整型数组,名为arr [10]指存放了10个元素
double data[20];
char ch[5];//字符数组,名为ch [5]指存放五个字符
也可以同时创建多个相同类型的数组
int arr 1 [10];
int arr 2 [2+8];
这里你或许还会有新的疑问, [ ] 这里面只能用常量或常量表达式吗?
但是呢,并不是所有情况下都可以使用变量,比如
补充知识点:
在C99标准之前,数组的大小必须是常量或者常量表达式;
在C99之后,数组的大小可以是变量,这是为了支持变长数组;
变长数组的意思是 数组的大小是通过变量来指定的。
上图中的代码想要实现该怎么办呢?
#include <stdio.h>
int main()
{
int n = 10;
scanf("%d",&n);
int arr[n];
return 0;
}
但是呢,这里还有一个限制条件,就是只能在支持C99标准的编译器上编译。
支持C99的编译器:
- GCC:GNU Compiler Collection 的缩写,它在其编译器集合中提供了 C 编译器,支持 C99 标准。GCC 是一款广泛使用的开源编译器。
- Clang:基于 LLVM 的 C 编译器,支持 C11 标准,同时也对 C99 有较好的支持。
- Intel C++ Compiler:英特尔的 C++编译器,也支持 C99 标准。
- Keil:在 Keil 编译器中,可通过相关设置使其支持 C99(变量声明在执行语句之后)。具体操作是在“Options for Target”中的“C/C++”选项卡下,勾选“C99 Mode”。
1.2 数组的初始化
数组的初始化:在创建数组的同时给数组的内容一些合理初始值(初始化)。
直接代码演示:
int arr1[10] = { 1,2,3 };//不完全初始化,剩余的元素,默认初始化为0
int arr2[] = { 1,2,3,4 };
int arr3[5] = { 1,2,3,4,5 };//完全初始化
char arr4[3] = { 'a',98,'c' };
char arr5[] = { 'a','b','c'};
char arr6[] = { "abcdef" };//[]括号里面也是可以不指定大小的
注意:
1. 不完全初始化(所给元素比指定元素要少),剩余的元素,默认初始化为 0;
2. 括号[ ]里面是可以不指定大小的,这样的话该数组大小(指定值)就等于所给的元素(初始化)。
数组在创建的时候如果不想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
但是对于下面的代码要会区分,内存中如何分配。
char arr1[10] = { 'a','b','c'};
char arr2[10] = { "abc" };
对于arr1,里面的字符为:a b c 0 0 0 0 0 0 0
对于arr2,里面的字符为:a b c \0 0 0 0 0 0 0
1.3 一维数组的使用
对于数组的使用,我们要介绍了一个操作符: [ ] , 下标引用操作符。它其实就是数组访问的操作符。
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//编号 0 1 2 3 4 5 6 7 8 9
//编号是0开始的,这里的编号就是数组的下标
printf("%d\n", arr[4]);
return 0;
}
打印结果是:
图中可知打印 arr[4]的结果,我们可以得到整数5
使用下标引用操作符arr[i]
来访问数组中索引为 i
的元素。
如果我们想把数组的内容全部打印出来,那么范围该怎么确定?
范围不需要我们计算,使用sizeof函数会自动帮我们计算范围。
代码如下:
int main()
{
int arr[] = { 1, 2, 3 ,4 ,5 ,6, 7,8 ,9,10 };
//下标范围 0 9
//printf("%d\n", arr[4]);
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//自动计算范围 左边计算的时总数组的大小 右边的是第一个元素大小
for (i = 0; i < sz; i++)//正序打印
{
printf("%d ", arr[i]);//i是0时打印1,i是1时打印2...
}
for(i = sz-1;i >= 0;i--)//倒序打印
{
printf("%d ", arr[i]);
}
return 0;
}
下标用来定位数组
注意;使用下标引用操作符时,一定要确保索引值在有效范围内,否则可能导致访问越界错误。
总结:
1.数组是使用下标来访问的,下标是从0开始的。
2.数组的大小可以通过计算得到。
int sz = sizeof(arr) / sizeof(arr[0]);//计算公式
1.4 一维数组在内存中的存储
数组在内存中是连续存储的,这意味着数组中的元素在内存中是一个紧挨着一个排列的。
比如说,有一个整数类型的一维数组 int arr[5] = {1, 2, 3, 4, 5} 。
注:地址是16进制的
在 C 语言中,一个整数通常占用 4 个字节的内存空间。
假设这段连续存储空间的起始地址为 0*1000 ,由于在 C 语言中,一个整数通常占用 4 个字节的存储空间。那么,数组中的第一个元素将被存储在地址 0*1000
,第二个元素紧接着存储在地址 0*1004
,第三个元素在 0*1008
,第四个元素在 0*100C
,第五个元素则在 0*1010
。
这种连续存储有两个重要的特点和影响:
一方面,它使得随机访问数组元素的速度非常快。比如说,如果想要获取第三个元素,只需要通过简单的计算 起始地址 + 元素大小 * 索引 (也就是 1000 + 4 * 2 = 1008 ),就能直接找到并访问到第三个元素 3 ,几乎不需要额外的时间去查找。
另一方面,它也带来了一些不便。比如说,如果要在数组中间插入一个新元素,那就需要把插入位置后面的所有元素都向后移动,以腾出空间插入新元素。这是一个比较耗时的操作。同样,删除数组中间的元素时,也需要把后面的元素向前移动来填补空缺。
为了更加直观的解释,我们来用表格辅助理解:
假设有一个整数数组int num[3] = {10,20,30 } ,其在内存中的存储情况如下:
内存地址 | 存储的值 |
0 * 2000 | 10 |
0 * 2004 | 20 |
0 * 2008 | 30 |
如果现在要在第二个位置插入一个新元素 15 ,那么原有的 20 和 30 都需要向后移动 4 个字节,变成:
内存地址 | 存储的值 |
0 * 2000 | 10 |
0 * 2004 | 15 |
0 * 2008 | 20 |
0 * 200C | 30 |
同样,如果要删除第二个元素 15 ,则 20 和 30 需要向前移动:
内存地址 | 存储的值 |
0 * 2000 | 10 |
0 * 2004 | 20 |
0 * 2008 | 30 |
综上所述,C 语言中一维数组在内存中的连续存储方式在提供快速随机访问的同时,也在插入和删除操作上带来了一定的复杂性。
当我们知道这一特点后,有助于我们在编程实践中根据具体需求合理地选择和使用数组,或者考虑其他更适合特定操作的数据结构。
所以,理解 C 语言中一维数组在内存中的连续存储方式,对于我们有效地使用数组、优化程序性能以及避免一些常见的错误(比如内存越界访问)都非常重要。
代码展示:
int main()
{
int arr[] = { 1, 2, 3 ,4 ,5 ,6, 7,8 ,9,10 };
//下标范围 0 9
//printf("%d\n", arr[4]);
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;
}
结果展示:
随着数组下标的增加,元素的地址,也在有规律的递增。
2、二维数组
二维数组可以看作是由多个一维数组组成的数组
2.1 二维数组的创建
//数组创建
int arr[3][4];
char arr[3][3];
double arr[1][2];
//3 2 1
//1 2 3
//3 1 2
int main()
{
int arr[3][3];
return 0;
}
2.2 二维数组的初始化
int main()
{
int arr[3][3] = { 3,2,1,1,2,3,3,1,2 };//完全初始化
return 0;
}
这样排列元素,会先找前三个放在第一行,再找三个放在第二行,最后三个放在第三行。也就是会按顺序放
如果所给的元素不够怎么办?
int main()
{
int arr[3][3] = { 3,2,1,1,2,3,3};//不完全初始化
return 0;
}
我们可以看到少的位置被自动补上了 0
我们还有另一种方法排放元素
int main()
{
int arr[3][3] = { { 3,2},{1,1},{2,3} };//不完全初始化
return 0;
}
如果数据不够的时候,我们可以按照分小组的方法,把想要的数据放在适当的位置。
二维数组如果有初始化,行可以省略,列不能省略。
int arr[ ][4] = {{1,2},{3,4}};
2.3 二维数组的使用
代码展示:
#include <stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
int i = 0;
for (i = 0;i < 3; i++)//确定行
{
int j = 0;
for (j = 0; j < 4; j++)//确定列
{
printf("%d", arr[i][j]);
}
printf("\n");
}
return 0;
}
结果展示:
也可以打印其中的单个元素
printf("%d\n",arr[2][0]);
//结果:3
2.4 二维数组在内存中的存储
二维数组可以理解为:一维数组的数组
在 C 语言中,二维数组在内存中是按照行优先(row-major order)的方式连续存储的。
假设我们有一个二维数组 int arr[2][3] ,其在内存中的存储方式类似于将其看作一个一维数组。 先存储第一行的所有元素,然后再存储第二行的元素。
也就是说,内存中元素的排列顺序是 arr[0][0] 、 arr[0][1] 、 arr[0][2] 、 arr[1][0] 、 arr[1][1] 、 arr[1][2] 。
以具体的内存地址为例,如果 arr[0][0] 的地址为 1000 ,且每个 int 类型占用 4 个字节,那么 arr[0][1] 的地址就是 1004 , arr[0][2] 的地址是 1008 , arr[1][0] 的地址是 1012 ,依此类推。
这种连续存储的方式使得可以通过简单的地址计算来快速访问二维数组中的元素。
但需要注意的是,在处理二维数组时,要确保索引不越界,以免访问到非法的内存地址导致程序出错。
2.5 二维数组的实际应用
二维数组在实际编程中有许多应用场景,以下是一些常见的例子:
1. 图像处理:可以用来存储图像的像素信息,其中行和列分别对应图像的高度和宽度。 - 例如,灰度图像可以用二维数组存储每个像素的灰度值。
2. 矩阵运算:如线性代数中的矩阵相加、相乘等操作。 - 在科学计算、机器学习和数值分析中经常用到。
3. 电子表格:类似于 Excel 中的表格数据,可以用二维数组表示行和列的数据。
4. 地图表示:将地图划分为网格,用二维数组存储每个网格的相关信息,如地形、资源等。
5. 游戏开发: - 表示游戏中的棋盘、地图布局。 - 存储游戏中多个角色的位置和状态。
6. 座位安排:例如在会议室、教室等场景中安排座位。
7. 文本处理:分析文本的二维结构,如表格形式的文本。
这些只是二维数组的一些常见应用场景,实际上,只要数据具有二维的特性并且需要进行批量处理,都可以考虑使用二维数组来进行存储和操作。
结语:
本篇文章即将落下帷幕,在这篇文章中,我们共同探索了一元数组与二元数组的奥秘。在下篇文章中,我们将继续深入挖掘数组知识的精髓,期待您的持续关注与阅读。让我们共同期待,在知识的海洋中,不断探索,不断前行。