五、C++運算符重載,使面向對象編程更方便
複數類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

