深入剖析C语言数组

文章目录

  • 前言
  • 一、数组的定义
    • 1. 何谓数组?
    • 2. 如何定义数组?
  • 二、数组的操作
    • 1. 定义一个数组
    • 2. 数组的使用
    • 3. 数组的初始化
    • 4. 字符数组和字符串数组
      • 4.1 字符数组和字符串数组的定义
      • 4.2 字符数组和字符串数组的区别
    • 5. 从内存角度分析数组
  • 三、示例代码
  • 四、二维数组
    • 4.1 二维数组定义的格式:
    • 4.2 二维数组的使用:
    • 4.3 二维数组的初始化
    • 4.4 从内存角度分析二维数组
    • 4.5 示例代码

前言

       本文将详细剖析下C语言数组的这个数据类型,数组类型在C语言中是使用的非常多的,可以说是一个非常基础且重要的类型,本文将为大家进行详细讲解,让大家知其然,更知其所以然。

一、数组的定义

1. 何谓数组?

       用通俗的话来讲,其实就是数组就是用来存储固定大小的具有相同数据类型的,在内存空间连续的的数据类型的集合。
       我们先抛开给出的定义不谈,我们来想想为什么C语言要导入数组这个概念。举个例子来说吧,假如一个班级有10名同学,我们要求这10名同学的语文成绩。如果在没有学习数组之前,我们的做法肯定是要定义出10个整形变量,然后对其进行赋值,试想一下,10个人就要定义了10个变量,那要是更多的人就需要定义出更多的变量了,很明显这种设计就显得很冗余,代码可读性低,这种设计不是我们想要的结果,在这种背景下,那有没有什么情况来解决呢,其实这就是我们今天的重点,定义一个数组,以上情况统统解决。
有个这个需求,我们再来看看上面说的数组的几大要点:

  1. 固定大小:就是需要我们在定义数组的时候需要将数组的大小给确定,从定义到程序结束,数组的大小都不能被改变。
  2. 相同数据类型:数组成员的数据类型都是一样。不可能同一个数组里面的成员有两种或以上的类型。
  3. 内存空间连续:数组的这些成员变量在内存空间上都是连续的,不是分散的。

2. 如何定义数组?

数组定义的格式:

<存储类型> <数据类型> <数组名>[元素个数];

数组的定义需要遵循以上格式。

  1. 存储类型:C语言的存储类型有4中,分别为 auto、static、 register、 extern。在一般情况下我们通常会对存储类型进行省略不写,但其实省略不写,就相当于存储类型就是 auto
  2. 数据类型:数据类型就是C语言的基本数据类型,像 int 、char、float 等等。这里定义的数据类型是什么,数组成员的数据类型就是什么
  3. 数组名:数组名只要符合标识符命名规格我们就可以随便取。这里需要注意的是数组名是一个地址常量(常量就意味着我们不能对数组名进行任何的赋值操作),数组名是数组的首地址
  4. 元素个数:数组成员的个数,C98及之前只能是常量整形或常量整形表达式。C99开始可以使用整形变量。因为数组的内存分配是在程序运行的时候才会分配。但是当数组内存分配完后,数组的大小是不会再发生变化的。如果使用变量的话,变量不能使用static、extern修饰,且变量必须先声明再进行赋值操作,而不能使用定义(初始化)的写法。

二、数组的操作

1. 定义一个数组

学习了数组的定义,那么我们可以尝试定义一个数组。

int a[5];

根据上面的讲解我们知道,这里定义了一个数组,这个数组的数组名是 a , 数组有5个成员,每个数组成员的数据类型都是 int。并且我们还应该知道一个 int 类型是 4 字节,这个数组有5个成员,所以数组的大小应该是 20 个字节。

2. 数组的使用

<数组名>[元素下标];
  1. 数组名:和数组定义的时候的数组名保持一致。
  2. 元素下标:下标是从0开始计数,取值范围为 0 ~ (元素个数 - 1)。这里需要注意编译器不会检查数组越界,非法访问内存,可能会造成段错误。

通过上面讲解我们就是知道 a[0] 代表的是数组里面的第一个元素,a[1] 代表的是数组里面的第2个元素, … … a[4] 代表的是数组里面的第5个元素,也是数组的最后一个元素。而 a[5] 是整个数组后面的内存,对 a[5] 进行操作就是内存的非法访问。这点非常值得大家注意。

