运算符承载,是编程更灵活
文章目录
- 运算符承载,是编程更灵活
- 1.复数类comlex
- 2.string类
- 3.迭代器iterator
- 4.vector容器 迭代器实现
- 5.容器的迭代器失效问题
- 6.深入理解new和delete原理
- 7.new和delete重载实现对象池应用
1.复数类comlex
-
定义复数类, 实现+的重载函数
# 复数类 */ 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+
-
对于原始的stl的string类, 使用迭代器, 一个个 输出
String str1 = "hello hzh"; string::iterator it = str1.begin(); //或者auto auto it = str1.begin(); for(; it!=str1.end();++it) {cout << *it <<" "; }
-
迭代器可以透明的访问 容器内部的 元素的值, 不需要考虑 类型
-
泛型算法–全局的函数—给所有容器用的
-
泛型算法, 有一套方式, 能够统一的遍历所有容器的元素–迭代器
-
写一个自定义的 迭代器, 嵌套在上一个自定义的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; };
-
c++11里,有方便的 迭代器调用方式:
for(char ch : str1) {cout << ch <<" "; }
-
迭代器功能:
提供一种统一的方式, 来透明遍历容器
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
//删除所有的偶数 for(; it!=vec.end(); ++it) {if(*it %2 ==0){// 第一次调用erase后, it就失效了, 不能再++了,vec.erase(it);}}
-
迭代器失效-2
//在所有偶数前面添加一个小于偶数值1的值 for(; it!=vec.end(); ++it) {if(*it %2 ==0){// 第一次调用insert后, it就失效了vec.insert(it, *it-1);}}
-
迭代器为什么会失效?
- 删除(erase)或增加(insert)it的地方后, 当前位置及后续的迭代器全部失效, 但是之前的仍然有效
- insert如果引起容器扩容, 会整体全部失效, 不是一块内存了
- 不同容器迭代器不能进行比较
-
stl的容器, 删除后解决办法, for里不要++, 并更新 迭代器, 当前位置it
//删除所有的偶数 for(; it!=vec.end();) {if(*it %2 ==0){// 第一次调用erase后, it就失效了, 不能再++了,it = vec.erase(it);}else{++it;}}
-
stl的容器,增加但不扩容, 解决办法, 要+两次
//在所有偶数前面添加一个小于偶数值1的值 for(; it!=vec.end(); ++it) {if(*it %2 ==0){// 第一次调用insert后, it就失效了vec.insert(it, *it-1);++it;} }
-
迭代器失效原理?
vector 解决失效的 代码 -整体使用链表来存储迭代器,-- 这也就导致了 链表节点处对不上, 将会失效 -
了解 原理, 知道什么时候会失效, 其余的代码, 讲的有点乱–网上看看
6.深入理解new和delete原理
-
new和delete 本质是 运算符重载
从汇编看, 会调用 operator new 和 operator delete -
回顾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: 仅释放内存
-
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;} }
-
new和delete能混用吗? cpp为什么要区分 单个元素释放和 数组释放?—面试重点
new/delete
new[]/delete[]
对于普通的 编译器内置类型(int等), 可以混用, new/delete[] new[]/delete
对于自定义类 类型, 有析构, 为了调用正确的析构, 开辟对象数组的时候, 会多开辟4个字节, 记录对象的个数, 混用会导致 无法正确释放 这多出来的 4字节 -
为什么自定义类, 需要额外开辟?
因为普通类型的大小是固定的,编译器可以直接计算。delete
不需要额外信息来释放内存, -
自己补充的剖析: 那么为什么,普通类型不需要?
本质是析构的问题, new/delete 不会主动计算有几个对象的
对于一般类型, new[]开辟一个完整的内存块, 由于没有析构, 不需要遍历, 所以直接释放即可
而有析构的类, 在delete时, 需要一个个遍历 析构函数, 而编译器不知道数组里有几个对象, 需要遍历几次, 因此必须开辟一个额外的空间,存储有几个 对象
7.new和delete重载实现对象池应用
对象池: 对象复用
对象池是一种通过预先创建并重复使用对象来减少创建和销毁开销,从而提升性能的设计模式。
- 对于这个程序, 会大量调用 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;
}