66【C++】vector(1)

目录

1. vector的遍历 

2. vector扩容机制

3. reserve

4. resize

5. insert 

6. vector 的底层逻辑

7. 小小疑惑

8. 打印任意类型的vector容器数据

9. 练习


1. vector的遍历 

int main()
{
	vector<int>v1;
	vector<int>v2(10, 1); 
	vector<int>v3(++v2.begin(), --v2.end());

   //vector的遍历:
	for (size_t i = 0; i < v3.size(); i++)
	{
		cout << v3[i] << "  ";
	}
	cout << endl;

	vector<int>::iterator it = v3.begin();
	while (it != v3.end())
	{
		cout << *it << "  ";
		++it;
	}
	cout << endl;

	for (auto e : v3)
	{
		cout << e << "  ";
	}
	cout << endl;

    return 0;

}

2. vector扩容机制

当向vector中插入元素,且当前可用容量不足时,会触发扩容,在 vs下是按大约1.5倍扩容,而g++下是按标准2倍扩的。

3. reserve

在C++标准中,vector::reserve(n)的行为明确规定:

  • 若n大于当前容量,则调整当前容量为n。
  • 若n的值小于等于当前容量,则容量不发生变化,容器不会释放空间。
#include<iostream>
#include<vector>
using namespace std;

int main()
{
    vector<int> v;
	v.reserve(100); //初始容量100
	cout << v.capacity() << endl;

	v.reserve(50);	
	cout << v.capacity() << endl; //n=50 < 当前容量100,不缩容,容量仍为100

	v.reserve(150);//大于当前容量,扩容到150
	cout << v.capacity() << endl;

    return 0;
}

在vs和g++中编译运行,第二个输出结果均为100,不会因reserve(50)而缩小。

4. resize

vector::resize用于调整容器中元素的数量,同时可能改变其容量。

void resize (size_type n);
void resize (size_type n, const value_type& val);
  • 当n<size()时,容量不变,减少元素数量到n,删除多余元素。
  • 当n>size()时,在容器末尾添加元素直到n个,若未指定val,新元素默认初始化(如int为0,类类型调用默认构造函数);若指定val,新元素初始化为val。

                                   若n<=capacity(),容量不变,仅增加元素。

                                   若n>capacity(),触发扩容。

int main()
{
    vector<int> v{ 1,2,3 };
	cout << v.size() << endl;
	cout << v.capacity() << endl << endl;

	//n>size()
	v.resize(5, 10);//新增两个元素,值为10
	cout << v.size() << endl;
	cout << v.capacity() << endl << endl;

	//n<size()
	v.resize(2);//保留前2个元素,删除后3个
	cout << v.size() << endl;
	cout << v.capacity() << endl << endl;//容量不变

    return 0;
}

运行结果:

5. insert 

vector::insert是用于在指定位置插入元素的成员函数,支持插入单个元素、多个元素或其它容器的元素。

int main()
{
    vector<int> v(5, 0);

	//插入多个相同元素
	v.insert(v.begin(), 2, 8);
	for (auto e : v)
	{
		cout << e << "  ";
	}
	cout << endl;

	//插入单个元素
	v.insert(v.begin()+3, 6);//在位置3插入6
	for (auto e : v)
	{
		cout << e << "  ";
	}
	cout << endl;

	//插入其它容器的元素
	vector<int> another = { 10,20,30,40,50 };
	v.insert(v.end(), another.begin(), another.end());//末尾插入another的元素
	for (auto e : v)
	{
		cout << e << "  ";
	}
	cout << endl;

    return 0;
}

运行结果:

6. vector 的底层逻辑

在C++中,std::vector是一种动态数组容器,底层实现逻辑基于连续的内存空间和动态内存管理。

vector内部通过三个关键指针管理数据:

  • 指向内存起始位置的指针start:指向数组中第一个元素。
  • 指向当前有效元素末尾的指针finish:指向最后一个元素的下一个位置,用于标记实际存储的元素范围。
  • 指向内存块末尾的指针end_of_storage:指向整个已分配内存块的末尾,用于标记当前容量。

这三个指针定义了vector的大小(size)容量(capacity)

  • 大小(size())finish - start,即当前实际存储元素的数量。
  • 容量(capacity())end_of_storage - start,即当前内存块可容纳的最大元素数量。

