C语言系统化精讲(七):C语言数组详解

文章目录

  • 一、数组的基本概念
  • 二、一维数组
    • 2.1 一维数组的定义
    • 2.2 一维数组初始化
    • 2.3 一维数组的引用
  • 三、二维数组
    • 3.1 二维数组的定义
    • 3.2 二维数组初始化
    • 3.3 二维数组的引用
  • 四、C语言数组是静态的,不能插入或删除元素
  • 五、C语言数组的越界和溢出
    • 5.1 数组越界
    • 5.2 数组溢出

2018年9月1日,中国女排在2018年亚运会上3:0战胜了泰国女排,夺得了冠军,此时,阿里巴巴董事长主席马云见证了中国女排夺冠的辉煌一刻。同时马云承诺,要帮中国女排姑娘们清理购物车。本示例就来模拟列出某女孩的购物车清单,具体代码如下所示:

从代码中可以看到,图中被框出来的部分 长相类似 都是一个 标识符一个中括号([]) 箭头的另一端写着两个大字――数组,接下来我们简单的介绍一下数组的成分。

  1. DailyUse、Makeup、Sports、Health:即标识符。它们就是按照命名标识符规则定义的标识符,是程序员DIY定义的:
  2. []:即中括号。他的作用就是扩住数组的长度,中括号中间写的是数组的长度。
  3. 100、50、200、i等:即数组的长度。表示定义这个数组的长度。

根据上述,数组的形式就类似 a[10],没错,这是数组的形式之一,接下来我们详细介绍本章内容――数组,了解数组的作用,如何定义数组、初始化数组以及使用数组。

一、数组的基本概念

在某女孩的购物车清单实例中可以看到:

char DailyUse[50] = {"纸抽,纸巾,收纳箱,水杯,垃圾袋,剪刀,挂钩,拖鞋,小闹钟"};

这行代码中,DailyUse[50] 表示多个字符类型的变量,可以使用同一个名字 DailyUse,从而组成的集合,它就是一个数组。我们都知道,要想把数据放到内存中,就需要先分配内存,要想放入4个整型数据,就得分配4个int类型的内存空间,例如:

int arr[4];

这样我们就给分配了4个int型的内存空间,共计占4×4=16个字节,并为它们起了一个名字arr。我们把这样的一组数据的集合,叫做 数组(Array); 它所包含的每一个数据叫做 数组元素(Element); 所包含的数据的个数称为 数组长度(Length), 例如 int arr[4]; 就定义了一个长度为4的整型数组,名字是arr。数组中的每个元素都有一个序号,这个序号从 0 开始,而不是从我们熟悉的1开始,称为 下标(Index)。

二、一维数组

一维数组就像12个月,我们可以将12月起同一个名字month,1月~12月就是数组month的元素,示意图如下图所示:

2.1 一维数组的定义

一维数组是用于存储一维数组中数据的集合,它的一般形式如下:

类型说明符 数组标识符[常量表达式];
// ①: 类型说明符表示数组中的所有元素类型
// ②: 数组标识符表示该数组型变量的名称,命名规则与变量名一致
// ③: 常量表达式定义了数组中存放的数据元素的个数,即数组长度。例如cArray[10],10表示数组中有10个元素,下标从0开始,到9结束

示例:

//1.定义一个长度为10的字符型数组
//char为数组元素的类型,而cArray表示的是数组变量名,括号中的10表示的是数组中包含的元素个数
char cArray[10];
//2.定义一个长度为5的整型数组
int number[5];
//3.定义一个长度为20的浮点型数组
float f[20];

定义数组还有以下几点注意事项:

同一个数组,数组的所有元素的数据类型都是相同的
数组名的书写规则应按照标识符的规定定义
允许在同一个类型说明中,说明多个数组和多个变量

int i,j,k,n,m1[10],m2[20];

方括号中常量表达式表示数组元素的个数,如number[5]表示数组number有5个元素。但是其下标从0开始计算。因此5个元素分别为:

int number[5];
number[0];
number[1];
number[2];
number[3];
number[4];

访问数组元素时,下标的取值范围为 0≤index<length 过大或过小都会越界,导致数组溢出,发生不可预测的情况,我们将在后续小节重点讨论,请大家务必要引起注意。

数组名不能与其它变量名相同。如:

int cArray; 
float cArray[10]; //错误

数组长度 length 最好是整数或者常量表达式,例如 10、20*4 等,这样在所有编译器下都能运行通过;如果 length 中包含了变量,例如 n、4*m 等,在某些编译器下就会报错,我们将在后续小节专门讨论这点。

