学习复数类CComplex
C++的运算符重载,使得对象的运算表现得和编译器内置数据类型一样
#include<iostream>
using namespace std;
template<typename T>
T sum(T a,T b)
{
return a + b;//底层 a+*(b) 调用a的一个方法,把b作为实参传入
}
class CComplex
{
public:
CComplex(int r = 0, int i = 0)
: mreal(r),mimage(i)
{
}
private:
int mreal;
int mimage;
};
int main(){
CComplex comp1(10, 10);
CComplex comp2(20, 20);
CComplex comp3 = comp1 + comp2;
return 0;
}
运算符重载的两种方法
CComplex operator+(const CComplex& c)
{
/*CComplex comp;
comp.mreal = this->mreal + c.mreal;
comp.mimage = this->mimage + c.mimage;
return comp;*/
return CComplex(this->mreal + c.mreal, this->mimage + c.mimage);
}
其次,编译器做对象运算的时候,会调用对象的运算符重载函数(优先调用成员方法);如果没有成员方法,就会在全局作用域找合适的运算符重载函数
所以我们也可以在类外定义一个全局函数作为运算符重载函数。
但是在类外就无法访问类的私有成员属性看怎么办呢?
这个时候可以使用友元
private:
int mreal;
int mimage;
friend CComplex operator+(const CComplex& lhs, const CComplex& rhs);
};
CComplex operator+(const CComplex& lhs, const CComplex& rhs)
{
return CComplex(lhs.mreal + rhs.mreal, lhs.mimage + rhs.mimage);
}
还可以对输出运算符进行重载
friend ostream& operator<<(ostream& out, const CComplex& src);
};
ostream& operator<<(ostream& out, const CComplex& src)
{
out << "mreal:" << src.mreal << " mimage:" << src.mimage << endl;
return out;
}
cout << comp3 << endl;
//mreal:30 mimage:30
模拟实现C++的string类代码
对于输入运算符重载:
friend istream& operator>>(istream& in, CComplex& src);
istream& operator>>(istream& in, CComplex& src)
{
in >> src.mreal >> src.mimage;
return in;
}
string类:
#include<iostream>
#include<string>
using namespace std;
int main(){
string str1;
string str2 = "aaa";//string(const char *)
string str3 = "bbb";
string str4 = str2 + str3;
string str5 = str2 + str3;
string str6 = str2 + str3;
return 0;
}
运行无误,说明提供了加法运算符的重载函数
此外,从:
cout << str6 << endl;
if (str5 > str6)
{
cout << str5 << ">" << str6 << endl;
}
else
{
cout << str5 << "<" << str6 << endl;
}
int len = str6.length();
for (int i = 0; i < len; i++)
{
cout<<str6[i];
}
等得出,string类也实现了如,中括号[ ],输出流 << ,比较符(><=)的重载,和length函数
string字符串对象的迭代器iterator实现
迭代器可以透明的访问容器的元素的值
泛型算法参数接收的都是迭代器
泛型算法的全局函数给所有容器用的
泛型算法,有一套方法,能够统一的遍历所有容器的元素 – 迭代器
在String类内定义对应的迭代器
//String 类内 public作用域下:
class iterator
{
public:
iterator(char* p = nullptr)
:_p(p)
{
}
private:
char* _p;
};
//返回容器底层首元素的迭代器表示
iterator begin() {
return iterator (_pstr);
}
//返回容器末尾元素的 后继位置 的迭代器表示
iterator end()
{
return iterator(_pstr + length());
}
vector容器的迭代器iterator实现
iterator -> 遍历所有元素 为什么方式都是一样的呢?
auto it = container.begin();
for(;it!=container.end();++it)
{
cout<<*it<<endl;
}
因为当我们遍历完当前元素以后,要到下一个元素时,把底层遍历都封装在了++运算符重载函数中
什么是容器的迭代器失效问题
示例代码1 — 删除容器中的偶数
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> vec;
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100 + 1);
}
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.erase(it);
}
}
return 0;
}
报错了!!!
为什么?
//第一次调用erase以后,迭代器it就失效了
//那么再进行++就报错
示例代码2 — 给vec容器中所有的偶数添加一个小于偶数值1的数字
auto it = vec.begin();
for (; it != vec.end(); ++it)
{
if (*it % 2 == 0)
{
vec.insert(it, *it-1);
//break;
}
}
也会报错
在遇到第一个偶数的时候,是把第一个偶数及其以后的迭代器全部失效
所以到底为什么会失效?
当容器调用erase,insert等方法后,当前位置到容器末尾元素的所有迭代器全部失效(即删一次可以,继续删不行)
如果insert引起了内存扩容,那么就全部迭代器都失效。
如何解决呢?
对插入,删除点的迭代器进行更新操作。
auto it = vec.begin();
for (; it != vec.end();)
{
if (*it % 2 == 0)
{
it = vec.erase(it);
}
else{
++it;
}
}
成功!
深入理解new的delete的原理
可以看到,new和delete操作本身也是函数重载符的调用
new和malloc的区别:
-
malloc按字节开辟内存;new开辟内存需要指定类型 (new int)
所以malloc开辟内存返回的都是void*
而new就是operator new -> int* -
malloc只负责开辟空间,而new不仅仅有malloc的功能,还可以进行数据的初始化。new int (20)
-
malloc开辟内存失败返回nullptr指针;new抛出的是bad_alloc类型的异常
free和delete的区别:
- delete:先调用析构函数,再free();
- 当delete没有析构函数的目标时(int*),和free是一样的
void* operator new(size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new addr:" << p << endl;
return p;
}
//delete p; 调用p指向对象的析构函数,再调用operator delete释放空间
void operator delete(void* ptr)
{
cout << "operator delete addr:" << ptr << endl;
free(ptr);
}
场景题:如果要你设计,来检测内存泄漏的问题(检查new操作有没有对应的delete)
答:可以重写new和delete操作,添加一个映射表来一 一对应。
场景题:new和delete能混用吗?c++为什么区分单个元素和数组的内存分配和释放呢?
即如下代码
int* p = new int;
delete[]p;
int* q = new int[10];
delete q;
答:上面这样是可以的,因为int类型和int数组都没有析构函数,所以不只是都是一个free()操作,是可以混用的
但是如果是下面这种情况
class Test {
public:
Test(int data = 10)
:ptr(new int(data))
{
cout << "构造Test()" << endl;
}
~Test()
{
delete ptr;
cout << "~Test()" << endl;
}
private:
int* ptr;
};
int main(){
//1 ---- 没问题
Test* p1 = new Test();
delete p1;
// 2 ---- 混用 ---- 出错
Test* p1 = new Test();
delete []p1;
//3 ---- 没问题
Test* p1 = new Test[5];
delete []p1;
// 4 ---- 混用 ---- 出错
Test* p1 = new Test[5];
delete p1;
}
深入理解new
对于Test *p2 = new Test[5];
内存并不是仅仅开辟4 X 5= 20个字节的内存空间,而是还要额外开辟4个字节来存储对象的个数。
所以,在析构的时候,使用delete [ ]p2; 系统就会根据那四个字节的数量和起始地址来获取每个对象的起始地址,再把该地址送入相应的析构函数。
所以对于内置数据类型,可以混用,但是对于自定义数据类型,最好不要混用,。
new和delete重载实现的对象池应用
原代码:
#include<iostream>
#include<vector>
using namespace std;
/*运算符重载:成员方法,全局方法
*
内存池,进程池,线程池,连接池 对象池
*/
template<typename T>
class Queue
{
public:
Queue()
{
_front = _rear = new QueueItem();
}
~Queue()
{
QueueItem* cur = _front;
while (cur != nullptr)
{
_front = _front->_next;
delete cur;
cur = _front;
}
}
void push(const T& val)//入队
{
QueueItem *item = new QueueItem(val);
_rear->_next = item;
_rear = item;
}
void pop()
{
if (empty())
return;
QueueItem* first = _front->_next;
_front->_next = first->_next;
if (_front->_next == nullptr)
{
_rear = _front;
}
delete first;
}
T front()const
{
return _front->next->_data;
}
bool empty()const { return _front == _rear; }
private:
struct QueueItem
{
QueueItem(T data = T())
:_data(data)
, _next(nullptr) {};
T _data;
QueueItem* _next;
};
QueueItem* _front;//指向头节点
QueueItem* _rear;//指向队尾
};
int main()
{
Queue<int> que;
for (int i = 0; i < 100000; ++i)
{
que.push(i);
que.pop();
}
cout << que.empty() << endl;
return 0;
}
可以发现,我们在短时间内,大量的调用一小块内存的 new 和 delete 操作
就需要频繁的开辟和删除内存
我们可以产生一个QueueItem的对象池(含有100000个QueueItem节点)
即,内存早已经开辟好了,直接拿就好了,用完了也不要删除,直接归还给内存池
就省去了对内存开辟和删除的操作,对程序性能提升很大
即:给QueueItem提供自定义内存管理
通过重写new和delete函数进行内存管理
#include<iostream>
#include<vector>
using namespace std;
/*
这段代码实现了一个基于自定义内存管理的模板类 Queue,用于模拟队列数据结构。
主要功能包括:
1. 入队(push)操作,将元素添加到队列尾部。
2. 出队(pop)操作,从队列头部移除元素。
3. 获取队首元素(front)操作。
4. 判断队列是否为空(empty)操作。
此外,通过自定义内存管理,减少了频繁的内存分配和释放操作,提高了性能。
*/
template<typename T>
class Queue
{
public:
// 构造函数,初始化队列,创建一个空的头节点
Queue()
{
_front = _rear = new QueueItem();
}
// 析构函数,释放队列中所有节点的内存
~Queue()
{
QueueItem* cur = _front;
while (cur!= nullptr)
{
_front = _front->_next;
delete cur;
cur = _front;
}
}
// 入队操作,将给定值添加到队列尾部
void push(const T& val)
{
QueueItem *item = new QueueItem(val);
_rear->_next = item;
_rear = item;
}
// 出队操作,从队列头部移除元素
void pop()
{
if (empty())
return;
QueueItem* first = _front->_next;
_front->_next = first->_next;
if (_front->_next == nullptr)
{
_rear = _front;
}
delete first;
}
// 获取队首元素,如果队列为空则返回默认值(这里假设 T 有默认构造函数)
T front()const
{
if (empty())
return;
return _front->next->_data;
}
// 判断队列是否为空
bool empty()const { return _front == _rear; }
private:
// 队列节点结构体
struct QueueItem
{
// 构造函数,可接受一个初始值用于初始化数据成员
QueueItem(T data = T())
:_data(data)
, _next(nullptr) {};
// 重载 new 运算符,实现自定义内存管理
static void* operator new (size_t size)
{
// 如果内存池中没有可用节点
if (_itemPool == nullptr)
{
// 开辟一块足够大的内存空间,用于存储多个 QueueItem 对象
_itemPool = (QueueItem*)new char[POOL_ITEM_SIZE * sizeof(QueueItem)];
QueueItem* p = _itemPool;
for (; p < _itemPool + POOL_ITEM_SIZE - 1; ++p)
{
// 将内存池中的节点链接起来
p->_next = p + 1;
}
// 最后一个节点的 next 指针设置为 nullptr
p->_next = nullptr;
}
// 从内存池中取出一个节点
QueueItem* p = _itemPool;
_itemPool = _itemPool->_next;
return p;
}
// 重载 delete 运算符,实现自定义内存管理
static void operator delete(void* ptr)
{
QueueItem* p = (QueueItem*)ptr;
// 将释放的节点放回内存池
p->_next = _itemPool;
_itemPool = p;
}
T _data;
QueueItem* _next;
static QueueItem* _itemPool;
// 定义内存池中一次分配的节点数量
static const int POOL_ITEM_SIZE = 100000;
};
QueueItem* _front; // 指向队列头节点
QueueItem* _rear; // 指向队列尾节点
};
template<typename T>
typename Queue<T>::QueueItem* Queue<T>::_itemPool = nullptr;
int main()
{
Queue<int> que;
for (int i = 0; i < 100000; ++i)
{
que.push(i);
que.pop();
}
cout << que.empty() << endl;
return 0;
}