数组


现在要整理全班的程序设计小考成绩了,现在希望写个小程序,全班共有 40 名学生,必须有 40 个变量来存储学生的成绩,现在问题来了,根据之前学过的,难道要定义 40 个名称不同的变量来存储学生成绩吗?

不会这么麻烦的,C++ 提供数组(array),可以定义一个以索引(index)作为识别的数据结构,定义数组的方式如下:

数据类型 名称[长度];

长度必须是个编译时期常数,以下是几个定义的范例:

int number[10];    // 定义 10 个元素的整数数组
double score[10];  // 定义 10 个元素的浮点数数组
char ascii[10];    // 定义 10 个元素的字符数组

若要动态定义数组长度,可以使用一些数据结构与动态内存定义来解决,这在之后才会说明。

定义数组之后,数组的元素值是未初始化的,若想在定义时初始化数组全部的元素值,可以如下:

int number[10] = {0};
double score[10] = {0.0};
char ascii[10] = {'\0'};
bool flag[10] = {false};

上面的几个定义,整数数组中的元素都会被初始化为 0,浮点数数组则会被初始化为 0.0,字符数组会被初始化为空字符('\0'),而bool数组会被初始化为false

也可以在定义数组时初始化所有的数组值,例如:

int number[5] = {0, 1, 2, 3, 4};
double score[5] = {87.0, 78.0, 99.5, 69.5, 82.5};
char ascii[5] = {'A', 'B', 'C', 'D', 'E'};
bool flag[5] = {false, true, false, true, false};

要访问数组中的元素值时,可以使用下标(Subscript)运算符[]加上索引」,索引值由 0 开始,下面这个简单的程序是个示范:

#include <iostream> 
using namespace std; 

int main() { 
    constexpr int LEN = 10;
    int number[LEN] = {0}; 

    for(int i = 0; i < LEN; i++) {
        cout << number[i] << " "; 
    }
    cout << endl; 

    for(int i = 0; i < LEN; i++) {
        number[i] = i; 
    }

    for(int i = 0; i < LEN; i++) {
        cout << number[i] << " "; 
    }
    cout << endl; 

    return 0; 
}

执行结果如下:

0 0 0 0 0 0 0 0 0 0
0 1 2 3 4 5 6 7 8 9

数组在使用时,得知数组长度是必要的,不可以访问超过数组长度的内存,这会发生无法预期的结果,数组本身并不知道自己的长度信息,在上面的范例中,使用了LEN来记录长度,不过,有没有办法计算出长度呢?可以使用底下的方式:

#include <iostream> 
using namespace std; 

int main() { 
    int number[5] = {0, 1, 2, 3, 4};
    int length = sizeof(number) / sizeof(number[0]);

    for(int i = 0; i < length; i++) {
        cout << number[i] << " "; 
    }
    cout << endl; 

    return 0; 
}

数组索引值由 0 开始不是没有原因的,数组名称存储了数组内存的首个位置的地址,而索引值表示数组元素是相对于数组首个内存地址的位移量(offset),位移的量与数据类型长度有关,如果是int整数,每次位移时是一个int整数的长度,例如在上例中number[0]索引值为 0 时,表示位移量为 0,自然就是指第一个元素,而number[9]就是指相对于首个元素的位移量为 9。

C++ 17 的iterator提供了size函数,可以用来计算数组大小,不过目前编写文件时使用的 g++ 编译器需要加上-std=c++17才可以使用。

在 C++ 11 提供了beginend函数,begin会返回数组首个元素的地址,end返回最后一个元素下个位置的地址,当对地址值进行运算时,会以数据类型的长度偏移,因此能有以下顺序遍历数组的方式:

#include <iostream> 
using namespace std; 

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

    for(auto offset = begin(number); offset != end(number); offset++) {
        auto n = *offset;
        cout << n << " "; 
    }
    cout << endl; 

    return 0; 
}

offset是个指针(pointer),类型会是int*,存储的是地址,而*offset是获取存储于该地址的值,因为之后才会谈到指针,这边就先用auto让编译器推断类型,基于以上的原理,在 C++ 11 提供了 for range 语法,可用于顺序遍历数组的任务:

#include <iostream> 
using namespace std; 

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

    for(auto n : number) {
        cout << n << " "; 
    }
    cout << endl; 

    return 0; 
}

若在定义数组时指定各个索引处的的值,可以不用定义数组元素大小,例如:

int number[] = {1, 2, 3};
double weight[] = {0.4, 3.2, 1.0, 4.2};
char ch[] = {'A', 'B'};

上面定义中,number[]的元素个数会是 3,weight[]的个数会是 4,而chs[]的个数会是 2。

如果使用constconstexpr来修饰数组,每个索引位置就成为只读。例如:

constexpr int number[] = {1, 2, 3};
number[1] = 10; // error: assignment of read-only location 'number[1]'

不可以将数组直接指定给另一数组,例如:

int arr1[5];
int arr2[5];
...
arr1 = arr2; // 错误!不能直接指定数组给另一个数组

若要将数组指定给另一个数组,只能顺序逐个元素进行复制,例如:

constexpr int LENGTH = 5;
int arr1[LENGTH];
int arr2[LENGTH];
...
for(int i = 0; i < LENGTH; i++) {
    arr1[i] = arr2[i];
}

直接比较两个数组是否相同的话,并不是比较其内容,而是比较两个数组变量的地址值,若想比较两个数组元素内容是否相同,也要用逐个元素进行比对。

如果打算对数组进行排序、寻找、反转等操作,可以使用包含algorithm标头文件:

#include <algorithm>

例如下面这个程序直接示范了排序、寻找、反转等操作:

#include <algorithm>
#include <iostream> 

using namespace std; 

int main() { 
    int number[] = {30, 12, 55, 31, 98, 11};

    // 排序 
    sort(begin(number), end(number));
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    cout << "输入搜寻值:";
    int search = 0;
    cin >> search;

    int* addr = find(begin(number), end(number), search);
    cout << (addr != end(number) ? "找到" : "没有")
         << "搜寻值" 
         << endl;

    // 反转 
    reverse(begin(number), end(number));
    for(auto n : number) {
        cout << n << " ";
    }
    cout << endl;

    return 0; 
}

执行结果:

11 12 30 31 55 98
输入搜寻值:30
找到搜寻值
98 55 31 30 12 11

sortfind等函数,也可以作用在arrayvector等,实际上,这些函数搭配函数的传递会更有效用,这之后都会谈到。


展开阅读全文