第8章 数组

作者: 橡树人 | 来源:发表于2020-02-27 06:56 被阅读0次

英文原版:P161

截止目前,我们接触到的变量都是标量:可存储单个数据项。

C语言支持聚合变量:可存储多个数据项。C语言中有两类聚合变量:数组和结构体。

本章会集中讨论一维数组,因为在C语言中一维数组比多维数组更重要。第12章会提供更多有关数组的信息。第16章会介绍结构体。

本章的主要内容:

  • 8.1节介绍如何声明和使用一维数组。
  • 8.2节介绍如何声明和使用多维数组。
  • 8.3节介绍C99标准中可变长度数组。

8.1 一维数组

  • 数组下标
  • 程序示例:逆序输出一个组数
  • 数组初始化
  • 指定初始化子
  • 程序示例:检查一个数中是否有重复数字
  • 对数组使用sizeof运算符
  • 程序示例:计算利率

什么是数组?

  • 数组是一种数据结构,该数据结构存储多个具有相同数据类型的数据项。
  • 数组中的元素可根据其在数组中的位置来一个一个地选出来。

最简单的数组就是一维数组。
从概念上讲,一维数组的元素是一个接一个地排成一行(或者一列)。

数组声明

  • 声明数组需要声明数组元素的类型和个数。
  • 数组元素的类型可以是任何类型;
  • 数组的长度可使用任何一个常数表达式来设置;
#define N 10

int a[N];

数组下标

  • 数组的下标从0开始;
  • 长度为n的数组的元素索引范围是0到n-1;
  • 使用“数组名[下标]”格式就可以访问数组元素;

例1 清空数组a

for (i = 0; i< N; i++) {
  a[i] = 0;
}

例2 把数据读进数组a

for (i = 0; i< N; i++) {
 scanf("%d", &a[i]);
}

例3 对数组a求和

for (i = 0; i< N; i++) {
  sum += a[i];
}

程序示例:逆序输出一组数

源文件reverse.c

/** Reverse a series of numbers */

#include <stdio.h>

#define N 10

int main(void) {
    int a[N], i;

    printf("Enter %d numbers: ", N);
    for (i =0; i<N; i++) {
        scanf("%d", &a[i]);
    }

    printf("In reverse order:\n");
    for (i = N-1; i >= 0; i--)
    {
        printf(" %d\n", a[i]);
    }
    printf("\n");

    return 0;
}

数组初始化

规则:

  • 初始化式不能全是空,至少得在大括号里放一个0;
  • 初始化式的长度大于数组的长度也是非法的;

例1 用大括号括起来的常量表达式类给数组初始化

int a[10] = {1,2,3,4,5,6,7,8,9,10};

例2 如果初始化式比数组长度短,则将数组剩余元素设置为0,比如

int a[10] = {1,2,3,4,5,6};//等价于数组a的初始化式为{1,2,3,4,5,6,0,0,0,0}

例3 将数组全部初始化为0,可以这么做:

int a[10] = {0};

例4 如果有初始化式,则数组的长度可以省略不写,由编译器根据初始化式的长度来确定数组的长度:

int a[] = {1,2,3,4,5,6,7,8,9,10};

指定初始化式

例1 假设想让数组的2号元素为29,9号元素为7,14号元素48,其余值为0,则该如何给该数组初始化?
方法一:

int a[15] = {0,0,29,0,0,0,0,0,0,7,0,0,0,0,48};

方法二:指定初始化式

int a[15] = {[2]=49, [9] = 7, [14] = 48};

中括号里的数字为指示符,比如2914

指示符有哪些性质?

  • 指示符必须是常量表达式;
  • 如果待初始化的数组长度为n,则指示符的范围是0到n-1;
  • 如果数组的长度省略了,则指示符可是任意非负整数,编译器需根据最大的指示符来确定数组的长度;

例2 确定数组的长度

int b[] = {[5] = 10, [23] = 13, [11] = 36, [15] = 29};

则该数组的长度为24。
例3 混合使用逐个元素和指定初始化式来初始化数组

int c[10] = {5,1,9, [4] = 3, 7, 2, [8] = 6};//{5, 1, 9, 0, 3, 7, 2, 0, 6, 0}

检查重复数字

源文件repdigit.c

#include <stdio.h>
#include <stdbool.h>

int main(void) {
    //使用初始化式来初始化数组,且初始化式的长度小于数组的长度
    bool digit_seen[10] = {false};
    int digit;
    long n;

    printf("Enter a number:");
    scanf("%ld", &n);

    while(n > 0){
        digit = n%10;
        if (digit_seen[digit]) {
            break;
        }
        digit_seen[digit] = true;
        n /= 10;
    }

    if (n > 0) {
        printf("Repeated digit\n");
    }else {
        printf("No repeated digit\n");
    }
}

对数组使用sizeof运算符

sizeof运算符可判断一个数组的大小。
例1 有一个10个整数的数组a,则sizeof(a)等于40。

可使用sizeof运算符来判断一个数组元素的大小。

例2 有一个10个整数的数组a,则数组a的长度等于sizeof(a)/sizeof(a[0])

程序示例:计算收益

源文件interest.c

#include <stdio.h>

#define NUM_RATES ((int)(sizeof(value)/sizeof(value[0])))
#define INITIAL_BALANCE 100.00

int main(void) {
    int i, low_rate, num_years, year;
    double value[5];

    printf("Enter interest rate: ");
    scanf("%d", &low_rate);
    printf("Enter number of years:");
    scanf("%d", &num_years);

    printf("\nYears");
    for (int i = 0; i < NUM_RATES; ++i)
    {
        printf("%6d%%", low_rate+i);
        value[i] = INITIAL_BALANCE;
    }
    printf("\n");

    for(year = 1; year <= num_years; year++){
        printf("%3d   ", year);
        for (i = 0; i < NUM_RATES; i++) {
            value[i] += (low_rate + i)/100.0 *value[i];
            printf("%7.2f", value[i]);
        }
        printf("\n");
    }


    return 0; 
}

