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

运算符承载,是编程更灵活

1.复数类comlex

  1. 定义复数类, 实现+的重载函数

    # 复数类
    */  
    class CComplex  
    {  
    public:  
        //这个构造函数可以有三种情形
        // (int, int) (int) ()
        CComplex(int r = 0, int i = 0)  
        :mreal(r), minage(i) {}  
        
        // 复数类与复数类相加
        CComplex operator+(const CComplex &src)
        {
            //CComplex comp;
            //comp.mreal= this->mreal+src.mreal;
            //comp.minage= this->minage+src.minage;
            //return comp;
            return CComplex(this->mreal+src.mreal,this->minage+src.minage );
        }
        
        //后置++
        CComplex operator++(int)
        {
            //CComplex comp;
            //comp.mreal= this->mreal+src.mreal;
            //comp.minage= this->minage+src.minage;
            //return comp;
            return CComplex(this->mreal++,this->minage++ );
        }
        
        //前置++
        CComplex& operator++()
        {
            //CComplex comp= *this;
            mreal+=1;
            minage+=1;
            //return comp;
            return *this;
        }
    	//+=    
        CComplex& operator+=(const CComplex &src)
    {
        this->mreal += src.mreal;
        this->minage += src.minage;
        return *this;
    }
        
        void show(){...}
    private:  
        int mreal;  
        int minage;  
        friend CComplex operator+(const CComplex &lhs, const CComplex &src);
         // 友元函数声明
        friend std::ostream& operator<<(std::ostream &os, const CComplex &src);
    };  
    //但是需要友元, 类外访问私有, 这个不是类成员方法, 所以不需要作用域
    CComplex operator+(const CComplex &lhs, const CComplex &src)
        {
            //CComplex comp;
            //comp.mreal= this->mreal+src.mreal;
            //comp.minage= this->minage+src.minage;
            //return comp;
            return CComplex(lhs.mreal+src.mreal,lhs.minage+src.minage );
        }
    
    // 全局 operator<< 重载  注意, 流不能用const修饰
    std::ostream& operator<<(std::ostream &os, const CComplex &src)
    {
        os << "Real: " << src.mreal << ", Imaginary: " << src.minage;
        return os;
    }
    
    
    int main()  
    {  
        CComplex comp1(10, 10);  
        CComplex comp2(20, 20); 
        //需要+重载
        CComplex comp3 = comp1 + comp2;  
        
        //与整型相加
    	CComplex comp4 = comp1 + 40;  
        /*
        40是int, 一般是要找对应的+重载: operator(int)->但是, 编译器会先int转化为复数类, 找有没有 CComplex(int) 的构造函数, 而正好, 构造函数的三种情形有这个->因此不用写重载了, 编译器会使用构造函数把这个转化为复数类
        */
        
        // 编译器做对象运算时, 优先调用对象的成员方法, 如果没有, 会在全局作用域找合适的
        
        
        //下面这个整型加, 需要在 全局作用域有重载函数
        CComplex comp5 = 30 + comp2;//这是不行的, int在前, 上一个可以 是因为编译器调用了 左边comp1的+, 这个则没有
        
        //operator++() 后置++       operator++(int) 后置++
        comp5 = comp1++;
        comp5 = ++comp1;
        
        //+=重载
        comp5 += comp1;
        
        //cout<< 重载   ostream
        // cin>>重载    istream
        return 0;  
    }
    

2.string类

string更灵活简便, 有+重载,> == < 重载, const char*可没有, 需要调函数

仅供参考!

#include <iostream>
#include <cstring>

class String
{
public:
    // 构造函数
    String(const char *p = nullptr)
    {
        if (p != nullptr)
        {
            _pstr = new char[strlen(p) + 1];
            strcpy(_pstr, p);
        }
        else
        {
            _pstr = new char[1];
            *_pstr = '\0';
        }
    }

    // 析构函数
    ~String()
    {
        delete[] _pstr;
        _pstr = nullptr;
    }

    // 拷贝构造函数
    String(const String &other)
    {
        _pstr = new char[strlen(other._pstr) + 1];
        strcpy(_pstr, other._pstr);
    }

    // 赋值运算符重载
    String &operator=(const String &other)
    {
        if (this != &other) // 防止自赋值
        {
            delete[] _pstr;
            _pstr = new char[strlen(other._pstr) + 1];
            strcpy(_pstr, other._pstr);
        }
        return *this;
    }
	
