❀保持低旋律节奏->个人主页

专栏链接:《C++学习》、《Linux学习》


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++;
		}

在这里插入图片描述

左值和右值

结合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中的两个甜糖——缺省值

  1. 深剖拷贝构造和赋值运算符重载 (深拷贝)

拷贝构造是实现从无到有的构造
而运算符重载是对两个已有的变量/函数/自定义类型进行 替换

  1. 缺省值的适用场景
因此回到代码上来,在调用运算符重载函数的时候,私有成员已经经过初始化,但是拷贝构造私有成员没有初始化。所以在适用拷贝构造的时候必须经过初始化才能适用。

传统写法是在拷贝构造后面加上初始化列表

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 所有适用场景

  1. const auto &it : v ——不拷贝不修改
    容器内的元素只读取不修改
    容器内元素的类型是自定义类型(如果是自定义类型步&那么肯定会涉及到拷贝的问题)高效且安全

  2. auto it :v ——修改
    容器内的元素都是可修改的

  3. 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}); // <--- 这样就可以!
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