vector的使用01

官方标准库查看

#include <bits/stdc++.h>

传送门

https://docs.microsoft.com/zh-cn/cpp/standard-library/cpp-standard-library-reference?view=vs-2019

1.vector的定义

vector属于std命名域的,因此需要通过命名限定
using std::vector;
  vector vInts;
using namespace std;


vector<int> a ;         //声明一个int型向量a
//1.指明大小的声明
vector<int> a(10) ;      //声明一个初始大小为10的向量

//2.指明大小并且(初始值相同)的向量
vector<int> a(10, 1) ;//声明一个初始大小为10且初始值都为1的向量

//3.用别的向量初始化
vector<int> b(a) ;       //声明并用向量a初始化向量b
//这两个向量的内存区域不同,但是如果做==是true的
//比较的是这两个向量的值


//4.用迭代器指定向量指定限定量的初始化
vector<int>b(a.begin(),a.begin()+3);
//[)是这样的区间,最后一个是开区间
//将a向量中从第0个到第2个(共3个)作为向量的初始值


//5.用数组初始化向量,指定限定量
int n[]={1,2,3,4,5};
//将数组n的前五个元素
[0,5)也即[0,4]
vector<int>a(n,n+5);

//6.将数组指定范围内的元素作为向量的处置
vector<int> a(&n[1], &n[4]) ;
[1,4)
[1,3];只有三个元素 (3-1)+1
//也是开区间

6的实例:

实例6

在这里插入图片描述

2.向量的基本操作

容量

1.a.size()向量元素多少
2.a.empty()向量判空
3.a.clear()清空向量元素
4.a.capacity()向量能装的元素多少
5… a=b(将b向量复制到a向量中)
6. 判断两个向量的值是否相同直接用==就可以了
7. 判断两个向量的地址是否相同用&取地址然后比较
8. a.resize()更改向量的大小
9. vec.shrink_to_fit(); 减少向量大小到满足元素所占存储空间的大小

修改
  1. vec.erase();删除元素
  • b.erase(b.begin()) ; //将起始位置的元素删除
  • b.erase(b.begin(), b.begin()+3) ; //将(b.begin(), b.begin()+3)之间的元素删除
  • c.erase(pos) // 删除pos位置的数据,传回下一个数据的位置。
  • c.erase(beg,end) //删除[beg,end)区间的数据,传回下一个数据的位置。
  • Remove_if()算法删除元素
  • remove_if(begin,end,op); #include <algorithm>
  • 前两个参数表示迭代的起始位置和这个起始位置所对应的停止位置。最后一个参数:传入一个回调函数
  • 通过迭代器无法得到容器本身,而要删除容器内的元素必须通过容器的成员函数来进行。
  • 因而此函数无法真正删除元素,只能把要删除的元素移到容器末尾并返回要被删除元素的迭代器,然后通过erase成员函数来真正删除。因为一般remove_if和erase函数是成对出现的。
#include <iostream>
#include <vector>
#include<iterator>
#include<algorithm>
#include<string>
using namespace std;

int main()
{
	int x = 5;
	std::vector<int> c{ 1,2,3,4,5,6,7 };
	c.erase(remove_if(c.begin(), c.end(), [x](int n) {return n < x; }), c.end());
	//删除结果为真的成员
	//这里有一个匿名函数
	//>https://blog.csdn.net/zhang14916/article/details/101058089在下面有传送门
	//1234为真
	//567为假
	for (int i = 0; i < c.size(); i++)
	{
		cout << c[i] << " ";
	}
}

Remove_if()有三个参数:
  1、 iterator _First:指向第一个数据的迭代指针。
  2、 iterator _Last:指向最后一个数据的迭代指针。
  3、 predicate _Pred:一个可以对迭代操作的条件函数。
  实际上是根据条件对迭代地址进行了修改

传送门
在这里插入图片描述

  • remove函数
  • remove(beg,end,cosntt T& value)
  • remove()会移除区间{beg,end)中每一个“与value相等”的元素;
    3)两个算法都返回变动后的序列的新逻辑终点(也就是最后一个未被移除元素的下一个位置);

(4)这些算法都是把原本置于后的的未移除元素向前移动,覆盖被移除元素;

(5)未被移除的元素在相对次序上保持不变;

(6)调用者在调用此算法之后,应保证从此采用返回的新逻辑终点,而不再使用原始终点end;

(7)op不应该在函数调用过程中改变自身状态;

