数组是含有多个数据项的数据结构,并且这些数据项都具有相同的数据类型。这些数据项称为数组的元素,我们可以根据元素在数组中的位置来选取元素。
最简单的数组就是一维数组。数组元素在内存中是依次排列的,如下图所示:
声明一个数组,我们需要指定数组元素的类型和数量。如:
int a[10];
const int* p; // pointer to const 常量指针
int* const p; //constant pointer 指针常量
const int* const p;
数组元素可以是任何类型,数组的长度则必须是常量表达式 (能够在编译期间求值的表达式)。因为程序以后可能需要调整数组的长度,所以一般情况下,我们会使用宏来定义数组的长度:
数组索引
我们可以用数组索引来访问数组中的元素。在 C 语言中,数组索引是从 0 开始的,所以长度为 n 的数组索引范围为 0 ~ n-1。假如 a 是含有 10 个元素的数组,那么这些元素可以依次标记为 a[0], a[1], ..., a[9],如下图所示:
/* clears a */for (i = 0; i < N; i++) a[i] = 0;
/* reads data into a */for (i = 0; i < N; i++) scanf("%d", &a[i]);
/* sums the element of a */for (i = 0; i < N; i++) sum += a[i];
C 语言有一个常被人诟病的问题:不检查数组索引越界 (why?)。当数组索引越界时,程序的行为是未定义的,也就是说任何情况都可能发生。
#include<iostream>
//#include<stdio.h>
using namespace std;
void test0(){
int arr[] = {1, 2, 3};
printf("arr[3] = %d\n", arr[3]);
}
int main()
{
test0();
}
数组初始化
数组可以像其它变量一样进行初始化,不过数组的初始化需要一些技巧。
数组初始化式最常见的形式是,用大括号包含一组常量表达式,常量表达式之间用逗号分隔。
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
如果初始化式比数组短,那么剩余元素被初始化为 0:
int a[10] = {1, 2, 3, 4, 5, 6}; /* initial value of a is {1, 2, 3, 4, 5, 6, 0, 0, 0, 0} */
利用这个特性,我们很容易把数组元素全部初始化为 0:
int a[10] = {0};
注意:初始化式不能比数组长,也不能完全为空。
如果给定了初始化式,我们也可以省略数组的长度:
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
对数组使用 sizeof 运算符
我们可以使用 sizeof 运算符确定数组的大小 (字节)。如果数组 a 包含 10 个整数,那么 sizeof(a) 的值通常为 40。sizeof 运算符也可以确定数组元素的大小,两者相除即得到数组的长度:
sizeof(a) / sizeof(a[0])
这样做有个好处:即使日后数组的长度发生改变,这个 for 循环是不需要发生变化的。为了可读性和通用性,我们往往把 sizeof(a) / sizeof(a[0]) 定义为带参数的宏:
#define SIZE(a) (sizeof(a) / sizeof(a[0]))
...
for (i = 0; i < SIZE(a); i++) a[i] = 0;
【练】用户输入初始金额,利率和投资年数,程序将打印一张表格。表格将显示输入的利率以及紧随其后 4 个更高利率下的总金额。程序的会话如下:
#include<iostream>
//#include<stdio.h>
using namespace std;
void test0(){
double init_balance, rate;
int years;
cout<<"Enter initial balance:";
cin>>init_balance;
cout<<"Enter initial rate:";
cin>>rate;
cout<<"Enter number of years:";
cin>>years;
cout<<"\nYears ";
for(int i = 0; i < 5; i++){
//cout<<i+rate<<"% ";
printf("%5.2lf%%", i+rate);
}
cout<<endl;
double value[5];
for(int i = 0; i < 5; i++)
value[i] = init_balance;
for(int j = 1; j <= years; j++){
cout<<j<<" ";
for(int i = 0; i < 5; i++){
value[i] += value[i] * (rate+i)/100;
printf("%7.2lf",value[i]);
}
cout<<endl;
}
}
int main()
{
test0();
return 0;
}
多维数组
数组可以有任意维数。其中最常用的是一维数组,其次是二维数组,一般很少见到更高维的数组。如下,我们声明了一个二维数组 (类似数学上的矩阵):
int matrix[5][9];
数组 matrix 有 5 行 9 列,且行和列的索引都是从 0 开始的,如下图所示:
就像一维数组和 for 循环是好伙伴一样,二维数组和嵌套的 for 循环是好伙伴。例如,我们可以通过下面的方式创建一个维度为 10 的单位矩阵:
#define N 10
double identity[N][N];
int row, col;
for (row = 0; row < N; row++)
for (col = 0; col < N; col++)
if (row == col) identity[row][col] = 1.0;
else identity[row][col] = 0.0;
多维数组初始化
通过嵌套一维数组初始化式,我们可以构建二维数组的初始化式:
int matrix[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}};
更高维数组的初始化式可以采取类似的方法构建,C 语言提供了多种方法来简化多维数组的初始化。
- 如果初始化式的长度不够,那么剩余元素被初始化为 0。如,下面的初始化式只填充了数组的前 3 行,后面 2 行将被初始化为 0。
int matrix[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。
int matrix[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}};
- 甚至,我们可以省略内存的大括号 (不推荐)。
int matrix[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};
编译器一旦发现值足以填满一行,它就开始填充下一行。
注意:在初始化式中省略内层大括号是非常危险的,因为不小心多写或者少写一个值都会影响后面元素的初始化。
常量数组
无论是一维数组还是二维数组,我们都可以在声明时加上const修饰符而变成 "常量":
const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
程序在运行期间不会对数组进行修改。
const不仅仅可以修饰数组,它可以修饰任意变量。但是const在数组声明中特别有用,因为数组经常包含一些不会发生改变的信息。编译器酌情进行优化,运行效率更高。
【练】写一个随机发牌的程序。用户指定发几张牌,程序打印手牌。程序的会话如下: