施磊老师c++笔记(三)

c++模板编程-学习cpp类库的编程基础

1.函数模板

内容:
模板的实例化, 模板函数, 模板类型参数, 模板非类型参数, 模板的实参推演, 模板的特例化, 模板函数模板的特例化非模板函数的重载关系

区分 函数模板 和 模板函数的概念!!!

  1. 模板的意义?
    对类型也可以进行参数化了

    // 原始的int的cmp函数
    bool cmp(int a, int b)
    {
        return a>b;
    }
    cmp(10, 20);
    
    //现在的 模板函数
    template<class或者typename T> //定义一个模板参数列表--尽量用template
    bool cmp(T a, T b)  // 这是一个函数模板
    {
        return a>b;
    }
    
    //使用
    cmp<int>(10,20) --------- //函数调用点: 模板实例化为原始的 int 的 cmp函数
    cmp<double>(10.4,20.5)
    cmp(10,20) // 模板的实参推演
    

    在函数调用点, 编译器用用户指定的类型, 从原模板实例化一份函数代码出来(类型是变化的, int会出来int, double会出来double)

  2. 模板的实参推演?
    根据用户传入的实参的类型, 推导出模板函数参数的具体类型

    cmp(10.4,20)// 这是错误的
    
    此时将不能使用模板的实参推演, 需要手动确定
    cmp<int>(10.5,20)// double会转化为int
    
  3. 函数模板是无法编译的, 因为不确定类型
    函数的实例化是在调用点进行
    模板函数才是编译器所编译的

  4. 字符串是不能直接比较的
    const char * 用>比较, 是比较内存值大小
    需要使用strcmp函数

  5. 如果这个模板的类型是const char *, 将需要进行模板特例化

    // const char* 特例化
    template<> //定义一个模板参数列表--尽量用template
    bool cmp<const char*>(const char* a, const char* b)  // 这是一个函数模板
    {
        return strcmp(a,b)>0;
    }
    
  6. 非模板函数–普通函数 以及调用关系

    // 这是普通函数
    bool cmp(const char* a, const char* b)  // 这是一个函数模板
    {
        return strcmp(a,b)>0;
    }
    
    //调用关系
    对比上个的特例化, 二者存在时, cmp("aaa", "bbb")优先使用普通函数, 编译器优先处理成普通函数符号, 没有时, 才会找特例化
    
    
  7. 模板函数, 模板的特例化, 非模板函数的重载关系
    重载和模板一定要分清楚, 有些书说, 这是重载, 重载是函数名相同, 参数不同
    但要注意, 模板的函数名, 是函数名<类型>,这才是完整的函数名符号, 这个可不一样

  8. 函数模板的声明和定义不能跨文件?

    当不在头文件时, 而是普通的两个cpp文件:
    对于一般的函数模板, 是不能把 声明和定义分开放置的, 因为函数模板不参与编译, 只有实例化后的模板函数 才会编译
    模板特例化是可以声明和定义分开放的, 因为编译后有确定的 函数符号(UND)

    定义和声明都放在头文件是可以的:
    模板定义 放到头文件, 声明放在主文件, 因为include头文件 在预编译时, 直接展开即可, 所以可以看到模板定义的地方, 即 定义和声明实际在一个文件

  9. 那 8 的问题有办法吗?
    有, 直接声明时, 指定类型-------尽量不要这么写

    定义在头文件
    
    声明这么写:
    template bool cmp<int> (int, int); // double类似
    

2.理解模板函数

  1. 模板的非类型参数
    必须是 整数类型(整型或者地址,引用都可以)c++20之后好像可以浮点数了, 只能使用,不能修改
    指针和引用必须指向静态存储期的对象(如全局变量)

    template<typeanem T, int size> // size是非类型参数
    void sort(T *arr)
    {
    	排序代码...
    }
    
    //使用
    int arr[]={....};
    const int size = sizeof(arr)/sizeof(int); 
    sort<int, size>(arr);   // size在这里定义为是一个常量, 可以使用具体数字代替
    
    
    
  2. 类模板!—重点在于 类名到底是什么?
    一定要注意: 模板名称+类型参数列表=类名称
    而不再是一般的 类名了, 这会导致很多错误
    类名<类型> 才是现在的 类名
    类外定义方法, 必须注意, 这个作用域的问题

    template<typename T>
    class SeqStark   // 模板名称    +类型参数列表=类名称
    {
    public:
    	//构造和析构函数名不需要加<T>, 其他出现的模板地方都加上类型
        SeqStark(int size=10);
        ~SeqStark();
        SeqStark<T>(const SeqStark<T> &stark); //拷贝构造函数
    	SeqStark<T>& operator=(const const SeqStark<T> &stark);
    .....
        
    }
    
    #include <iostream>
    #include <stdexcept>
    
    template<typename T>
    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<T> &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<T>& operator=(const SeqStark<T> &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<int> 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;
    }
    
  3. 类模板是 选择性的 实例化
    只有被调用的, 才会实例化, 具体看后续的代码调用, 才会实例化
    类模板->实例化->模板类

3.实现cpp的vector向量容器

#include <iostream>
using namespace std;

template<typename T>
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<T>& 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<T>& operator=(const Vector<T>& 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<int> 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;
    


}

4.理解容器空间配置器allocator的重要性

上一节的vector容器, 当 类型是是下面这个:

class Test
{
public:
    Test()
    {
        cout<< "Test"<<endl;
    }
~Test()
    {
        cout<< "~Test"<<endl;
    }
}


//执行
Vector<Test> vect;  //会执行size次构造和析构, 这是不合理的
  1. 为什么会出现这个问题?
    因为new 会做两件事, 开辟内存和构造Test对象, 导致初始化Vector对象时, 调用多次构造
    析构呢, 应该是析构有限元素, 而没有元素,析构是无意义的

  2. 也引出了new和molloc的区别?

    new:
    
    适用于 C++ 中需要动态创建对象的场景。
    
    支持构造函数和析构函数,适合面向对象编程。
    
    malloc:
    
    适用于 C 语言或需要直接操作内存的场景。
    
    不涉及对象的构造和析构,适合底层内存管理。
    
  3. 上文实现, 在实际pop元素时, 并没有析构这个对象.

  4. 所以,现在需要做什么?
    首先要把内存开辟和Test对象构造分开处理----否则会造成空容器构造对象
    其次, 要把析构对象和释放内存分开----从容器删除元素时, 需要析构这个对象, 因为可能占用外部资源

  5. 容器空间配置器allocator?
    就做了3里面的事

    template<typename T>
    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类型的析构函数
        }
    }
    
  6. 使用allocator—有点麻烦, 慢慢看

    #include <iostream>
    using namespace std;
    
    template<typename T>
    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<typename T, typename Alloc = Allocator<T>>  //Alloc默认是Allocator<T>
    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<T>& 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<T>& operator=(const Vector<T>& 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<Test> vec;
        
        vec.push_back(t1);
        vec.pop_back();
        cout << "----------" << endl;
        return 0;  
    
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值