(8)由于会发生元素变动,所以这些算法不可用于关联式容器,关联式容器提供了功能相似的成员函数erase();

(9)list提供了一个等效成员函数remove():不是重新赋值元素,而是重新安排指针,因此具有更加性能;

(10)复杂性:线性;

  1. vec.insert()插入元素
  • a.insert(a.begin(), 3, 1000) ; //将1000分别插入到向量元素位置的0-2处(共3个元素)
  • a.insert(a.begin(), 1000); //将1000插入到向量a的起始位置前
  • b.insert(b.begin(), a.begin(), a.end()) ; //将a.begin(), a.end()之间的全部元素插入到b.begin()前
    12.vec.emplace_back()/vec.put_back()末尾添加元素
    13.vec.pop_back()末尾删除元素
    14.vec.assign()
  • 通过替换旧元素为向量元素分配新值
  • 1.分配常量值 (int size, int value)
    • size -要分配的值数
    • value -要分配给向量名称的值
  • 2.从数组或列表中分配值的语法:
    • vectorname.assign(arr, arr + size)
    • arr-要分配给向量的数组
    • size-必须从头开始分配的元素数。
  • 3.用于修改向量值的语法
    • vectorname.assign(InputIterator first, InputIterator last)
    • first -输入迭代器到初始位置范围。
    • last -输入迭代器到最终位置范围。
    • 将区间[first,last)的元素赋值到当前的vector容器中,或者赋n个值为x的元素到vector容器中,这个容器会清除掉vector容器中以前的内容。
    • 呼叫前存放在容器中的任何元素都被销毁,取而代之的是新建的元素(不进行元素分配)。如果新矢量大小超过当前矢量容量,则会导致分配的存储空间自动重新分配
    • 没有返回值

c.assign(beg,end)c.assign(n,elem)
  将[beg; end)区间中的数据赋值给c。将n个elem的拷贝赋值给c。

15.vec.swap(a,b)交换两个向量的元素

  • b.swap(a) ; //a向量与b向量进行交换
迭代器

16.vector.begin()
17.vector.end()
18.vec.cbegin()指向常量的开始指针
19.vec.cend();指向常量的结束指针
20.c.rbegin()逆向
21.c.rend()
22.c.reserve()

  • 为了避免多次push_back()或emplace_back()到这重新分配内存的问题
  • reserve的作用是更改vector的容量(capacity),使vector至少可以容纳n个元素。
  • 如果n大于vector当前的容量,reserve会对vector进行扩容。其他情况下都不会重新分配vector的存储空间
  • 如果一个vector使用默认的capacity,那么在push_back操作的时候,会根据添加元素的数量,动态的自动分配空间,2^n递增;如果声明vector的时候,显式的使用capacity(size_type n)来指定vector的容量,那么在push_back的过程中(元素数量不超过n),vector不会自动分配空间
  • ,当push_back的元素数量大于n的时候,会重新分配一个大小为2n的新空间,再将原有的n的元素和新的元素放入新开辟的内存空间中。
    (注:重新分配内存,并不会在原有的地址之后紧跟着分配的新的空间,一般会重新开辟一段更大的空间(内存地址发生变化),再将原来的数据和新的数据(copy)放入新的空间)
元素的访问

20.vec.front()
21.vec.back()
22.vec.at(int pos)
23. int* p = vec.data();
24.c.reserve() // 保留适当的容量

  • 下标访问: vec[1]; //并不会检查是否越界
  • at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常
    • 下标访问异常vec[]会发生runtime异常,并不会检查下标
    • at方法会出现debug异常,不是编译时出现的
  • //at是索引下标访问
  • 访问第一个元素: vec.front();
  • 访问最后一个元素: vec.back();
  • 返回一个指针: int* p = vec.data();

其他函数

  • c.max_size() // 返回容器中最大数据的数量。
  • get_allocator
    • C++ 函数std::vector::get_allocator()返回与向量关联的分配器
    • 相当于帮忙new了指定大小的空间
      在这里插入图片描述

在这里插入图片描述
data函数是没有参数的
得到的是指针指向vector的首地址,相当于数组的首地址
在这里插入图片描述

这样写是得不到的

//可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。这是是C++11的特性。

在这里插入图片描述

这个方法也可以,但是没必要

vector的遍历方式

1.采用下标遍历

 for(i=0;i<v.size();i++)  
    {  
        cout<<v[i]<<" ";  
    }  

2.采用迭代器遍历

 vInt::const_iterator iter=v.begin();//注意需要用const_iterator,因为容器是const类型  
    while(iter!=v.end())  
    {  
        cout<<*iter++<<" ";  
    }  

