目录
前言:
1、 数组的概念
1.1 什么是数组
1.2 为什么学习数组?
2. ⼀维数组的创建和初始化
2.1 数组创建
2.2 数组的初始化
2.3 数组的类型
2.3.1 什么是数组类型?
2.3.2 数组类型的作用
3、 一维数组的使用
3.1 数组下标
3.2 数组元素的打印
编辑
3.3 数组元素的输入
4 一维数组在内存中的存储
4.1 内存单元地址
4.2 数组在内存中的存储
5、sizeof计算数组元素个数
6、二维数组的创建
6.1 二维数组的概念
6.2 ⼆维数组的创建
7、二维数组的初始化
7.1 完全初始化
7.2 不完全初始化
7.3 按行初始化
8. ⼆维数组的使用
8.1 ⼆维数组的下标
8.2 ⼆维数组的输入和输出
9、二维数组在内存中的存储
9.1 二维数组的存储
9.2 二维数组的实际应用
结语:
前言:
本篇文章将会介绍C语言中数组的相关知识,
1、 数组的概念
1.1 什么是数组
数组是一组相同类型元素的集合。
你可以这么理解什么是数组:想象一个书架,上面整齐地排列着同一类书籍,这就类似于一个数组。每本书的位置都是固定的,而且它们都属于同一类型(比如都是小说或者都是传记)。
特点:
•数组中存放的是1个或者多个数据,但是数组元素个数不能为0
• 数组中存放的多个数据,类型是相同的。
1.2 为什么学习数组?
在了解了什么是数组的情况下,我们又会产生一个疑问,为什么要引入数组这个概念呢?我们学习数组的目的是什么呢?
我们拿一个例子来说明:
存放一串整数,我们用代码可以怎么表示?
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = 3;
//如果我们要存1~100个数字呢?
//数组是一组相同类型的元素的集合
return 0;
}
如果我们要存1~100的数字,难道我们就这样写下去吗?那也太麻烦了,这时数组就应运而生了!
人类的进步有一部分就来自与人类的懒惰,因为我们想要更加方便的记录我们想要的东西,所以数组便被创造出来了,学习数组就是为了方便我们写代码。
当我们了解了为什么学习数组后,我们还需要知道该怎么使用它。
数组分为⼀维数组和多维数组,多维数组⼀般⽐较多⻅的是⼆维数组。
2. ⼀维数组的创建和初始化
2.1 数组创建
⼀维数组创建的基本语法如下:
1 type arr_name[ 常量值 ];
存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。
- type 指定的是数组中存放数据的类型,可以是: char 、 short 、 int 、 float 等,也可以自定义的类型。
- arr_name 指的是数组名的名字,这个名字根据实际情况,起的有意义就⾏。
- [] 中的常量值是⽤来指定数组的大小的,这个数组的大小是根据实际的需求指定就⾏。
比如说:
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”。
2.2 数组的初始化
数组的初始化:在创建数组的同时给数组的内容一些合理初始值(初始化)。
方式:数组的初始化一般使用大括号,将数据放在大括号中。
直接代码演示:
//完全初始化
int arr1[3] = { 1,2,3 };
char arr2[3] = { 'a',98,'c' };
//不完全初始化,剩余的元素,默认初始化为0
int arr3[10] = { 1,2,3 };
//[]括号里面也是可以不指定大小的
char arr4[] = { 'a','b','c'};
char arr5[] = { "abcdef" };
//错误初始化,初始化的值多于元素个数
int arr6[4] = { 1,2,3,4,5};
我们可以调试来辅助理解不完全初始化:
//不完全初始化,剩余的元素,默认初始化为0
int arr3[10] = { 1,2,3 };
我们可以看到 1 2 3是我们输入的初始值,剩下的全为 0
字符串也默认是 0
char arr[6] = { 'a','b','c'};
这里\0的 ASVLL 为 就为0,0就是\0。
注意:
1. 不完全初始化(所给元素比指定元素要少),剩余的元素,默认初始化为 0;
2. 数组如果初始化了,就可以省略掉数组的大小,那么编译器就会根据初始化的内容来自动推算数组的元素的个数;
3.初始化的值不能比数组的元素更多 { 初始值} <= [ 元素值 ]。
对于下面的代码要会区分在内存中是如何分配。
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
2.3 数组的类型
2.3.1 什么是数组类型?
数组也是有类型的,数组算是⼀种⾃定义类型,去掉数组名留下的就是数组的类型。如下:
1 int arr1[10];
2 int arr2[12];
3 char ch[5];
1 arr1数组的类型是 int [10]
2 arr2数组的类型是 int [12]
3 ch数组的类型是 char[5]
注意区分数组元素类型和数组类型:
拿第一个举例:
int arr1[10];
int 是数组元素的类型
int [10]是数组的类型 //10不能省略,10也是类型的一部分
刷题过程中要看清楚问的是数组元素类型还是数组类型。
问题:int [10]和 int [5] 这两个数组类型是否相同?
答案:不一样
2.3.2 数组类型的作用
int main()
{
int arr[10] = { 0 };
printf("%zd\n", sizeof(arr));//数组名
printf("%zd\n", sizeof(int[10]));//数组类型
printf("%zd\n", sizeof(int[5]))
return 0;
}
我们用数组名算数组大小,这里是 结果是40 的原因是 10个整型,一个整型 4 个字节。
而我们使用数组类型计算也能得到相同结果。可以更加确定数组类型是 int[10]
从打印结果我们也可以证明int [10]和 int [5] 这两个数组类型是把不同的。
sizeof函数是 C 语言和 C++语言中的一个运算符,用于计算数据类型或变量占用的内存字节数。
不知道大家有没有对 %zd\n" 有所疑惑,为什么这里要使用%zd 呢?
sizeof的返回值的类型是 size_t 对于该类型的打印占位符用%zd。就只有这个作用
3、 一维数组的使用
学习了⼀维数组的基本语法,⼀维数组可以存放数据,存放数据的⽬的是对数据的操作,那我们如何 使⽤⼀维数组呢?
3.1 数组下标
C语⾔规定数组是有下标的,下标是从 0 开始的,假设数组有 n 个元素,最后⼀个元素的下标是 n-1,下标就相当于数组元素的编号,如下:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
下标的作用:准确、快速地找到数组中特定位置的元素,从而对其进行操作和使用
辅助理解:
假设我们把 C 语言中的数组想象成一个书架。 这个书架上一格一格地摆放着很多书,每一格就相当于数组中的一个元素。 而下标呢,就像是每一格的编号。
比如说,我们有一个数组 int arr[5] = {10, 20, 30, 40, 50}; 这里的数字 0、1、2、3、4 就是下标。 如果我们想要找到并使用第三本书(也就是数字 30 ),我们就通过下标 2 来找到它(因为数组下标从 0 开始,所以第三格对应的下标是 2 )。 再比如,你想要从这个书架上取走最后一本书(数字 50 ),你就通过下标 4 来找到它。
所以,下标就是帮助我们准确、快速地找到数组中特定位置的元素,从而对其进行操作和使用。
那我们应该怎么找呢?
在C语⾔中数组的访问提供了⼀个操作符 [ ] ,这个操作符叫:下标引⽤操作符。
有了下标访问操作符,我们就可以轻松的访问到数组的元素了,⽐如我们访问下标为7的元素,我们就 可以使⽤ arr[7] ,想要访问下标是3的元素,就可以使⽤ arr[3] ,如下代码:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", arr[7]);// 打印结果8
printf("%d\n", arr[3]);// 打印结果4
}
注意:使用下标引用操作符时,一定要确保索引值在有效范围内,否则可能导致访问越界错误。
总结:
1.数组是使用下标来访问的,下标是从0开始的。
2.数组的大小可以通过计算得到。
int sz = sizeof(arr) / sizeof(arr[0]);//计算公式
3.2 数组元素的打印
接下来,如果想要访问整个数组的内容,那怎么办呢?
只要我们产⽣数组所有元素的下标就可以了,那我们使⽤for循环产⽣0~9的下标,接下来使⽤下标访 问就⾏了。 如下代码:
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
打印结果:
3.3 数组元素的输入
如果我们想要自己输入值,该怎么做?
如下代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
//输入
for(i=0; i<10; i++)
{
scanf("%d", &arr[i]);。//这里需要去地址,arr[i]是数组的一个元素,而不是数组名
}//输出
for(i=0; i<10; i++)
{
printf("%d ", arr[i]);//这里不要写成了arr,这样无法打印数组的内容
}
return 0;
}
注意:
- 数组名是地址,但数组元素不是地址。
- arr是数组名,数组名是数组的起始地址 printf("%d ", arr[i])不要写成了printf("%d ", arr)
- 输入的值超过循环的值时不会报错,只会取你循环的值,比如说你要循环10个值,而你输入了11个值,那么第11个元素就不打印。
结果演示:
4 一维数组在内存中的存储
4.1 内存单元地址
内存:内存会被划分为一个个的内存单元,一个单元的大小是1个字节,每个内存单元都有一个编号,这个编号就是地址。
辅助理解:
我们可以把 C 语言中的内存想象成一个超级大的停车场。
这个停车场被划分成了一个个的停车位,每个停车位就相当于一个内存单元,而每个停车位的大小是 1 个字节。
每一个停车位都有一个独特的编号,就像我们在现实中的停车场,每个车位都有一个标识号码一样。在 C 语言中,这个编号就是内存单元的地址。
比如说,我们要把一辆红色的车(代表一个数据)停到停车场里。我们需要知道哪个停车位是空的,这个空的停车位的编号(地址)就是我们要找的地方。
又比如,我们想要找到之前停在停车场里的那辆红色的车,就需要通过之前记住的那个停车位的编号(地址)去找到它。
所以,内存单元的地址就像是停车场里停车位的编号,帮助我们准确找到存储数据的位置
4.2 数组在内存中的存储
数组在内存中是连续存储的,这意味着数组中的元素在内存中是一个紧挨着一个排列的。
比如说,有一个整数类型的一维数组 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;
}
结果展示:
我们发现这里打印的地址比较长,不方便我们进行观察。
当我们使用x64的环境时,所打印的地址较长。
当我们使用x86的环境时,所打印的地址较短。
从上面结果我们可以发现地址打印出来的时候,是按照16进制的形式显示的。(16进制计算)
随着下标的增加,地址是由小到大变化的 。
5、sizeof计算数组元素个数
在遍历数组的时候,我们经常想知道数组的元素个数,那C语⾔中有办法使⽤程序计算数组元素个数 吗?
答案是有的,可以使⽤sizeof。
sizeof可以计算数组的大小
#include <stido.h>
int main()
{
int arr[10] = {0};
printf("%d\n", sizeof(arr)); //40
return 0;
}
//sizeof(arr) 计算的是数组的总大小
这⾥sizeof(arr) 输出的结果是40,计算的是数组所占内存空间的总大小,单位是字节。
一个int 是4个字节,这里有 10 个整型,所以最后打印出来的结果是40。
我们⼜知道数组中所有元素的类型都是相同的,那只要计算出⼀个元素所占字节的个数,数组的元素 个数就能算出来。这⾥我们选择第⼀个元素算⼤⼩就可以。
#include <stido.h>
int main()
{
int arr[10] = {0};
printf("%d\n", sizeof(arr[0])); //4
return 0;
}
//sizeof(arr[0]) 计算的是数组中一个元素的大小
接下来就能计算出数组的元素个数:
#include <stido.h>
int main()
{
int arr[10] = {0};
int sz = sizeof(arr)/sizeof(arr[0]);//元素总大小/单个元素大小
printf("%d\n", sz);
return 0;
}
这里的结果是:10,表⽰数组有10个元素。 以后在代码中需要数组元素个数的地方就不用固定写死了,使⽤上面的计算,不管数组怎么变化,计算出的大小也就随着变化了。
6、二维数组的创建
6.1 二维数组的概念
前⾯学习的数组被称为⼀维数组,数组的元素都是内置类型的,如果我们把⼀维数组做为数组的元 素,这时候就是⼆维数组,⼆维数组作为数组元素的数组被称为三维数组,⼆维数组以上的数组统称 为多维数组。
辅助理解:
想象一个围棋棋盘,它有横纵的线条划分出行和列,每个交叉点就可以看作是二维数组中的一个元素
6.2 ⼆维数组的创建
那我们如何定义二维数组呢?语法如下:
type arr_name[常量值1][常量值2];
例如:
int arr[3][5];
double data[2][8];
解释:上述代码中出现的信息
- 3表⽰数组有3⾏
- 5表⽰每⼀⾏有5个元素
- int表⽰数组的每个元素是整型类型
- arr是数组名,可以根据⾃⼰的需要指定名字 data数组意思基本⼀致。
7、二维数组的初始化
二维数组的初始化和以为数组一样,也是使用大括号初始话的。
7.1 完全初始化
1 int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
2 int arr[3][5] = {{1,2,3,4,5},{ 2,3,4,5,6}, {3,4,5,6,7}};//向这样使用{}分开也是可以的
向第一种这样排列元素,会先找前5个放在第一行,再找5个放在第二行,最后5个放在第三行。也就是会按顺序放
我们可以通过调试来理解
7.2 不完全初始化
如果所给的元素不够怎么办?
所给数组元素不够就是不完全初始化
1 int arr1[3][5] = {1,2};
2 int arr2[3][5] = {0};
调试结果
7.3 按行初始化
我们还有另一种方法排放元素
int main()
{
int arr[3][3] = { { 3,2},{1,1},{2,3} };//不完全初始化
return 0;
}
如果数据不够的时候,我们可以按照分区间的方法,把想要的数据放在适当的位置。
二维数组如果有初始化,行可以省略,列不能省略。
int arr[ ][4] = {{1,2},{3,4}};
8. ⼆维数组的使用
8.1 ⼆维数组的下标
当我们掌握了⼆维数组的创建和初始化,那我们怎么使⽤二维数组呢?
其实⼆维数组访问也是使⽤下标的形式的,⼆维数组是有行和列的,只要锁定了行和列就能唯⼀锁定 数组中的⼀个元素。
C语⾔规定,二维数组的行是从0开始的,列也是从0开始的,如下所示:
1 int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
图中最右侧绿⾊的数字表示行号,第⼀行蓝色的数字表示列号,都是从0开始的,比如,我们说:第2行,第4列,快速就能定位出7。
#include <stdio.h>
int main()
{
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
printf("%d\n", arr[2][4]);
return 0;
}
8.2 ⼆维数组的输入和输出
访问⼆维数组的单个元素我们知道了,那如何访问整个⼆维数组呢?
其实我们只要能够按照⼀定的规律产⽣所有的⾏和列的数字就行;以上⼀段代码中的arr数组为例,行的选择范围是0~2,列的取值范围是0~4,所以我们可以借助循环实现⽣成所有的下标。
#include <stdio.h>
int main()
{
int arr[3][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7 };
int i = 0;//遍历⾏
//输⼊
for (i = 0; i < 3; i++)//产生行号
{
int j = 0;
for (j = 0; j < 5; j++)//产生列号
{
scanf("%d", &arr[i][j]);//输入数据
}
}
//输出
for (i = 0; i < 3; i++)//产生行号
{
int j = 0;
for (j = 0; j < 5; j++)//产生列号
{
printf(("%d ", arr[i][j]);//输出数据
}
printf("\n");
}
return 0;
}
输入和输出的结果
9、二维数组在内存中的存储
9.1 二维数组的存储
二维数组可以理解为:一维数组的数组
在 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 ,依此类推。
这种连续存储的方式使得可以通过简单的地址计算来快速访问二维数组中的元素。
但需要注意的是,在处理二维数组时,要确保索引不越界,以免访问到非法的内存地址导致程序出错。
代码展示:
#include<stdio.h>
int main()
{
int arr[3][5] = { 0 };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
}
return 0;
}
输出结果:
从输出的结果来看,每⼀⾏内部的每个元素都是相邻的,地址之间相差4个字节,跨⾏位置处的两个元 素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以⼆维数组中的每个元素都是连续存放的
9.2 二维数组的实际应用
二维数组在实际编程中有许多应用场景,以下是一些常见的例子:
1. 图像处理:可以用来存储图像的像素信息,其中行和列分别对应图像的高度和宽度。 - 例如,灰度图像可以用二维数组存储每个像素的灰度值。
2. 矩阵运算:如线性代数中的矩阵相加、相乘等操作。 - 在科学计算、机器学习和数值分析中经常用到。
3. 电子表格:类似于 Excel 中的表格数据,可以用二维数组表示行和列的数据。
4. 地图表示:将地图划分为网格,用二维数组存储每个网格的相关信息,如地形、资源等。
5. 游戏开发: - 表示游戏中的棋盘、地图布局。 - 存储游戏中多个角色的位置和状态。
6. 座位安排:例如在会议室、教室等场景中安排座位。
7. 文本处理:分析文本的二维结构,如表格形式的文本。
这些只是二维数组的一些常见应用场景,实际上,只要数据具有二维的特性并且需要进行批量处理,都可以考虑使用二维数组来进行存储和操作。
结语:
本篇文章到这里就结束了,该篇文章讲了C语言中数组的相关知识,希望能够对大家有所帮助,后面后讲解数组相关例题,敬请期待!!!