7. 小小疑惑

1. 为什么const对象需要const_iterator?

const对象的核心特性是:其内部数据不能被修改。

vector类中,iterator是T*(指向的元素可以修改,比如*it = 10;),而const_iterator是constT*(指向的元素不可修改*it = 10; 会报错)。

当用const vector<int> v;时,v是只读的,它的迭代器必须是const_iterator(确保不能通过迭代器修改元素),如果返回iterator,就可能通过迭代器修改const对象的数据,就会违反const语义,所以const对象必须调用返回const_itrator的begin()、end()。

typedef T* iterator;
typedef const T* const_iterator;  

//非const版本迭代器
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

//const版本迭代器(供const对象使用)
const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

这里要注意const版本迭代器const成员函数一定要返回const_iterator,如果写成:

typedef T* iterator;

//非const版本迭代器
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

//const版本迭代器(供const对象使用)
iterator begin() const
{
	return _start;
}

iterator end() const
{
	return _finish;
}

那么进行下面操作:

const vector<int> v;  // 常量容器,元素不可修改
iterator it = v.begin();  // begin() const返回iterator(int*)
*it = 10;  // 就能通过迭代器修改const容器的元素,彻底违背const语义!

这里的v是const对象,但通过v.begin()返回的普通指针,依然能修改其指向的数据,这就是逻辑漏洞这种情况编译器不会报错,但会导致程序逻辑错误(意外修改const对象),这比编译错误更危险,会在运行时产生难以调试的bug。

解析:上面通过const对象间接修改了数据,看似违背了“const对象不应被修改”的原则,但编译器不认为这是错误的,因为编译器检查的是:begin()函数内部有没有修改v的成员比变量(比如_start = new int[10] ),const对象的自身状态没有被修改,被修改的是指针指向的数据,不在const语义的保护范围内,所以语法上合法,编译不报错。

注:const成员函数限制的是“通过this指针修改成员变量本身”(比如_start = new int[10] 这种修改指针本身的操作),但不限制“通过成员变量的指针修改其指向的数据”。

正确的写法(返回const_iterator)会让编译器强制检查,确保const容器的“只读”特性,这才是const关键字的设计初衷。此时*it=10会直接触发编译错误(const int* 不允许修改数据),从语法上阻止对const对象的修改。

正确的写法可以实现const  vector_iterator v 要达到的目的

  1. 不能修改成员变量本身的值,例如:v内部的_start 指针不能被重新指向其他地址(即vector自身状态不能被修改);
  2. 调用v的成员函数(如operator[]、begin()、end()等)时,会自动匹配 const版本重载,返回const T&(常量引用)或const_iterator(常量迭代器),限制通过v访问的“指针指向空间”(即元素)不能被修改。

const同时限制的这两部分共同确保了常量容器v既不能修改自身结构,也不能通过它修改内部存储的元素。const给容器加了一层“保护锁”,既锁死了容器的“框架”,也锁死了通过容器对“内容”的修改入口。

8. 打印任意类型的vector容器数据

如果容器是vector<double>等其它类型的,我们就需要需将其定义为函数模板,通过模板参数接收vector中元素的类型即可打印任何类型的vector。具体实现如下:

//使用模版,打印多种类型的数据
template<class T>
void print_vector(const vector<T>& v)
{												
	typename vector<T>::const_iterator it = v.begin(); 
	//编译器不知道T的具体类型,默认把它当作静态成员变量处理,当模版被实例化时,发现const_iterator实际是类型名,没有找到静态成员const_iterator,触发成员不存在而报错。
	//模板中访问依赖于参数的类型时,必须用typename声明,否则编译器会误判为非类型成员,导致实例化时找不到对应成员而报错。
	
	//auto it = v.begin(); //方便写法
	while (it != v.end())								
	{   
        cout << *it << " ";                   
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

如果我们想要打印其它容器(如数组、list等),那么我们将容器定义成模版参数,具体实现如下:

template<class Container>
void print_container(const Container& v)
{
	typename Container::const_iterator it = v.begin();		
	//auto it = v.begin(); //方便写法
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " "; 
	}
	cout << endl;
}

 9. 练习

【杨辉三角】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值