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

embedded/2025/3/13 21:43:56/

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

文章目录

  • 运算符承载,是编程更灵活
    • 1.复数类comlex
    • 2.string类
    • 3.迭代器iterator
    • 4.vector容器 迭代器实现
    • 5.容器的迭代器失效问题
    • 6.深入理解new和delete原理
    • 7.new和delete重载实现对象池应用

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>>重载    istreamreturn 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,deleteString 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 Worldif (str1 > str2)std::cout << "str1 is greater than str2" << std::endl;else if (str1 < str2)std::cout << "str1 is less than str2" << std::endl;elsestd::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, 每次新元素都要newq.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 100000template<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 指向 frontcout << "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;
}

http://www.ppmy.cn/embedded/172354.html

相关文章

Python刷题:Python基础

今天刷的是PythonTip的Python 入门挑战中的题&#xff0c;整体难度不高&#xff0c;适合小白练手以及巩固知识点。下面会进行详细讲解。 每日一句 梦想不会发光&#xff0c;发光的是追逐梦想的我们。 只要你真的愿意为自己的梦想去努力&#xff0c; 最差的结果&#xff0c;…

Github 2025-03-11 Python开源项目日报Top10

根据Github Trendings的统计,今日(2025-03-11统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10TypeScript项目1免费API集合 创建周期:2900 天开发语言:Python协议类型:MIT LicenseStar数量:280943 个Fork数量:30691 次关注…

面试之《前端常见的设计模式》

前端开发中运用多种设计模式可以提高代码的可维护性、可扩展性和可复用性。以下是一些常见的前端设计模式&#xff1a; 创建型模式 1. 单例模式 定义&#xff1a;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。应用场景&#xff1a;在前端中&#xff0c;像全局状…

Linux 常用 20 条指令,解决大部分问题

find&#xff1a;查找文件和目录 例:find /-name error.log 在/根目录下开始查找&#xff0c;名字为 error.log 的文件 ps&#xff1a;查看当前进程信息 例:ps -ef -e 代表显示所有进程 -f 代表使用详细的进程信息 vi&#xff1a;Linux 系统中重要的文本编辑工具 例:vi dm…

python之数据处理的安全(链家)

一、模块设计思路与核心价值 # 代码核心安全处理逻辑 element soup.select_one(css_selector) if element else default_value设计目标&#xff1a;构建具备自愈能力的爬虫系统&#xff0c;应对网页改版、反爬策略、网络抖动等复杂场景 核心价值&#xff1a; 数据完整性保障…

Spring Boot 各种事务操作实战(自动回滚、手动回滚、部分回滚)

概念 事务定义 事务&#xff0c;就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行&#xff0c;我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行&#xff0c;我们称该事务被提交。…

详细介绍ListView_DeleteItem

书籍&#xff1a;《Visual C 2017从入门到精通》的2.3.8 Win32控件编程 环境&#xff1a;visual studio 2022 内容&#xff1a;【例2.27】支持按Delete键删除某行的列表视图控件 说明&#xff1a;以下内容大部分来自腾讯元宝。 以下是关于**ListView_DeleteItem**函数的详细…

20250310:OpenCV mat对象与base64互转

代码: https://github.com/ReneNyffenegger/cpp-base64 指南:https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp/ 实操: