数组
数组
苏丙榅1. 数组概述
在C语言中,数组是一种存储多个相同类型元素的数据结构
。数组在内存中是连续存储数据的,通过索引访问和操作数组中的元素。
在C语言中,数组使用一个中括号来描述其固定的容量,格式如下:
1 | 数据类型 变量名[数组大小]; |
按数组元素类型的不同,可分为:数值数组
、字符数组
、指针数组
、结构数组
等类别。
1 | int a[10]; // 数值数组, 可以存储 10 个整形数值 |
通常情况下,数组元素下标的个数也称为维数
,根据维数的不同,可将数组分为一维数组、二维数组、三维数组、四维数组等。通常情况下,我们将二维及以上的数组称为多维数组
。
2. 一维数组
2.1 定义和使用
想要定义一个一维数组,有以下几个知识点需要我们注意:
数组名字符合标识符的书写规定(
数字、英文字母、下划线
)数组名
不能与其它变量名相同
,同一作用域内是唯一的方括号
[]
中常量表达式表示数组元素的个数int a[3]
表示数组a
有3
个元素数组的
下标从0开始计算
,因此3个元素分别为a[0],a[1],a[2]
定义数组时
[]
内最好是常量,使用数组时[]
内可以是常量,可以是变量- 在定义数组时 C99 标准允许在
[]
指定变量,但是有些编译器不支持这种语法,比如VS
,使用GCC
编译是没问题的。 - 数组的使用指的是
通过下标
读写数组中的元素的值
- 在定义数组时 C99 标准允许在
1 |
|
- 第 4 行:定义了一个数组,名字叫a,有10个成员,每个成员都是int类型
没有 a 这个变量,a 是数组的名字,但不是变量名,它是常量
- 数组名本身被视为指向数组第一个元素的指针(地址)常量
- 数组的10个成员分别是:a[0]…… a[9],没有a[10]
- 第 7 行:通过
for
循环依次给数组的各个元素赋值 - 第 12 行:通过
for
循环依次打印数组中各个元素的值
程序输出的结果如下:
1 | 100 101 102 103 104 105 106 107 108 109 |
2.2 数组名
关于数组名需要再次对其进行剖析,它(数组名)是一个地址的常量,代表数组中首元素的地址
:
数组名相当于单元楼,坐落于某个固定位置
数组元素相当于单元楼中的住户
数组下标相当于单元楼内住户的门牌号
定义一个数组相当于盖一座单元楼,很显然楼盖好了是不能挪地方的,所以数组被定义出来之后,数组名对应的内存地址也不允许挪地方,因此是常量,常量就是只读,不允许修改。
1 |
|
- 第 4 行:定义数组并给元素初始化
- 第 5 行:计算数组占用内存的大小,10个int类型,10 * 4 = 40
- 第 6 行:计算数组下标为 0 的元素占用内存大小,第 0 个元素为 int,4
- 第 7 行:打印数组的地址
- 第 8 行:打印数组中下标为 0 的元素(第一个元素)的地址
程序输出的结果如下:
1 | a = 000000A0E80FF8B8 |
通过输出的日志信息再次给大家强调这句话:数组名对应的地址和数组中第一个元素的地址是相同的。
2.3 初始化
在定义数组的同时进行赋值,称为初始化。全局数组若不初始化,编译器将其初始化为零。局部数组若不初始化,内容为随机值。
1 | int a1[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; |
a1[10]
:定义一个数组,同时初始化所有成员变量a2[10]
:初始化前三个成员,后面所有元素都设置为0a3[10]
:所有的成员都设置为0a4[]
:定义了一个数组,有5个成员[]中不指定元素个数,定义时必须初始化, 否则无法确定数组的固定大小
局部变量
在 C 语言中,局部变量有如下特点:
- 局部变量是在函数或块内
{}
部声明的变量,它们只在声明它们的函数或块的范围内可见。 局部变量在其所在的函数或块的生命周期内存在,并且每次进入该函数或块时都会重新创建
。- 局部变量必须在使用之前先声明,并且可以被初始化。
- 局部变量的作用域仅限于声明它们的函数或块。
以下是一个示例,展示了局部变量的使用:
1 | #include<stdio.h> |
在上述示例中,一共有如下几个局部变量:
x
是myFunction
函数内部的局部变量,只能在该函数内部可见和使用,作用域范围是3~12行
y
是if
结构内部的局部变量,作用域范围是6~9行
,只能在该范围内部可见和使用index
是main
函数内部的局部变量,只能在该函数内部可见和使用,作用域范围是15~20行
全局变量
在 C 语言中全局变量有如下特点:
- 全局变量是在所有函数外部声明的变量,它们在程序的整个执行过程中都有效。
- 全局变量在程序开始执行时创建,在程序结束时销毁。它们在内存中的位置固定不变,全局变量会自动进行初始化,值为 0。
- 全局变量可以在任何函数中访问和修改,如果全局变量跨文件访问需要使用
extern
关键字声明它们。 - 全局变量的作用域跨越整个程序。
1 |
|
在上述示例中,一共有如下2个全局变量,可以在程序的任何地方使用它们:
- 第 3 行:定义全局变量
index
但是没有初始化,默认初始化为 0 - 第 4 行:定义全局变量
x
并且初始化为 10
2.4 一展身手
学了数组之后,我们就能够处理更多的、更复杂的数据,满足更多的需求,下面来看几个例子。
2.4.1 找最大值
定义并初始化一个整形数组,找出数组中值最大的那个数。
1 |
|
2.4.2 数组逆置
定义并初始化一个数组,将数组中的数据首尾颠倒,比如:
- 原始数据:1, 2, 3, 4, 5, 6
- 逆置之后:6, 5, 4, 3, 2, 1
1 |
|
2.4.3 冒泡排序
冒泡排序规则:
- 比较相邻的两个元素。从序列的第一个元素开始,比较第一个和第二个元素的大小。
- 如果第一个元素大于第二个元素,则交换它们的位置,把较大的元素向后移动。
- 继续比较相邻的元素,逐个向后移动,直到比较到序列的倒数第二个元素和最后一个元素。
- 重复上述步骤,直到整个序列按照从小到大(或从大到小)的顺序排列。
冒泡排序的核心思想是通过反复交换相邻的元素来实现排序,每一轮遍历都会将当前未排序部分中最大(或最小)的元素交换到正确的位置上
。重复执行这个过程,直到整个序列有序。
1 |
|
在上述示例中,我们使用两层循环来遍历待排序序列,并通过比较相邻元素交换位置,将较大的元素逐步向后移动,最终实现对给定数组的升序排序。输出结果为 11 12 22 25 34 64 90
。
3. 二维数组
3.1 定义和使用
在 C 语言中,二维数组是由多个一维数组按照一定规律排列而成的数据结构。它可以被看作是一个表格或矩阵。二维数组的声明和使用如下所示:
1 | 类型说明符 数组名[常量表达式1][常量表达式2]; |
常量表达式1
表示第一维下标的长度,对应矩阵的行常量表达式2
表示第二维下标的长度,对应矩阵的列
1 | int array[12][6]; // 定义一个12行6列的数组 |
关于二维数组的命名规则和一维数组是一样的,下图定义了一个3行4列的数组,数组名为a
其元素类型为整型,该数组的元素个数为3×4个,即:
二维数组a
是按行进行存放的,先存放a[0]
行,再存放a[1]
行、a[2]
行,并且每行有4个元素
,也是依次存放的。
二维数组在概念上是二维的:其
下标在两个方向上变化
,对其访问一般需要两个下标
。在
内存中并不存在二维数组
,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的
。
1 |
|
- 第 4 行:定义了一个二维数组,
4行3列
,在内存中要连续存储 12 个元素 - 第 11 行:通过两个下标访问二维数组中的某个元素,并给其赋值
- 第 20 行:通过两个下标访问二维数组中的某个元素的值
3.2 数组名
和一维数组的名字一样,二维数组名也是一个地址的常量,代表数组中首元素的地址
。
1 |
|
- 第 4 行:定义了一个二维数组,3行4列,共12个元素
- 第 5 行:打印
二维数组的地址
,数组名为数组首元素地址
- 第 6 行:打印
第 0 行的首(起始)地址
,即:a[0]- 每一行的一维数组都有4个整形元素,在此记作
int[4]
- 可以将
a[0]
视为第一行一维数组的名字,一维数组的名字对应的就是一个地址 - 基于上面一条进行类推,打印第
2、3、4
行一维数组的起始地址对应的名字就是a[1]、a[2]、a[3]
- 每一行的一维数组都有4个整形元素,在此记作
- 第 7 行:打印
第0行0列的首(起始)地址
,即:&a[0][0]
- 上文说到
a[0]
代表第一行的一维数组,这个一维数组有4个元素,如果取出这个一维数组中的第一个元素就应该写成这样:a[0][0]
- 如果想要得到数组元素的地址,方法和获取变量的地址相同,即在数组元素名字或者变量名前加取地址符
&
- 上文说到
- 第 10 行:计算二维数组占用的内存大小
- 第 11 行:计算二维数组的一行占用的内存大小
- 第 12 行:计算二维数组的一个元素占用的内存大小
- 第 14行:计算二维数组的行数
- 第 15 行:计算二维数组的列数
- 第 16 行:计算二维数组的总元素个数
程序打印的结果如下:
1 | a = 000000ECF89EFBB8 |
通过输出的信息我们可以得出这样一个结论:二维数组的地址和二维数组首行的起始地址以及二维数组第一个元素的地址是内存中的同一个位置(重合)。
3.3 初始化
二维数组的初始化和一维数组类似,下面举例说明:
1 | int a1[3][4] = |
a1
:分段赋值,按照逻辑结构对行、列分别赋值,每行对应一个{}
a2
:连续赋值,数据在内存中就是以这种形式存储的a3
:可以只给部分元素赋初值,未初始化的部分自动被设置为 0a4
:所有的成员都设置为0a5
:行对应的[]
中不指定元素个数,定义时必须对数组初始化列对应的[]不允许为空
3.4 一展身手
需求:使用二维数组进行班级小组学生成绩统计。
假设该小组有5人,考试科目为:语文、数学、英语
- 求各科平均分
- 求各科不及格的人数
1 |
|
4. 多维数组(了解)
多维数组的定义与二维数组类似,其语法格式具体如下:
1 | 类型 数组名 [n1][n2]…[nn]; |
对于3维以及以上维度的数组在编程的时候是很少、很少、很少用的,下面以三维数组举例:
1 | int a[3][2][3]; |
上面定义了一个三维数组,数组的名字是a
:
- 数组的长度为3,每个数组的元素又是一个二维数组
- 二维数组的长度是2,并且这个二维数组中的每个元素又是一个一维数组
- 一维数组的长度是3,元素类型是 int。
实例中的三维数组在内存中的存储结构如下图:
1 |
|
5. 字符数组
5.1 定义
C语言中,没有字符串这种数据类型,可以通过char的数组来替代,字符串一定是一个char的数组,但char的数组未必是字符串
:
- 以数字
0
(字符‘\0’
的 ascii 值为 0)结尾的char
数组就是一个字符串 - 如果
char
数组没有以字符\0
结尾,那么它就不是字符串,只是一个普通的字符数组 - 字符串是一种特殊的
char
类型数组。
1 |
|
程序打印的结果如下:
1 | c1 = h ello烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫 |
- 第 4 行:定义并初始化一个
char
类型的一维数组,不是字符串 - 第 5 行:打印有乱码,因为字符数组末尾没有
’\0’
结束符 - 第 6 行:定义并初始化一个
char
类型的一维数组,是字符串 - 第 7 行:打印没有乱码,因为字符数组末尾以字符
’\0’
结束 - 第 8 行:定义并初始化一个
char
类型的一维数组,是字符串 - 第 9 行:打印没有乱码,因为字符数组末尾以字符
’\0’
结束,如果字符串中有多个\0
结束符,数据读到第一个结束符就结束了,会忽略后边的内容,因此world
没有被输出到屏幕。
5.2 初始化
字符数组的初始化和前面讲到的数组的初始化方式类似,下面举例说明:
1 |
|
程序输出的结果如下:
1 | buf = abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫 |
buf1
:不指定长度, 没有0结束符,元素个数就是数组长度- 将这个字符数组数据按照字符串打印,因为没有
\0
结束符,将数组后面内存中的数据也一并读出来了,所以会尾部会出现乱码
- 将这个字符数组数据按照字符串打印,因为没有
buf2
:指定长度,后面没有赋值的元素,自动补0buf3
:将所有元素赋值为0buf4
:数组元素个数大于数组的容量,数组越界- 数组越界相当于访问了非法内存,可能会导致程序异常退出
buf5
:定义并初始化了一个指定长度的字符数组,没有\0
结束符buf6
:定义并初始化了一个指定长度的字符数组,有\0
结束符- 第四个字符的整型值为
0
,也就是字符\0
,此处会进行隐式类型转换
- 第四个字符的整型值为
buf7
:定义并初始化了一个指定长度的字符数组,有\0
结束符- 结束符为数组的第4个字符,
\0
后的字符在打印的时候会被舍弃
- 结束符为数组的第4个字符,
buf8
:使用字符串初始化字符数组,编译器会自动在尾部补\0
,推荐buf9
:使用字符串初始化字符数组,编译器会自动在尾部补\0
,但是'\0'
后面最好不要连着数字,有可能几个数字连起来刚好是一个转义字符'\ddd'
八进制字义字符,后面两个dd的范围是 0~7
'\xdd'
十六进制转移字符,后面两个dd的范围是 0~9,a~f,A~F
\012
相当于\n
5.3 一展身手
现有两个字符串str1 和 str2,请将这两个字符串合并,将最终的结果存储到一个字符数组dst中。
1 |
|