首页 > 编程笔记 > C语言笔记 阅读:88

C语言数组的定义和使用(图文并茂,非常详细)

在 C语言中,数组是一种非常重要的数据类型,能够存储多个同类型数据,本节我将带大家深入学习数组的相关知识。

初识C语言数组

数组是由一系列相同类型的数据对象依次排列组成的。这些数据对象被称作数组的元素。

例如,下图展示了一个由 int 类型组成的数组,用于存放多个 int 类型的数据。


图 1 数组排布

数组有两个重要的特点:
在之前学习的知识中,我们可以用如下代码声明各种类型的单个变量。
char c;
int n;
long l;
float f;
double df;
既然数组是由一系列相同类型的数据对象依次排列组成的,那么声明数组至少要提供以下三个参数:
声明数组的公式如下图所示:


图 2 声明数组的公式

数组的声明由数组名、元素类型、元素数量组成。例如:
char c[5];
int n[10];
long l[3];
float f[2];
double df[1];

C语言数组初始化

在声明变量时,我们讨论过初始化和赋值的区别:
int n = 100;  //  初始化为100
n = 100;      //  赋值为100
在上面的代码中,第一行声明了一个 int 类型的变量 n,并将其初始化为 100;第二行代码将值 100 赋予了变量 n。

初始化和赋值的区别在于:初始化时,等号左边为变量的声明,等号右边为初始值;而赋值时,等号左边为变量,等号右边为新值。

在初始化中,等号并不是赋值运算符,而只是作为赋值操作符的一种语法形式。

变量不能被多次初始化,否则会导致重复定义错误。例如,下面的代码会引发错误。
int n =100;
int n = 123;  //  错误,变量已被重复定义
然而,对变量进行多次赋值是可以的。例如,下面的代码是正确的。
int n;
n = 123;
n = 456;
对于基本数据类型,赋值和初始化似乎没有明显的差别,二者都是将一个值装入变量中。然而,对于我们接下来要讨论的数组,赋值和初始化之间存在一些差异。

数组的初始化和基础数据类型的初始化类似,也是在声明变量时使用等号并在其右侧写入需要为数组初始化的值,如下图所示:


图 3 数组初始化公式

根据上图编写一个数组初始化示例:
int arr[10] = {1, 2 ,3 ,4 ,5 ,6, 7, 8, 9, 0};
上述代码声明了一个由 10 个 int 类型数据对象组成的数组,并将数组初始化为 1、2、3、4、5、6、7、8、9 和 0。具体的数组内容如下图所示:


图 4 数组内容

在数组初始化时,等号右侧被称为初始化列表。初始化列表指定数组的元素应该被初始化为哪些值,并用逗号将它们分隔开,最后用花括号将它们括起来。

1) 初始化列表长度小于数组长度

如果初始化列表的长度小于数组长度,例如:
int arr[10] = {1, 2 ,3 ,4 ,5};
那么剩下的元素将会被填充为 0,如下图所示:


图 5 初始化列表长度小于数组长度

2) 初始化列表长度大于数组长度

如果初始化列表的长度大于数组长度,例如:
int arr[10] = {1, 2 ,3 ,4 ,5 ,6, 7, 8, 9, 10, 11};
那么最后一个值 11 将无法被初始化到任何一个元素上,因此代码将无法编译通过。

3) 让初始化列表决定数组长度

如果需要初始化一个数组的多个值,但又懒得数清楚有多少个值,则可以在数组声明的方括号中不写任何数字。这样,数组的长度将由初始化列表确定,例如:
int arr[] = {1726, 838, 938, 138, 58, 82, 83, 343, 456, 534, 645, 8938, 9382, 83, 343};

访问数组元素

数组已经被初始化完成,现在我们需要访问这个数组中的各个元素。要访问数组中的某个元素,可以使用如下图所示的公式:


图 6 访问数组中元素的公式

通过使用“数组名[下标]”的形式,我们可以访问数组中的元素。

需要注意的是,数组下标是从 0 开始计数的。当使用 0 作为下标时,我们访问的是数组中的第一个元素。需将从 0 开始计数作为一条规则,如下图所示:


