C++——vector容器、动态容器
文章目录
- vector 定义、输出格式
- ————vector底层实现————
- vector迭代器失效问题
- ————————push_back、pop_back、insert
- 左值和右值
- ————————resize底层实现
- ————————vector()深拷贝、operator =
- 匿名对象的使用
- CPP中的两个甜糖——缺省值
- CPP中的两个甜糖——范围for 所有适用场景
- ————————swap
- ————————initializer_list 初始化
- 多参数的隐式类型转换——插入的知识点
vector 定义、输出格式
vector 可以理解为一个动态的容器,主要目的是为了方便存储。
- vector 三种种定义格式
第一种 vector<type>x(5) 指定容器内有5个元素,每个元素默认初始化为0vector<int>x(5,1) 指定容器内有5个元素,每个元素初始化为1。
vector<int>v1(5);vector<int>v2(5, 1);
int arr1[] = {1,2,3,4,5}
vector<int>v3(arr1,arr1+5)
string s1 = "My name is ljy";
vector<int>v4(s1.begin(), s1.end());
输入数组的地址范围(下面是自定义类型,那就需要输入迭代器的范围)
与定义一的意义不同,这里是将输入的地址范围 初始化给v3 直接拿取
int arr1[] = {1,2,3,4,5};vector<int>v3(arr1, arr1 + 5);string s1 = "My name is ljy";vector<int>v4(s1.begin(), s1.end());
第三种 initializer_list 格式定义
//vector 一种特殊的定义格式,它会将{} 隐式转化为Initializer_list 类型vector<int>v5 = { 1,2,3,4,5,6,7,8,9,0 };vector<int>v6({ 1,2,3,4,5,6,7,8,9,0 });
- vector的两种输入格式
第一种角标遍历,vector支持C语言中 采用角标遍历的方法
void printf1(const vector<int>& v)
{for (int i = 0; i < v.size() - 1; i++){cout << v[i] << " ";}cout << endl;
} //角标遍历
第二种 迭代器遍历
vector最权威的输出格式,采用迭代器可支持 string、vector、list、数组……
void printf3(const vector<int>& v)
{std::vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;
}
- 代码运行
#include<iostream>
#include<vector>
#include<string>
using namespace std;void printf1(const vector<int>& v)
{for (int i = 0; i < v.size() - 1; i++){cout << v[i] << " ";}cout << endl;
} //角标遍历
void printf2(const vector<int>& v)
{for (auto n : v){cout << n << " ";}cout << endl;
} //auto自动识别
void printf3(const vector<int>& v)
{std::vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";it++;}cout << endl;
}int main()
{vector<int>v1(5);printf1(v1);vector<int>v2(5, 1);printf2(v2);int arr1[] = {1,2,3,4,5};vector<int>v3(arr1, arr1 + 5);printf3(v3);string s1 = "My name is ljy";vector<int>v4(s1.begin(), s1.end());printf3(v4);return 0;
}
————vector底层实现————
- 默认构造、push_back、capacity、reserve、析构
vector.h
#pragma once
#include<vector>
#include<iostream>
using std::endl;
using std::cout;
using std::vector;namespace My_vector
{template<class T>class vector{using iterator = T*;public:vector() //如果开始vector一个非空的数组 改怎么办呢?:_start(nullptr),_finish(nullptr),_end_of_strage(nullptr){}size_t capacity() const{return _end_of_strage - _finish;}size_t size() const{return _start - _finish;}void reserve(size_t n){size_t sz = _finish - _start;if (n > capacity()){T* tmp = new T[n];if (_start){memcpy(tmp, _start, sizeof(T) * sz);delete[]_start;}_start = tmp;size = _start + sz;capacity = _start + n;}}void push_back(T& x){if (_finish == _end_of_strage){reserve(capacity() == 0 ? 4 : capacity() * 2);}*_finish = x; //为什么不是_finish +1呢?_finish += 1;}~vector(){if (_start){delete[] _start;_start = _finish = _end_of_strage = nullptr; // 好习惯,防止野指针}}private:T* _start;T* _finish;T* _end_of_strage;};
}
vector.cpp
#include "vector.h"void printf_v(vector<int>& v)
{for (auto x : v){cout << x << " ";}
}void push_v(vector<int>&v)
{int n = 10;for (int i = 1; i <= n; i++){v.push_back(i);}
}int main()
{vector<int>v = { 0,0,0,0,0 }; // 为为什么vector<int> 这个模板作用域<> 里面的intpush_v(v);printf_v(v);return 0;
}
vector迭代器失效问题
————————push_back、pop_back、insert
insert中pos迭代器失效问题——野指针
void insert(iterator pos, const T& x) //const——引用的T类型的x对象无法修改{if (_finish == _end_of_strage){int size_t = pos - _start;reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;}//如果发生扩容会出现迭代器失效的问题assert(pos >= _start);assert(pos <= _finish);//_finish是最后的位置+1iterator end = _finish - 1;while (end>=pos){*(end + 1) = *end;end--;}*pos = x;_finish++;}
左值和右值
结合string类和对象上const修饰的成员函数
我的疑问是 pos既然经过 reserve之后变为野指针
那为什么 pos = _start + len 这里不会报错呢?
int size_t = pos + _start 这里的pos 是右值,就相当于 我要对pos进行实际化需求的使用 对pos进行解引用
pos = _start + len 而这里的pos 是左值 就相当于我定义了一个pos变量,但是我不使用它,我用来干嘛,用来接受_start+len 的结果值
- 左值是一个持久化存在的内存地址,int a 创建之后持续化存在
- 右值是一个临时变量 int a = 5 + 6 5和6在用完之后内存就会被释放
在类和对象形参问题上
T&x 这里得x只能接受左值
const T&x 这里得x既能接受左值也能接受右值
小结:因此 一个常量如:5,6 在赋予T&x上就会出现权限放大得问题
所以许多形参为了避免出现问题 采用const T&x作为形参
————————resize底层实现
//匿名对象 创建方法 T val = T() 第二个没有函数变量//对于整形如果没有传参,那么为0.//对于内置类型,如果没有传参。那么就传它的默认构造函数void resize(size_t n, T val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);//插入数据for (auto it == _finish; it != _start + n; it++){*it = val;}_finish = _start + n;}}
————————vector()深拷贝、operator =
拷贝构造和赋值运算符重载
vector(const vector<T>& v)//深拷贝要用引用,值传参会调用拷贝构造,增大代价{reserve(v.capacity());for (const auto &it : v){push_back(it);}}vector<int>& operator=(vector<int>& v){if (this != &v){clear();reserve(v.size());for (auto it = v.begin(); it != v.end(); it++){push_back(*it);}}return *this;}
匿名对象的使用
- 匿名对象的创建——两种不同的创建形式
T val = T() 自定义模板
int a = int() 默认为0
int a = int(1) 为1
int a ={1}
int a = {}
匿名对象适用场景
void resize(size_t n, T val = T())两个形参,当我们没有传入第二个形参的时候,如果是自定义类型那么就调用它的默认构造,如果是int 那么初始化为0,如果是double那么初始化为0.0
CPP中的两个甜糖——缺省值
- 深剖拷贝构造和赋值运算符重载 (深拷贝)
拷贝构造是实现从无到有的构造
而运算符重载是对两个已有的变量/函数/自定义类型进行 替换
- 缺省值的适用场景
传统写法是在拷贝构造后面加上初始化列表
vector<T>::vector(const vector<T>& v) // 应该用模板参数T而非固定int: _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) // 成员初始化
{reserve(v.capacity());for (const auto& it : v) // 使用const引用提高效率{push_back(it);}
}
但是我们可以利用缺省值来省略这一步,在私有成员命名后面加上缺省值
private:T* _start = nullptr;T* _finish = nullptr;T* _end_of_storage = nullptr;
CPP中的两个甜糖——范围for 所有适用场景
-
const auto &it : v ——不拷贝不修改
容器内的元素只读取不修改
容器内元素的类型是自定义类型(如果是自定义类型步&那么肯定会涉及到拷贝的问题
)高效且安全 -
auto it :v ——修改
容器内的元素都是可修改的 -
auto& it:v ——对自身进行修改
容器对自身需要进行修改的
实例
vector(const vector<T>& v)//深拷贝要用引用,值传参会调用拷贝构造,增大代价{reserve(v.capacity());for (const auto &it : v){push_back(it);}}
对于深拷贝——且是vector自定义类型,我们不希望修改v容器内的元素,只希望读取然后push_back 到新this指针中那么这是,const auto& it : v 可就太完美了
————————swap
swap——string中swap的使用
void swap(vector<int>& v){swap(_start, v._start);swap(_finish, v._finish);swap(_end_of_storage, v._storage);}
vector(const vector<T>& v)//深拷贝要用引用,值传参会调用拷贝构造,增大代价{//swap写法vector <T>& tmp(v.begin(), v.end());swap(tmp;)//传统写法reserve(v.capacity());for (const auto &it : v){push_back(it);}}
————————initializer_list 初始化
vector(initializer_list<T> il){reserve(il.size());for (const auto& e : il){push_back(e);}}
多参数的隐式类型转换——插入的知识点
class AA {
public:// 单参数构造函数AA(int a) : a_(a) {std::cout << "构造函数被调用" << std::endl;}int a_;
};void printAA(const AA& obj) {std::cout << "AA对象的值是: " << obj.a_ << std::endl;
}int main() {printAA(10); // <--- 注意这里!// 编译器看到你给了一个int,但函数需要AA。// 它发现AA有一个接受int的构造函数,于是就自动做了转换:// 它会临时创建一个AA对象: AA temp(10);// 然后把这个temp传给printAA函数。// 这就是“单参数”的隐式类型转换。
}
现在我们转化为多参数的隐式类型转换
假设我们的 AA 类需要两个参数来构造:
cpp
运行
class AA {
public:// 多参数构造函数AA(int a, int b) : a_(a), b_(b) {}int a_;int b_;
};void printAA(const AA& obj) {std::cout << "AA对象的值是: (" << obj.a_ << ", " << obj.b_ << ")" << std::endl;
}现在问题来了,我们能不能这样调用 printAA 函数?
printAA(1, 2); // 这样行不行?
答案是:不行。为什么不行?因为编译器在进行隐式类型转换时,它只允许一次转换。printAA(1, 2) 这种写法,编译器会认为你想把 1 转换成 AA,然后把 2 也转换成 AA,但函数只需要一个 AA 参数,所以它会直接报错,说参数数量不匹配。如何让 “多参数对象” 也能隐式转换?方法:使用花括号 {}
printAA({1, 2}); // <--- 这样就可以!