3.利用copy函数遍历
在泛型算法中,提供了一个复制函数copy,copy的函数原型大致为:copy(b,e,b1),作用是将迭代器范围[b,e)内的元素复制到以迭代器b1开始的位置。
设想,如果我们将b1设置为一个输出流迭代器ostream_iterator,那么我们就把内容复制到输出流cout,相当于显示除了所有元素



#include<iostream>  
#include<vector>  
#include<iterator>//用到几种迭代器  
#include<algorithm>//用到几个泛型算法  


typedef vector<int> vInt;  
void print_vec3(const vInt &v)//方法三,将容器的内容复制到cout绑定的迭代器  
{  
    copy(v.begin(),v.end(),ostream_iterator<int>(cout," ") );  
    cout<<endl;  
}  

实例6
实例6的代码如上图

c++数组的大小这样求
sizeof(p)/sizeof(p[0])
功能:

3.、元素的输入及访问

元素的输入和访问可以像操作普通的数组那样, 用cin>>进行输入, cout<<a[n]这样进行输出:

size() 是当前vector容器真实占用的内存大小,也就是容器当前拥有多少个元素;
capacity() 是指在发生realloc前能允许的最大元素数,也即预分配的内存空间。

sizeof (vector)取决于vector里面存放的数据类型,
10个bool就是10个字节,计算起来等于vector.capacity ()*单个数据类型大小

???

解释:
vector应该是从堆上分配内存,所有大小与元素个数无关
sizeof(vector)取决于vector类的具体实现,STL是个完全开放的东西,谁都可以来实现vector类。

在这里插入图片描述
在这里插入图片描述
sizeof(vector)在vs2019下大小不变为16

sizeof(vector)

3.运算符重载分析(==<完美转发引用折叠)

==运算符重载

判断两个数组是一样的,要满足两个条件:
①两个Vector容器的大小要一样 ②固定位置上的值要一样

< 重载运算符

<重载运算符的比较过程:依次比较数组中存储的元素的值

①若第一个数组的值小于第二个数组的值,返回true;反之,若小于,则返回false。例如[1, 2, 3] 和 [2, 3, 4] ,返回true

②若前面出现的元素的值都相等;若第一个数组的长度小于第二个数组的长度,则返回true;否则返回false

push_back()和emplace_back()之间的区别
  • push_back():先向容器尾部添加一个右值元素(临时对象),然后调用构造函数构造出这个临时对象,最后调用移动构造函数将这个临时对象放入容器中并释放这个临时对象。
    注:最后调用的不是拷贝构造函数,而是移动构造函数。因为需要释放临时对象,所以通过std::move进行移动构造,可以避免不必要的拷贝操作

  • emplace_back():在容器尾部添加一个元素,调用构造函数原地构造,不需要触发拷贝构造和移动构造。因此比push_back()更加高效。
    内存优化主要体现在使用了就地构造(直接在容器内构造对象,不用拷贝一个复制品再使用)+强制类型转换的方法来实现

  • 函数声明的变化
    emplace_back() 是从 C++11 起新增到 vector 中的方法,最初的函数声明为:

template< class… Args >
void emplace_back( Args&&… args );
之后在 C++14 之后,将无返回值 void 改为了返回对插入元素的引用:

template< class… Args >
reference emplace_back( Args&&… args );

  • 两个的源码

void push_back(const _Ty& _Val) { // insert element at end, provide strong guarantee
        emplace_back(_Val);
    }
 
    void push_back(_Ty&& _Val) { // insert by moving into element at end, provide strong guarantee
        emplace_back(_STD move(_Val));

- std::move

std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::move基本等同于一个类型转换:static_cast<T&&>(lvalue);

C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.。
对指针类型的标准库对象并不需要这么做.
vector是连续的,把x向vector内存拷贝这步是必须的,std::move的作用是减少不必要的临时对象复制构造(x_old-【优化这里】-x–再copy到vector内存)

一下内容来自

https://www.cnblogs.com/refantasy/p/10026080.html
传送门

引用折叠

对引用的理解

Class A
{
    A()
    {// do something}
};

A GetA()
{
    return A();
}

int main()
{
    A a1 = GetA();   // a1是左值
    A&& a2 = GetA(); // a2是右值引用
    return 0;
}

a1是左值,在构造时使用了GetA() 产生的临时对象,之后GetA()产生的临时对象会销毁。

a2是右值引用,其指向的就是GetA()所产生的对象,这个对象的声明周期是和a2的声明周期是一致的。即少了临时对象,从而省去了临时对象的构造和析构

由此可见右值引用的好处,在新代码中,右值引用是值得大力使用的。但是,在使用的时候,有例外情况了:T&&并不是一定表示右值,比如,如果它绑定的类型是未知的话,既可能是左值,又可能是右值。

template<typename T>
void f(T&& param);

f(10); // 10是右值&&
int x = 10;
f(x);  // x是左值&

由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或右值引用的参数初始化,这是经过类型推导的T&&类型,相比右值引用(&&)会发生类型的变化,这种变化就称为引用折叠。

- 引用:

所以引用的类型就有两种形式:左值引用T&和右值引用T&&

- 折叠:

左值-左值 T& &
左值-右值 T& &&
右值-左值 T&& &
右值-右值 T&& &&

1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)
2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)