8.2节 多维数组

  • 多维数组初始化
  • 常量数组

跟其他语言相比,多维数组在C语言中的角色相对较弱,因为C语言使用指针数组来存储多维数组,使用指针数组来存储多维数组比其他语言更为灵活。

C语言是以行主序的方式来在内存中存储二维数组的,先第0行,再第1行,依次类推。

例1 二维数组m[5][9]

/**
* 两维数组的声明
* 两维数组元素的访问
*/
int m[5][9];//声明了一个叫m的两维数组
//使用m[i][j]来访问在i行j列的元素

m数组在内存中的行主序存储示意图:


二维数组m的行主序示意图.png

例2 初始化一个数组,来用作单位矩阵。
方法一:使用嵌套循环

#define N 10

double ident[N][N];
int row, col;

for (row = 0; row < N; row++){
  for (col = 0; col < N; col++) {
    if (row == col) {
      ident[row][col] = 1.0;
    }else {
      ident[row][col] = 0.0;
    }
  }
}

方法二:使用指定初始化式

#define N 10

double ident[N][N]={[0][0]=1.0, 
                    [1][1]=1.0,
                    [2][2]=1.0, 
                    [3][3]=1.0,
                    [4][4]=1.0, 
                    [5][5]=1.0,
                    [6][6]=1.0,
                    [7][7]=1.0,
                    [8][8]=1.0, 
                    [9][9]=1.0};

例3 初始化二维数组m[5][9]

// 内部初始化式满5个
int m[5][9] = {{1,1,1,1,1,0,1,1,1},
               {0,1,0,1,0,1,0,1,0},
               {0,1,0,1,1,0,0,1,0},
               {1,1,0,1,0,0,0,1,0},
               {1,1,0,1,0,0,1,1,1}};
// 内部初始化式不满5个,则将剩余元素全部设置为0
m[5][9] = {{1,1,1,1,1,0,1,1,1},
           {0,1,0,1,0,1,0,1,0},
           {0,1,0,1,1,0,0,1,0}};
// 内部初始化式不足一行,则将该行剩余元素置为0
m[5][9] = {{1,1,1,1,1,0,1,1,1},
           {0,1,0,1,0,1,0,1},
           {0,1,0,1,1,0,0,1},
           {1,1,0,1,0,0,0,1},
           {1,1,0,1,0,0,1,1,1}};

常量数组

保留字:const
效果:不应该对声明为const的数组进行修改,编译器能检测到修改某个数组元素的尝试
优点:方便代码阅读,帮助编译器捕获错误
使用范围:数组、变量

例1 常量数组

const char hex_chars[] = 
  {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
   'A', 'B', 'C', 'D', 'E', 'F'};

程序示例:发牌

源文件:deal.c

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define NUM_SUITS 4
#define NUM_RANK 13

int main(void)
{
    bool in_hand[NUM_SUITS][NUM_RANK] = {false};
    int num_cards, rank, suit;
    const char rank_code[] = {'2', '3', '4', '5', '6', '7', '8',
                              '9', 't', 'j', 'q', 'k', 'a'};

    const char suit_code[] = {'c', 'd', 'h', 's'};

    srand((unsigned) time(NULL));

    printf("Enter number of cards in hand: ");
    scanf("%d", &num_cards);

    printf("Your hand:");
    while(num_cards > 0) {
        suit = rand() % NUM_SUITS;
        rank = rand() % NUM_RANK;
        if (!in_hand[suit][rank]) {
            in_hand[suit][rank] = true;
            num_cards--;
            printf(" %c%c", rank_code[rank], suit_code[suit]);
        }

    }
    printf("\n");


    return 0;
}

8.3节 可变长度数组VLA

如何指定可变长度数组的长度?

  • 常量表达式
  • 变量
  • 任意表达式

可变长度数组有哪些优势?

  • 没必要在声明数组时随便给定一个长度,因为可在程序运行时按需准确地计算出元素个数。
  • 常见于除了main以外的其他函数f,因为每次调用f时,数组的长度可以不一样。

可变长度数组有哪些缺陷?

  • 没有静态存储期限;
  • 没有初始化式;

例1 使用变量来指定数组长度
reverse2.c

/** Reverse a series of numbers using a variable-length array */

#include <stdio.h>

int main(void) {
    int i, n;

    printf("How many numbers do you want to reverse? ");
    scanf("%d", &n);

    int a[n];

    printf("Enter %d numbers: ", n);
    for (i =0; i<n; i++) {
        scanf("%d", &a[i]);
    }

    printf("In reverse order:\n");
    for (i = n-1; i >= 0; i--)
    {
        printf(" %d\n", a[i]);
    }
    printf("\n");

    return 0;
}

例2 使用任意表达式来指定数组长度

int a[3*i + 5];
int b[j+k];
int c[m][n];

提问和解答

问题1 数组的下标为什么是从0开始,而不是从1开始?
答:让数组的下标从0开始,有两个好处:1,使编译器能简化点;2,使数组下标运算能更快点;

问题2 如果想让数组下标从1开始到10而不是到9,该怎么做?
答:一个常用的技巧是声明数组为11个元素,而不是10个。此时,虽然数组的下标是从0到10,但可以忽略0号元素。

相关文章

网友评论

    本文标题:第8章 数组

    本文链接:https://www.haomeiwen.com/subject/jsbzchtx.html