7 类模板array和vector、异常捕获
1 array对象
array对象是一组具有相同类型的、连续的内存区域。要引用array对象中的一个特定区域或元素,需通过指定array对象名称和该特定元素在array对象中的位置编号(position number)来完成。
位置编号更正规的叫法是下标(subscript)或索引(index)。每个array对象中第一个元素下标都为0。
下标必须是一个整数或整数表达式(使用任何整数类型)。若程序以一个表达式作为下标,那么程序要计算这个表达式以确定下标。如:
c[a+b] += 2;
一个带下标的array对象名是一个左值——它和其他非array对象的变量名一样,可以在赋值语句左边使用。
array对象下标括起来的方括号实际上是C++中的一个运算符,它和圆括号具有相同的优先级。
2 array对象的声明
array对象的声明语法规范为:
array<类型,大小> 对象名;
表示法<类型,大小>表明array是一个类模板。编译器将根据元素的类型和array对象的大小来分配合适的内存空间。如:
array<int,12> c;
array对象的大小必须是个无符号整型,但大多数数据类型都可以作为其元素的值的类型。如一个string
类型的array对象能用来存储字符串。
3 使用array对象的例子
下面给出一些例子说明如何声明array对象,如何初始化对象,以及如何进行一些常见的array对象的操作。
3.1 声明array对象以及用循环来初始化array对象的元素
#include<iostream>
#include<iomanip>
#include<array>
using namespace std;
int main()
{
array<int,5> n;
for (size_t i = 0; i < n.size(); ++i)
{
n[i] = 0;
}
cout << "Element" << setw(13) << "Value" << endl;
for (size_t j = 0; j < 10; ++j)
{
cout << setw(7) << j << setw(13) << n[j] << endl;
}
}
和其他自动变量一样,自动的array对象不会隐式地初始化为0,尽管static
的array对象是这样的。
控制变量i和j指定了array对象的下标,它们的类型被声明为size_t
。根据C++标准,size_t
表示的是一种无符号的整型,建议任何表示array对象大小和对象下标的变量采用这个类型。
类型size_t
在std命名空间中定义并在头文件<cstddef>
中,被其他各种各样的头文件所包含。在编译使用了size_t
类型的程序时,若产生它未定义的错误,那么只需要将<cstddef>
头文件包含到该程序中即可。
3.2 在声明中用初始化列表初始化array对象
在array对象的声明中,也可以对其元素进行初始化。例如:
array<int,5> n = {1,2,3,4,5};
如果初始化值的个数少于array对象元素的个数,那么剩下的array对象元素都被初始化为0。如:
array<int,5> n = {};
如果初始化值的个数大于array对象元素的个数,会导致一个编译错误。
3.3 用常量变量指定array对象的大小
const size_t arraySize = 5;
array<int,arraySize> s;
上面使用const
限定符声明一个所谓的常量变量arraySize,其值为5。这个常量变量用于指定array对象的大小,必须在声明时用一个常量表达式来初始化,而且之后不能再修改。常量变量也称为命名常量或只读常量。
int main()
{
const int x; //Error: x没被初始化
x = 7; //Error: 不可以修改一个常量变量
}
3.4 array对象下标的边界检查
在使用[]
运算符访问array对象的元素时,C++并没有提供自动对array对象边界检查的机制,来防止程序员引用一个不存在的元素。因此,一个执行中的程序可以“离开”array对象的任何一端而不会产生警告。这是一个执行时的逻辑错误,并不是语法错误。
3.5 静态局部array对象和自动局部array对象
将static
应用于局部array对象的声明,使得array对象不会在每次程序调用该函数时都进行创建和初始化,也不会在每次该函数结束时被销毁。这样可以提高性能,特别是在使用大型array对象时。
例如:
void staticArrayInit(void)
{
static array<int,arraySize> array1;
}
3.6 基于范围的for语句
基于范围的for语句可以不适用计时器就完成所有元素的遍历,从而不需要自己实现对边界的检查功能。
语法格式为:
for(范围变量声明: 表达式)
语句
其中范围变量声明含有一个类型名称和一个标识符(例如 int item
),表达式是需要迭代遍历的array对象。范围变量声明的类型必须和array对象的元素类型一致。
例如:
array<int,5> items = {1,2,3,4,5};
for(int item: items)
cout<<item<<" ";
第2、3行等价于:
for(int counter = 0;counter < items.size();++counter)
cout<<items[counter]<<" ";
使用基于范围的for语句不需要访问元素的下标。
4 array对象的排序和查找
排序
对数据排序,即按照某种特定的次序(升序或降序)对数据进行排列,是最重要的计算应用之一。
查找
常常可能需要判断一个array对象是否有与某个关键值相匹配的值。在一个array对象中发现一个特定元素的过程称为查找。
示范说明
#include <iostream>
#include<iomanip>
#include <array>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
const size_t arraySize = 7;
array<string, arraySize> colors = { "red","orange","yellow","green","blue","indigo","violet" };
cout << "Unsorted array:\n";
for (string color : colors)
cout << color << " ";
sort(colors.begin(), colors.end());
cout << "\nSorted array:\n";
for (string item : colors)
cout << item << " ";
bool found = binary_search(colors.begin(), colors.end(), "indigo");
cout << "\n\n\"indigo\" " << (found ? "was" : "was not")
<< " found in colors" << endl;
found = binary_search(colors.begin(), colors.end(), "cyan");
cout << "\"cyan\" " << (found ? "was" : "was not")
<< " found in colors" << endl;
}
运行结果为:
Unsorted array:
red orange yellow green blue indigo violet
Sorted array:
blue green indigo orange red violet yellow
"indigo" was found in colors
"cyan" was not found in colors
上面例子中,先创建了一个未排序的array对象,然后使用C++标准库函数sort
对array对象colors
按升序进行排序。sort
函数的实参制定了需要排序的元素的范围。
后用binary_search
函数确定一个值是否在array对象中,首先值的序列必须是已按升序排好序的,而且binary_search
函数不会核实这件事。
5 多维array对象
可以用两个维度(即下标)的array对象表示数值表格,这样的数值表格包含的信息是按照行和列排列的。
按照惯例,第一个下标表示元素的行,第二个下标表示元素的列。其中每个元素都用a[i][j]
形式的元素名称来标识。
TIPS:
使用
a[x,y]
不正确地引用一个二维array对象元素a[x][y]
是一个错误。事实上,由于C++计算表达式x,y
的值得到的是y
(逗号分隔的表达式的最后一项),所以a[x,y]
被认为是a[y]
。
#include <iostream>
#include <array>
using namespace std;
const size_t rows = 2;
const size_t columns = 3;
void printArray(const array<array<int, columns>, rows>&);
int main()
{
array<array<int, columns>, rows> array1 = { 1,2,3,4,5,6 };
array<array<int, columns>, rows> array2 = { 1,2,3,4,5 };
cout << "Values in array1 by row are:" << endl;
printArray(array1);
cout << "\nValues in array2 by row are:" << endl;
printArray(array2);
}
void printArray(const array<array<int, columns>, rows> & a)
{
for (auto const& row : a)
{
for (auto const& element : row)
{
cout << element << " ";
}
cout << endl;
}
}
执行结果是:
Values in array1 by row are:
1 2 3
4 5 6
Values in array2 by row are:
1 2 3
4 5 0
嵌套的array对象的类型声明中,每个array对象其元素的类型被指定为:
array<int,columns>
即每个array对象都包含两个元素,每个元素又是一个含3个int
元素的一维array对象。
为处理二维array对象的元素,我们采用一个嵌套的基于范围的for
语句。
auto
关键字告诉编译器根据这个变量的初始化值来确定它的数据类型。
6 C++标准库类模板vector的介绍
C++标准库类模板vector
与类模板array
类似,而且支持动态的大小调整。标准类模板vector
在头文件<vector>
中定义,并且属于命名空间std。
创建vector对象
#inlcude<iostream>
#include<vector>
int main()
{
vector<int> integers1(7);
}
上面创建了一个存储int
类型值的vector
对象,其中integers1
包含7个元素。在默认情况下,每个vector
对象的所有元素都被设置为0。
像array
对象一样,可以定义vector
对象来存储大多数数据类型的数据,只需要把vector<int>
中的int
替换成适当的数据类型即可。
vector成员函数size
可以使用vector
的成员函数size
来获得integers1
的大小(即元素个数):
integers1.size()
vector对象比较
if (integers1 == integers2)
{
cout << "integers1 and integers2 are equal" << endl;
}
一个vector对象可以用==
或!=
运算符直接与另一个vector
对象进行比较。
一个vector对象用另一个vector对象来初始化及赋值
C++标准库类模板vector
允许再创建一个新的vector
对象时,用一个已有的vector
对象的内容来初始化它。
vector<int> integers3(integers1);
这里创建一个vector
对象integers3
,并用integers1
的一个副本初始化它,此举会调用vector
的拷贝构造函数,执行复制操作。
integers1 = integers2;
vector
对象间可以使用赋值运算符(=
)。
使用运算符[]访问vector对象的元素
像array
对象一样,使用方括号访问vector
对象的元素时,C++不需要将进行边界检查。
标准类模板vector
在它的成员函数at
中提供了边界检查的能力(类模板array
也是这么做的)。
异常处理:处理超出边界的下标
异常处理能够创建可以解决异常的容错程序。很多情况下,在处理异常的同时还允许程序继续运行,就像没遇到异常一样。
若函数检测到一个问题,例如一个无效的array对象下标或一个无效的实参,它抛出一个异常,也就是一个异常发生了。
try-catch语句
为了处理一个异常,需要将可能抛出一个异常的任何代码放置在一个try语句中。catch语句包含了如果异常发生则处理异常的代码。
try
{
cout << "\nAttempt to display integers1.at(15)" << endl;
cout << integers1.at(15) << endl;
}
catch (out_of_range& ex)
{
cerr << "An exception occured: " << ex.what() << endl;
}
其中vector
成员函数at
提供了边界检查和抛出异常的功能,即这个函数的实参是一个无效的下标时,就会抛出一个异常。在默认情况下将导致程序终止。如果下标有效,at
函数返回在此指定位置上的元素。
当程序以实参15调用vector成员函数at
时(第4行),这个函数试图访问在位置15上的元素,超出了vector
对象integers1
的边界。此刻,integers1
只有10个元素。因为边界检查是在执行期间进行的,因此 vector
成员函数at
产生一个异常。具体地说,就是第4行抛出一个out_of_range
异常(在头文件<stdexcept>
中),通知程序这个问题。在这个时候,try
语句块立即终止,同时catch
语句块开始执行。请注意,在try
语句块中声明的任何变量此时都超出了它们的作用域,也就是说在catch
语句块中都不可访问。
这个catch
语句块声明了一个类型(out_of_range
)和一个接收引用的异常形参(ex
)。
异常形参的what成员函数
当第6
∼
\sim
∼ 9行捕捉到异常时,程序显示一则消息说明出现的问题。第8行调用这个异常对象的what
成员函数,来得到并显示存储在此异常对象中的错误消息。在这个例子中,一旦消息被显示,这个异常就视为被处理了,然后程序继续执行catch
语句块结束的右花括号之后的语句。
改变vector对象的大小
vector
对象和array
对象之间的一个主要区别是vector
对象可以动态增长以容纳更多的元素。
integer3.push_back(1000);
这里调用vector
对象的push_back
成员函数,在这个对象尾部加一个新的元素,这个元素的值是1000,integers3
的大小也会增加1。