做一个默默无私奉献的程序猿

C语言数组的讲解(一)

2017-11-07  本文已影响98人  长风留言

谨记

每个人心中都有一片海,自己不扬帆,没人帮您启航,久了就是一片死海。人生,就是一场自己与自己的较量:让积极打败消极,让快乐打败忧郁,让勤奋打败懒惰,让坚强打败脆弱。在每一个充满希望的清晨,告诉自己:努力,就总能遇见更好的自己。

今天这篇文章将为读者介绍C语言中一个非常重要的知识点————数组,相信的知识已经不能满足我们实际开发的需要,所以,为了开发更快捷、更方便,那么今天开始我们就走进C语言的重点知识点。

通过本篇文章,你将学到如下知识点:

一、一维数组

1、数组的定义
数组就是具有一定顺序关系的若干变量的一个集合,我们简称数组,其中每一个变量我们称为数组的元素,数组的几个关键点:

在C语言中,如果程序员要想使用数组,那么首先必须的去定义。数组的代表就是一维数组。

一维数组就是只有一个下标的数组,我们称为一维数组。定义格式:
<存储类型> <数据类型> <数组名>[<常量表达式>]
存储类型指的是auto, register, static, extern。若省略,相当于auto。
数据类型可以是任一种基本数据类型或构造数据类型。
数组名是用户定义的数组标识符。
方括号中的常量表达式表示数据元素的个数,也称为数组的长度。

对于数组的定义,需要注意一下几点:

  • 1、数组的数据类型,其实就是指的数组元素的取值数据类型,对于同一个数组,数组元素的数据类型都是一样的。
  • 2、数组名应当符合标识符的命名规定,即由字母、数字和下划线组成,但不能以数字开头。
  • 3、在同一作用域中,数组名不能和其他变量的名称一样,
  • 4、方括号中常量表达式表示数组元素的个数,如a[6]表示数组a有6个元素,它需要在数组定义时就确定下来,不能随着程序的运行动态更改。它的下标从0开始计算,因此6个元素分别为a[0]、a[1]、a[2]、a[3]、a[4]、a[5]。
  • 5、不能用变量来表示数组的长度,只能是常量表达式或者常量
  • 6、允许多个相同数据类型的数组和变量声明和定义。
int num[3];//定义了一个名称叫做num的数组,数组中可以存储3个int类型的数据
   // num = 15;//会报错,系统不知道应该给谁赋值
    // 只要定义一个C语言的数组, 系统就自动会给数组中的每一块小得存储空间一个编号
    //这个编号从0开始,依次递增
    //数组中系统自动绑定的编号,我们称为索引
    num[0] = 10;
    num[1] = 12;
    num[2] = 20;
注意点:C语言中规定了数组必须逐个元素引用,而不能整体引用。数组的引用实际上就是数组元素的引用。数组元素的一般表示方法为:  数组名[下标]
若数组在定义时指定有n个元素,则数组的下标范围为0~(n-1)。如果数组的下标越界,则有可能导致以下几种结果:
① 若越界访问的内存空间是空闲的,程序可能不受影响,仍能正确运行。
② 若越界访问的空间已经被占用,且写了很重要的数据。在这种情况下,若程序执行了非法写操作,则程序可能会异常终止或崩溃。
③ 若越界访问的空间是空闲的,程序只是进行了读操作,则程序能继续运行,但无法得出正确的结果。
④ 其他情况。

2、一维数组的初始化
一维数组初始化严格上来说有7种。
1、局部数组初始化
对于普通局部数组,若定义时,没有初始化,则数组中元素的值,是不确定的。
2、static数组不初始化
对于static修饰的数组,若定义时,没有初始化,则数组中元素的值默认为0
3、全局数组不初始化
对于全局数组,若定义时,没有初始化,则数组中元素的值默认也为0。
4、全部初始化
与变量在定义时初始化一样,数组也可以在定义时进行初始化,如对整型数组进行初始化。
int a[10]={1, 2, 9, 21, 8, 10, 7, 24, 0, 20};
此处还是要注意,数组只能通过下标逐个引用元素。定义数组时,对数组元素的初始化,只能写成一行,不能换行写。
5、部分初始化:数组在定义时可以对其中的部分数据进行初始化。当“{}”中值的个数少于元素个数时,只给前面部分元素赋值。例如,如下定义就是对数组的前5个数据初始化,而后5个数据自动赋0。
比如:int a[10] = {1,2,3,5} 前面4个有数据初始化,后面6个数据会自动赋0
6、数组全部赋值
若想要对数组中的元素全部赋值,则可以省略数组下标中的常量。编译器会根据初始化列表自动计算数组元素的个数,如下所示。
int a[]={1, 2, 5, 23, 8, 10, 7, 24, 0, 22};
7、数组全部初始化为0
特殊写法: int a[10] = {0};

