c++學習總結(四)——運算子過載與標準模板庫(STL)
一、心得總結
運算子過載使得使用者自定義的資料以一種更簡潔的方式工作。例如在做ATM模擬系統時,使用過載“<”來比較時間,可以簡化程式,減少程式碼。另外,我們也可以過載運算子函式,將運算子用於操作自定義的資料型別。過載運算子函式可以對運算子做出新的解釋,即定義使用者所需要的各種操作。但運算子過載後,原有的基本語義不變,包括:不改變運算子的優先順序,不改變運算子的結合性,不改變運算子所需要的運算元,不能建立新的運算子。優先順序和結合性主要體現在過載運算子的使用上,而運算元的個數不但體現在過載運算子的使用上,更關係到函式定義時的引數設定。例如,一元運算子過載函式不能有兩個引數,呼叫時也不能作用於兩個物件。不能建立新的運算子,只有系統預定義的運算子才能被過載。
STL是C++標準程式庫的核心,深刻影響了標準程式庫的整體結構。
C++語言中大部分預定義的運算子都可以被過載。下列是可以過載的運算子:
+ - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= == != <= >= && || ++ -- ->* ' -> [] () new delete
一下運算子不能被過載:
. .* :: ?; sizepf
二、內容總結及例項分析
&1運算子過載
1.1過載運算子的語法形式
運算子函式是一種特殊的成員函式或友元函式。成員函式的語句格式為:
型別 類名::operator op(引數表)
{
// 相對於該類定義的操作
}
其中,“型別”是函式的返回型別。“類名”是要過載該運算子的類。“op”表示要過載的運算子。函式名是"operator op",由關鍵字operator和被過載的運算子op組成。“引數表”列出該運算子所需要的運算元。
1.2用成員或友元函式過載運算子
1.一元運算子
一元運算子不論前置或後置,都要求有一個運算元:
Object op 或 op Object
當過載為成員函式時,編譯器解釋為:
Object.operator op()
函式operator op所需的運算元由物件Object通過this指標隱含傳遞,所以引數表為空。
當過載為友元函式時,編譯器解釋為:
operator op(Object)
函式operator op所需的運算元由引數表的引數Object提供。
2.二元運算子
任何二元運算子都要求有左、右運算元:
當過載為成員函式時,編譯器解釋為:
Object.operator op(ObjectR)
左運算元由物件OpbjectL通過this指標傳遞,右運算元由引數ObjectR傳遞。
過載為友元函式時,編譯器解釋為:
operator op(Object.ObjectR)
左、右運算元都由引數傳遞。
不管是成員函式還是友元函式過載,運算子的使用方法都相同。但由於它們傳遞引數的方法不同,因此導致實現的程式碼不同,應用場合不同。
1.2.1用成員函式過載運算子
當一元運算子的運算元,或者二元運算子的左運算元是該類的一個物件是,過載運算子函式一般定義為成員函式。
成員運算子函式的原型在類的內部宣告格式如下:
class X
{
//...
返回型別operaor運算子(形參表);
//...
}
再類外定義成員運算子函式的格式如下:
返回型別X::operator運算子(形參表)
{
函式體
}
1.2.2.雙目運算子過載為成員函式
對雙目運算子而言,成員運算子函式的形參表中僅有一個引數,它作為運算子的右運算元,此時當前物件作為運算子的左運算元,它是通過this指標隱含地傳遞給函式的。
例:
#include<bits/stdc++.h>
class complex{
public:
complex(){real=0,imag=0;}
private:
double real;
double imag;
};
complex(double r,double i){real=r;imap=i;}
complex operator +(complex &c2);
void display();
complex complex::operator +(complex &c2)
{
return complex(real+c2.real,imag+c2.imag);
}
void complex::display()
{
cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{
complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
cout<<"c1=";c1.display();
cout<<"c2=";c2.display();
cout<<"c1+c2=";c3.diaplay();
return 0;
}
一般而言,如果在類X中採用成員函式過載雙目運算子@,成員運算子函式[email protected]所需的一個運算元由物件aa通過this指標隱含地傳遞,它的另一個運算元bb在引數表中顯示,aa和bb是類X的兩個物件,則以下兩種函式呼叫方法是等價的:
[email protected];//隱式呼叫
[email protected](bb);//顯式呼叫
1.2.3單目運算子過載為成員函式
對單目運算子而言,成員函式運算子函式的引數表中沒有引數,此時當前物件作為運算子的一個運算元。
有一個Time類,包含資料成員minute(分)和(秒),模擬秒錶,每次走一秒,滿60進一分鐘,此時秒又從0開始算。要求輸出分和秒的值。
class time
{
public:
Time(){minute=0;sec=0;}
Time(int m,int s):minute(m),sec(s){}
Time operator++(); //宣告前置自增運算子"++"過載函式
Time operator++(int); //聲明後置自增運算子“++”過載函式
private:
int minute;
int sec;
};
Time Time::operator++()
{
if(++sec>=60)
{
sec-=60; //滿60進1分鐘
++minute;
}
return *this; //返回當前物件值
Time Time::operator++(int) //定義後置自增運算子“++"過載函式
{
Time temp(*this)
sec++;
if(sec>=60)
{
sec-=60;
++minute;
}
return temp; //返回的是自加前的物件
}
}
1.2.4.用友元函式過載運算子
友元函式過載運算子常用於運算子的左右運算元型別不同的情況
<1>.在第一個引數需要隱式轉換的情形下,使用友元函式過載
<2>.友元函式沒有this指標,所需運算元都必須在引數表顯式宣告,很容易實現型別的隱式轉換
<3>.C++中不能用友元函式過載的運算子有= () [] ->
成員函式運算子函式與友元運算子函式的比較
(1)成員運算子函式比友元運算子函式少帶一個引數(後置的++、--需要增加一個形參)。
(2)雙目運算子一般可以被過載為友元運算子函式或成員運算子函式,但當運算元型別不相同時,必須使用友元函式。
1.3幾個典型運算子過載
1.3.1.過載++與--
自增個自減運算子有前置和後置兩張形式。C++規定,前置形式過載為一元運算子函式,後置形式過載為二元運算子函式。
例如,設有類A的物件Aobject,其前置自增表示式和後置自增表示式說明如下。
(1)前置自增表示式
++Aobject
若用成員函式過載,則編譯器解釋為:
Aobject.operator++()
對應的函式原型為:
A&A::operator++();
若用友元函式過載,則編譯器解釋為:
operator++(Aobject)
對應的函式原型為:
friend A&operator++(A&)
(2)後置自增表示式
Aobject++
成員函式過載的解釋為:
Aobject.operator++(0)
對應的函式原型為:
friend A&operator++(A &,int);
在此,引數0是一個偽值,用於與前置形式過載相區別。另外,友元過載函式返回類型別的引用是為了減少函式返回時物件複製的開銷,可以根據需要選擇是否返回類型別的引用是為了減少函式返回時物件複製的開銷,可以根據需要選擇是否返回類型別的引用。
#include<iostream>
using namespace std;
class Increase
{
public:
Increase(){value=0;}
void display()const{cout<<value<<'\n';};
Increase operator++(); //前置
Increase operator++(int); //後置
private:
unsigned value;
};
Increase Increase::operator++()
{
value++;return *this;
}
Increase Increase::operator++(int)
{
Increase temp;temp.value=value++;return temp;
}
int main()
{
Increase a,b,n;int i;
for(i=0;i<10;i++)a=n++;
cout<<"n=";n.display();cout<<"a=";a.display();
for(i=0;i<10;i++)b=++n;
cout<<"n=";n.display();cout<<"b=";b.display();
}
1.3.2.過載賦值運算子
賦值運算子過載用於物件資料的複製,只能用成員函式過載。過載函式原型為:
類名 & 類名::operator=(類名);
#include<iostream>
#include<cstring>
using namespace std;
class Name
{
public:
Name(char *pN);
Name(const Name&); //複製建構函式
Name& operator=(const Name&); //過載賦值運算子
~Name();
protected:
char *pName;
int size;
};
.......
int main()
{
Name Obj1("ZhangSan");
Name Obj2=Obj1; // 呼叫複製建構函式
Name Obj3("NoName");
Obj3=Obj2=Obj1; // 呼叫過載賦值運算子函式
}
注意,過載賦值運算子函式和複製建構函式的實現十分相似。不同的是,過載函式返回*this,以符合語言版本的原有賦值語義。
賦值建構函式和過載賦值運算子函式雖然都是實現資料成員的複製,但執行時機不同。前者用於物件的初始化,後者用於程式執行時修改物件的資料。
C++提供系統版本的過載賦值運算,實現資料成員的簡單複製。這一點和淺複製的操作一樣。所以,對於用指標管理堆的資料成員,以及大多數重要的類,系統的賦值運算子操作往往不夠,需要程式設計師自己進行過載。
運算子函式operator=必須過載為成員函式,而且不能被繼承。
1.3.3.過載運算子[]和()
運算子[]和()是二元函式,[]和()只能用成員函式過載,不能用友元函式過載。
1.過載下標運算子[]
下標運算子[]是二元運算子,用於訪問資料物件的元素。其過載函式呼叫的一般形式為:
物件[運算子]
例如,類X有過載函式:
int & X::operator[](int);
其中,x是X類的物件,則呼叫函式的表示式;
x[k],被解釋為:x.operator[](k)
#include<iostream>
using namespace std;
class vector
{
public:
vector(int n){v=new int[n];size=n;}
~vector(){delete[]v;size=0;}
int &operator[](int i){return v[i];}
private:
int *v;
int size;
};
int main()
{
vector a(5);
a[2]=12;
cout<<a[2]<<endl;
}
2.過載函式呼叫運算子
函式呼叫運算子()可以看成一個二元運算子。其過載函式呼叫的一般形式:
物件(表示式表)
其中,“表示式表”可以為空。例如,類A有過載函式:
int A::operator;
若a是A類物件,則呼叫函式的表示式:
a(x,y)被解釋為:a.operator()(x,y)
例,設x是類X的一個物件,則表示式
x(arg1,arg2,...)
可被解釋為x.operator()(arg1,arg2,...)
#include<iostream>
using namespace std;
class F
{
public:
double operator()(double x,double y);
};
double F::operator()(double x,double y){return x*x+y*y;}
int main()
{
F f;
cout<<f(5.2,2.5)<<endl;
}
1.3.4.過載流插入和流提取運算子
istream和ostream是C++的預定義流類,cin是istream的物件,cout是ostream的物件,運算子<<由osream過載為插入操作,用於輸出基本型別資料,運算子>>由istream過載為提取操作,用於輸入基本型別資料,用友元函式過載<<和>>,輸出和輸入使用者自定義的資料型別
過載輸出運算子“<<”(只能被過載成友元函式),不能過載成成員函式
定義輸出運算子“>>”過載解釋的一般格式如下:
istream&operator<<(ostream& out),class_name& obj)
{
out<<obj.item1;
out<<obj.item2;
...
out<<obj.itemn;
return out;
}
過載輸入運算子“>>”(只能被過載成友元函式)
定義輸入運算子“>>”過載函式的一般格式如下:
istream& operator>>(istream &in,class_name& obj)
{
in>>obj.item1;
in>>obj.item2;
...
in>>obj.itemn;
return in;
}
&2標準模板庫(STL)
2.1STL概述
STL由一些可適應不同需求的集合類,以及在這些資料集合上操作的演算法構成
STL內的所有元件都由模版構成,其元素可以是任意型別
STL是所有C++編譯器和所有作業系統平臺都支援的一種庫
STL元件
1.容器(container)——管理某類物件的集合
2.迭代器(Lierator)——在物件集合上進行遍歷
3.演算法(Algorithm)——處理集合內的元素
4.容器介面卡(Container adaptor)
5.函式物件(functor)
STL容器類別
1.序列式容器——排列次序取決於插入時機和位置
2.關聯式容器——排列順序取決於特定準則
STL容器的共同能力
1.所有容器中存放的都有值而非引用。如果希望存放的不是副本,容器元素只能是指標。
2.所有元素都形成一個次序(order),可以按相同的次序一次或多次遍歷每個元素
STL容器元素的條件
1.必須能夠通過拷貝建構函式進行復制
2.必須可以通過賦值運算子完成賦值操作
3.必須可以通過解構函式完成銷燬動作
4.序列式容器元素的預設建構函式必須可用
5.某些動作必須定義operator==,例如搜尋操作
6.關聯式容器必須定義出排序準則,預設情況是過載operator<
對於基本資料型別(int ,long,char,double...)而言,以上條件總是滿足。
STL容器的共同操作
1.初始化
1.產生一個空容器
std::list<int>l;
2.以另一個容器元素為初值完成初始化
std::list<int>l;
...
std::vector<float>c(l.begin(),l.end());
3.以陣列元素為初值完成初始化
int array[]-{2,4,6,1345};
...
srd::set<int>c(array,array+sizeo(array)/sizeo(array[0]);
2.與大小相關的操作(size operator)
size()——返回當前容器的元素數量
empty()——判斷容器是否為空
max_size()——返回容器能容納的最大元素數量
3.比較
==,!=,<,<=,>,>=
比較操作兩端的容器必須屬於同一型別
如果兩個容器內的所有元素按序相等,那麼這兩個容器相等
採用字典式順序判斷某個容器是否小於另一個容器
4.賦值和交換
swap用於提高賦值操作效果
5.與迭代器相關的操作
begin()——返回一個迭代器,指向第一個元素
end()——返回一個迭代器,指向最後一個之後
rbegin()——返回一個逆向迭代器,指向逆向遍歷的第一個元素
rend()——返回一個逆向迭代器,指向遍歷的最後一個元素之後
容器的共同操作
1.元素操作
insert(pos,e)——將元素e的拷貝安插與迭代器pos所指的位置
erase(beg,end)——移除[beg,end]區間內的所有元素
clear()——移除所有元素
2.迭代器
1.可遍歷STL容器內全部或部分元素的物件
2.指出容器中的一個特定位置
3.迭代器的基本操作
操作 效果
* 返回當前位置上的元素值。如果該元素有成員,可以通過迭代器以operator->取用
++ 將迭代器前進至下一元素
==和!= 判斷兩個迭代器是否指向同一位置
= 為迭代器賦值(將所指元素的位置賦值過去)
4.所有容器都提供兩種迭代器
操作 效果
begin() 返回一個迭代器,指向第一個元素
end() 返回一個迭代器,指向最後一個元素之後
5.所有容器都提供兩種迭代器
container::iterator以“讀、寫”模式遍歷元素
container::const_iterator以“只讀”模式遍歷元素
2.2.STL容器
1. vector
1.vecror模擬動態陣列
2.vector的元素可以是任意型別T,但必須具備賦值和拷貝能力(具有public拷貝建構函式和過載的賦值操作符)
3. 必須包含的標頭檔案#include<vector>
4.vector的大小(size)和容量(capacity)
size返回實際元素個數
capacity返回能容納的元素最大數量。如果插入元素時,元素個數超過capacity需要從新配置內部儲存器。
5.構造、拷貝和析構
操作 效果
vector<T>c 產生空的vector
vector<T>c1(c2) 產生同類型的c1,病將複製c2的所有元素
vectorc<T>(n) 利用型別T的預設建構函式和拷貝建構函式生成一個大小為n的vector
vectorc<T>(n,e) 產生一個大小為n的vector,每個元素都是e
vector<T>(beg,end) 產生一個vector,以區間[beg,end]為元素初值
vector<T>() 產生所有元素並釋放記憶體
6.非變動操作
操作 效果
c.size() 返回元素個數
c.empty() 判斷容器是否為空
c.max_size() 返回元素最大可能數量(固定值)
c.capacity() 返回重新分配空間前可容納的最大元素數量
c.reserve() 擴大容量為n
c1==c2 判斷c1是否等於c2
c1!=c2 判斷c1是否不等於c2
c1<c2 判斷c1是否小於c2
c1>c2 判斷c1是否大於c2
c1<=c2 判斷c1是否小於等於c2
c1>=c2 判斷c1是否大於等於c2
7.賦值操作
操作 效果
c1=c2 將c2的全部元素賦值給c1
c.assign(n,e) 將元素e的n個拷貝賦值給c
c.assign(beg,end) 將區間[beg,end]的元素賦值給c
c1.swap(c2) 將c1和c2元素互換
swap(c1,c2) 同上,全域性函式
8.元素存取
操作 效果
at(idx) 返回索引idx所標識的元素的引用,進行越界檢查
operator[](idx) 返回索引idx所標識的元素的引用,不進行越界檢查
front() 返回第一個元素的引用,不檢查元素是否存在
back() 返回最後一個元素的引用,不檢查是否存在
9.迭代器相關函式
操作 效果
begin() 返回一個迭代器,指向第一個元素
end() 返回一個迭代器,指向最後一個元素
rbegin() 返回一個迭代器,指向逆向遍歷的第一個元素
rend() 返回一個迭代器,指向逆向遍歷的最後一個元素
10.安插元素
操作 效果
c.insert(pos,e) 在pos位置插入元素e的副本,並返回新元素位置
c.insert(pos,n,e) 在pos位置插入n個元素e的副本
c.insert(pos,beg,end) 在pos位置插入區間[beg,end]內所有元素
c.push_back(e) 在尾部新增一個元素e的副本
11.移除元素
操作 效果
c.pop_back() 移除最後一個元素但不返回最後一個元素
c.erase(pos) 刪除pos位置的元素,返回下一個元素的位置
c.erase(beg,end) 刪除區間[beg,end]內所有元素,返回下一個元素的位置
c.clear() 移除所有元素,清空容器
c.resize(num) 將元素數量改為num,(正價的元素用defalut)建構函式產生,多餘的元素 被刪除
c.resize(num,e) 將元素數改為num(增加的元素是e的副本)
2.map/multimap
1.使用平衡二叉樹管理元素
2.元素包含兩部分(key,value),key和value可以是任意型別
3.必須包含的標頭檔案#include<map>
4.根據元素的key自動對元素排序,因此根據元素的key進行定位很快,但根據元素的value定位很慢
5.不能直接改變元素的key,可以通過operator[]直接存取元素值
6.map中不允許key相同的元素,multimap執行key相同的元素
7.構造、拷貝和析構
操作 效果
map c 產生空的map
map c1(c2) 產生同類型的c1,並複製c2的所有元素
map c(op) 以op為排序準則產生一個空的map
map c(beg,end) 以區間[beg,end]內的元素產生一個map
map c(beg,end,op) 以op為排序準則,以區間[beg,end]內的元素產生一個map
~map() 銷燬所有元素並釋放記憶體
8.非變動性操作
操作 效果
c.size() 返回元素個數
c.empty() 判斷容器是否為空
c.max_size() 返回元素最大可能數量
c1==c2 判斷c1是否等於c2
c1!=c2 判斷c1是否不等於c2
c1<c2 判斷c1是否小於c2
c1>c2 判斷c1是否大於c2
c1<=c2 判斷c1是否小於等於c2
c1>=c2 判斷c1是否大於等於c2
9.複製
操作 效果
c1=c2 將c2的全部元素賦值給c1
c1.swap(c2) 將c1和c2的元素互換
swap(c1,c2) 同上,全域性函式
10.特殊搜尋操作
操作 效果
count(key) 返回“鍵值等於key”的元素個數
find(key) 返回“鍵值等於key”的第一個元素,找不到返回end
lower_bound(key) 返回“鍵值大於等於key”的第一個元素
upper_bound(key) 返回“鍵值大於key”的第一個元素
equal_range(key) 返回”鍵值等於key”的元素區間
11.迭代器相關函式
操作 效果
begin() 返回一個雙向迭代器,指向最後一個元素之後
end() 返回一個雙向迭代器,指向最後一個元素之後
rbegin() 返回一個逆向迭代器,指向逆向遍歷的第一個元素
rend() 返回一個逆向迭代器,指向逆向遍歷的最後一個元素
12.安插元素
操作 效果
c.insert(pos,e) 在pos位置起點插入e的副本,並返回新元素位置
c.insert(e) 插入e的副本,並返回新元素位置
c.c.insert(beg,end) 將區間[beg,end]內所有元素的副本插入到c中
13.移除元素
操作 效果
c.erase(pos) 刪除迭代器pos所指位置元素,無返回值
c.erase(val) 移除所有值為val的元素,返回移除元素個數
c.erase(beg,end) 刪除區間[beg,end]內所有元素,無返回值
c.clear() 移除所有元素,清空容器
3.set/multiset
1.使用平衡二叉樹管理元素
2.集合石一種包含已排序物件的關聯容器
3.必須包括的標頭檔案#include<set>
4.map容器石鍵值對的集合,好比以人名為鍵的地址和電話號碼。相反地,set容器只是單純的鍵的集合。當我們想知道某位使用者是否存在使,使用set使容器是最合適的
5.set中不允許key相同的意思,multiset允許key相同的元素
6.
操作 效果
返回指向第一個元素的迭代器 |
|
清除所有元素 |
|
返回某個值元素的個數 |
|
如果集合為空,返回true |
|
返回指向最後一個元素的迭代器 |
|
返回集合中與給定值相等的上下限的兩個迭代器 |
|
刪除集合中的元素 |
|
返回一個指向被查詢到元素的迭代器 |
|
返回集合的分配器 |