C++——vector容器、动态容器
文章目录
vector 定义、输出格式
vector 可以理解为一个动态的容器,主要目的是为了方便存储。
- vector 三种种定义格式
第一种 vector<type>x(5) 指定容器内有5个元素,每个元素默认初始化为0
vector<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> 这个模板作用域<> 里面的int
push_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是最后的位置+1
iterator end = _finish - 1;
while (end>=pos)
{
*(end + 1) = *end;
end--;
}
*pos = x;
_finish++;
}

左值和右值
我的疑问是 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
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}); // <--- 这样就可以!
更多推荐



所有评论(0)