    // 加法运算符重载--未优化, 多了一次new,delete
    String operator+(const String &other) const
    {
        char *newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];
        strcpy(newtmp, _pstr);
        strcat(newtmp, other._pstr);
        String newString(newtmp);
        delete[]newtmp;
        return newString;
    }
    
    // 加法运算符重载--小优化后
    String operator+(const String &other) const
    {
        String newString;
        newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];
        strcpy(newString._pstr, _pstr);
        strcat(newString._pstr, other._pstr);
        return newString;
    }

    // 比较运算符重载
    bool operator>(const String &other) const
    {
        return strcmp(_pstr, other._pstr) > 0;
    }

    bool operator<(const String &other) const
    {
        return strcmp(_pstr, other._pstr) < 0;
    }

    bool operator==(const String &other) const
    {
        return strcmp(_pstr, other._pstr) == 0;
    }

    // 长度方法
    size_t length() const
    {
        return strlen(_pstr);
    }

    // 下标运算符重载
    char &operator[](size_t index)
    {
        return _pstr[index];
    }

    const char &operator[](size_t index) const
    {
        return _pstr[index];
    }

    // 输出运算符重载
    friend std::ostream &operator<<(std::ostream &os, const String &str)
    {
        os << str._pstr;
        return os; 
    }

    // 输入运算符重载
    friend std::istream &operator>>(std::istream &is, String &str)
    {
        char buffer[1024];
        is >> buffer;
        str = String(buffer);
        return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b
    }

private:
    char *_pstr;
};

int main()
{
    String str1 = "Hello";
    String str2 = "World";
    String str3 = str1 + " " + str2;

    std::cout << str3 << std::endl; // 输出: Hello World

    if (str1 > str2)
        std::cout << "str1 is greater than str2" << std::endl;
    else if (str1 < str2)
        std::cout << "str1 is less than str2" << std::endl;
    else
        std::cout << "str1 is equal to str2" << std::endl;

    std::cout << "Length of str1: " << str1.length() << std::endl;

    return 0;
}

3.迭代器iterator

继续优化 operator+

  1. 对于原始的stl的string类, 使用迭代器, 一个个 输出

    String str1 = "hello hzh";
    string::iterator it = str1.begin(); //或者auto
    auto it = str1.begin(); 
    for(; it!=str1.end();++it)
    {
        cout << *it <<" "; 
    }
    
  2. 迭代器可以透明的访问 容器内部的 元素的值, 不需要考虑 类型

  3. 泛型算法–全局的函数—给所有容器用的

  4. 泛型算法, 有一套方式, 能够统一的遍历所有容器的元素–迭代器

  5. 写一个自定义的 迭代器, 嵌套在上一个自定义的String类

    // 自定义迭代器
        class iterator
        {
        public:
            iterator(char *ptr = nullptr) : _ptr(ptr) {}
    
            // 解引用操作符
            char &operator*() const
            {
                return *_ptr;
            }
    
            // 前置递增操作符
            iterator &operator++()
            {
                ++_ptr;
                return *this;
            }
    
            // 后置递增操作符
            iterator operator++(int)
            {
                iterator tmp = *this;
                ++_ptr;
                return tmp;
            }
    
            // 相等操作符
            bool operator==(const iterator &other) const
            {
                return _ptr == other._ptr;
            }
    
            // 不相等操作符
            bool operator!=(const iterator &other) const
            {
                return _ptr != other._ptr;
            }
    
        private:
            char *_ptr;
        };
    
        // 返回指向字符串开头的迭代器
        iterator begin()
        {
            return iterator(_pstr); //这样写不能引用哈, 因为是局部变量
        }
    
        // 返回指向字符串结尾的迭代器
        iterator end()
        {
            return iterator(_pstr + length());  
        }
    
    private:
        char *_pstr;
    };
    
    
  6. c++11里,有方便的 迭代器调用方式:

    for(char ch : str1)
    {
        cout << ch <<" "; 
    }
    
  7. 迭代器功能:
    提供一种统一的方式, 来透明遍历容器

4.vector容器 迭代器实现

    // 自定义迭代器
    class iterator
    {
    public:
        iterator(T* ptr = nullptr) : _ptr(ptr) {}

        // 解引用操作符
        T& operator*() const
        {
            return *_ptr;
        }

        // 前置递增操作符
        iterator& operator++()
        {
            ++_ptr;
            return *this;
        }

        // 后置递增操作符
        iterator operator++(int)
        {
            iterator tmp = *this;
            ++_ptr;
            return tmp;
        }

        // 相等操作符
        bool operator==(const iterator& other) const
        {
            return _ptr == other._ptr;
        }

        // 不相等操作符
        bool operator!=(const iterator& other) const
        {
            return _ptr != other._ptr;
        }

    private:
        T* _ptr; // 指向当前元素的指针
    };

    // 返回指向容器开头的迭代器
    iterator begin()
    {
        return iterator(_first);
    }

    // 返回指向容器末尾的迭代器
    iterator end()
    {
        return iterator(_last);
    }