当然给数组清零,还有其他写法,比如可以使用库函数memset,把数组用0来填充,需要引入头文件string.h。
int main(int argc, const char * argv[]) {
        int a[10];
    memset(a, 0, sizeof(a));
    return 0;
    }
    
    可以使用库函数bzero,把数组清0,需要引入头文件strings.h。
    int main(int argc, const char * argv[]) {
        int a[10];
        bzero(a, sizeof(a));

    return 0;
    }

3、一维数组内存分配
在内存中,数组中的元素占用连续的存储空间,并且根据每个元素所占存储空间来进行内存分配。数组名代表数组的起始地址,是地址常量,对数组名求sizeof(),可以得出数组在内存空间中所占用的总空间。类似的道理,可以很容易利用下面的表达式来计算出数组的元素个数:
数组的元素个数 = sizeof(数组名) / sizeof(数据类型)

#include <stdio.h>
int main(int argc, const char * argv[]) {
    //定义一个5个元素的数组
    int a[5] = {1,2,3,4,5};
    int  sizea = sizeof(a);//求数组所占用的空间大小
    printf("%d\n",sizea);
    printf("%p\n",a);
    printf("数组起始地址%p\n",&a[0]);
    int n = sizeof(a) / sizeof(int);//求数组元素个数
    for (int i = 0; i < n; i++) {
        printf("a[%d]的地址%p\n",i,&a[i]);
    }
    return 0;
}
运行结果:
            20
            0x7fff5fbff830
            数组起始地址0x7fff5fbff830
            a[0]的地址0x7fff5fbff830
            a[1]的地址0x7fff5fbff834
            a[2]的地址0x7fff5fbff838
            a[3]的地址0x7fff5fbff83c
            a[4]的地址0x7fff5fbff840
            Program ended with exit code: 0

通过上面的程序例子,我们可以看出,对数组名求地址,其实就是数组的第一个元素的地址,所以我们可以变像的说数组名就是一个指针,只不过是一个静态的指针,一个数组内存空间是连续的,从运行结果也可以看出来。

4、数组的排序
A、冒泡排序法
对于冒泡排序法,过程如下:
(1)比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止——第一趟冒泡排序,最终,最大的数被安置在最后一个元素位置上。
(2)对前n-1个数进行第二趟冒泡排序,最终,使次大的数被安置在第n-1个元素位置。
(3)重复上述过程,共经过n-1次冒泡排序后,排序结束。

     (省略了头文件和主函数,直接展示的核心代码)
    //任意输入10个元素的数组排序
    第一种:
    int a[N], i, j, k;
    for (i = 0; i < N; i++) {
        
        scanf("%d",&a[i]);
    }
    for (i = 0; i < N - 1; i++) {
        for (j = 0; j < N - 1 - i; j++) {
            if (a[j] > a[j+1]) {
                k = a[j];
                a[j] = a[j+1];
                a[j+1] = k;
            }
        }
    }
    printf("\n");
    for (i = 0; i < N; i++) {
        printf("%5d", a[i]);
    }
     第二种:
    //利用sizeof

    int a[] = {1,23,45,6,58,29},i,j,k,n;
    n = sizeof(a) / sizeof(a[0]);
    for (i = 0;  i < n - 1; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            if (a[j] > a[j+1]) {
                k = a[j];
                a[j] = a[j+1];
                a[j+1] = k;
            }
        }
    }

    for (i = 0; i < n; i++) {
        printf("%d\n",a[i]);
    }
        第三种,循环剥离
    int a[] = {1,23,45,6,58,29},i,j,k,n,index;
    n = sizeof(a) / sizeof(a[0]);
    for (i = 0;  i < n - 1; i++) {
        index = 0;
        for (j = 0; j < n - 1 - i; j++) {
            if (a[index] < a[j+1]) {
                index = j+1;//下标替换
            }
        }
        k = a[index];//赋值
        a[index] = a[n - 1 -i];
        a[n - 1 -i] = k;
    }
    for (i = 0; i < n; i++) {
        printf("%d\n",a[i]);
    }
    

B、选择排序法

选择排序的排序过程如下。
     (1)首先通过n-1次比较,从n个数中找出最小的, 将它与第一个数交换——第一次选择排序,结果最小的数被安置在第一个元素位置上。
     (2)再通过n-2次比较,从剩余的n-1个数中找出关键字次小的记录,将它与第二个数交换——第二次选择排序。
     (3)重复上述过程,共经过n-1次排序后,排序结束。
        (省略了头文件和主函数体)
    int a[N], i, j, r, t;//定义数组和变量
    printf("Please input %d numbers\n",N);
    for (i = 0; i < N; i++)//进行循环输入数组元素
        scanf("%d",&a[i]);
    for (i = 0; i < N-1; i++)//循环变量
    {
        r = i;
        for (j = i+1; j < N; j++)
            if (a[j] < a[r])
                r = j;
        if(r != i)//判断
        {
            t = a[r];
            a[r] = a[i];
            a[i] = t;
        }
    }
    printf ("the array after sort:\n");
    for (i = 0; i < N; i++)//打印
        printf ("%5d",a[i]);
    printf ("\n");

多维数组(二维数组)

1、多维数组定义
前面提到一维数组只有一个下标。那么具有两个或两个以上下标的数组,就称为多维数组。多维数组元素有多个下标,以标识它在数组中的位置。多维数组的说明与一维数组的说明基本类似,其说明的一般形式如下:
<存储类型><数据类型><数组名><常量表达式1><常量表达式2>…<常量表达式n>
可以看出,多维数组与一维数组的说明相比,只是增加了多个下标,其他特性基本与一维数组相同。例如:int b[2][3][4]。
在这里重点介绍二维数组,多维数组的用法可由二维数组类推而得到。

2、二维数组
二维数组定义的一般形式是:
<存储类型><数据类型><数组名>[常量表达式1][常量表达式2]
其中常量表达式1表示第一维下标的长度,即行数,常量表达式2 表示第二维下标的长度,即列数。例如:int a[2][3]。
说明了一个二行三列的数组,数组名为a,其下标变量的类型为整型。该数组的下标变量共有2×3个,即:
a[0][0],a[0][1],a[0][2]
a[1][0],a[1][1],a[1][2]
3、二维数组的初始化

==============二维数组============
格式:<存储类型><数据类型><数组名>[常量表达式1][常量表达式2]
其中常量表达式1表示第一维下标的长度,即行数,常量表达式2表示第二维下标的长度。即列数
初始化
① 降维给二维数组赋初值,即按行初始化。每一组的初始值都用{ }括起来。
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
//按降维给a数组元素全部初始化
int a[3][3] = {{1}, {4}};
//只初始化了部分数组元素,其他元素为0。第一行为1 0 0,第二行为4 0 0
② 按线性存储的形式给二维数组赋初值。
int a[2][3] = {1, 2, 3, 4, 5, 6};
//按线性存储形式给二维数组全部初始化
int a[3][3] = {1, 2 };
//只初始化了部分数组元素,其他元素为0。第一行为1 0 0,第二行为2 0 0,第三行为0 0 0。
③ 可以省略左边下标范围的方式,给二维数组赋初值。
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
//省略左边下标范围,给数组所有元素初始化
特别要注意的是,第一维的长度可以省略,但是,第二维的长度不能省,比如下面的写法:
int a[2][] = {{1, 2, 3}, {4, 5, 6}};
编译程序时,会有语法错误。
error: array type has incomplete element type
结论:二维数组第二维的长度不能省略。
// int a[2][] = {{1,2,3},{4,5,6}};//报错

4、二维数组内存分配
二维数组在概念上是二维的,也就是说其下标在两个方向上变化,有行和列的说法。下标变量在数组中的位置也处于一个平面之中,而不是象一维数组只是一个向量。但是内存却是连续编址的,是按一维线性排列的。如何在一维存储器中存放二维数组,
在C语言中,二维数组采取了和一维数组类似的存储方式,按行优先存,存储了第一行的元素,即存第二行的,依次类推。

int main(int argc, const char * argv[]) {
    int a[2][3] = {{8, 2, 6}, {1, 7, 9}}, i, j;;
    for (i = 0; i < 2; i++)
        for (j = 0; j < 3; j++)
            printf ("%d ", a[i][j]);
    printf ("\n");
    for (i = 0; i < 2; i++)
        for (j = 0; j < 3; j++)
            printf ("%p ", &a[i][j]);
    printf ("\n");
    return 0;
    }
    
    运行结果:
 8 2 6 1 7 9 
