运算符承载,是编程更灵活
文章目录
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>>重载 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+
-
对于原始的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, 每次新元素都要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;
}