图 7 数组下标

下面的示例展示了如何访问数组中的各个元素:
#include <stdio.h>
int main()
{
    int arr[10] = {1, 2 ,3 ,4 ,5 ,6, 7, 8, 9, 0};
    printf("%d", arr[0]); // 第 1 个元素
    printf("%d", arr[1]); // 第 2 个元素
    printf("%d", arr[2]); // 第 3 个元素
    printf("%d", arr[3]); // 第 4 个元素
    printf("%d", arr[4]); // 第 5 个元素
    printf("%d", arr[5]); // 第 6 个元素
    printf("%d", arr[6]); // 第 7 个元素
    printf("%d", arr[7]); // 第 8 个元素
    printf("%d", arr[8]); // 第 9 个元素
    printf("%d", arr[9]); // 第 10 个元素
}
运行结果为:

1234567890

1) 遍历数组的循环

由于数组下标是递增的,因此使用 for 循环可以更方便地访问每个元素,代码如下:
for(int i = 0; i < 10; i++)
{
    printf("%d\n", arr[i]);      // 访问下标为 i 的元素
}
在 for 循环中,i 从 0 递增到 9。使用 arr[i] 可以访问下标从 0 到 9 的元素。

当然,while 循环也可以达到同样的效果,代码如下:
int i = 0;
while(i < 10)
{
    printf("%d\n", arr[i++]);      // 访问下标为 i 的元素
}
这里特别将 i++ 放在函数调用中。表达式 i++ 的结果为 i 当前的值,所以第一次求 i++ 的值的结果为 0,而后缀自增表达式会在稍后将 i 加 1。这样写同样能够访问下标为 0~9 的元素。

你如果愿意,也可以将 i++ 写在单独的一行上,代码如下:
int i = 0;
while(i < 10)
{
    printf("%d\n", arr[i]);      // 访问下标为 i 的元素
    i++;
}

由于数组中的元素是可以访问的,因此也可以修改数组中的元素。下面的代码展示了如何修改数组中的元素:
int arr[10] = {0};        // 所有元素均被初始化为 0
printf("%d\n", arr[5]);   // 输出第 6 个元素的值
arr[5] = 123;            // 为第 6 个元素赋值为 123
printf("%d\n", arr[5]);   // 输出第 6 个元素的值
运行结果为:

0
123

总结:使用赋值表达式可以为数组中的元素赋一个新值。赋值公式为:
数组名[下标] = 表达式;

2) 小心数组越界

如果数组只有 10 个元素,但我们访问或修改了这 10 个元素以外的元素,那么结果是未定义的。这意味着程序可能看上去可以运行,但是实际的运行结果却异常或奇怪。

警告:切勿越界访问或修改数组元素。

由于 C语言编译器无法检查数组是否越界,因此编译时无法发现此类问题。例如,下面的代码是错误的:
int arr[10] = {0};       // 所有元素都被初始化为 0
printf("%d\n", arr[10]); // 应访问下标为 0~9 的元素,访问下标为 10 的元素则属于越界访问

// 如果使用循环遍历数组,则需要特别注意循环条件
for(int i = 0; i <= 10; i++)
{
    printf("%d\n", arr[i]); // 访问下标为 i 的元素
}
循环条件为 i <= 10,这意味着 i 可以为 10。这样也将导致数组越界访问。

3) 不初始化数组会怎样

在下面的示例中,我们尝试访问一个未初始化的数组:
#include <stdio.h>

int main()
{
    int arr[10]; // 声明一个包含10个整数的数组
    for(int i = 0; i < 10; i++)
    {
        printf("%d\n", arr[i]); // 打印数组的第i个元素
    }
    return 0;
}
程序的运行结果为:

1
0
4203625
0
0
0
38
0
0
0

可以看出,结果是一些奇怪的值。这是因为未初始化的数组中包含一些无意义的数值。

数组不一定需要初始化,就像变量一样。但是,如果程序开始时不初始化数组,则通常在之后为其元素赋值,以避免使用无意义的数值。

