五、C++運算子過載,使面向物件程式設計更方便
阿新 • • 發佈:2022-05-17
複數類CComplex
編譯器做物件運算的時候,會呼叫物件的運算子過載函式(優先呼叫成員方法);如果沒有成員方法,就砸全域性作用域找合適的運算子過載函式
++
和--
運算子是單目運算子,在引數列表裡放上一個int表示其在數的前面還是後面:operator++()
表示前置,operator++(int)
表示後置,括號裡的int沒有任何作用。
複數類的具體實現:
// // Created by 26685 on 2022-05-16 13:43. // Description:CComplex.h // #ifndef C___CCOMPLEX_H #define C___CCOMPLEX_H #include <iostream> using namespace std; class CComplex { friend CComplex operator+(const CComplex &l, const CComplex &r); friend iostream &operator<<(ostream &os, const CComplex &src); friend istream & operator>>(istream& is, CComplex &src); public: explicit CComplex(int r = 0, int i = 0) : _real(r), _image(i) {} // CComplex operator+(const CComplex& src) const{ // return CComplex(this->_real+src._real,this->_image+src._image); // } void show() { cout << "real: " << _real << " image: " << _image << endl; } CComplex &operator++() { ++_real; ++_image; return *this; } CComplex operator++(int) { return CComplex(_real++, _image++); } private: int _real; int _image; }; inline CComplex operator+(const CComplex &l, const CComplex &r) { return CComplex(l._real + r._real, l._image + r._image); } inline iostream &operator<<(ostream &os, const CComplex &src) {//過載輸出操作 os << "real: " << src._real << " image: " << src._image << endl; } inline istream & operator>>(istream& is,CComplex &src){//過載輸入操作 is>>src._real>>src._image; } #endif //C___CCOMPLEX_H
主函式:
int main(){ CComplex cp1(10,15); CComplex cp2(20,30); CComplex cp3=cp2+cp1; cp3.show(); CComplex cp4=cp3++; cp4.show(); cp3.show(); CComplex cp5= ++cp3; cp5.show(); cp3.show(); cout<<cp4; CComplex cp6; cin>>cp6; cout<<cp6; return 0; }
模擬實現string類的程式碼
// // Created by 26685 on 2022-05-16 14:30. // Description:String.h // #ifndef C___STRING_H #define C___STRING_H #include <iostream> #include <cstring> class String { friend std::ostream &operator<<(std::ostream &os, const String &src); public: String(const char *src = nullptr) { if (src == nullptr) { _pstr = new char[1]; *_pstr = '\0'; } else { _pstr = new char[strlen(src) + 1]; strcpy(_pstr, src); } } ~String() { delete[] _pstr; _pstr = nullptr; } String(const String &src) { _pstr = new char[strlen(src._pstr) + 1]; strcpy(_pstr, src._pstr); } bool operator>(const String &str) const { return strcmp(_pstr, str._pstr) > 0; } bool operator<(const String &str) const { return strcmp(_pstr, str._pstr) < 0; } bool operator==(const String &str) const { return strcmp(_pstr, str._pstr) == 0; } int length() const { return strlen(_pstr); } char &operator[](int index) { return _pstr[index]; } char *c_str() const { return _pstr; } private: char *_pstr; }; inline std::ostream &operator<<(std::ostream &os, const String &src) { os << src._pstr; return os; } inline String operator+(const String& l,const String& r){ char* ptmp=new char[strlen(l.c_str())+ strlen(r.c_str())+1]; strcpy(ptmp,l.c_str()); strcat(ptmp,r.c_str()); String temp(ptmp); delete[] ptmp; return temp; } #endif //C___STRING_H
目前程式碼中的加法的過載運算效率不高,需要進一步改進。
上面程式碼的加法過載函式,會生成臨時物件,影響效能。
暫時改進為:
inline String operator+(const String &l, const String &r) {
// char *ptmp = new char[strlen(l.c_str()) + strlen(r.c_str()) + 1];
String temp;
temp._pstr=new char[strlen(l.c_str()) + strlen(r.c_str()) + 1];//避免了開闢兩次記憶體空間
strcpy(temp._pstr, l.c_str());
strcat(temp._pstr, r.c_str());
// String temp(ptmp);
// delete[] ptmp;
return temp;
}
String物件的迭代器的實現
迭代器可以透明的訪問容器內部元素的值
foreach遍歷容器,其底層是用迭代器實現的
迭代器的功能:提供一種統一的方式,透明的遍歷容器
在對迭代器加加時,一般用前置的++,因為不會生成新的物件,效率會高一些
/**
* 迭代器的實現, 放在String類中
*/
class Iterator{
public:
Iterator(char* p= nullptr):_p(p){}
bool operator!=(const String::Iterator&it){//判斷兩個迭代器是否相等
return _p!=it._p;
}
void operator++(){
++ _p;
}
char& operator*(){return *_p;}
private:
char* _p;
};
Iterator begin(){
return {_pstr};
}
Iterator end(){
return {_pstr+length()};
}
實現vector容器中的迭代器
迭代器一般實現成容器的巢狀結構。
在VectorT類中新增以下程式碼:
class iterator {
public:
iterator(const T *p = nullptr)
: _ptr((int *) p) {}
bool operator!=(const VectorT::iterator &it) {
return _ptr != it._ptr;
}
void operator++() {
++_ptr;
}
T &operator*() { return *_ptr; }
private:
T *_ptr;
};
iterator begin(){
return {_first};
}
iterator end(){
return {_last};
}
迭代器的失效問題
1、呼叫erase後,當前位置到末尾元素的迭代器就會失效。
2、呼叫insert後,當前位置到末尾元素的迭代器就會失效
3、容器擴容後迭代器也會失效
首元素到插入點/刪除點的迭代器依然有效
迭代器失效該怎麼解決?要對迭代器進行更新操作!
不同容器的迭代器是不能進行比較運算的
vector中迭代器的實現(包含迭代器失效的判斷)
//
// Created by 26685 on 2022-05-15 20:33.
// Description:
//
#ifndef C___VECTORT_H
#define C___VECTORT_H
#include "AllocatorT.h"
using namespace std;
/**
* 容器底層記憶體開闢,記憶體釋放,物件構造和析構都通過allocator實現
* @tparam T
* @tparam Alloc
*/
template<typename T, typename Alloc=AllocatorT<T> >
class VectorT {
public:
VectorT(int size = 10) {
// _first=new T[size];
_first = _alloctor.allocate(size);
_last = _first;
_end = _first + size;
}
~VectorT() {
// delete[] _first;
//使用allocator對vector逐個刪除
for (T *p = _first; p != _last; ++p) {
_alloctor.destory(p);
}
_alloctor.deallocate(_first);
_first = _last = _end = nullptr;
}
VectorT(const VectorT<T> &src) {
int size = src._end - src._first;
// _first = new T[size];
_first = _alloctor.allocate(size);
int len = src._last - src._first;
for (int i = 0; i < len; i++) {
// _first[i] = src._first[i];
_alloctor.contruct(_first + 1, src._first[i]);
}
_last = _first + len;
_end = _first + size;
}
VectorT<T> &operator=(const VectorT<T> &src) {
if (src == *this) {
return *this;
}
//delete[] _first;
for (T *p = _first; p != _last; p++) {
_alloctor.destory(p);
}
_alloctor.deallocate(_first);
int size = src._end - src._first;
_first = new T[size];
int len = src._last - src._first;
for (int i = 0; i < len; i++) {
// _first[i] = src._first[i];
_alloctor.contruct(_first + 1, src._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
T &operator[](int index) {
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}
void push_back(T val) {
if (full()) {
expend();
}
//*_last++ = val;
_alloctor.construct(_last, val);
_last++;
}
void pop_back() {
if (empty()) { return; }
verify(_last - 1, _last);
--_last;
_alloctor.destory(_last);
}
T back() const {
return *(_last - 1);
}
bool full() const {
return _last == _end;
}
bool empty() const {
return _first == _last;
}
int size() const {
return _last - _first;
}
/**
* 實現迭代器
*/
class iterator {
friend void VectorT<T, Alloc>::verify(T *first, T *last);
friend iterator VectorT<T, Alloc>::insert(iterator it,const T& val);
friend iterator VectorT<T, Alloc>::erase(iterator it);
public:
/*iterator(const T *p = nullptr)
: _ptr((int *) p) {}*/
/**
* 根據新的成員變數實現新的建構函式,使其能實現迭代器失效
* @param pvec 容器指標
* @param ptr 位置指標
*/
iterator(VectorT<T, Alloc> *pvec = nullptr, T *ptr = nullptr) : _ptr(ptr), _pVec(pvec) {
Iterator_Base *itb = new Iterator_Base(this, _pVec->_head._next);//構造新節點
_pVec->_head._next = itb;//將頭結點連線新節點
}//接下來就是在改變陣列的過程中使迭代器失效
bool operator!=(const VectorT<T, Alloc>::iterator &it) {
/**
* 判斷迭代器是否失效
*/
if (_pVec == nullptr || _pVec != it._pVec) {
throw "iterator incompatable!";
}
return _ptr != it._ptr;
}
void operator++() {
if (_pVec == nullptr) {
throw "iterator invalid!";
}
++_ptr;
}
T &operator*() {
if (_pVec == nullptr) {
throw "iterator invalid!";
}
return *_ptr;
}
private:
T *_ptr;
/**
* 實現迭代器失效,首先要新增一個指向容器的指標
*/
VectorT<T, Alloc> *_pVec;
};
/**
* 根據新的成員方法生成相應的begin和end方法
* @return
*/
iterator begin() {
return {this, _first};
}
iterator end() {
return {this, _last};
}
/**
* 最後一步:判斷迭代器是否失效
* @param first
* @param last
*/
void verify(T *first, T *last) {
Iterator_Base *pre = &this->_head;
Iterator_Base *it = this->_head._next;
while (it != nullptr) {
if (it->_cur->_ptr > first && it->_cur->_ptr <= last) {
//迭代器失效,把iterator持有的容器指標置null
it->_cur->_pVec = nullptr;
//刪除當前迭代器節點,繼續判斷後面的迭代器節點是否失效
pre->_next = it->_next;
delete it;
it = pre->_next;
}else{
pre=it;
it=it->_next;
}
}
}
/**
* 插入操作
* @param it 迭代器位置
* @param val 插入的值
* @return 迭代器
*/
iterator insert(iterator it,const T& val){
/*
* 不考慮擴容,
* 不考慮指標的合法性
*/
verify(it._ptr-1,_last);
T* p=_last;
while(p>it._ptr){
_alloctor.construct(p,*(p-1));
_alloctor.destory(p-1);
p--;
}
_alloctor.construct(p,val);
_last++;
return {this,p};
}
iterator erase(iterator it){
verify(it._ptr-1,_last);
T* p=it._ptr;
while(p<_last-1){//元素向前移
_alloctor.destory(p);
_alloctor.construct(p,*(p+1));
p++;
}
_alloctor.destory(p);
_last--;
return {this,it._ptr-1};
}
private:
T *_first;//表示vector起始位置
T *_last;//表示vector定義元素的末尾
T *_end;//表示vector的末尾
Alloc _alloctor;//負責記憶體管理
/**
* 在連結串列的結構中儲存每個迭代器
*/
struct Iterator_Base {
Iterator_Base(iterator *c = nullptr, VectorT<T, Alloc>::Iterator_Base *n = nullptr) : _cur(c), _next(n) {}
iterator *_cur;
Iterator_Base *_next;
};
/**
* 頭結點
*/
Iterator_Base _head;
void expend() {//size擴大兩倍
int size = _end - _first;
// T *ptmp = new T[size * 2];
T *ptmp = _alloctor.allocate(2 * size);
for (int i = 0; i < size; i++) {
//ptmp[i] = _first[i];
_alloctor.construct(ptmp + i, _first[i]);
}
//delete[] _first;
for (T *p = _first; p != _last; p++) {
_alloctor.destory(p);
}
_alloctor.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + (2 * size);
}
};
#endif //C___VECTORT_H
深入理解new和delete的原理
1、malloc和new的區別:
- malloc按位元組開闢記憶體,new開闢記憶體時需要指定型別,如
new int[10]
,所以malloc開闢記憶體返回的都是void* - malloc只負責開闢空間,new不僅有malloc的功能,還可以進行資料的初始化
- malloc開闢記憶體失敗返回nullptr指標,new丟擲的是bad_alloc型別的異常
2、free和delete的區別:
- delete呼叫解構函式,free是記憶體釋放
檢查記憶體洩漏要重寫new和delete
new和delete能混用嗎?C++為什麼要區分單個元素和陣列的記憶體分配和釋放呢?
對於內建型別int等,可以混用。但是對於自定義的類,就不能混用,因為自定義的類型別有解構函式,為了正確的解構函式,在開闢物件陣列的時候會在陣列前多開闢4個位元組,記錄物件的個數。
//兩個操作符的過載
void* operator new(size_t size){
void* p=malloc(size);
if(p== nullptr){
throw bad_alloc();
}
cout<<"opeartor new addr:"<<p<<endl;
return p;
}
void operator delete (void *ptr) noexcept{
cout<<"opeartor delete addr:"<<ptr<<endl;
free(ptr);
}
new和delete過載實現物件池應用
物件池是在堆上開闢的靜態連結串列
//
// Created by 26685 on 2022-05-17 9:40.
// Description:
//
#ifndef C___QUEUEWITHITEMPOOL_H
#define C___QUEUEWITHITEMPOOL_H
#include <iostream>
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;//尾部元素置為新值,與front區分開
}
void pop(){
if(empty()){
return;
}
QueueItem* first=_front->_next;
_front->_next=first->_next;
if(_front->_next== nullptr){//如果佇列只有一個有效節點
_rear=_front;
}
delete first;
}
bool empty() const{
return _front==_rear;
}
T front()const {
return _front->_next->_data;
}
private:
/**
* 實現一個鏈式的佇列,帶有頭結點
*/
struct QueueItem {
QueueItem(T data=T()):_data(data),_next(nullptr){}
//過載new實現物件池
void* operator new (size_t size){
if(_itemPool== nullptr){//如果未開闢空間;如果當前記憶體池使用完,最後一個元素指向的也是nullptr,會分配新的記憶體池
_itemPool=(QueueItem*)new char[POOL_ITEM_SIZE*sizeof(QueueItem)];//開闢物件池
//我們用char,按位元組開闢,因為如果用new QueueItem,
//就又會呼叫到當前這個方法了,
//我們現在就是在給QueueItem自定義new運算子過載
QueueItem* p=_itemPool;
for(;p<_itemPool+POOL_ITEM_SIZE-1;++p){
p->_next=p+1;//初始化連續連結串列
}
p->_next= nullptr;
}
//新建queueItem的時候會使用物件池中未使用的節點,然後指向下一個未使用的節點
QueueItem* p=_itemPool;
_itemPool=_itemPool->_next;
return p;
}
void operator delete (void* ptr){
QueueItem* p=(QueueItem*)ptr;
p->_next=_itemPool;
_itemPool=p;
}
T _data;
QueueItem *_next;
static const int POOL_ITEM_SIZE=100000;
static QueueItem *_itemPool;
};
QueueItem* _front;//指向頭結點
QueueItem* _rear;//指向隊尾,
};
template<typename T>
typename Queue<T>::QueueItem* Queue<T>::QueueItem::_itemPool= nullptr;
#endif //C___QUEUEWITHITEMPOOL_H