就是左值引用会传染,只有纯右值&& && = &&,沾上一个左值引用就变左值引用了

  • 万能引用
    所谓的万能引用并不是C++的语法特性,而是我们利用现有的C++语法,自己实现的一个功能。因为这个功能既能接受左值类型的参数,也能接受右值类型的参数。所以叫做万能引用。

void PrintType(T&& param)可以接受任何类型的参数。

重点来了!编译器将T推导为 int& 类型。当我们用 int& 替换掉 T 后,得到 int & &&。
MD,编译器不允许我们自己把代码写成int& &&,它自己却这么干了 =。=
那么 int & &&到底是个什么东西呢?!(它是引用折叠,刚开始就说了啊 =。=)

所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。

规则就是:

如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。

​ 《Effective Modern C++》

也就是说,int& &&等价于int &。void PrintType(int& && param) == void PrintType(int& param)

现在我们重新整理一下思路:编译器不允许我们写下类似int & &&这样的代码,但是它自己却可以推导出int & &&代码出来。它的理由就是:我(编译器)虽然推导出T为int&,但是我在最终生成的代码中,利用引用折叠规则,将int & &&等价生成了int &。推导出来的int & &&只是过渡阶段,最终版本并不存在。所以也不算破坏规定咯。

关于有的人会问,我传入的是一个左值a,并不是一个左值引用,为什么编译器会推导出T 为int &呢。

首先,模板函数参数为 T&& param,也就是说,不管T是什么类型,T&&的最终结果必然是一个引用类型。如果T是int, 那么T&& 就是 int &&;如果T为 int &,那么 T &&(int& &&) 就是&,如果T为&&,那么T &&(&& &&) 就是&&。很明显,接受左值的话,T只能推导为int &。


/*
 * T type      : int &
 * T &&        : int & &&
 * param type  : int &
*/


/*
 * T type      : int
 * T &&        : int &&
 * param type  : int &&
*/

万能引用就是利用模板推导和引用折叠的相关规则,生成不同的实例化模板来接收传进来的参数。

完美转发

好了,有了万能引用。当我们既需要接收左值类型,又需要接收右值类型的时候,再也不用分开写两个重载函数了。
那么,什么情况下,我们需要一个函数,既能接收左值,又能接收右值呢?

答案就是:转发的时候。

/*
 *  Boost库在这里已经不需要了,我们将其拿掉,可以更简洁的看清楚转发的代码实现
 */

#include <iostream>
using namespace std;

// 万能引用,转发接收到的参数 param
template<typename T>
void PrintType(T&& param)
{
	f(param);  // 将参数param转发给函数 void f()
}

// 接收左值的函数 f()
template<typename T>
void f(T &)
{
	cout << "f(T &)" << endl;
}

// 接收右值的函数f()
template<typename T>
void f(T &&)
{
	cout << "f(T &&)" << endl;
}

int main(int argc, char *argv[])
{
	int a = 0;
	PrintType(a);//传入左值
	PrintType(int(0));//传入右值
}

我们执行上面的代码,按照预想,在main中我们给 PrintType 分别传入一个左值和一个右值。PrintType将参数转发给 f() 函数。f()有两个重载,分别接收左值和右值。

正常的情况下,PrintType(a);应该打印f(T&),PrintType(int());应该打印f(T&&)。

但是,真实的输出结果是

f(T &);
f(T &);

为什么明明传入了不同类型的值,但是void f()函数**只调用了void f(int &)**的版本。这说明,不管我们传入的参数类型是什么,在void PrintType(T&& param)函数的内部,param都是一个左值引用!