0x7fff5fbff810
 0x7fff5fbff814
 0x7fff5fbff818
 0x7fff5fbff81c
 0x7fff5fbff820
 0x7fff5fbff824
 
Program ended with exit code: 0

深入理解二维数组,在数组定义形式你可以知道,其实一个二维数组只是比一维数组多了一个下标,那么,从内存分配的角度上来看的话,我们可以把一个二维数组看作是有很多个一维数组构成。例如:int a[3][4]。可以理解成二维数组含有三个元素:a[0],a[1],a[2]。每个元素a[i]由包含四个元素的一维数组组成。举一个程序代码例子

#include <stdio.h>

#define DEBUG 0

int main(int argc, const char * argv[]) {
   
    /*
     我们也可以把二维数组,看成由多个一维数组组成。例如:int a[3][4]。可以理解成二维数组含有三个元素:a[0],a[1],a[2]。每个元素a[i]由包含四个元素的一维数组组成。
     */
    int a[3][4] = {{8, 2, 6, 4}, {1, 4, 7, 9}};
    //预编译指令
#if DEBUG
    a++;
    a[0]++;
    a[1]++;
    a[2]++;
    a[3]++;

#endif
    printf("a   :%p   a+1   :%p\n\n", a, a+1);
    printf("a[0]:%p   a[0]+1:%p   &a[0][1]=%p\n", a[0], a[0]+1, &a[0][1]);
    printf("a[1]:%p   a[1]+1:%p   &a[1][1]=%p\n", a[1], a[1]+1, &a[1][1]);
    printf("a[2]:%p   a[2]+1:%p   &a[2][1]=%p\n", a[2], a[2]+1, &a[2][1]);
    printf("a[3]:%p   a[3]+1:%p   &a[3][1]=%p\n", a[3], a[3]+1, &a[3][1]);

     //有一个3×4的矩阵,要求输出其中值最大的元素的值,以及它的行号和列号
    int max, i, j, r = 0, c = 0;
    int b[3][4] ={{24, 89, 2, 41}, {3, 11, 9, 1}};
    
    max = b[0][0];
    
    for (i = 0; i < 3; i++)
        for (j = 0; j < 4; j++)
            if (b[i][j] > max)
            {
                max = b[i][j];
                r = i;
                c = j;
            }
    
    printf("Max=%d, row=%d, column=%d\n", max, r, c);

    return 0;
}
运行结果:
a   :0x7fff5fbff7e0   a+1   :0x7fff5fbff7f0

a[0]:0x7fff5fbff7e0   a[0]+1:0x7fff5fbff7e4   &a[0][1]=0x7fff5fbff7e4
a[1]:0x7fff5fbff7f0   a[1]+1:0x7fff5fbff7f4   &a[1][1]=0x7fff5fbff7f4
a[2]:0x7fff5fbff800   a[2]+1:0x7fff5fbff804   &a[2][1]=0x7fff5fbff804
a[3]:0x7fff5fbff810   a[3]+1:0x7fff5fbff814   &a[3][1]=0x7fff5fbff814
Max=89, row=0, column=1
Program ended with exit code: 0

通过运行结果我们可以得出结论:
1、 a是二维数组名,是地址常量。
2、 a[0],a[1], a[2], a[3]实际上都是一维数组名,代表一维数组的起始地址,也都是地址常量。
3、 a+1和a的地址差16个字节,相当于4个数组元素。因此,可以看出a代表第1行的地址,a+1代表第2行的地址。
4、 a[0]+1和a[0]的地址差4个字节,相当于1个数组元素。因此,a[0]+1相当于元素&a[0][1], a[1]+1相当于元素&a[1][1],a[2]+1相当于元素&a[2][1]。
如果读者还想继续深入研究二维数组,可以自行查阅一些相关资料,当然,后面的文字中,我也会单独的讲解。

总结

希望读者认真学习,一步一个脚印,踏踏实实的把基础落实好。这篇文章主要介绍了C语言的构造数据类型数组,重点掌握一维数组,了解和熟悉二维数组,望读者掌握好。

结尾

最后,希望读者在读文章的时候发现有错误或者不好的地方,欢迎留言,我会及时更改,感谢你的阅读和评论已经点赞收藏。

上一篇 下一篇

猜你喜欢

热点阅读