对于vector, 是可以用下标的

5.容器的迭代器失效问题

新学两个 容器 的函数, 添加和删除
容器对象.insert(it, val) 容器对象.erase(it) --这两都是 要传入迭代器!!

  1. 迭代器失效-1

    //删除所有的偶数
    for(; it!=vec.end(); ++it)
    {
        if(*it %2 ==0)
        {
            // 第一次调用erase后, it就失效了, 不能再++了,
            vec.erase(it);
        }
    
    }
    
  2. 迭代器失效-2

    //在所有偶数前面添加一个小于偶数值1的值
    for(; it!=vec.end(); ++it)
    {
        if(*it %2 ==0)
        {
            // 第一次调用insert后, it就失效了
            vec.insert(it, *it-1);
        }
    
    }
    
  3. 迭代器为什么会失效?

    1. 删除(erase)或增加(insert)it的地方后, 当前位置及后续的迭代器全部失效, 但是之前的仍然有效
    2. insert如果引起容器扩容, 会整体全部失效, 不是一块内存了
    3. 不同容器迭代器不能进行比较
  4. stl的容器, 删除后解决办法, for里不要++, 并更新 迭代器, 当前位置it

    //删除所有的偶数
    for(; it!=vec.end();)
    {
        if(*it %2 ==0)
        {
            // 第一次调用erase后, it就失效了, 不能再++了,
            it = vec.erase(it);
        }
        else
        {
            ++it;
        }
    
    }
    
  5. stl的容器,增加但不扩容, 解决办法, 要+两次

    //在所有偶数前面添加一个小于偶数值1的值
    for(; it!=vec.end(); ++it)
    {
        if(*it %2 ==0)
        {
            // 第一次调用insert后, it就失效了
            vec.insert(it, *it-1);
            ++it;
        }
    }
    
  6. 迭代器失效原理?
    vector 解决失效的 代码 -整体使用链表来存储迭代器,-- 这也就导致了 链表节点处对不上, 将会失效

  7. 了解 原理, 知道什么时候会失效, 其余的代码, 讲的有点乱–网上看看

6.深入理解new和delete原理

  1. new和delete 本质是 运算符重载
    从汇编看, 会调用 operator new 和 operator delete

  2. 回顾1.8节, new, delete, malloc, free 区别
    malloc按字节开辟内存;new开辟需要指定类型 new int[10]
    so, malloc 开辟内存返回的都是 void*, operator new-> int*
    malloc只负责开辟, new不仅开辟, 还初始化
    malloc 错误返回 nullptr, new抛出bad_alloc类型异常

    delete: 调用析构, 再free, free: 仅释放内存

  3. new实现–简化版

    //先开辟内存, 再调用对象构造函数
    void* operator new(size_t size)
    {
        void *p = malloc(size);
        if(p == nullptr)
            throw bad_alloc();
        return p;
    }
    //调用 对象的 析构, 再释放
    void operator delete(void* p) noexcept
    {
        if (p != nullptr) {
            free(p);  // 释放由 malloc 分配的内存
        }
    }
    //delete 操作符通常被标记为 noexcept,表示它不会抛出异常。这是为了确保在析构对象时不会因为内存释放失败而抛出异常。
    
    
    // 自定义 new[] 操作符
    void* operator new[](size_t size)
    {
        void* p = std::malloc(size);  // 使用 malloc 分配内存
        if (p == nullptr) {
            throw std::bad_alloc();  // 如果分配失败,抛出 bad_alloc 异常
        }
        return p;
    }
    
    // 自定义 delete[] 操作符
    void operator delete[](void* p) noexcept
    {
        std::free(p);  // 使用 free 释放内存
    }
    
    
    int main()
    {
        try
        {
            int *p=new int;
            delete p;
        }
        catch(const bad_alloc &err)
        {
            cerr << err.what() << endl;
        }
    }
    
  4. new和delete能混用吗? cpp为什么要区分 单个元素释放和 数组释放?—面试重点
    new/delete
    new[]/delete[]
    对于普通的 编译器内置类型(int等), 可以混用, new/delete[] new[]/delete
    对于自定义类 类型, 有析构, 为了调用正确的析构, 开辟对象数组的时候, 会多开辟4个字节, 记录对象的个数, 混用会导致 无法正确释放 这多出来的 4字节

  5. 为什么自定义类, 需要额外开辟?
    因为普通类型的大小是固定的,编译器可以直接计算。delete 不需要额外信息来释放内存,

  6. 自己补充的剖析: 那么为什么,普通类型不需要?
    本质是析构的问题, new/delete 不会主动计算有几个对象的
    对于一般类型, new[]开辟一个完整的内存块, 由于没有析构, 不需要遍历, 所以直接释放即可
    而有析构的类, 在delete时, 需要一个个遍历 析构函数, 而编译器不知道数组里有几个对象, 需要遍历几次, 因此必须开辟一个额外的空间,存储有几个 对象