#define AI 3 //定义符号常量                              
int cArray[1+2]; //定义数组
int number[8+AI]; //定义数组
// 不建议:
int i=5;
int number[i];

2.2 一维数组初始化

给一维数组初始化就是给一维数组赋予初值。初始化的一般格式如下:

类型说明符 数组标识符[常量表达式]={值1,值2,值3,值4…};
//{}中间的各个值就是各元素的初值,各值之间用英文逗号隔开,定义结束后,用英文分号结束此句代码。例如:
int number[5]={1,2,3,4,5};
// number[0]=1, number[1]=2, number[2]=3, number[3]=4, number[4]=5

在C语言中,对一维数组的初始化可以用以下几种方法实现:

//1.在定义数组时直接对数组元素赋初值,例如:
int iArray[8]={1,2,3,4,5,6,7,8};
//经过上面的定义和初始化之后,数组中的元素
//iArray[0]=1,iArray[1]=2,iArray[2]=3,iArray[3]=4,iArray[4]=5,iArray[5]=6,iArray[6]=7,iArray[7]=8

//2.只给一部分元素赋值,未赋值的部分元素值为0
int iArray[8]={1,2,3};
//经过上面的定义和初始化之后,数组中的元素
//iArray[0]=1,iArray[1]=2,iArray[2]=3,iArray[3]=0,iArray[4]=0,iArray[5]=0,iArray[6]=0,iArray[7]=0

//3.在对全部数组元素赋初值时可以不指定数组长度
int iArray[]={1,2,3,4};

//4.先定义数组再给数组赋值
int a[4];
a[0]=120;
a[1]=1345;
a[2]=1700;
a[3]=122;

2.3 一维数组的引用

数组定义完成后就要使用该数组,可以通过引用数组元素的方式使用该数组中的元素。数组元素表示的一般形式如下:

数组标识符[下标]

其中的下标可以是整型常量和整型表达式。如果为小数时,C编译器会自动取整,例如:

这段代码的第2行是引用数组,其中 iArray 是数组变量的名称,2为数组的下标。前面介绍过数组的下标是从0开始的,也就是说下标为0表示的是第一个数组元素。因此第3个元素下标是2,所以这句引用的是数组 iArray 的第3个元素。在C语言中只能逐个地使用下标变量,而不能一次引用整个数组。例如,逐个输出数组元素,代码如下:

这段代码实现的功能是输出数组 iArray 的各个元素,使用 iArray[i] 引用数组是合法的。但是:

这段代码也是想要输出数组各元素,但是它不是逐个输出,而是用一条语句输出数组,这样就是不合法的,不能输出数组各个元素值。示例: 使用一维数组输出某高中班级前5名的总成绩,代码如下:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:01-array.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月10日 15:32:08
*   描    述:
*
================================================================*/


#include <stdio.h> //包含头文件
int main(){ //主函数
    int grade[5]; //定义数组
    int i = 0, j = 0;
    // 从控制台读取用户输入
    printf("请输入前5名各学生成绩: \n");
    for(;i<5;i++){ //输入学生成绩
        printf("第%d名成绩: ", i + 1);
        scanf("%d", &grade[i]); //注意取地址符&,不要遗忘
    }
    // 依次输出数组元素
    printf("前5名各学生成绩如下: \n");
    for(;j<5;j++){
        printf("第%d个成绩是: %d\n", j+1, grade[j]);
    }
    return 0;    
}

程序运行结果如下图所示:

三、二维数组

在第二小节讲解了一维数组,一维数组的下标只有一个,而如果下标是两个呢?在C语言中,将下标是两个的称为二维数组,例如 Array[m][n] 表示第m行第n列。例如,一个书柜布局如下图所示,利用书柜的行、列能够快速找到书的位置,而二维数组就类似行和列。

3.1 二维数组的定义

二维数组的声明与一维数组类似,一般形式如下:

数据类型 数组名[常量表达式1][常量表达式2];
// "常量表达式1"被称为行下标 "常量表达式2" 被称为列下标
// 如果有二维数组array[n][m],则二维数组的下标取值范围如下:
// 行下标的取值范围0~n-1 列下标的取值范围0~m-1 二维数组的最大下标元素是array[n-1][m-1]

例如,定义一个3行5列的整型数组,代码如下:

int array[3][5];//这一行代码说明了一个3行5列的数组,数组名为array,其下标变量的类型为整型。
//该数组的下标变量共有3×5个,即
array[0][0] array[0][1] array[0][2] array[0][3] array[0][4]
array[1][0] array[1][1] array[1][2] array[1][3] array[1][4]
array[2][0] array[2][1] array[2][2] array[2][3] array[2][4]

在C语言中,二维数组是按行排列的,即按行顺次存放,先存放 array[0] 行,再存放 array[1] 行。每行中的5个元素,也是依次存放。

3.2 二维数组初始化

在C语言中,对二维数组的初始化可以用以下几种方法实现:

//1.可以将所有数据写在一个大括号内,按照数组元素排列顺序对元素赋值
int array[2][2]={1,2,3,4};
//表示的二维数组形式如下:
1 2 
3 4 // 即array[0][0]的值是1,array[0][1]的值是2,array[1][0]的值是3,array[1][1]的值是4

//2.在为所有元素赋初值时,可以省略行下标,但是不能省略列下标
int array[][3]={1,2,3,4,5,6};//系统会根据数据的个数进行分配,一共有6个数据,而数组每行分为3列,当然可以确定数组为2行 形式:
1 2 3
4 5 6

//3.也可以分行给数组元素赋值
int array[2][3]={{1,2,3},{4,5,6}};

//4.在分行赋值时,可以只对部分元素赋值,未被赋值的元素系统默认的值为0
int array[2][3]={{1,2},{4,5}};

//5.二维数组也可以直接对数组元素赋值 这种赋值的方式就是使用数组引用的数组中的元素
int array[2][2];
array[0][0] = 1;
array[0][1] = 2; 
array[1][0] = 1;
array[1][1] = 2;

3.3 二维数组的引用

二维数组元素的引用一般形式为:

//不管是行下标还是列下标,其索引都是从0开始的
数组名[行下标][列下标]; //说明:二维数组的下标可以是整型常量或整型表达式
int array[2][2]={{5,2},{5,4}};//定义二维数组
array[1][1];//引用二维数组元素

例如:用二维数组求下表中分科的平均成绩。

具体代码如下:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:02-array.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月10日 17:09:13
*   描    述:
*
================================================================*/

#include <stdio.h>
int main() {
  int k, l, arr[3][5]; // 定义变量和二维数组
  int score = 0, course[3];//定义变量和二维数组
  printf("请输入成绩: \n");
  for (k = 0; k < 3; ++k) { //遍历二维数组行
    for (l = 0; l < 5; ++l) { //遍历二维数组列
      printf("array[%d][%d]=", k, l);
      scanf("%d", &arr[k][l]);
      score += arr[k][l]; //计算成绩
    }
    course[k] = score / 5; //求每科的平均成绩
    score = 0;//重新赋值
  }
  printf("数学的平均成绩是(取整数): %d\n", course[0]);
  printf("语文的平均成绩是(取整数): %d\n", course[1]);
  printf("英语的平均成绩是(取整数): %d\n", course[2]);
  return 0;
}

程序运行结果如下图所示:

四、C语言数组是静态的,不能插入或删除元素

在C语言中,数组一旦被定义后,占用的内存空间就是固定的,容量就是不可改变的,既不能在任何位置插入元素,也不能在任何位置删除元素,只能读取和修改元素,我们将这样的数组称为 静态数组。 反过来说,如果数组在定义后可以改变容量,允许在任意位置插入或者删除元素,那么这样的数组称为 动态数组。 Python,JavaScript 等解释型的脚本语言一般都支持动态数组,而 C,C++,Java 等编译型的语言一般不支持动态数组。总之,C语言中的数组是静态的,一旦定义后长度就不能改变了,大家要注意这一点,不要尝试去插入或删除元素。如果由于项目要求,必须要在数组中插入或者删除元素,只能再造一个新数组!

C语言数组为什么是静态的?

不能插入和删除数组元素有时候会非常麻烦,比如一个数组保存了某个班级的学生学号,现在有一名学生退学了,就得把 TA 从数组中剔除,但是C语言并不支持这么做,这就给编程带来了不小的麻烦。数组元素都是紧挨着排布的,中间没有空隙,不管是插入元素还是删除元素,都得移动该元素后面的内存:在第 i 个元素后面插入一个新元素时,第 i 个元素后面的所有元素都要往后移动一个元素的位置,从而给新元素腾出位置来。如果该数组后面紧跟的是其它有用数据,那么为了防止覆盖有用数据,还不敢直接往后移动元素,必须得重新开辟一块内存,把所有的元素都复制过去。删除第 i 个元素就比较简单了,不管三七二十一,把第 i 个元素后面的所有元素都向前移动即可。插入和删除数组元素都要移动内存,甚至重新开辟一块内存,这是相当消耗资源的。如果一个程序中有大量的此类操作,那么程序的性能将堪忧,这有悖于「C语言非常高效」的初衷,所以C语言并不支持动态数组。另外,很多时候我们需要把数组的地址保存到一个变量里面(等大家学到指针时就会见到这种情况),如果数组重新开辟了内存,而变量里面的地址不跟着改变的话,后续再使用该变量就会导致错误。让C语言本身去维护这些变量的值,以保持同步更新,这又是不可能做到的,所以这个矛盾无法从根本上解决。

总之,为了保证程序执行效率,为了防止操作错误,C语言只支持静态数组,不支持动态数组。

五、C语言数组的越界和溢出

5.1 数组越界

C语言数组是静态的,不能自动扩容,当下标小于零或大于等于数组长度时,就发生了 越界(Out Of Bounds), 访问到数组以外的内存。如果下标小于零,就会发生 下限越界(Off Normal Lower); 如果下标大于等于数组长度,就会发生 上限越界(Off Normal Upper)。

C语言为了提高效率,保证操作的灵活性,并不会对越界行为进行检查,即使越界了,也能够正常编译,只有在运行期间才可能会发生问题。请看下面的代码:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:04-array.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月11日 10:30:55
*   描    述:
*
================================================================*/


#include <stdio.h>
int main() {
  int a[3] = {101, 201, 301}, i;
  for (i = -2; i <= 4; i++) {
    printf("a[%d]=%d\n", i, a[i]);
  }
  return 0;
}

程序运行结果如下图所示:

越界访问的数组元素的值都是不确定的,没有实际的含义,因为数组之外的内存我们并不知道是什么,可能是其它变量的值,可能是函数参数,可能是一个地址,这些都是不可控的。

由于C语言的 放任 我们访问数组时必须非常小心,要确保不会发生越界。每个C语言程序员的生涯中都遇到过越界错误,我拿项上人头作保证,所以千万不要大意,因为越界错误有时候不容易发现,也不容易复现。当发生数组越界时,如果我们对该内存有使用权限,那么程序将正常运行,但会出现不可控的结果(如上例所示);如果我们对该内存没有使用权限,或者该内存压根就没有被分配,那么程序将会崩溃。

5.2 数组溢出

当赋予数组的元素个数超过数组长度时,就会发生 溢出(Overflow)。 如下所示:

//数组长度为3,初始化时却赋予5个元素,超出了数组容量,所以只能保存前3个元素,后面的元素被丢弃
int a[3] = {1, 2, 3, 4, 5};

一般情况下数组溢出不会有什么问题,顶多是丢失多余的元素。但是当以字符串的形式输出字符数组时,就会产生不可控的情况,请看下面的代码:

/*================================================================
*   Copyright (C) 2023 AmoXiang All rights reserved.
*   
*   文件名称:04-array.c
*   创 建 者:AmoXiang
*   创建日期:2023年10月11日 10:30:55
*   描    述:
*
================================================================*/


#include <stdio.h>
int main()
{
  char str[5] = "https://blog.csdn.net/xw1680?";
  puts(str);
  return 0;
}

程序运行结果如下图所示:

字符串的长度大于数组长度,数组只能容纳字符串的前面一部分,也就是 "https" 即使编译器在最后添加了 '\0' 它也保存不到数组里面,所以 printf() 扫描数组时不会遇到结束符 '\0' 只能继续向后扫描。而后面内存中的数据我们不知道是什么,字符能否识别,何时遇到 '\0' 这些都是不确定的。当字符无法识别时,就会出现乱码,显示奇怪的字符。由此可见,在用字符串给字符数组赋值时,要保证数组长度大于字符串长度,以容纳结束符 '\0'

至此今天的学习就到此结束了,笔者在这里声明,笔者写文章只是为了学习交流,以及让更多学习C语言的读者少走一些弯路,节省时间,并不用做其他用途,如有侵权,联系博主删除即可。感谢您阅读本篇博文,希望本文能成为您编程路上的领航者。祝您阅读愉快!

    好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
    如果我的博客对你有帮助、如果你喜欢我的博客内容,请 点赞评论收藏 一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
 编码不易,大家的支持就是我坚持下去的动力。点赞后不要忘了 关注 我哦!

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年12月7日
下一篇 2023年12月7日

相关推荐