英文原版: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};
中括号里的数字为指示符,比如2
、9
、14
。
指示符有哪些性质?
- 指示符必须是常量表达式;
- 如果待初始化的数组长度为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数组在内存中的行主序存储示意图:

例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号元素。
网友评论