7.new和delete重载实现对象池应用

对象池: 对象复用
对象池是一种通过预先创建并重复使用对象来减少创建和销毁开销,从而提升性能的设计模式。

  1. 对于这个程序, 会大量调用 new和delete, 影响性能
template<typename T>
class Queue
{
public:
    Queue()
    {
        _front = _rear = new QueueItem();
    }
	
    //析构需要遍历
    ~Queue()
    {
        while (_front != nullptr)
        {
            QueueItem* temp = _front;
            _front = _front->_next;
            delete temp;
        }
    }

    // 添加入队元素
    void push(const T& data)
    {
        QueueItem* newItem = new QueueItem(data);
        _rear->_next = newItem;
        _rear = newItem;
    }

    // 出队操作
    bool pop(T& data)
    {
        if (_front == _rear)
        {
            return false; // 队列为空
        }

        QueueItem* temp = _front->_next;
        data = temp->_data;
        _front->_next = temp->_next;

        if (_rear == temp)
        {
            _rear = _front; // 如果出队的是最后一个元素,重置 _rear
        }

        delete temp;
        return true;
    }

private:
    struct QueueItem
    {
        QueueItem(T data = T()) : _data(data), _next(nullptr) {}
        T _data;
        QueueItem* _next;
    };

    QueueItem* _front; // 指向头节点
    QueueItem* _rear;  // 指向队尾
};



int main()
{
    Queue<int> q;

    q.push(10); //大量调用 new和delete, 每次新元素都要new
    q.push(20);
    q.push(30);

    int data;
    while (q.pop(data))
    {
        std::cout << "Dequeued: " << data << std::endl;
    }

    return 0;
}

解决办法: 使用对象池, new和delete重载, 使得push不需要开辟新的

#include <iostream>
using namespace std;

#define POOL_ITEM_SIZE 100000

template<typename T>
class Queue
{
private:
    struct QueueItem
    {
        T _data;
        QueueItem* _next;
        static QueueItem* _itemPool;

        QueueItem(T data = T()) : _data(data), _next(nullptr) {} //零构造

        // 重载 operator new,使用对象池管理内存
        void* operator new(size_t size)
        {
            if (_itemPool == nullptr)
            {
                // 预分配 POOL_ITEM_SIZE 个 QueueItem
                _itemPool = reinterpret_cast<QueueItem*>(new char[POOL_ITEM_SIZE * sizeof(QueueItem)]);
                QueueItem* p = _itemPool;
                for (0; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)
                {
                    p->_next = p + 1; // 链接对象池中的空闲对象
                }
                p->_next = nullptr; // 结束链表
            }

            // 从对象池取出一个对象
            QueueItem* p = _itemPool;
            _itemPool = _itemPool->_next;
            //p->_next = nullptr; // 防止误用
            return p;
        }

        // 重载 operator delete,将对象归还到池中
        void operator delete(void* ptr)
        {
            QueueItem* obj = static_cast<QueueItem*>(ptr);
            obj->_next = _itemPool;
            _itemPool = obj;
        }
    };

    QueueItem* _front; // 头指针(哨兵)
    QueueItem* _rear;  // 尾指针

public:
    Queue()
    {
        _front = new QueueItem(); // 创建哨兵节点
        _rear = _front;           // 初始时 rear 指向 front
        cout << "Queue" << endl;
    }

    ~Queue()
    {
        while (_front != nullptr)
        {
            QueueItem* temp = _front;
            _front = _front->_next;
            delete temp; // 归还到对象池
        }
        cout << "~Queue" << endl;
    }

    // 入队操作
    void push(const T& data)
    {
        QueueItem* newItem = new QueueItem(data); // 从对象池获取, new重载了
        _rear->_next = newItem;
        _rear = newItem;
    }

    // 出队操作
    bool pop(T& data)
    {
        if (_front->_next == nullptr)
        {
            return false; // 队列为空
        }

        QueueItem* temp = _front->_next;
        data = temp->_data;
        _front->_next = temp->_next;

        if (_rear == temp)
        {
            _rear = _front; // 如果出队的是最后一个元素,重置 _rear
        }

        delete temp; // 归还到对象池
        return true;
    }
};

// 初始化静态成员变量
template<typename T>
typename Queue<T>::QueueItem* Queue<T>::QueueItem::_itemPool = nullptr;

int main()
{
    Queue<int> q;
    q.push(10);
    q.push(20);
    q.push(30);

    int value;
    while (q.pop(value))
    {
        std::cout << "Popped: " << value << std::endl;
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值