再看一个例子:
#include <stdio.h>
int main()
{
    int arr[10];
    int n = 0;
    for(int i = 0; i < 10; i++)
    {
        arr[i] = n;
        n = n + 2;
    }

    for(int i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}
尽管示例程序中的数组未被初始化,但随后使用循环将从 0 开始的偶数分别赋值给每个元素。运行结果为:

0 2 4 6 8 10 12 14 16 18

数组占用空间大小

下面是一个使用 sizeof 运算符计算不同类型数组的大小的例子。
#include <stdio.h>
int main() {
    char arr1[10];
    short arr2[10];
    int arr3[10];
    long long arr4[10];
    float arr5[10];
    double arr6[10];

    printf("Size of arr1: %d\n", sizeof(arr1));
    printf("Size of arr2: %d\n", sizeof(arr2));
    printf("Size of arr3: %d\n", sizeof(arr3));
    printf("Size of arr4: %d\n", sizeof(arr4));
    printf("Size of arr5: %d\n", sizeof(arr5));
    printf("Size of arr6: %d\n", sizeof(arr6));
}
运行程序后,我们得到的输出结果为:

Size of arr1: 10
Size of arr2: 20
Size of arr3: 40
Size of arr4: 80
Size of arr5: 40
Size of arr6: 80

通过观察输出结果,我们可以总结出一个规律:数组占用的空间大小等于单个元素占用的空间大小乘以数组元素个数。

因此,我们可以得出以下计算结果:
sizeof(arr1) = sizeof(char) * 10 = 10
sizeof(arr2) = sizeof(short) * 10 = 20
sizeof(arr3) = sizeof(int) * 10 = 40
sizeof(arr4) = sizeof(long long) * 10 = 80
sizeof(arr5) = sizeof(float) * 10 = 40
sizeof(arr6) = sizeof(double) * 10 = 80

C语言数组赋值

让我们思考一下,是否能够整体为数组赋值呢?下面的代码能够正确运行吗?
int arr1[5] = {0};
int arr2[5] = {1, 2, 3, 4, 5};
arr1 = arr2;
在上面的代码中,第一个数组元素全部被初始化为 0,第二个数组元素被初始化为 1、2、3、4 和 5。我们想通过赋值运算符将 arr2 数组的值赋给 arr1 数组,但是这种写法无法通过编译。

那么,我们能否重新将数组初始化呢?例如,下面的代码:
arr1 = {1, 2, 3, 4, 5};
答案是否定的,初始化列表只能存在于初始化中。这也是赋值与初始化之间区别的一个体现:在数组初始化中,可以使用初始化列表,但在赋值操作中不可以。

虽然无法整体对数组进行赋值,但是可以使用其他方式将一个数组的值赋给另一个数组。

1) 逐个元素赋值

虽然不能整体对数组进行赋值,但是可以通过循环逐个元素对数组进行赋值,例如:
#include <stdio.h>
int main()
{
    int arr1[5] = {0};
    int arr2[5] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)
    {
        arr1[i] = arr2[i];
        printf("%d ", arr1[i]);
    }
    return 0;
}
在上面的代码中,我们使用了一个循环将 arr2 数组的值逐个地赋给 arr1 数组,然后输出 arr1 数组的元素值。这种方式虽然比较麻烦,但却是一种可行的方案。

2) 内存复制

除了使用循环单个为数组元素赋值的方式,我们还可以使用 memcpy() 函数进行内存复制。例如:
#include <stdio.h>
#include <memory.h>

int main()
{
    int arr1[5] = {0};
    int arr2[5] = {1, 2, 3, 4, 5};
    memcpy(arr1, arr2, sizeof(arr1));
    for (int i = 0; i < 5; i++)
        printf("%d ", arr1[i]);

    return 0;
}
程序中引入了头文件 memory.h 以便使用 memcpy() 函数。memcpy() 函数有三个参数,分别为目标数组名、源数组名和需要复制的字节数。该函数会将源数组的数据复制到目标数组中,复制的字节数取决于第三个参数。

需要注意第三个参数,假设复制的字节数为 N,那么有可能会出现如下两种情况:

相关文章