天天看点

C++编程:复合数据类型—数组

作者:尚硅谷教育

C++中不仅有基本数据类型,还提供了更加灵活和丰富的复合数据类型。

在程序中为了处理方便,常常需要把具有相同类型的数据对象按有序的形式排列起来,形成“一组”数据,这就是“数组”(array)。

C++编程:复合数据类型—数组

数组中的数据,在内存中是连续存放的,每个元素占据相同大小的空间,就像排好队一样。

1. 数组的定义

数组的定义形式如下:

数据类型 数组名[元素个数];

  • 首先需要声明类型,数组中所有元素必须具有相同的数据类型;
  • 数组名是一个标识符;后面跟着中括号,里面定义了数组中元素的个数,也就是数组的“长度”;
  • 元素个数也是类型的一部分,所以必须是确定的;

int a1[10]; // 定义一个数组a1,元素类型为int,个数为10

const int n = 4;

double a2[n]; // 元素个数可以是常量表达式

int i = 5;

//int a3[i]; // 错误,元素个数不能为变量

需要注意,并没有通用的“数组”类型,所以上面的a1、a2的类型分别是“int数组”和“double数组”。这也是为什么我们把数组叫做“复合数据类型”。

2. 数组的初始化

之前在讲到for循环时,提到过使用范围for循环可以遍历一个“序列”,用花括号括起来的一组数就是一个序列。所以在给数组赋值时,也可以使用这样的序列。

int a3[4] = {1,2,3,4};

float a4[] = {2.5, 3.8, 10.1}; // 正确,初始值说明了元素个数是3

short a5[10] = {3,6,9}; // 正确,指定了前三个元素,其余都为0

//long a6[2] = {3,6,9}; // 错误,初始值太多

//int a6[4] = a3; // 错误,不能用另一个数组对数组赋值

需要注意的是:

  • 对数组做初始化,要使用花括号{}括起来的数值序列;
  • 如果做了初始化,数组定义时的元素个数可以省略,编译器可以根据初始化列表自动推断出来;
  • 初始值的个数,不能超过指定的元素个数;
  • 初始值的个数,如果小于元素个数,那么会用列表中的值初始化靠前的元素;剩余元素用默认值填充,整型的默认值就是0;
  • 如果没有做初始化,数组中元素的值都是未定义的;这一点和普通的局部变量一致;

3. 数组的访问

(1)访问数组元素

数组元素在内存中是连续存放的,它们排好了队之后就会有一个队伍中的编号,称为“索引”,也叫“下标”;通过下标就可以快速访问每个元素了,具体形式为:

数组名[元素下标]

这里也是用了中括号来表示元素下标位置,被称为“下标运算符”。比如a[2]就表示数组a中下标为2的元素,可以取它的值输出,也可以对它赋值。

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

cout << "a[2] = " << a[2] << endl; // a[2] = 3

a[2] = 36;

cout << "a[2] = " << a[2] << endl; // a[2] = 36

需要注意的是:

  • 数组的下标从0开始;
  • 因此a[2]访问的并不是数组a的第2个元素,而是第三个元素;一个长度为10的数组,下标范围是0~9,而不是1~10;
  • 合理的下标,不能小于0,也不能大于 (数组长度 - 1);否则就会出现数组下标越界;

(2)数组的大小

所有的变量,都会在内存中占据一定大小的空间;而数据类型就决定了它具体的大小。而对于数组这样的“复合类型”,由于每个元素类型相同,因此占据空间大小的计算遵循下面的简单公式:

数组所占空间 = 数据类型所占空间大小 * 元素个数

这样一来,即使定义的时候没有指定数组元素个数,现在也可以计算得出了:

// a是已定义的数组

cout << "a所占空间大小:" << sizeof(a) << endl;

cout << "每个元素所占空间大小:" << sizeof(a[0]) << endl;

// 获取数组长度

int aSize = sizeof(a) / sizeof(a[0]);

cout << "数组a的元素个数:" << aSize << endl;

这里为了获取数组的长度,我们使用了sizeof运算符,它可以返回一个数据对象在内存中占用的大小(以字节为单位);数组总大小,除以每个数据元素的大小,就是元素个数。

(3)遍历数组

如果想要依次访问数组中所有的元素,就叫做“遍历数组”。我们当然可以用下标去挨个读取:

cout << "a[0] = " << a[0] << endl;

cout << "a[1] = " << a[1] << endl;

但这样显然太麻烦了。更好的方式是使用for循环:

// 获取数组长度

int aSize = sizeof(a) / sizeof(a[0]);

for (int i = 0; i < aSize; i++ )

{

cout << "a[" << i << "] = " << a[i] << endl;

}

循环条件如果写一个具体的数,很容易出现下标越界的情况;而如果知道了数组长度,直接让循环变量i小于它就可以了。

当然,这种写法还是稍显麻烦。C++ 11标准给我们提供了更简单的写法,就是之前介绍过的范围for循环:

for (int num: a )

{

cout << num << endl;

}

当然,这种情况下就无法获取元素对应的下标了。

4. 多维数组

之前介绍的数组只是数据最简单的排列方式。如果数据对象排列成的不是“一队”,而是一个“方阵”,那显然就不能只用一个下标来表示了。我们可以对数组进行扩展,让它从“一维”变成“二维”甚至“多维”。

int arr[3][4]; // 二维数组,有三个元素,每个元素是一个长度为4的int数组

int arr2[2][5][10]; // 三维数组

C++中本质上没有“多维数组”这种东西,所谓的“多维数组”,其实就是“数组的数组”。

