C语言基础入门:深入解析数组

Tips:本文主要面向C语言初学者及新手,属于系列文章,笔者借此巩固自身同时也希望帮助更多新人更快的入门,如有不当或错误,欢迎指正。

目录

前言

一、数组的定义

1.1数组的概念        

1.2数组的定义格式

1.3注意事项 

1.4数组元素的访问

1.5数组的特点 

1.6常见错误:数组越界

二、一维数组

1.数组初始化

2.数组的大小及元素个数   

3.清零函数

4.字符数组 

4.1字符数组的初始化

4.2字符数组的输入输出

4.3strlen()函数 

三、二维数组

3.1二维数组的定义

3.2二维数组元素的访问

3.3二维数组的初始化

3.4二维数组的大小

3.5二维数组遍历

 四、数组名的另外含义

总结


前言

        前面几篇文章我们有讲解过C中的常见数据类型,而本篇我们将讲解一种我们人为构造的数据类型——数组。同时,数组也是C中第一个开始我们重点掌握的内容,熟悉了数组,我们才能做出更多 的操作。


一、数组的定义

1.1数组的概念        

        在C语言中, 数组属于构造数据类型。一个数组内包含单个或者多个数组元素,这些数组元素可以是基本数据类型,也可以是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。

1.2数组的定义格式

        通常情况下,我们的数组格式为以下形式:

        存储类型            数据类型               数组名[元素个数]      = {         内容         }

例:     auto                    int                        num[5]                 = { 1,2,3,4,5 }

这就是创建了一个存储类型为auto(自动型)的整型(int)数组,这个数组名为num,包含有1~5五个元素。

对数组进行初始化可以直接整体赋值,也可以单独的一个一个赋值:

int num[5]={1,2,3,4,5}    <==等同于==> a[0] = 1;a[1] = 2;a[2] = 3;a[3] = 4;a[4] = 5;

1.3注意事项 

        1.我们在定义一个数组时,非特殊需求下不会对其存储类型进行声明,选择省略,此时并非是未对其定义,而是编译时默认将其视作为auto型。

        2.C语言中定义数组时的数据类型就是数组内元素的数据类型,二者必须保持统一

        3.数组名需要符合标识符命名规则

        4.同一函数中,数组名不要和变量名重复,避免引起冲突,编译崩溃

        5.数组必须先定义,后使用。只能逐个引用数组元素,不能一次引用整个数组。
 

1.4数组元素的访问

        数组一般情况下包含了诸多元素,那我们只需要获取其中一个元素是怎么获取的呢?在C语言中,我们利用的是数组的下标来“访问”的数组内各个元素。

即:数组名[下标号],下标可以是常量,也可以是表达式,若数组未初始化,则访问的元素值将会是随机数。

如上图所示,这里需要注意的是,数组访问时,下标是从0开始,如果如上图这样使用,下标为1时候获得的元素反而是第二个元素。

同样的,我们也可以通过输入函数scanf等修改元素:

1.5数组的特点 

         数组有两个特点,一点是前文我们提及到的同一数组内数据类型相同;另外一点就是数组内的元素内存是连续的

        我们的数据只要是成功创建,他就会占据我们的空间,这部分空间是虚拟存在的,并不是我们电脑上存储东西的实际物理内存(FLASH或者说ROM)。只有当运行我们的这个程序时,它才会从虚拟空间将其拷贝到我们的内核空间,然后执行程序内容。当运行程序时,系统会自动生成一个进程,这个进程便是我们运行的程序。系统会为每一个进程创建一个0-4G(32位系统)的虚拟内存,而我们的数据如果没有其他说明,便是存放在其中的栈区,程序运行结束时,便会从栈区自动的释放掉,减少对于系统内存的占用。

内存图

 如果读者还是不太清晰这方面,可以暂做了解,后面我会在C进阶内容中将这里再展开详细讲解。

数组便是通过在存放时,各个元素的存储是连续的,例如上文讲解到用到的a[5],在32位系统中若假设第一个元素a[0]存放的地址是0x0000(一般情况下,我们习惯用16进制表示地址),由于是int数据类型,每一个元素都占4字节,则其在系统内存中的存放实际是如下图所示:

 当然,我们也可以通过代码查看各个数据的地址情况,从而判断:

#include <stdio.h>

int main(void)
{
    int a[5]={1,2,3,4,5};

    int i = 0;
    for(;i<5;i++)
    {
        printf("a[%d] = %d     ",i,a[i]);
        printf("a[%d]的地址是: %p\n",i,&a[i]);
    }
    return 0;
}

其运行结果如下:

数组的输出一般采取for循环遍历输出

 1.6常见错误:数组越界

        数组越界是常见的一种错误,由于C是较为重视运行效率,在一些情况下数组越界有可能不会报错,增加很大的后期维护工作。

所谓的数组越界,实际上产生的原因便是访问的元素超出了数组本身的长度,如下图所示:

         我们定义的数组a本身是不存在a[5]的,当我们尝试输出一下a[5]时候,他居然是可以访问取出的。这是因为我们已经访问的不再是我们数组定义的数据了,此时访问到的数据是不属于系统给数组a开辟的内存的,属于是“界外内存”。这时候我们是对其进行的输出显示操作,但是如果我们采取的是输入等操作,对a[5]所在空间输入一个数据,就会将原本这个空间存放的数据覆盖掉。如果很不凑巧你这个内存空间原本存放的数据非常重要,这个错误就会导致你重要数据的丢失

        如果读者是在循环时不小心数组越界,就会导致死循环:

将a数组中的所有元素遍历修改为0但是数组越界

 由于数组越界的存在,所以建议读者使用时多加注意。

二、一维数组

1.数组初始化

//全部初始化
int a[5] = {1,2,3,4,5};

//部分初始化
int a[5] = {1,2};
/* 此时a数组实际是 1,2,0,0,0 在初始化数组时未初始化的元素则为0 */

//未初始化
int a[5];
/* 未初始化的数组的各个元素值为随机值,只能另外单独对元素赋值,未赋值的元素依旧为随机值 */

一维数组初始化时,可以省略元素个数。 

2.数组的大小及元素个数   

        数组的大小通过sizeof关键字实现,也可以人为计算,计算方式为:

元素数据类型所占字节数  *  元素个数  =  数组的大小      

int main(void)
{
    int a[5]={1,2,3,4,5};

    printf("%d ",sizeof(a));      //sizeof(数组名)

    return 0;
}

输出结果为20,即  4  *  5。

元素个数的获取,也可以通过sizeof(数组名)/sizeof(数据类型)方式获得。

需要明确的一点是,对于一维数组而言,数组的大小即是数组的长度。 这一点在讲解下文的二维数组时会加深理解。

3.清零函数

4.字符数组 

        C语言不像C++,python等编程语言,有专门的字符串数据类型(string),故而想要表示一串字符,需要通过字符数组的方式来存放,该类数组就是元素数据类型为字符型的数据。

4.1字符数组的初始化

1.字符串赋值初始化

char a[] = {"hello"};

//等价于

char a[] = "hello";

该类赋值方式较为简单,但是需要注意的一点是这种的初始化系统会在最后补充一个  '\0'  来作为字符串结束标志,此时若是使用sizeof函数可以查看到其是占据内存空间的。

2.逐个字符赋值初始化

char b[] = {'h','e','l','l','o'};

这种方式的初始化较为麻烦,但是这种方式的初始化,不会添加字符串的结束标志  '\0',数组的大小不会受到影响,使用sizeof可知其大小为5。

若是此时我们将数组a的内容赋值给数组b,则此时会报错,因为b在前面初始化时给其开辟的空间只有5,而“hello”内容却是6个字节,超出了其容量。

4.2字符数组的输入输出

4.3strlen()函数 

        strlen函数是string库中的函数之一,主要作用是用来计算字符数组的长度,它的计算长度是实际长度,不包含  ‘\0’  。

size_t   strlen(const  char  *s)

其参数是我们要计算的字符串的首地址(即字符数组名),返回值的size_t是类int型的一个宏定义,输出使用时依旧使用%d格式控制即可。

