学习C++类库的编程基础
4.1 理解函数模板
C++模板的意义:对类型也可以参数化
函数模板
模板的实例化
模板函数
模板类型参数
模板非类型参数
模板的实参推演
模板的特例化(专用化)
模板函数,模板的特例化,非模板函数的重载关系
对于
int sum(int a, int b){return a+b;}
模板例子:
#include<iostream>
using namespace std;
template<typename T>
bool compare(T a, T b) //是一个函数模板
{
cout << "template compare" << endl;
return a > b;
}
/*
在函数调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来
bool compare<int>(int a,int b)
{
}
*/
int main(){
compare<int>(10, 20);
return 0;
}
这使得我们可以只用一套代码实现一套逻辑,至于具体的实例化,交给编译器就行了
此外,模板还有实例推演:
可以根据用户传入的实参类型,来推导出模板类型参数的具体类型
例如可以直接conpare(int a,int b)
而省略掉<int>
但是如果是compare(10,12.5)
呢?
由于我们之前定义的是bool compare(T a, T b)
,a与b是同一类型,此时编译器无法判断,就会报错。
我们可以直接指定compare<int>(10,12.5)
,那么他就会强转为int类型,并且抛出一个警告,说明在T类型转换时可能会发生数据丢失。
紧接着又出现了一个问题
对于compare("aaa","bbb")
编译器推导出的类型是const char * 类型
那么a>b实际上对比的是地址大小,这没有意义
所以我们要对函数模型。提供const char *类型的特例化版本
#include<iostream>
using namespace std;
template<typename T>
bool compare(T a, T b) //是一个函数模板
{
cout << "template compare" << endl;
return a > b;
}
/*
在函数调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来
bool compare<int>(int a,int b)
{
}
*/
template<>//特例化
bool compare(const char* a, const char* b)
{
cout << "const char * compare" << endl;
return strcmp(a, b) > 0;
}
int main(){
compare<int>(10, 20);
compare("aaa", "bbb");
return 0;
}
结果
其实,在STL中,有很多的对于字符串类型的特例化版本
4.2 理解类模板
使用类模板实现栈
#include<iostream>
using namespace std;
template<typename T>
class SeqStack
{
public:
SeqStack(int size = 10)
:_pstack(new T[size])
, _top(0)
, _size(size)
{}
~SeqStack()
{
delete[]_pstack;
_pstack = nullptr;
}
SeqStack(const SeqStack<T>& stack)
:_top(stack._top)
,_size(stack._size)
{
_pstack = new T[_size];
for (int i = 0; i < _top; ++i)
{
_pstack[i] = stack._pstack[i];
}
}
SeqStack<T>& operator = (const SeqStack<T>& stack)
{
if (this == &stack) return *this;
delete[] _pstack;
_top = stack._top;
_size = stack._size;
_pstack = new T[_size];
for (int i = 0; i < _top; ++i)
{
_pstack[i] = stack._pstack[i];
}
return *this;
}
void push(const T& val)
{
if (full()) expand();
_pstack[_top++] = val;
}
void pop()
{
if (empty()) return;
--_top;
}
T top() const
{
if (empty())
throw "stack is empty";
return _pstack[_top - 1];
}
bool full() const
{
return _top == _size;
}
bool empty() const
{
return _top == 0;
}
private:
T* _pstack;
int _top;
int _size;
//顺序栈底层数组按照2倍方式扩容
void expand()
{
T* ptmp = new T{ _size * 2 };
for (int i = 0; i < _top; ++i)
{
ptmp[i] = _pstack[i];
}
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main(){
SeqStack<int> S;
S.push(10);
S.push(20);
S.push(30);
cout << S.top();
S.pop();
cout << S.top();
return 0;
}
4.3 实现STL里vector代码
#include<iostream>
using namespace std;
template<typename T>
class vector
{
public:
vector(int size = 10)
{
_first = new T[size];
_last = _first;
_end = _first + size;
}
~vector()
{
delete[] _first;
_first = _last = _end = nullptr;
}
vector(const vector<T>& rhs)
{
_first = new T[];
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T>& rhs)
{
if (this == &rhs)
return *this;
delete[]_first;
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val)
{
if (full())
expend();
*_last++ = val;
}
void pop_back()
{
if (empty())
return;
--_last;
}
T back()const//返回容器末尾的元素值
{
return *(_last - 1);
}
bool full()const { return _last == _end; }
bool empty()const { return _first == _last; }
int size()const { return _last - _first; }
private:
T* _first;//指向数组起始位
T* _last;//指向数组中有效元素的后继位置
T* _end;//指向数组空间的后继位置
void expend()//容器的二被扩容接口
{
int size = _end - _first;
T* ptmp = new T[2 * size];
for (int i = 0; i < size; i++)
{
ptmp[i] = _first[i];
}
delete[]_first;
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
int main(){
vector<int> v;
for (int i = 0; i < 20; i++)
{
v.push_back(rand() % 100);
cout << v.back()<< " ";
}
cout << endl;
while (!v.empty())
{
cout << v.back() << " ";
v.pop_back();
}
return 0;
}
虽然我们实现了一个vector容器
但是其实距离真正的vector还差很多
主要是差一个空间配置器allocator
4.4 理解容器空间配置器allocator的重要性
在上一节的基础上:
class Test
{
public:
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
};
int main(){
vector<Test> v;
return 0;
}
明明是空容器,结果创建了10个Test对象
- 需要把内存开辟和对象构造分开处理
- 析构容器有效元素,然后释放_first指针指向的内存
当使用pop()的时候,仅仅是_last–了,原来Test指向的内存并没有析构掉,当创建新对象时,原来的内存就丢掉了。
- 只需要析构对象,要把对象的析构和内存释放分离开
空间配置器allocator做四件事情
- 内存开辟/内存释放 对象构造/对象析构
template<typename T>
class Allocator
{
T* allocator(size_t size)//负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocator(void* p)//负责内存释放
{
free(p);
}
void construct(T* p, const T& val)//负责对象构造
{
new (p) T(val);//定位new
}
void destroy(T* p)//负责对象的析构
{
p->~T();//
}
};
和c++内置的allocator空间配置器一样
#include<iostream>
using namespace std;
template<typename T>
class Allocator
{
T* allocate(size_t size)//负责内存开辟
{
return (T*)malloc(sizeof(T) * size);
}
void deallocator(void* p)//负责内存释放
{
free(p);
}
void construct(T* p, const T& val)//负责对象构造
{
new (p) T(val);//定位new
}
void destroy(T* p)//负责对象的析构
{
p->~T();//
}
};
/*
容器底层,内存开辟,内存释放,对象构造和析构,都通过allocator来实现
*/
template<typename T,typename Alloc = Allocator<T>>
class vector
{
public:
vector(int size = 10,const Alloc &alloc=Allocator<T>())
{
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector()
{
for (T* p = _first; p != _last; ++p)
{
//把_first指针指向的数组的有效元素进行析构操作
_allocator.destroy(p);
}
//释放堆上的数组内存
_allocator.deallocator(_first);
_first = _last = _end = nullptr;
}
vector(const vector<T>& rhs)
{
//_first = new T[];
_first = _allocator.allocate(size);
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
//_first[i] = rhs._first[i];
_allocator.construct(_first + 1, rhs._first);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T>& rhs)
{
if (this == &rhs)
return *this;
delete[]_first;
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val)
{
if (full())
expend();
*_last++ = val;
}
void pop_back()
{
if (empty())
return;
--_last;
}
T back()const//返回容器末尾的元素值
{
return *(_last - 1);
}
bool full()const { return _last == _end; }
bool empty()const { return _first == _last; }
int size()const { return _last - _first; }
private:
T* _first;//指向数组起始位
T* _last;//指向数组中有效元素的后继位置
T* _end;//指向数组空间的后继位置
Alloc _allocator;
void expend()//容器的二被扩容接口
{
int size = _end - _first;
T* ptmp = new T[2 * size];
for (int i = 0; i < size; i++)
{
ptmp[i] = _first[i];
}
delete[]_first;
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
int main(){
return 0;
}