l 二维数组int arr[3][4]表示:arr是一个有三个元素的数组,其中的每个元素都是一个int数组,包含4个元素;

l 三维数组int arr2[2][5][10]表示:arr2是一个长度为2的数组,其中每个元素都是一个二维数组;这个二维数组有5个元素,每个元素都是一个长度为10的int数组;

一般最常见的就是二维数组。它有两个“维度”,第一个维度表示数组本身的长度,第二个表示每个元素的长度;一般分别把它们叫做“行”和“列”。

(1)多维数组的初始化

和普通的“一维”数组一样,多维数组初始化时,也可以用花括号括起来的一组数。使用嵌套的花括号可以让不同的维度更清晰:

数据类型 数组名[行数][列数] = {数据1, 数据2, 数据3, …};

数据类型 数组名[行数][列数] = {

{数据11, 数据12, 数据13, …},

{数据21, 数据22, 数据23, …},

};

需要注意:

  • 内嵌的花括号不是必需的,因为数组中的元素在内存中连续存放,可以用一个花括号将所有数据括在一起;
  • 初始值的个数,可以小于数组定义的长度,其它元素初始化为0值;这一点对整个二维数组和每一行的一维数组都适用;
  • 如果省略嵌套的花括号,当初始值个数小于总元素个数时,会按照顺序依次填充(填满第一行,才填第二行);其它元素初始化为0值;
  • 多维数组的维度,可以省略第一个,由编译器自动推断;即二维数组可以省略行数,但不能省略列数。

// 嵌套的花括号的初始化

int ia[3][4] = {

{1,2,3,4},

{5,6,7,8},

{9,10,11,12}

};

// 只有一层花括号的初始化

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

// 部分初始化,其余补0

int ia3[3][4] = {

{1,2,3},

{5,6}

};

int ia4[3][4] = {1,2,3,4,5,6};

// 省略行数,自动推断

int ia5[][4] = {1,2,3,4,5};

(2)访问数据

也可以用下标运算符来访问多维数组中的数据,数组的每一个维度,都应该有一个对应的下标。对于二维数组来说,就是需要指明“行号”“列号”,这相当于数据元素在二维矩阵中的坐标。

// 访问ia的第二行、第三个数据

cout << "ia[1][2] = " << ia[1][2] << endl;

// 修改ia的第一行、第二个数据

ia[0][1] = 19;

同样需要注意,行号和列号都是从0开始、到 (元素个数 - 1) 结束。

(3)遍历数组

要想遍历数组,当然需要使用for循环,而且要扫描每一个维度。对于二维数组,我们需要对行和列分别进行扫描,这是一个双重for循环:

cout << "二维数组总大小:" << sizeof(ia) << endl;

cout << "二维数组每行大小:" << sizeof(ia[0]) << endl;

cout << "二维数组每个元素大小:" << sizeof(ia[0][0]) << endl;

// 二维数组行数

int rowCnt = sizeof(ia) / sizeof(ia[0]);

// 二维数组列数

int colCnt = sizeof(ia[0]) / sizeof(ia[0][0]);

for (int i = 0; i < rowCnt; i++)

{

for (int j = 0; j < colCnt; j++)

{

cout << ia[i][j] << "\t";

}

cout << endl;

}

同样,这里利用了sizeof运算符:

  • 行数 = 二维数组总大小 / 每行大小
  • 列数 = 每行大小 / 每个元素大小

当然,也可以使用范围for循环:

for (auto & row : ia)

{

for (auto num : row)

{

cout << num << "\t";

}

cout << endl;

}

这里的外层循环使用了auto关键字,这也是C++ 11新引入的特性,它可以自动推断变量的类型;后面的&是定义了一个“引用”。关于这部分内容,会在后面继续介绍。

5. 数组的简单排序算法

数组排序指的是给定一个数组,要求把其中的元素按照从小到大(或从大到小)顺序排列。

这是一个非常经典的需求,有各种不同的算法可以实现。我们这里介绍两种最基本、最简单的排序算法。

(1)选择排序

选择排序是一种简单直观的排序算法。

它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后追加到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

C++编程:复合数据类型—数组

选择排序可以使用双重for循环很容易地实现:

#include<iostream>

using namespace std;

int main()

{

int arr[] = {5, 9, 2, 7, 4, 3, 12, 6, 1, 5, 7};

int size = sizeof(arr) / sizeof(arr[0]);

// 选择排序

for (int i = 0; i < size; i++)

{

for (int j = i + 1; j < size; j++)

{

if (arr[j] < arr[i])

{

// 如果arr[j]更小,就和arr[i]交换位置

int temp = arr[i];

arr[i] = arr[j];

arr[j] = temp;

}

}

}

// 输出

for (int num : arr)

cout << num << "\t";

cin.get();

}

(2)冒泡排序

冒泡排序也是一种简单的排序算法。

它的基本原理是:重复地扫描要排序的数列,一次比较两个元素,如果它们的大小顺序错误,就把它们交换过来。这样,一次扫描结束,我们可以确保最大(小)的值被移动到序列末尾。这个算法的名字由来,就是因为越小的元素会经由交换,慢慢“浮”到数列的顶端。

C++编程:复合数据类型—数组

冒泡排序的代码实现也非常简单,同样是使用双重for循环:

// 冒泡排序

for (int i = 0; i < size; i++)

{

for (int j = 0; j < size - i - 1; j++)

{

if (arr[j] > arr[j+1])

{

int temp = arr[j+1];

arr[j+1] = arr[j];

arr[j] = temp;

}

}

}

继续阅读