strlen与sizeof的区别 :

        1.sizeof明确来说实际上是关键字,而strlen是string库的函数

        2.sizeof计算的是数据所占空间的大小,strlen计算的是字符串的实际长度

        3.当计算的字符数组元素个数省略时候,sizeof计算的数组大小会包含 '\0' ,而strlen计算的数组长度不包括  '\0' 。

三、二维数组

        上述讲的只有一个下标的数组统称为是一维数组,而多个下标的数组我们就称作是多维数组

读者可以理解为一维数组类似于一根有端点的直线,端点之间也有众多点,每个点都代表一个元素;而二维数组类似于一个平面直角坐标系下的平面,我们需要确定(x,y)两者才可以确定该点的值;而三维数组就类似于三维直角坐标系下的(x,y,z),需要三者才可以确定值……

此时想要去访问元素就不能像之前一样单个下标即可定位具体的元素,下面将会以二维数组为例讲解多维数组的基础知识。

3.1二维数组的定义

通常情况下,我们的二维数组格式为以下形式:

        存储类型            数据类型               数组名[行数][列数]    

例:     auto                    int                        num[3][4]                 

这就是创建了一个存储类型为auto(自动型)的整型(int)数组,这个数组名为num,行数为3,列数为4;

同样的,二维数组乃至多维数组都需要注意数组越界问题,上述创建的三行四列的二维数组实际是a[0][0]到a[2][3]共12个数据。

3.2二维数组元素的访问

二维数组依旧是通过下标来访问的元素,但是此时相当于(x,y)坐标,多了一个需要确定的下标,通过控制行下标列下标来决定访问的元素。

3.3二维数组的初始化

二维数组与一维数组的初始化类似,分为全部初始化、部分初始化、以及未初始化三种形式。

int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};          //按三行四列顺序赋值全部初始化
    
int b[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};    //按行赋值全部初始化

int c[3][4] = {1,2,3,4,5,6,7,8};                     //三行四列顺序赋值部分初始化
    
int d[3][4] = {{1,2,3},{5,6,7}};                     //三行四列顺序赋值部分初始化  

int e[3][4];                                         //未初始化

由此可看出, 初始化时未赋值则其为0,若是未初始化,则元素值为随机值,需对其单独赋值。

需要注意的是,在数组定义初始化时我们可以省略行坐标,上文中的a[3][4]也可以以a[][4]来定义,但是不可以省略列坐标

3.4二维数组的大小

二维数组的大小与一维数组就有区别了,一维数组中我们讲到过,一般情况下一维数组的大小与长度一致。而二维数组的大小,就是实际占据的空间大小,与长度无关

我们一般使用关键字sizeof(二维数组名)来确定二维数组的大小,同样的也可以采取人为计算的方式计算。

3.5二维数组遍历

一维数组遍历,在上述中以图片形式展现,为使用for循环遍历,而二维数组的遍历,我们在C语言中一般采取的是for嵌套来遍历。

 四、数组名的另外含义

对于一维数组而言,数组名除了代表一个数组之外,也表示是一个数组的首地址。我们可以直接使用数组名来提高我们的日常编程使用。

   int a[5] = {0,1,2,3,4};

   printf("a[0] :%d\n",a[0]);
   printf("a的首地址是:%p\n",a);
   printf("a的第一个元素的地址是:%p\n",&a[0]);
一维数组a

对于二维数组a[3][4]而言,a[0]实际上是二维数组a的首行行地址,即a[0]这第一行的行地址,也可以用数组名a表示。而a[1]表示的是a+1的地址,是第二行的首地址。而a[0]此时本身已经是行地址的级别,a[0]+1的话则是在行地址的基础上精准到了列地址,表明是a[0][1]的地址。

a[0]+1获得的是地址,取地址内的内容使用取内容符号 *

总结

        本篇讲解了第一个构造数据类型数组的定义,使用,注意事项等。下一篇将会讲解C语言中大名鼎鼎的“指针”,学习了本篇数组的初学者,建议网络上查找一些练习题巩固,加深对数组的理解。

物联沃分享整理
物联沃-IOTWORD物联网 » C语言基础入门:深入解析数组

发表评论