vector: resize( )/reserve() 与 capacity/size 关系:
#include <iostream>
#include <vector>
using namespace std;
//capacity 变化规律
void TestVector( )
{
vector<int> v1;
v1.push_back( 1 );
cout << v1.capacity( ) << endl;// 1
v1.push_back( 2 );
cout << v1.capacity( ) << endl;// 2
v1.push_back( 3 );
cout << v1.capacity( ) << endl;// 3
v1.push_back( 4 );
cout << v1.capacity( ) << endl;// 4
v1.push_back( 5 );
cout << v1.capacity( ) << endl;// 6
v1.push_back( 6 );
cout << v1.capacity( ) << endl;// 6
v1.push_back( 7 );
cout << v1.capacity( ) << endl;// 9
v1.push_back( 8 );
cout << v1.capacity( ) << endl;// 9
v1.push_back( 9 );
cout << v1.capacity( ) << endl;// 9
//STL 中 Vector 的 push_back 调用的是 Insert
//STL 中 capacity = capacity + capacity / 2
//但如果对于初始容量为0或1则用另一种增容方法(先用上面的方法增容,但操作完后容量没变时,使用第二种方法)
//if ( capacity < size( ) + 1 )
//capacity = size( ) + 1;
v1.push_back( 10 );
cout << v1.capacity( ) << endl;// 13
}
//reserve
void TestVector( )
{
vector<int> v1;
//但像上面那样的话, 不断地在增容, 代价非常大 --> resize / reserve
v1.reserve( 6 );
v1.size( );//0
v1.capacity( );//6
}
//多次reserve
void TestVector( )
{
vector<int> v1;
v1.reserve( 6 );
v1.reserve( 3 );
//再给的容量比现在容量小, 容量不会减小
v1.size( );//0
v1.capacity( );//6
//说明不会缩小容量, 不会有什么影响.
}
//resize
void TestVector( )
{
vector<int> v1;
v1.resize( 6 );
//resize会改变 size
//它会给初始值
//再插入数据,会插入在前6个数据(0)的后面
v1.size( );//6
v1.capacity( );//6
//此时, vector里面的值 全为0( T( ) )
//void resize( size_type n, value_type val = value_type( ) );
//如可以这样给 v1.resize( 6, 10 ); 则 前六个数据均为10
}
void TestVector( )
{
vector<int> v1;
v1.resize( 6, 10 );
v1.resize( 3, 5 );
v1.size( );//3 --> 10, 10, 10 注意,第二次resize 给的 5值并不会覆盖前三个 元素10
v1.capacity( );//6
//说明 resize( ) 会改变 元素个数
//不管是 reserve( ) 还是 resize( ) 容量只要增加了就不会减少
}
//reserve 影响capacity
//resize 影响size + capacity
//vector支持operator[]
//for ( size_t i = 0; i < v.size( ); ++i )
// cout << v[i] << " ";
//注意, 如果是 list 则不要写 l.size( ) 因为每次计算都会遍历它一遍,效率太低
//Vector 没有头插头删 因为代价太大
//List 双向循环链表
#pragma once
#include <iostream>
using namespace std;
#include "TypeTraits.h" //类型萃取
//template <typename T>
//struct __VectorIterator
//{
// //给一个指向数组中 数据的指针
//};
//
//template <typename T>
//class Vector
//{
//public:
//
//
//protected:
// T* _a;
// size_t _size;
// size_t _capacity;
//};
//以上是原版的设计, 不是 STL 的设计
//STL 中 Iterator 是一个 T*(原生类型)的指针
Vector 没有头插头删 因为代价太大
List 双向循环链表
//
//
//#pragma once
//
//
//#include <iostream>
//using namespace std;
//#include "TypeTraits.h" //类型萃取
template <typename T>
struct __VectorIterator
{
//给一个指向数组中 数据的指针
};
template <typename T>
class Vector
{
public:
protected:
T* _a;
size_t _size;
size_t _capacity;
};
//
以上是原版的设计, 不是 STL 的设计
STL 中 Iterator 是一个 T*(原生类型)的指针
template <typename T>
class Vector
{
//typedef T* Iterator; //如果写在这, 没有访问限定符, 默认为私有成员 (即使是 typedef 也受访问限定符 限制)
//delete/free NULL 都不会出错 , 系统会检查
public:
typedef T* Iterator;
typedef const T* ConstIterator;
Vector( )
: _start( 0 )
, _finish( 0 )
, _endOfStorage( 0 )
{}//如上图
//Vector 的 拷贝构造 做了些什么事情? 深拷贝 --> 1.容量有多少拷多少/2.空间有多少拷多少 --> STL是怎么做的?
Vector( const Vector<T>& v )
: _start( 0 ) //先别急着调用 _Expand, 先把成员变量初始化掉, 不然直接调用时它们是随机值, 算的Size( ) 等状态都不对
, _finish( 0 )
, _endOfStorage( 0 )
{
//1.开空间
/*this->*/_Expand( /*v.Capacity( )*/v.Size( ) );//两种方式
//2.拷贝
//内置类型, memcpy拷贝更快一点 --> 类型萃取(_Expand( ) 也可用类型萃取优化) strcpy针对字符串, memcpy 针对内置类型(void* 一个字节一个字节浅拷贝)
//也就是通过类型萃取,内置类型与自定义类型用不同方式拷贝,为了安全性和效率
//for ( size_t i= 0; i < v.Size( ); ++i )
//{
// _start[i] = v[i]; //前一个是原生指针 调用[]. 第二个是对象, 调用 operator[]( )
//}
Copy( _start, v._start, v.Size( ) );
_finish = _start + v.Size( ); //注意, 必须是 v.Size( ). 因为当前 this 指针对象的 Size( ) 是通过 _finish 计算的, 而 _finish 才在计算中.原_finish 为0.
_endOfStorage = _start + v.Size( ); //我们这里采用的是: 是空间有多少,拷贝多少.所以 _endOfStorage其实与_finish是相等的
}
~Vector( )
{
if ( NULL != _start )
{
delete[] _start;
_start = _finish = _endOfStorage = NULL;
}
}
void Resize( size_t n, const T& val = T( ) )
{
if ( n > Capacity( ) )
_Expand( n );
if ( n < Size( ) )
{
Iterator nPos = _start + n; //从这个数据往后进行析构(包括这个数据)
while ( nPos != _finish )
nPos->~T( ); //调用它析构函数
_finish = _start + n; //把 size 减小, 即把 _finish 的指向改变。 容量不变
}
else//n >= size --> 1.n < capacity 2. n > capacity ( 但我们已经扩容过,所以 n 一定小于capacity )
{
Iterator pos = _finish;
while ( pos != ( _start + n ) )
{
//*pos = val; 这样不对, 因为这样 解引用 pos 则 pos 必须是已经存在值(必须是一个已经存在的对象, 但此时,它有可能为未初始化空间)。 如果 pos 是string , operator=( )重载是对两个已经存在对象而言,这里就有问题
//STL中 空间配置器(内存池(只开空间,没有构造))分配空间, 这样就不对了。 必须用 new 定位表达式。
//但是我们这 这段空间 是 new(调用过构造函数初始化) 出来的, 有初始值. 所以可以这样做
//new 定位表达式 --> 有空间,未初始化时 则把这个对象构造到这块空间上去. operator=( ) 时
*pos = val;
}
}
}
void Reserve( size_t n )
{
_Expand( n );
}
void PushBack( const T& x ) //PushBack 不会改变 x
{
if( _finish >= _endOfStorage )
{
//_Expand( Capacity( ) * 2 + 3 ); //以前做法
//STL 做法
size_t capacity = Capacity( ) + Capacity( ) / 2;
if ( capacity < Size( ) + 1 )
capacity = Size( ) + 1;
_Expand( capacity );
}
*_finish = x;
++_finish;
}
//--_finish
void PopBack( )
{}
//Vector 的 Insert 也有迭代器失效( 插入数据时, 空间不够, 增容, 旧空间析构, 但迭代器还指向旧空间, 所以失效 ), 解决
void Insert( Iterator pos, const T& x )
{}//挪动数据
//解决 迭代器失效(返回下一个位置的迭代器)
Iterator Erase( Iterator pos )
{}//挪动数据
inline size_t Capacity( ) const
{
return _endOfStorage - _start;
}
inline size_t Size( ) const
{
return _finish - _start; //Size( ) 要小心算, 你看_Expand. _start 已经不是以前的 _start, 而是新空间位置.( 用旧的 _finish - 新的 _start ) 所以值要提前保存.
}
Iterator Begin( )
{
return _start; //原生指针是 一种 天然的迭代器 ( 像指针一样 ++/--/*/-> )
}
Iterator End( )
{
return _finish;
}
ConstIterator Begin( ) const
{
return _start;
}
ConstIterator End( ) const
{
return _finish;
}
const/*因为是引用, 所以这也要加const*/T& operator[]( size_t index ) const//operator[]可读可写 const对象只读不能修改
{
//assert( index < Size( ) ); //断言: 真,无影响。 假, 报错。
if ( index >= Size( ) )
//如果不用断言
throw out_of_range( "out of range" );
return _start[index];
}
T& operator[]( size_t index )
{
if ( index >= Size( ) )
throw out_of_range( "out of range" );
return _start[index];
}
void _Expand( size_t n )
{
if ( n < Capacity )
return;
//realloc 为什么内置类型可以, 自定义类型不可以?
size_t size = Size( );
T* tmp = new T[n]; //失败会抛出异常. 所以不用考虑失败时我们该做什么.
//拷贝 --> 如图
//用类型萃取进行优化
//for ( size_t i = 0; i < Size( )/*size*/; ++i )//因为每次 Size( ) 都需要建立栈帧 有开销, 所以 将Size( )方法 改为 内联函数 或者用一个变量 保存Size( )的结果
//tmp[i] = _start[i];
//但我们这里不这样拷贝,我们用类型萃取,进行优化
Copy( tmp, _start, size );
delete[] _start;//本来应该用空间配置器来管理空间,但我们现在没有, 所以用 new 和 delete[]
_start = tmp;
_finish = _start + size; //不能用 Size( )
_endOfStorage = _start + n;
}
protected:
Iterator _start;
Iterator _finish;
Iterator _endOfStorage;
};
void TestVector( )
{
Vector<int> v1;
//疯狂增容 , 每一步都增容
//为什么 这么设计 ( 每次增加 1/2 ) 呢 , 因为 空间不是找系统要, 有内存池(找它要,它分配空间), 代价不是很大.
//如果每次 2倍2倍的增加, 有可能增加的太多, 考虑到有可能有空间的浪费.
v1.PushBack( 1 );
v1.PushBack( 2 );
v1.PushBack( 3 );
v1.PushBack( 4 );
//v1.Print( ); 不要这样实现, 别让它成为成员函数
PrintVector( v1 );
//Vector<int>::Iterator it = v1.Begin( );
别 在类里面 封装 Print( ) 函数 ,不然 ,我要 奇数, 要偶数, 你还提供好多 Print( )?. 所以我用迭代器把访问权限 给你, 你 自己去 用 迭代器打印(在类外面实现 你自己想要的 Print函数)
//while ( v1.End( ) != it )
//{
// cout << *it << " ";
// ++it;
//}
//cout << endl;
}
//不要把 Print 封装成 类的成员函数
//非const(如对象) 也可以传给 const(如调用const成员函数) 权限范围在缩小 , 我可以改变传给你 不能改变, 是可以给你的
void PrintVector( const Vector<int>&/*给引用(别让它深拷贝)因为给 引用, 所以加 const*/ v )
{
//Vector<int>::ConstIterator it = v.Begin( ); //But const 对象必须调用 const 成员函数 const 修饰 v. 本来 v 不能改变, 但普通迭代器可以改变数据( 遍历时可以修改 ), 所以这, 用 const迭代器
//while ( v.End( ) != it )
//{
// //*it = 10; //出错! it是ConstIterator. const T* --> 解引用后 const修饰指针指向的值
// cout << *it << " ";
// ++it;
//}
//cout << endl;
//这种遍历方式太麻烦,我们换种方式
//类里面, 调用函数 ,可以直接用名字(因为有隐含的this指针), 但类外面, 必须通过对象调用
//类里面, 写 不能修改的成员函数 时, 最好写成 const 成员函数( 在()后加const(修饰this 指针指向的内容) ) --> 因为 const/非const 对象都可以调用它(有参数this指针)
//但是对于其中一定会改变数据的方法。 就别加const修饰
//对于 可读,可修改的 就 提供一个 const的成员函数, 一个非const成员函数.
for ( size_t i = 0; i < v.Size( ); ++i )
{
cout << v[i] << " ";
}
cout << endl;
}
int main( )
{
TestVector( );
return 0;
}
利用类型萃取优化 数据拷贝:
#pragma once
struct __TrueType
{};
struct __FalseType
{};
template <typename T>
struct TypeTraits
{
typedef __FalseType IsPodType; //我们默认把T当作自定义类型, 因为内置类型用 自定义类型处理方式没问题, 反过来就错了
};
template <>
struct TypeTraits<int>
{
typedef __TrueType IsPodType;
};
template <>
struct TypeTraits<char>
{
typedef __TrueType IsPodType;
};
template <>
struct TypeTraits<double>
{
typedef __TrueType IsPodType;
};
//如 指针数组 指针也算原生类型
template <class T>
struct TypeTraits<T*>
{
typedef __TrueType IsPodType;
};
template <typename T>
T* Copy( T* dst, T* src, size_t n )
{
memcpy( dst, src, n * sizeof( T )/*memcpy 参数为字节数(void*),(一个一个强转后拷贝)*/ ); //内置
for ( size_t i = 0; i < n; ++i ) //自定义
dst[i] = src[i]; //operatpr=( )
return dst;
}
//STL 版本
//处理内置类型
template <typename T>
T* __Copy( T* dst, T* src, size_t n, __TrueType )
{
memcpy( dst, src, n * sizeof( T ) );
return dst;
}
//处理自定义类型
template <typename T>
T* __Copy( T* dst, T* src, size_t n, __FalseType )//没给参数 ,因为我只是区分, 构成重载即可
{
for ( size_t i = 0; i < n; ++i )
dst[i] = src[i];
return dst;
}
//形参 可以 不接受 实参
template <typename T>
T* Copy( T* dst, T* src, size_t n )
{
return __Copy( dst, src, n, TypeTraits<T>::IsPodType( ) );
}