目录
注
本笔记参考:《C++ PRIMER PLUS(第6版)》
类型组合
以结构的组合为例:
struct S1
{
int year;
char data;
};
- 创建结构S1的变量:
S1 s_01, s_02, s_03; s_01.tear = 2000; //使用成员运算符访问成员
-
创建指向结构S1的指针:
S1* pa = &s_02; pa->year = 2000; //使用间接成员运算符访问成员
-
创建结构S1的数组:
S1 sArr[3]; //sArr是一个数组 sArr[0].data = 'I'; //sArr[0]是一个结构 (sArr + 1)->year = 2001; //(数组名也是一个指针)相当于 sArr[1].year = 2001
-
创建S1的 结构指针 数组:
S1* arp_1[3] = { &s_01, &s_02, &s_03 }; const S1* arp_2[3] = { &s_01, &s_02, &s_03 }; //此处const的使用:防止指针指向的值被修改 std::cout << arp[1]->data << std::endl;
其中,arp[1]就是一个指针,可以使用间接成员运算符。
-
创建S1的 结构指针数组 指针:
const S1** ppa = arp_2; //数组名arp指向该数组的首元素的地址 //因为前面的arp_2使用了const,此处也要使用,保持类型的统一
注意,数组arp内的元素就是结构指针,也就是说,ppa指向的地址实际上存储的是一个指针的值,为了类型上的一致,需要使用一个二级指针。但是这种声明容易出错,为了方便,可以使用C++11带有的auto:
auto ppb = arp_2;
如果解引用二级指针,就会得到一个一级指针,也就是说,*ppa 是一个指向数组arp第一个元素的指针,即 &s_01 :
std::cout << (*ppa)->year << std::endl; std::cout << (*(ppb + 1))->year << std::endl;
上述输出的第二行语句,如果使用的是 (*ppb + 1) ,意思是将访问的地址向后移动1个字节,这样会造成输出错误。
二级指针ppb的访问权限: +1 跳过数组arp_2的一个元素;
一级指针*ppb的访问权限:+1 跳过1个字节。
数组的替代品
模板类vector和array是数组的替代品,接下来将进行简单的介绍。
模板类vector
类似于string类,vector也是一种动态数组:可以在运行阶段设置长度,在末尾添加新数据,在中间插入新数据。
实际上,vector内部确实使用了new与delete来管理内存。
要使用vector对象,要知道:
- vector对象需通过头文件vector进行引用;
- vector位于名称空间std中;
- 这种模板使用不同语法指定其的存储类型和元素数。
使用例:
#include<iostream>
#include<vector>
int main()
{
using namespace std;
vector<int> vi; //创建一个int类型的数组,可存储 0 个元素
int n = 0;
cin >> n;
vector<double>vd(n); //创建一个double类型的数组,可存储 n 个元素
return 0;
}
由于vector对象可以在插入或者添加值时自动调整长度,因此可以将 vi 的初始长度设置为 0 (后继的长度调整需要使用vector包内的方法)。
vector对象的声明方式:
vector类的功能比数组更加强大,但是效率却更低,而原本的数组不怎么方便和安全。为此,C++11新增了模板类array。
模板类array(C++11)
类array同样位于名称空间std中,使用时需要包含头文件array。
与数组一样,array对象的长度是固定的,并且被存储在栈(静态内存分配)中。它拥有与数组比肩的效率,并且更加安全、方便。
使用例:
#include<iostream>
#include<array>
int main()
{
using namespace std;
array<int, 5> ai; //创建一个array类,类型是int,可存储5个元素
array<double, 4> ad = { 1.2, 3.1, 3.1416, 4.3 };
return 0;
}
array对象的声明方式:
比较数组、vector对象和array对象
通过例子比较两者的区别:
#include<iostream>
#include<vector>
#include<array>
int main()
{
using namespace std;
double a_1[4] = { 1.2, 2.4, 3.6, 4.8 }; //原始的数组类型
vector<double> a_2(4); //创建一个初始可以包含4个元素的vector对象
//C98:没有初始化vector的快捷方式
a_2[0] = 1.0 / 3.0;
a_2[1] = 1.0 / 5.0;
a_2[2] = 1.0 / 7.0;
a_2[3] = 1.0 / 9.0;
//C++11:创建,并初始化array对象
array<double, 4> a_3 = { 3.14, 2.72, 1.62, 1.41 };
array<double, 4> a_4;
a_4 = a_3; //这种处理方式在大小相同的array对象中可以成立
//使用array对象
cout << "a_1[2]:" << a_1[2] << "\t 其地址为 " << &a_1[2] << endl;
cout << "a_2[2]:" << a_2[2] << " 其地址为 " << &a_2[2] << endl;
cout << "a_3[2]:" << a_3[2] << "\t 其地址为 " << &a_3[2] << endl;
cout << "a_4[2]:" << a_4[2] << "\t 其地址为 " << &a_4[2] << endl;
//错误的存储操作
cout << endl << "进行操作:a_1[2] = 20.2;" << endl;
a_1[-2] = 20.2;
cout << "上述操作将会导致:" << endl;
cout << "\ta_1[-2]:" << a_1[-2] << "\t其地址为 " << &a_1[-2] << endl;
cout << "\ta_3[2] :" << a_3[2] << "\t其地址为 " << &a_3[2] << endl;
cout << "\ta_4[2] :" << a_4[2] << "\t其地址为 " << &a_4[2] << endl;
return 0;
}
程序执行的结果是(本次编译在Ubantu系统中进行):
【分析】
① 首先要注意三点:
- 数组、vector对象和array对象都可以通过标准数组表示法访问元素;
- array对象与数组存储在栈中,而vector对象存储在堆中;
- array对象可以整体赋值,或者在对象之间进行赋值。但数组和vector对象只能逐元素操作。
② 另外,上述提到了一个错误的操作:
a_1[-2] = 20.2;
实际上,在一些编译器中,上述这种语句会引发警告:
因为 a_1[-2] 等价于 *(a_1 - 2) ,所以这条语句的含义是将a_1的指向 向前移动两个double元素的单位,在这个位置进行信息的存储。
注:C++不检查这种越界访问错误。因此,这种错误可能影响内存中其他正在运行的程序,这是不安全的。
那么vector对象和array对象有禁止这种行为吗?答案是可以禁止,或者说,仍然可以书写上述这种不安全的代码:
a_2[-2] = .5;
a_3[200] = 1.4;
尽管在程序执行时,可能引发assert警告:
③ 为了解决②中的问题,可以使用vector对象和array对象的成员函数at():
a_2.at(1) = 2.3; //将2.3输入到a_2[1]中
这个成员函数将会在运行期间捕获非法索引,程序将默认中断。(代价是:这种额外的检查将会运行更长时间)