什么是数组
数组是一种存储固定大小的相同类型元素的数据结构。
数组可以包含任意类型的元素,例如整数、浮点数、字符等。在内存中,数组通常是连续存储的,每个元素按照一定的间隔存储。
通过使用数组,可以有效地存储和访问一系列相同类型的数据。数组可以用于各种场景,例如存储学生的成绩、保存图像像素值等。
在C语言中,数组是一种重要的数据结构,并且具有高效的访问和操作特性。使用数组可以提高代码的可读性和可维护性。
一维数组
一维数组是指只有一个维度的数组,通常是一列或一行的数据元素组成。一维数组中的每个元素可以通过一个唯一的索引来访问。例如,一个包含5个整数的一维数组可以表示为 [1, 2, 3, 4, 5]。在许多编程语言中,一维数组可以通过定义一个变量名称和方括号([])来表示。
声明
C语言中的一维数组的声明方式为:
类型 数组名[大小];
其中,类型
表示数组元素的数据类型,数组名
表示数组的名称,大小
表示数组中元素的个数。
例如,声明一个包含5个整数的数组:
int numbers[5];
指定数组大小
需要注意的是,数组的大小是固定的,声明后不能改变。
如果需要动态的大小,可以使用指针和动态内存分配。
数组大小只能用整型常量(strlen()和sizeof()的返回值被视为整型常量,const int在C语言不是整型常量,但是在C++是)或者整型常量表达式
#define we 10
int a[we];
int b[sizeof(int)];
int d[3+4];
const int h=10;
int c[h];
//这在c语言是不可以的,但是在C++中是可以的
如果不确定数组的大小,可以使用省略号来让编译器根据初始化列表的项数自动推断数组大小:
int arr[] = {1, 2, 3, 4}; // 编译器会自动推断数组大小为4
初始化
在C语言中,一维数组的初始化可以通过以下几种方式实现:
逐个赋值:
使用循环或直接为每个元素赋值的方式进行初始化。例如,我们可以使用以下方式初始化一个整数数组:
int arr[5];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;
使用大括号初始化列表:
可以使用大括号来直接为数组赋初值,元素之间用逗号分隔。例如,我们可以使用以下方式初始化一个整数数组:
int arr[5] = {1, 2, 3, 4, 5};
这种方式可以在数组创建的同时完成初始化,更加简洁。
部分初始化:
可以只为数组的某些位置指定初始值,其余位置将自动赋值为0。例如,我们可以使用以下方式初始化一个整数数组:
int arr[5] = {1, 2};
这样,数组的前两个元素分别为1和2,后三个元素自动赋值为0。
指定初始化
这个特性可能在某些编译器上不支持(比如VS2022)
C语言中,可以使用指定初始化器来为数组的指定位置赋初值,其余位置将自动赋值为0。
指定初始化器的语法是在大括号中指定每个元素的值,并用逗号分隔。
例如,我们可以使用指定初始化器来初始化个长度为5的整数数组
int arr[5] = {[1] = 2, [3] = 4};
上面的代码中,数组的前两个元素分别为1和2,第三个元素为3,第四个元素为4,而第五个元素将自动赋值为0。
你可以根据需要选择性地指定初始化器来初始化数组的指定位置,而不必为每个元素都提供初值。这样可以使代码更加清晰和简洁,
无论使用哪种方式,只要保证赋值的元素个数和数组的长度一致即可完成初始化。
注意
如果在数组初始化时,会发生以下情况:
- 如果初始化列表的项数小于数组元素个数,则多余的元素会被自动初始化为0。
int arr[5] = {1, 2, 3}; // arr[3]和arr[4]会被初始化为0
- 如果初始化列表的项数大于数组元素个数,则会导致编译错误。
int arr[3] = {1, 2, 3, 4}; // 编译错误,初始化列表的项数超过了数组元素个数
因此,要确保初始化列表的项数与数组元素个数相匹配,以避免潜在的错误。
访问
C 语言中,数组通常是顺序存储的,在内存中按照连续的地址存储。
这意味着数组的元素是紧密排列的,可以通过指针和索引进行高效的访问
可以通过下标来访问数组中的元素,下标从0开始。
int a[5]={1,2,3,4,5};
例如,访问数组中的第一个元素:
a[0];//对应第一个元素
a[2];//对应第三个元素
a[4];//对应第四个元素
数组越界
在 C 语言中,数组的越界访问是一种错误的行为,指的是访问数组的索引超出了数组的有效范围。
这种行为是未定义的,会导致程序的不可预测行为,并且可能引发诸如程序崩溃、数据损坏、安全漏洞等问题。
例如,如果一个数组的大小为5,有效的索引范围是0到4。如果访问超出范围的索引,就会发生越界访问错误。
int arr[5] = {1, 2, 3, 4, 5};
int x = arr[10]; // 越界访问错误,arr 的有效索引范围是 0 到 4
为了避免越界访问错误,应该始终确保数组的索引在有效范围内。在编写代码时,应该特别注意循环和指针操作,以确保不会出现数组越界访问。
注意事项
1.声明数组时,[]内不能使用变量(但是可以定义变长数组)
也就是说,下面这种情况是不允许的
int a=9;
int b[a];
2.创建数组时既不给数组指定大小,也不初始化是不对的
int b[];//这是不对的
3.在创建数组时初始化数组
我们在创建数组时就应该养成初始化数组的习惯,
如果你想后面再对其进行赋值,那可以先将数组的值全设置为0;就像下面这么做
int b[10]={0};
4.不能把一个数组赋给另一个数组,可以把数组赋给指针
下面这种操作是不行的
int a[2] = { 3 };
int b[2] = a[2];
下面这种操作是可以的
int a[2] = { 3 };
int* b = a;
数组名的意义
在 C 语言中,数组名表示数组的首地址。它是一个常量指针,指向数组中第一个元素的地址。
我们可以举个例子
int a[10];
int* const b=&a[0];
这样子数组名a和指针b几乎是一样的了 ,即b不能改变但是可以改变*b
数组名的意义主要体现在以下几个方面:
-
访问数组元素:可以使用数组名和索引来访问数组中的元素。例如,对于数组
int arr[5] = {1, 2, 3, 4, 5};
,可以使用arr[0]
、arr[1]
等来访问数组中的元素。 -
数组作为函数参数:当数组作为函数参数传递时,实际上传递的是数组的地址。因此,函数内部可以通过数组名来访问和修改数组的元素,对数组的修改会影响到原始数组。
-
指针运算:数组名可以被视为指向数组首元素的指针。因此,可以使用指针运算来处理数组。例如,
arr + 1
表示数组第二个元素的地址,*(arr + 2)
表示数组第三个元素的值。 -
数组大小:使用
sizeof
运算符可以获取数组的大小。例如,sizeof(arr)
返回整个数组的大小(字节数),可以根据数组大小来进行内存分配和操作。
总之,数组名在 C 语言中具有指向数组首地址、访问数组元素、作为函数参数和指针运算等重要作用。它是与数组相关的重要概念之一。
指针创建一维数组
指针可以用来创建一维数组。创建一维数组的过程通常包括以下步骤:
- 声明指向数据类型的指针变量。
- 使用动态内存分配函数(例如
malloc
)为数组分配内存空间。 - 对数组进行赋值和操作。
- 使用完数组后,使用
free
函数释放内存空间。
下面是一个示例,展示如何使用指针创建一维数组:
#include <stdio.h>
#include <stdlib.h>
int main() {
int size;
printf("Enter the size of the array: ");
scanf("%d", &size);
// 动态分配内存空间
int* arr = (int*)malloc(sizeof(int) * size);
// 检查内存分配是否成功
if (arr == NULL) {
printf("Memory allocation failed.\n");
return 1;
}
printf("Enter the elements of the array:\n");
for (int i = 0; i < size; i++) {
scanf("%d", &arr[i]);
}
printf("The elements of the array are: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 释放内存空间
free(arr);
return 0;
}
在上述示例中,首先要求用户输入数组的大小。然后使用malloc
函数为数组分配大小为size
的内存空间。接下来,使用循环从用户获取数组的元素值,并打印出数组的元素。最后,使用free
函数释放数组的内存空间。
请注意,在使用完数组后,务必记得释放动态分配的内存空间,以防止内存泄漏。
一维数组传参
一维数组可以作为函数参数传递给函数。在函数定义中,可以声明一个接收一维数组的形参,并在函数调用时将数组作为实参传递给该函数。
那我们怎么设计这个函数接口呢?
我们先以传下面这个数组为例
int a[10];
我们要知道数组名代表数组首元素的地址,所以在设计参数时,我们不仅可以用数组形式表示,还可以用指针形式表示
所以以下四种函数接口设计都是等价的
int sum(int*ar);
int sum(int*);
int sum(int ar[]);
int sum(int []):
但是在函数定义中不能省略参数名,所以以下这两种函数函数定义等价
int sum(int*ar)
{
}
int sum(int ar[])
{
}
二维数组
在 C 语言中,二维数组是一个表格状的数据结构,它由多行和多列组成。可以理解为一个由行和列组成的矩阵(或者说是一个表格)。
二维数组的声明和初始化格式如下:
类型 数组名[行数][列数];
例如,声明一个 3 行 4 列的整型数组如下:
int myArray[3][4];
我们怎么理解呢?
这个声明表明为myArray数组内有3个int数组,每个数组里存着4个int型数据
存储形式
我们虽然说二维数组是有维度的,但是内存是线性存储的,也就是说二维数组还是跟一维数组的存储方式一样,都是线性的。
在内存中,二维数组是以连续的块存储的。可以将二维数组看作是一个一维数组,其中每个元素又是一个一维数组。
二维数组的第一行元素存储在内存的连续地址空间中,第二行元素紧接着存储在下一个连续地址空间中,以此类推。
我们可以这么理解:二维数组的元素是一维数组
声明
规则基本和一维数组一样
但是二维数组支持缺省
比如说不写行数是可以的
int a[][4];
但是不写列数和什么都不写是不可以的
int a[3][];
int b[][];
初始化
在C语言中,可以通过以下几种方法来初始化二维数组:
-
声明数组的同时进行初始化:
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
上述代码声明并初始化了一个2行3列的整数二维数组。第一行的元素为1、2、3,第二行的元素为4、5、6。
-
逐个为数组元素赋值:
int matrix[2][3];
matrix[0][0] = 1;
matrix[0][1] = 2;
matrix[0][2] = 3;
matrix[1][0] = 4;
matrix[1][1] = 5;
matrix[1][2] = 6;
上述代码先声明了一个2行3列的整数二维数组,然后逐个为数组元素赋值。
-
使用循环结构进行初始化:
int matrix[2][3];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
matrix[i][j] = i * 3 + j + 1;
}
}
上述代码使用了两个嵌套的循环,对每个数组元素进行赋值。这里使用了行索引和列索引的关系来计算每个元素的值。
需要注意的是,二维数组的维度必须在声明时确定,所以在初始化时需要保证每行的元素个数一致。
数组名
在C语言中,二维数组的数组名表示的是数组首元素的地址,但是通常二维数组被视为元素是一维数组的一维数组,所以二维数组首元素地址是第一个子数组的地址。
假设有一个二维数组matrix[3][4],其数组名为matrix。在使用数组名matrix时,它会被解释为第一个元素matrix[0]的地址,而matrix[0]相当于一个int一维数组,它的首元素地址就是matrix[0][0]的地址。
例如,以下代码片段演示了二维数组的数组名的用法:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("matrix[0]的地址:%p\n", matrix); // 输出matrix[0]的地址
printf("matrix[0][0]的地址:%p\n", &matrix[0][0]); // 输出matrix[0][0]的地址
return 0;
}
运行结果如下所示:
matrix[0]的地址:0x7ffe44df1820
matrix[0][0]的地址:0x7ffe44df1820
可以看到,matrix和&matrix[0][0]的值是相同的,它们都表示二维数组的第一个元素matrix[0]的地址。
二维数组作形参
在C语言中,可以将二维数组作为函数的形参传递。
我们要先理解二维数组数组名的意义:数组首元素(二维数组的首元素是个数组)地址,即一个数组指针
所以函数形参的形式可以设置为数组指针形式
有两种常用的方式来声明和传递二维数组作为函数形参:
使用具体的数组维度来声明形参
毕竟是数组,我们可以将函数形参设置为数组形式
int A(int arr[3][4]);//可以
int A(int arr[][4]);//可以
int A(int arr[][]);//不可以
示例
#include <stdio.h>
void printMatrix(int matrix[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printMatrix(matrix, 2);
return 0;
}
在上面的例子中,printMatrix函数的形参matrix声明为int matrix[][3],没有指定第一个维度的大小。这允许传递不同行数的二维数组,但是第二个维度必须为3。在函数内部,可以通过循环来访问和处理二维数组的元素。
使用指针形式的数组指针来声明形参
我们在上面谈过函数参数可以设置为数组指针——指向二维数组第一个子数组
也就是说,下面这种形式
int A(int (*a)[3]);
注意后面的3对应的是第一个子数组的元素个数
此外我们还可以借用别的方法
#include <stdio.h>
void printMatrix(int **matrix, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int *ptr[2];
ptr[0] = matrix[0];
ptr[1] = matrix[1];
printMatrix(ptr, 2, 3);
return 0;
}
在上面的例子中,printMatrix函数的形参matrix声明为int **matrix,并额外传递了行数和列数。在函数内部,使用双重指针形式的数组指针来访问和处理二维数组的元素。在main函数中,通过将二维数组的第一行和第二行的指针存储在ptr数组中,然后将ptr传递给printMatrix函数来实现二维数组的传递。