第四课.容器(一)

C++设置有实用的基础容器,分为两大类:序列型容器,关联型容器

序列型容器:数组

数组是内存中一组连续的同类型存储区,连续也是物理意义上的连续,数组将连续的存储区合并为一个整体对象,比如:

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

数组的声明:int arr[10]
类型名称int表示数组元素的类型;
名称arr是数组的名称;
整数10表示数组元素的个数;
数组一旦声明,元素个数就不能再改变;


在内存中的会连续存储(int占4字节),另外发现默认初始值为0:

0x006FFDF0  01 00  ..
0x006FFDF2  00 00  ..
0x006FFDF4  02 00  ..
0x006FFDF6  00 00  ..
0x006FFDF8  03 00  ..
0x006FFDFA  00 00  ..
0x006FFDFC  04 00  ..
0x006FFDFE  00 00  ..
0x006FFE00  05 00  ..
0x006FFE02  00 00  ..
0x006FFE04  06 00  ..
0x006FFE06  00 00  ..
0x006FFE08  00 00  ..
0x006FFE0A  00 00  ..
0x006FFE0C  00 00  ..
0x006FFE0E  00 00  ..
0x006FFE10  00 00  ..
0x006FFE12  00 00  ..
0x006FFE14  00 00  ..
0x006FFE16  00 00  ..

下标表示了元素在数组容器内的位置,可以通过下标访问数组的元素,下标从0开始计:

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

for (int index = 0; index < 10; index++) 
{
    std::cout << arr[index] << ' ';
}

fig1

数组的下标

在开始认识下标之前,先考虑一个问题:100米的路径,每10米需要一个栏杆,总共需要多少栏杆?
答案是11个栏杆,和人的常识 100 m 10 m = 10 \frac{100m}{10m}=10 10m100m=10相违背,这正好反应了大部分编程语言会让初学者忽视的off-by-one error(差一错误);


再重新考虑一个问题:现在有一个整数x满足x>=16且x<=37,那么x的可能取值数量为多少?
令x的下界为low,上界为high,当low与high重合时,x取值数量为1,据此扩展,x的取值数量为high-low+1;
所以这个问题x可以取37-16+1=22个数


high-low+1中,+1操作很容易被忽略,所以需要考虑一种编程技巧:用左闭右开区间来表示范围;比如x>=16且x<=37,现在表达为x>=16且x<38;这样,就可以直接用high-low来表示取值范围;
值得一提,这也是为什么程序设计上,推荐以下方式遍历数组:

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

for (int index = 0; index < 10; index++) {
    std::cout << arr[index] << ' ';
}

而不是:

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

for (int index = 0; index <= 9; index++) {
    std::cout << arr[index] << ' ';
}

数组增删改查

数组元素的添加和删除

从数组尾部增加元素:
fig2

从数组尾部删除元素:
fig3
可见,在尾部的元素增加和删除,时间复杂度为 O ( 1 ) O(1) O(1)
对于不在尾部的元素增加:
fig4
同理,不在尾部的元素删除:
fig5
可见,对于不在尾部的元素增加和删除,时间复杂度为 O ( n ) O(n) O(n)

数组元素访问与修改

由于数组的存储在物理上是连续的,这使其访问和遍历变得高效,访问与修改元素有两种方式,一种是通过下标,另一种是通过指针:

arr[2]; //用下标访问元素
arr[6]=100; //用下标修改元素

int *p=arr;
*(p+2); //用指针访问元素
*(p+6)=100; //用指针修改元素

通过指针访问的例子,容易发现,数组的名字实际上也是数组第一个元素的地址;
也可以尝试查找数组元素:

//运算符sizeof返回变量所占字节数
int len=sizeof(arr)/sizeof(arr[0]);
//左闭右开区间
//取值数=len-0=len
for (int index=0;index<len;index++)
{
	if (arr[index]==3)
	{
		return index;
	}
}

二维数组

二维数组定义为:包含行轴与列轴两个维度的数组;二维数组访问如下:

int darr[2][4] = { {1,2,3,4},{5,6,7,8} };
for (int row = 0; row < 2; row++)
{
    for (int col = 0; col < 4; col++)
    {
         cout << darr[row][col] << " ";
    }
    //每读完一行换一次行打印
    cout << endl;
}

上面的访问顺序是一行一行访问的,这比按列访问快速,因为二维数组在物理内存中是按行连续存储的:

0x0118FAF0  01 00  ..
0x0118FAF2  00 00  ..
0x0118FAF4  02 00  ..
0x0118FAF6  00 00  ..
0x0118FAF8  03 00  ..
0x0118FAFA  00 00  ..
0x0118FAFC  04 00  ..
0x0118FAFE  00 00  ..
0x0118FB00  05 00  ..
0x0118FB02  00 00  ..
0x0118FB04  06 00  ..
0x0118FB06  00 00  ..
0x0118FB08  07 00  ..
0x0118FB0A  00 00  ..
0x0118FB0C  08 00  ..
0x0118FB0E  00 00  ..

这样循环可以减少CPU跨切循环层的次数,所以速度会更快

动态数组Vector

使用之前的基本数组,无法实现动态扩容插入元素,因为一旦声明,存储区域就被固定了:

int arr[]={1,2,3,4};
//根据左闭右开区间,4处于开区间上
//arr[4]访问到了非法地址会出错
arr[4]=5;

为了解决这种情况,C++出现了动态数组Vector,Vector是面向对象方式的动态数组;
使用Vector容器,可以实现动态扩容,其属于功能更强的序列容器;

//将vector导入当前文件
#include<vector>

//vector是std命名空间下的一个类
std::vector<int> vec={1,2,3,4};

//进行插入元素操作
vec.push_back(5);

在开发过程中,一般会把声明的语句写在h头文件中,再通过include包含;而把逻辑实现写在cpp文件中;


std::vector<int> vec={1,2,3,4};中,<>确定数组元素的类型;push_back是封装在vec对象中的方法,可以动态扩容;
Vector的遍历可以由循环实现,而且借助vec内部的方法capacity和size可以查看动态数组当前的容量和已经存储的元素个数,遍历会更加方便:

#include<iostream>
using namespace std;

for (int index=0;index<vec.size();index++)
{
	cout<<vec[index]<<endl;
}

注意到,之前的插入操作是从尾部插入,如果要从中间插入就不能再使用push_back,而应该是insert:

vector<int> vec={1,2,3,5};

//想插入得到1,2,3,4,5
//必须是--vec.end(),如果是vec.end()--,会变成尾部插入元素
/*
比如
int i=6;
i++返回6,++i返回7
*/
vec.insert(--vec.end(),4);
//或者
vec.insert(vec.end()-1,4);

同理,Vector的删除元素也有在尾部和在中间的情况:

//尾部删除
vec.pop_back();

//中间删除
vec.erase(vec.end()-1);

注意此处的细节,vec.erase(vec.end()-1)vec.pop_back()是等价的操作,只有写成vec.erase(vec.end()-2)才是真正从中间删除元素4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值