3. 数组的初始化

  1. 完全初始化:在定义数组时对每个成员都进行初始化。
    如: int a[5] = {1,2,3,4,5};        //即 a[0] = 1, a[1] = 2 … 以此类推
  2. 不完全初始化:初始化时对部分数组成员赋予初始值,没有赋值的元素默认为0。
    如: int a[5] = {1,2};        //即 a[0] = 1, a[1] = 2,其余成员值为0
  3. 缺省型初始化:即省略数组元素个数。此时数组元素个数为初始化的个数
    如:int a[] = {1,2,3};        //等价于 int a[3] = {1,2,3};此时数组元素个数为3。

4. 字符数组和字符串数组

       字符数组和字符串数组是完全符合上面所讲的数组的规定的。这里之所以单独拿出来说一下是因为字符数组和字符串数组是平时考试和面试经常喜欢考的。

4.1 字符数组和字符串数组的定义

1. 字符数组,即每个数组元素的类型是字符类型。
例如 :
              完全初始化:char a[5] = {‘h’, ‘e’, ‘l’, ‘l’. ‘o’};
              不完全初始化:char a[5] = {‘h’, ‘e’, ‘l’};
              缺省型初始化:char a[] = {‘h’, ‘e’, ‘l’};
2. 字符串数组,数组每个元素的类型是也是字符类型。但有一点需要注意,字符串是以 ‘\0’ 结束的。
例如 :
              完全初始化:char a[5] = {“hell”};      //5个元素值分别是 ‘h’ ‘e’ ‘l’ ‘l’ ‘\0’
            不完全初始化:char a[5] = {“he”};      //5个元素值分别是 ‘h’ ‘e’ ‘\0’ ‘\0’ ‘\0’
            缺省型初始化:char a[] = {“hello”};    //数组有6个成员,后面还有一个 ‘\0’ 字符

4.2 字符数组和字符串数组的区别

       在讲字符数组和字符串数组的区别之前,我们首先需要明确一个概念。C语言严格意义上来讲没有字符串类型。因为我们没有那个关键字可以用来定义一个字符串。C语言中的字符串是若干字符在地址上连续,我们就将其认为是字符串。所以:

  1. char a[6] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
  2. char a[6] = {“hello”};

这两种定义是完全等价的,编译器对其处理的完全相同的。这里尤其值得强调的一点就是C语言的字符串是以 ‘\0’ 字符结束的,哪怕你不写,他都是隐藏在字符串后面的。那也就是

  1. “hello\0”
  2. “hello”

这两种写法也是完全等价的,并且都是占6个字节。

5. 从内存角度分析数组

       其实讲完了上面,数组的基础知识就已经讲完了。但为了让大家从更深层次的理解数组,在这里从内存的角度来为大家讲解一下数组在内存中是怎么存储的。
深入剖析C语言数组_第1张图片
       定义一个数组,其实编译器就会从内存中分配一段连续的地址来对这些数组的值进行存储。我们这里定义了 int a[5], 那编译器就会在内存中分配出20个字节给这个数组使用。那么编译器是怎么处理这段内存的呢?其实对编译器而言,这段内存就叫a, 也就是数组名,并且这个数组名a指向这段内存的首地址,也就是指向数组的首地址。这段内存中前4个字节是用来存储a[0]的值,后面以此类推。
       前面讲到了数组的第一个元素下标是0, 然后如果定义 int a[5], 那么下标里面的取值最大就是4,如果说我们取值 a[5], 首先编译器对数组的越界访问不会进行检查,如果说你对 a[5] 进行任何操作,其结果是未知的,因为我们不知道编译器是如何处理这段内存的,我们只有操作申请的内存才是安全的,a[5] 这里的内存编译器没有分配给我们,所以我们不能够随意使用。这点尤为重要,也是许多初学者容易犯的错。

三、示例代码

源代码:

#include 

int main()
{
    /*数组的不完全初始化,对第一元素初始化为0,后面没有初始化的默认也是0*/
    int score[10] = {0};
    int i;
    float average = 0;

    printf("请输入10个学生的分数: ");
    for (i = 0; i < 10; i++) {
        scanf("%d", &score[i]);
        average += score[i];
    }

    average /= 10;

    printf("该10个学生的平均分为: %f\n", average);

    return 0;
}

运行结果:

请输入10个学生的分数: 50 60 70 80 90 100 99 44 89 8810个学生的平均分为: 77.000000

四、二维数组

       二维数组从内存角度来看可以理解成多个一维数组的集合,从理解的角度来看可以理解成数学中的矩阵。二维数组的定义和用法基本上和一维数组的要求一模一样。

4.1 二维数组定义的格式:

<存储类型> <数据类型> <数组名>[行元素个数][列元素个数];

二维数组定义的格式的要求和一维数组一模一样,详细可以看上面一维数组的格式讲解。这里举个例子来说,
例如:int a[3][3];
存储类型是 auto, 数组中每个成员是 int 类型,数组名是 a, 行元素有3个,列元素也有3个。

4.2 二维数组的使用:

<数组名>[行元素下标][列元素下标];

同样的,数组名和定义时的数组名相同,行元素下标和列元素下标的取值范围是 0 ~ (行/列 元素个数 - 1) 。
比如定义: int a[3][3];
那么,a[0][0]:代表第一行第一个元素。 a[0][2] :代表第一行第三个元素。
           a[2][0]:代表第三行第一个元素。a[2][2]:代表第三行第三个元素。
           a[0][3]/a[3][3]:错误做法,超出了数组下标取值范围。

4.3 二维数组的初始化

  1. 完全初始化:在定义数组时对每个成员都进行初始化。
    如: int a[2][2] = {1,2,3,4};   或   int a[2][2] = {{1, 2}, {3, 4}}
    两种写法等效,但第二种写法可读性更高。//即a[0][0] = 1, … a[1][1] = 4。
  2. 不完全初始化:初始化时对部分数组成员赋予初始值,没有赋值的元素默认为0。
    如: int a[2][2] = {1,2,3};  或   int a[2][2] = {{1,2}; {3}};
    两种写法也等效,即 a[0][0] = 1, a[0][1] = 2,a[1][0]=3,a[1][1]=0。
  3. 缺省型初始化:省略行数,列数一定不能省
    如:int a[][2] = {1,2,3};
    编译器会解释成 int a[2][2] = {1, 2, 3};

4.4 从内存角度分析二维数组

深入剖析C语言数组_第2张图片
       如图所示,假如定义一个三行三列的数组,从理解上我们可以从矩形的角度出发,第一行就是 a[0], 第二行就是 a[1], 第三行就是 a[2]。a[0] 行的第一个元素就是 a[0][0],也就是1,其余以此类推。但内存是一维的,二维数组在内存上的存储也如上图所示。在定义时,编译器会分配一块内存叫a, a指向这块内存的首地址。其中前12个字节(一个int 4字节,一行有三个所以是12字节)叫a[0], 并且a[0]也指向这块内存的首地址,a[1], a[2] 也以此类推。这里有点要注意,这里的a[0],a[1], a[2]是一个地址常量。
       我们上面说过,如果定义的是三行三列的数组,那么第一行列下标最大只能是2,也就是 a[0][2] 这个元素,那如果 a[0][3] 是代表的那个元素呢,其实从内存的角度不难看出,a[0][3]就是 a[0][2] 后面的那个元素,也就是a[1][0]。那也就是说 a[0][3] 和 a[1][0] 这两个是完全等价的。像这样类推,其实 a[0][8] 也就是 a[2][2]。其实我们这样做就把二维数组给转换成了一维数组,因为我们的内存是一维的。也就说说,二维数组在理解上是二维的,但是实际存储也就是一维的。但是有点要注意,虽然可以这样干,但是我们不推荐这样做,因为我们既然定义了二维数组,说明我们的场景就是适合二维的,既然是二维的,数组下标就不要超出约定,因为良好的编程习惯就是这样养成的,哈哈~

4.5 示例代码

源代码:

#include 

int main()
{
    /*数组的不完全初始化,对第一元素初始化为0,后面没有初始化的默认也是0*/
    int score[3][3] = {0};
    int i, j;
    float chinese = 0, math = 0, english = 0;

    
    for (i = 0; i < 3; i++) {
        if (i == 0) printf("请输入3个学生的语文成绩: ");
        else if (i == 1) printf("请输入3个学生的数学成绩: ");
        else if (i == 2) printf("请输入3个学生的英语成绩: ");

        for (j = 0; j < 3; j++){
            scanf("%d", &score[i][j]); 
            if (i == 0) chinese += score[i][j];
            else if (i == 1) math += score[i][j];
            else if (i == 2) english += score[i][j];
        }
    }

    printf("该3个学生的语文、数学、英语的平均分分别为: %f, %f, %f\n", chinese/3, math/3, english/3);

    return 0;
}

运行结果:

请输入3个学生的语文成绩: 89 98 88
请输入3个学生的数学成绩: 87 90 99
请输入3个学生的英语成绩: 78 67 893个学生的语文、数学、英语的平均分分别为: 91.666664, 92.000000, 78.000000

你可能感兴趣的