内容:
模板的实例化, 模板函数, 模板类型参数, 模板非类型参数, 模板的实参推演, 模板的特例化, 模板函数模板的特例化非模板函数的重载关系
区分 函数模板 和 模板函数的概念!!!
模板的意义?
对类型也可以进行参数化了
// 原始的int的cmp函数
bool cmp(int a, int b)
{
return a>b;
}
cmp(10, 20);
//现在的 模板函数
template //定义一个模板参数列表--尽量用template
bool cmp(T a, T b) // 这是一个函数模板
{
return a>b;
}
//使用
cmp(10,20) --------- //函数调用点: 模板实例化为原始的 int 的 cmp函数
cmp(10.4,20.5)
cmp(10,20) // 模板的实参推演
在函数调用点, 编译器用用户指定的类型, 从原模板实例化一份函数代码出来(类型是变化的, int会出来int, double会出来double)
模板的实参推演?
根据用户传入的实参的类型, 推导出模板函数参数的具体类型
cmp(10.4,20)// 这是错误的
此时将不能使用模板的实参推演, 需要手动确定
cmp(10.5,20)// double会转化为int
函数模板是无法编译的, 因为不确定类型
函数的实例化是在调用点进行
模板函数才是编译器所编译的
字符串是不能直接比较的
const char * 用>比较, 是比较内存值大小
需要使用strcmp函数
如果这个模板的类型是const char *, 将需要进行模板特例化
// const char* 特例化
template<> //定义一个模板参数列表--尽量用template
bool cmp(const char* a, const char* b) // 这是一个函数模板
{
return strcmp(a,b)>0;
}
非模板函数–普通函数 以及调用关系
// 这是普通函数
bool cmp(const char* a, const char* b) // 这是一个函数模板
{
return strcmp(a,b)>0;
}
//调用关系
对比上个的特例化, 二者存在时, cmp("aaa", "bbb")优先使用普通函数, 编译器优先处理成普通函数符号, 没有时, 才会找特例化
模板函数, 模板的特例化, 非模板函数的重载关系
重载和模板一定要分清楚, 有些书说, 这是重载, 重载是函数名相同, 参数不同
但要注意, 模板的函数名, 是函数名<类型>,这才是完整的函数名符号, 这个可不一样
函数模板的声明和定义不能跨文件?
当不在头文件时, 而是普通的两个cpp文件:
对于一般的函数模板, 是不能把 声明和定义分开放置的, 因为函数模板不参与编译, 只有实例化后的模板函数 才会编译
模板特例化是可以声明和定义分开放的, 因为编译后有确定的 函数符号(UND)
定义和声明都放在头文件是可以的:
模板定义 放到头文件, 声明放在主文件, 因为include头文件 在预编译时, 直接展开即可, 所以可以看到模板定义的地方, 即 定义和声明实际在一个文件
那 8 的问题有办法吗?
有, 直接声明时, 指定类型-------尽量不要这么写
定义在头文件
声明这么写:
template bool cmp (int, int); // double类似
模板的非类型参数
必须是 整数类型(整型或者地址,引用都可以)c++20之后好像可以浮点数了, 只能使用,不能修改
指针和引用必须指向静态存储期的对象(如全局变量)
template // size是非类型参数
void sort(T *arr)
{
排序代码...
}
//使用
int arr[]={....};
const int size = sizeof(arr)/sizeof(int);
sort(arr); // size在这里定义为是一个常量, 可以使用具体数字代替
类模板!—重点在于 类名到底是什么?
一定要注意: 模板名称+类型参数列表=类名称
而不再是一般的 类名了, 这会导致很多错误
类名<类型> 才是现在的 类名
类外定义方法, 必须注意, 这个作用域的问题
template
class SeqStark // 模板名称 +类型参数列表=类名称
{
public:
//构造和析构函数名不需要加, 其他出现的模板地方都加上类型
SeqStark(int size=10);
~SeqStark();
SeqStark(const SeqStark &stark); //拷贝构造函数
SeqStark& operator=(const const SeqStark &stark);
.....
}
#include
#include
template
class SeqStark {
private:
T* data; // 存储栈元素的数组
int capacity; // 栈的容量
int top; // 栈顶指针
public:
// 构造函数
SeqStark(int size = 10)
: capacity(size)
, top(-1)
,data = new T[capacity]{}
// 析构函数
~SeqStark() {
delete[] data;
}
// 拷贝构造函数
SeqStark(const SeqStark &other) : capacity(other.capacity), top(other.top) {
data = new T[capacity]; // 类型不确定
// 不要使用 memcpy, 如果是数组里是对象, 浅拷贝, 外部资源会出问题
for (int i = 0; i <= top; ++i) {
data[i] = other.data[i];
}
}
// 赋值运算符重载, 加了引用才能 支持链式赋值
SeqStark& operator=(const SeqStark &other) {
if (this != &other) {
delete[] data;
capacity = other.capacity;
top = other.top;
data = new T[capacity];
for (int i = 0; i <= top; ++i) {
data[i] = other.data[i];
}
}
return *this;
}
// 压栈操作
void push(const T& value) {
if (top == capacity - 1) {
throw std::overflow_error("Stack is full");
}
data[++top] = value;
}
// 弹栈操作
void pop() {
if (top == -1) {
throw std::underflow_error("Stack is empty");
}
--top;
}
// 查看栈顶元素
T& peek() const {
if (top == -1) {
throw std::underflow_error("Stack is empty");
}
return data[top];
}
// 判断栈是否为空
bool isEmpty() const {
return top == -1;
}
// 获取栈的大小
int size() const {
return top + 1;
}
};
int main() {
// 使用类模板创建一个整数栈
SeqStark intStack(5);
// 压栈操作
intStack.push(10);
intStack.push(20);
intStack.push(30);
// 查看栈顶元素
std::cout << "Top element: " << intStack.peek() << std::endl;
// 弹栈操作
intStack.pop();
std::cout << "Top element after pop: " << intStack.peek() << std::endl;
// 判断栈是否为空
if (intStack.isEmpty()) {
std::cout << "Stack is empty" << std::endl;
} else {
std::cout << "Stack is not empty" << std::endl;
}
// 获取栈的大小
std::cout << "Stack size: " << intStack.size() << std::endl;
return 0;
}
类模板是 选择性的 实例化
只有被调用的, 才会实例化, 具体看后续的代码调用, 才会实例化
类模板->实例化->模板类
#include
using namespace std;
template
class Vector
{
private:
T* _first;//数组起始,与数组名
T* _last;//数组最后位置的下一个
T* _end;//空间的后面位置
void expand() //二倍扩容
{
int size = _last - _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;
// 这里注意堆内存和指向堆内存的指针
//堆内存:一旦分配,会一直存在,直到显式释放
/*
如果指针是局部变量(比如在函数内部定义的),那么它的生命周期仅限于该函数的作用域。函数结束时,指针变量会被销毁,但它指向的内存不会被自动释放。
如果指针是全局变量或类的成员变量,那么它的生命周期会与程序或对象的生命周期一致。
*/
}
public:
Vector(int size = 10)
{
_first = new T[size];
_last = _first;
_end = _first + size;
}
~Vector()
{
delete[]_first;
_first = _last = _end = nullptr;
}
Vector(const Vector& src)
{
int size = src._end - src._first;
_first = new T[size];
int len = src._last - src._first;
for (int i = 0; i < len; i++)
{
_first[i] = src._first[i];
}
_last = _first + len;
_end = _first + size;
}
Vector& operator=(const Vector& src)
{
if (this == &src)
{
return *this;
}
delete[]_first;
int size = src._end - src._first;
_first = new T[size];
int len = src._last - src._first;
for (int i = 0; i < len; i++)
{
_first[i] = src._first[i];
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val) //向容器末尾添加元素
{
if (full())
{
expand();
}
*_last++ = val;
}
void pop_back() //向容器末尾删除元素
{
if (empty())
{
return;
}
-- _last;
}
bool full()const
{
return _last == _end;
}
bool empty()const
{
return _last == _first;
}
T back()const // 返回末尾元素
{
return *(_last-1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值
}
};
int main()
{
Vector vec;
for (int i = 0; i < 20; i++)
{
vec.push_back(rand() % 100);
}
while (!vec.empty())
{
cout << vec.back() << endl;
vec.pop_back();
}
return 0;
}
上一节的vector容器, 当 类型是是下面这个:
class Test
{
public:
Test()
{
cout<< "Test"< vect; //会执行size次构造和析构, 这是不合理的
为什么会出现这个问题?
因为new 会做两件事, 开辟内存和构造Test对象, 导致初始化Vector对象时, 调用多次构造
析构呢, 应该是析构有限元素, 而没有元素,析构是无意义的
也引出了new和molloc的区别?
new:
适用于 C++ 中需要动态创建对象的场景。
支持构造函数和析构函数,适合面向对象编程。
malloc:
适用于 C 语言或需要直接操作内存的场景。
不涉及对象的构造和析构,适合底层内存管理。
上文实现, 在实际pop元素时, 并没有析构这个对象.
所以,现在需要做什么?
首先要把内存开辟和Test对象构造分开处理----否则会造成空容器构造对象
其次, 要把析构对象和释放内存分开----从容器删除元素时, 需要析构这个对象, 因为可能占用外部资源
容器空间配置器allocator?
就做了3里面的事
template
class Allocator
{
T* allocate(size_t size) 开辟内存
{
return (T*)malloc(sizeof(T)*size);
}
void deallocate(void *p) // 释放内存
{
free(p);
}
void construct(T* p, const T &val) //对象构造
{
new (p) T(val); //定位new
/*
作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。
p 是一个指针,指向一块预先分配好的内存。
T(val) 表示调用类型 T 的构造函数,并传递参数 val。
new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。
*/
}
void destroy(T *p) // 对象析构
{
p->~T(); //~T()代表T类型的析构函数
}
}
使用allocator—有点麻烦, 慢慢看
#include
using namespace std;
template
struct Allocator
{
T* allocate(size_t size)// 开辟内存
{
return (T*)malloc(sizeof(T) * size);
}
void deallocate(void* p) // 释放内存
{
free(p);
}
void construct(T* p, const T& val) //对象构造
{
new (p) T(val); //定位new
/*
作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。
p 是一个指针,指向一块预先分配好的内存。
T(val) 表示调用类型 T 的构造函数,并传递参数 val。
new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。
*/
}
void destroy(T* p) // 对象析构
{
p->~T(); //~T()代表T类型的析构函数
}
};
template> //Alloc默认是Allocator
class Vector
{
private:
T* _first;//数组起始,与数组名
T* _last;//数组最后位置的下一个
T* _end;//空间的后面位置
Alloc _allocator;//定义容器空间配置对象
void expand() //二倍扩容
{
int size = _last - _first;
//T* ptmp = new T[2 * size];
T* ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; i++)
{
//ptmp[i] = _first[i];
_allocator.construct(ptmp + i, _first[i]);
}
//delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p); //析构_first指针指向的数组的有效元素
}
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
public:
Vector(int size = 10)
{
//_first = new T[size];
// 只开辟内存
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~Vector()
{
//delete[]_first;
// 析构有效的元素并释放内存
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p); //析构_first指针指向的数组的有效元素
}
_allocator.deallocate(_first); //释放堆上的数组内存
_first = _last = _end = nullptr;
}
Vector(const Vector& src)
{
int size = src._end - src._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = src._last - src._first;
for (int i = 0; i < len; i++)
{
//_first[i] = src._first[i];
_allocator.construct(_first + i, src._first[i]);
}
_last = _first + len;
_end = _first + size;
}
Vector& operator=(const Vector& src)
{
if (this == &src)
{
return *this;
}
//delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p); //析构_first指针指向的数组的有效元素
}
int size = src._end - src._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = src._last - src._first;
for (int i = 0; i < len; i++)
{
//_first[i] = src._first[i];
_allocator.construct(_first + i, src._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T& val) //向容器末尾添加元素
{
if (full())
{
expand();
}
//*_last++ = val;
_allocator.construct(_last, val);
_last++;
}
void pop_back() //向容器末尾删除元素
{
if (empty())
{
return;
}
--_last;
_allocator.destroy(_last);
}
bool full()const
{
return _last == _end;
}
bool empty()const
{
return _last == _first;
}
T back()const // 返回末尾元素
{
return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值
}
};
class Test
{
public:
Test()
{
cout << "Test" << endl;
}
~Test()
{
cout << "~Test" << endl;
}
Test(const Test&)
{
cout << "Test(const)" << endl;
}
};
int main()
{
Test t1,t2;
cout << "----------" << endl;
Vector vec;
vec.push_back(t1);
vec.pop_back();
cout << "----------" << endl;
return 0;
}