大家只需要己住,任何的函数内部,对形参的直接使用,都是按照左值进行的。

我们可以通过一些其它的手段改变这个情况,比如使用 std::forward 。

传入 PrintType 实参是右值类型

根据以上的分析,可以知道T将被推导为值类型,也就是不带有引用属性,假设为 int 。那么,将T = int 带入forward。

int&& forward(int &param)
{
	return static_cast<int&&>(param);
}

param在forward内被强制类型转换为 int &&(static_cast<int&&>(param)), 然后按照int && 返回,两个右值引用最终还是右值引用。最终保持了实参的右值属性,转发正确。

传入 PrintType 实参是左值类型

根据以上的分析,可以知道T将被推导为左值引用类型,假设为int&。那么,将T = int& 带入forward。

int& && forward(int& &param)
{
	return static_cast<int& &&>(param);
}

通过引用折叠,我们实现了万能模板。在万能模板内部,利用forward函数,本质上是又利用了一遍引用折叠,实现了完美转发。其中,模板推导扮演了至关重要的角色。

#include <iostream>
#include <vector>

using namespace std;

struct Student {
    string name;
    int age;

    Student(string&& n, int a)
        :name(std::move(n)), age(a)
    {
        cout << "构造" << endl;
    }

    Student(const Student& s)
        : name(std::move(s.name)), age(s.age)
    {
        cout << "拷贝构造" << endl;;
    }

    Student(Student&& s)
        :name(std::move(s.name)), age(s.age)
    {
        cout << "移动构造" << endl;
    }

    Student& operator=(const Student& s);
};

int main()
{
    vector<Student> classes_one;
    vector<Student> classes_two;

    cout << "emplace_back:" << endl;
    classes_one.emplace_back("xiaohong", 24);

    cout << "push_back:" << endl;
    classes_two.push_back(Student("xiaoming", 23));
}

为什么要分成两个容器分开插入来查看结果呢?因为一起插入时,会触发未知的拷贝构造操作,以及多次进行emplace_back()或push_back()操作时,也会触发拷贝构造操作。

拷贝构造操作

当多次插入操作导致vector已满时,就要分配一块更大的内存(比原始大小多50%),
将原始数据复制过来并释放之前的内存。原始数据的复制就是这些拷贝构造操作。

这些拷贝构造操作是什么呢?这和vector的原理有关。当多次插入操作导致vector已满时,就要分配一块更大的内存(比原始大小多50%),将原始数据复制过来并释放之前的内存。原始数据的复制就是这些拷贝构造操作。

int main()
{
    vector<Student> classes;

    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
    classes.emplace_back("xiaohong", 24);
}

在这里插入图片描述

二维向量

与数组相同, 向量也可以增加维数, 例如声明一个mn大小的二维向量方式可以像如下形式:
//创建一个10
5的int型二维向量
vector< vector > b(10, vector(5));

c++匿名函数

C++中的匿名函数通常为[capture](parameters)->return-type{body},当parameters为空的时候,()可以被省去,当body只有“return”或者返回为void,那么”->return-type“可以被省去,下面将将对其中的参数一一解释

parameters:存储函数的参数
return-type:函数的返回值
body:函数体

capture:

[]        //未定义变量.试图在Lambda内使用任何外部变量都是错误的.
[x, &y]   //x 按值捕获, y 按引用捕获.
[&]       //用到的任何外部变量都隐式按引用捕获
[=]       //用到的任何外部变量都隐式按值捕获
[&, x]    //x显式地按值捕获. 其它变量按引用捕获
[=, &z]   //z按引用捕获. 其它变量按值捕获

我们可以将匿名函数做函数指针使用
c.erase(remove_if(c.begin(), c.end(), [x](int n) {return n < x; }), c.end());
对一些STL容器函数sort,find等,其最后的一个参数时函数指针,我们也可以使用匿名函数来完成

#include<iostream>
template <class Callback>
int CollectFeatures(Callback CB)
{
	int count = 0;
	for (int i = 0; i < 10; i++)
	{
		if (CB(i))
		{
			count++;
		}
	}
	return count;
}
bool AddFeature(size_t Feature)
{
	return Feature % 2;
}
void main()
{
	
	int i = CollectFeatures([](size_t Feature) -> bool { return AddFeature(Feature); });
	std::cout << i << std::endl;
}

传送门

noexcept

C++11 为了替代 throw() 而提出的一个新的关键字,在
C++ 中使用函数异常声明列表来查看函数可能抛出的异常。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值