1. 程式人生 > >過載運算子和STL部分的課程筆記

過載運算子和STL部分的課程筆記

過載運算子和STL

第一部分:知識總結

一.運算子過載

1.C++語言中大部分預定義的運算子都可以被過載。以下運算子不可以被過載:

.*   :: ?:  sizeof

運算子過載後,原有的基本語義不變,包括:

不改變運算子的優先順序

不改變運算子的結合性

不改變運算子所需要的運算元

不能創造新的運算子

2.語法形式

運算子函式是一種特殊的成員函式或友元函式。成員函式的語句形式為:

型別 類名::operator op(引數表)

{

 

//相對於該類定義的操作

}

其中,“型別”是函式的返回型別。“類名”是要過載該運算子的類。“op”表示要過載的運算子。函式名是“operator op”由關鍵字operator和被過載的運算子op組成。“引數表”列出該運算子所需要的運算元

(1)一元運算子:

Object  op   或    op  Object

過載為成員函式,解釋為:

object .operatorop()

運算元由物件object 通過this指標隱含傳遞

過載為友元函式,解釋為:

operatorop(object)

運算元由引數表的引數object提供

(2)二元運算子

ObjectL  op ObjectR

過載為成員函式,解釋為:

ObjectL .operator op ( ObjectR )

   左運算元由ObjectL通過this指標傳遞,右運算元由引數ObjectR傳遞

過載為友元函式,解釋為:

operator op (ObjectL, ObjectR )

 左右運算元都由引數傳遞

(3)對雙目運算子而言,成員運算子函式的形參表中僅有一個引數,它作為運算子的右運算元,此時當前物件作為運算子的左運算元,它是通過this指標隱含地傳遞給函式的。

例如以下的例子,過載+運算子

#include<iostream>

using namespacestd;

class Complex

{public:

Complex(){real=0,imag=0;}

Complex(doubler,double i)

{real=r;imag=I;}

Complexoperator+(Complex&c2);

void display();

private:

double real;

double imag;

};

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.display( );

       return 0;

}

在這個程式中過載了+運算子,利用了傳引用的方式,傳遞了c2的內容

3.用友元函式過載

=有時,運算子的左右操作型別不同,用成員函式過載運算子會碰到麻煩。例如,定義有以下複數類:

ClassComplex

{public:

Complex(inta)

{Real=a;Imag=0;}

Complex(inta,int b)

{Real=a;Imag=b;}

Complexoperator+(Complex);

private:

int Real;

int Imag;

};

int f()

{Complexz(1,2),k(3,4);

z=z+25;

z=25+z;

//……

}

在函式f中,表示式:

z+25

被解釋為:z.operator+(25);

它引起呼叫過載運算子函式是左操作物件z,右操作物件數25通過Complex型別引數呼叫類的建構函式Complex(int a),建立臨時的物件執行函式;

但是表示式:

25+z;

被解釋為系統預定義版本時,z無法轉換成基本資料型別,而是被解釋為過載版本:25.operator+(z)

其中,整型值25不是Complex類物件,無法驅動函式進行運算。在此,用成員函式過載的“+”運算子不具備運算交換性。

如果用友元函式過載運算子,左,右運算元都由引數傳遞,則C++語言可以通過建構函式實現資料型別隱式轉換。

若過載形式為:

friendComplex operator+(Complex ,Complex);

則表示式:25+z

被解釋為operator+(25,z)

友元函式不能過載的運算子有:

=    ()    []    ->

成員運算子函式與友元運算子函式的比較

(1)成員運算子函式比友元運算子函式少帶一個引數(後置的++,--需要增加一個形參)。

(2)雙目運算子一般可以被過載為友元運算子函式或成員運算子函式,但當運算元型別不相同時,必須使用友元函式。

4.幾個典型的運算子過載

(1)運算子++和—有兩種方式

前置方式:++Aobject –Aobject

成員函式 過載A::A operator++();

解釋為 :Aobject.operator++();

友元函式 過載 friend A operator++(A&);

解釋為:operator++(Aobject);

後置方式:Aobject++ Aobject—

成員函式 過載 A::A operator++(int);

解釋為:Aobject.operator++(0);

友元函式 過載:friend A operator++(A&,int);

解釋為:operator++(Aobject,0)

#include<iostream>

using namespacestd;

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( ) ;

 }

(2)過載=運算子

賦值運算子過載用於物件資料的複製

operator=必須過載為成員函式

過載函式原型為:

類名&類名::operator=(類名)

例如定義Name類的過載

#include<iostream>

#include<string>

using namespacestd;

class Nmae

{public:

Name(char*pN);

Name(constName&);//複製建構函式

Name&operator=(constName&);

~Name();

protected:

char *pName;

int size;

};

int main()

{NameObj1(“zhangsan”);

Name Obj2=Obj1;//呼叫複製建構函式

Name Obj3(“NoName”);

Obj3=Obj2=Obj1;//呼叫了過載賦值運算子

}

Name::Name(char*pN)

{cout<<”Constructing”<<pN<<endl;

pName=newchar[strlen(pN)+1];

if(pName!=0)strcpy(pName,pN);//strcpy的功能是把字串pN複製到一分配好字串空間pName中

size=strlen(pN);//表示長度

}

Name::Name(constName&Obj)

{cout<<”Copying”<<Obj.pName<<”intoits own block\n”;

pName= newchar[strlen(Obj.pName)+1];

if(pName!=0)strcpy(pName,Obj.pName);

size=Obj.size;

}

Name&Name::operator=(constName&Obj)//過載了=運算子

{delete []pName;

pName=newchar[strlen(Obj.pName)+1];

if(pName!=0)strcpy(pName,Obj.pName);

size=Obj.size;

return *this;

}

Name::~Name()//解構函式,釋放記憶體

{cout<<”Destructing”<<pName<<endl;

delete []pName;

size=0;

}

(3)過載運算子[]和()

運算子 [] 和 () 是二元運算子

 [] 和 () 只能用成員函式過載,不能用友元函式過載

1.[]運算子用於訪問資料物件的元素

過載格式 型別 類::operator [](型別)

例如:

設y是類X的一個物件,則表示式

X【y】

可被解釋為

x.operator[](y);

例如以下程式:

#include<iostream>

using namespacestd;

class vector

{public:

vector(intn){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;

}

上面這個程式是什麼意思呢,vector(int n){v=new int [n];size=n;}

這一行程式碼是建立一個動態陣列的意思,動態陣列會在STL部分總結,那麼

vector a(5);

這一句的意思就是申請了一個大小為5的整形陣列,然後定義了a[2]為12,其他的就是隨機的大小

2.過載函式呼叫符()

()運算子用於函式呼叫

過載格式 型別 類::operator()(引數表)

例如

設x是類X的一個物件,則表示式

x(arg1,arg2,…)

可被解釋為

x.operator()(arg1,arg2,…)

#include<iostream>

using namespacestd;

class F

{public:

doubleoperator(double x,double y);

};

doubleF::operator()(double x,double y)

{return x*x+y*y;}

int main()

{F f;

cout<<f(5.2,2.5)<<endl;

}

(4)過載流插入和流提取運算子

istream和ostream是C++的預定義流類

cin是istream的物件,cout是ostream 的物件

運算子<<由ostream過載為插入操作,用於輸出基本型別資料

運算子>>由istream過載為提取操作,用於輸入基本型別資料

用友元函式過載>>和<<,輸入和輸出使用者自定義的資料型別

過載輸出運算子“<<”(只能被過載成友元函式,不能過載成成員函式)

定義輸出運算子“<<”過載函式的一般格式如下:

ostream&operator<<(ostream&out,class_name&obj)

{out<<obj.item1;

out<<obj.item2;

out<<obj.itemn;

return out;

}

過載輸入運算子“>>”(只能被過載為友元函式)

定義格式如下:

istream&operator>>(istream&in,classname&obj)

{in>>obj.item1;

in>>obj.item2;

….

in>>onj.itemn;

return in;

}

過載流插入和輸出的使用

#include<bits/stdc++.h>

using namespacestd;

class vector

{public:

vector(int size=1);

~vector();

int&operator[](int i);

frinedostream&operator<<(ostream&output,vector&);

friendistream&operator>>(istream&input,vector&);

private:

int *v;int len;

};

int main()

{int k;

cout<<”Inputthe length of vector A:\n”;

cin>>k;

vector A(k);

cout<<”Inputthe elements of vector A:\n”;

cin>>A;

cout<<”Outputthe elements of vector A:\n”;

cout<<A;

}

vector::vector(intsize)

{if(size<=0||size>100)

{cout<<”Thesize of”<<size<<”is null!\n”;

exit(0);

}

v=new int[size];

len=size;

}

vector::~vector(){delete[]v;len=0;}

int&vector::operator[](int i)

{if(i>=0&&i<len)return v[i];

cout<<”Thesubscript”<<i<<”is outside!\n”;

exit(0);

}

ostream&operator<<(ostream&output,vector&ary)

{for(inti=0;i<ary.len;i++)

output<<ary[i]<<”“;

output<<endl;

return output;

}

istream&operator>>(istream&input,vector&ary)

{for(inti=0;i<ary.len;i++)

input>>ary[i];

return input;

}

第二部分:心得總結

關於運算子的過載這一個部分,主要是在使用的時可以更方便一些

大體上分為兩種,一種是過載為成員函式,比如=就必須過載成成員函式,另一種是過載成友元函式,比如過載流插入和流輸出的時候,就必須過載成友元函式。

在類中定義的時候可以定義成這種形式:

型別 operator op(引數表);

然後在類外定義的時候

是這樣的形式

型別 類名::operator op(引數表)

{//所需要的操作

}

STL部分

第一部分:知識總結

1.STL是C++標準程式庫的核心,深刻影響了標準程式庫的整體結構

2.STL由一些可適應不同需求的集合類,以及在這些資料集合上操作的演算法

STL內的所有元件都由模板構成,其元素可以是任意型別

3.STL的元件

容器(Container)管理某類物件的集合

迭代器(iterator)在物件集合上進行遍歷

4.容器的類別

序列式容器——排列次序取決於插入時機和位置

關聯式容器——排列順序取決於特定準則

容器的特點:

在資料儲存上,有一種物件型別,它可以持有其它物件或指向其它對像的指標,這種物件型別就叫做容器。很簡單,容器就是儲存其它物件的物件,當然這是一個樸素的理解,這種物件還包含了一系列處理其它物件的方法,因為這些方法在程式的設計上會經常被用到,所以容器也體現了一個好處,就是容器類是一種對特定程式碼重用問題的良好的解決方案

容器還有另一個特點是容器可以自行擴充套件。在解決問題時我們常常不知道我們需要儲存多少個物件,也就是說我們不知道應該建立多大的記憶體空間來儲存我們的物件。顯然,陣列在這一方面也力不從心。容器的優勢就在這裡,它不需要你預先告訴它你要儲存多少物件,只要你建立一個容器物件,併合理的呼叫它所提供的方法,所有的處理細節將由容器來自身完成。它可以為你申請記憶體或釋放記憶體,並且用最優的演算法來執行您的命令。

序列式容器:vector,deque,list

關聯式容器:

和順序性容器不一樣,關聯式容器是非線性的樹結構,更準確的說是二叉樹結構。各元素之間沒有嚴格的物理上的順序關係,也就是說元素在容器中並沒有儲存元素置入容器時的邏輯順序。但是關聯式容器提供了另一種根據元素特點排序的功能,這樣迭代器就能根據元素的特點順序地獲取元素。

關聯式容器另一個顯著的特點是它是以鍵值的方式來儲存資料,就是說它能把關鍵字和值關聯起來儲存,而順序性容器只能儲存一種(可以認為它只儲存關鍵字,也可以認為它只儲存值)

包括:set,multiset,map,multimap;

STL容器的共同能力:

所有容器中存放的都是值而非引用,如果希望存放的不是副本,容器元素只能是指標。

所有元素都形成一個次序,可以按相同次序一次或者多次遍歷每個元素

STL容器元素的條件

必須能夠通過拷貝建構函式進行復制

必須可以通過賦值運算子完成複製操作

必須可以通過解構函式完成銷燬動作

序列式容器元素的預設建構函式必須可用

某些動作必須定義operator=例如搜尋操作

關聯式容器必須定義出排序準則,預設情況是過載operator<

STL容器的共同操作:

初始化:產生一個空容器

std::list<int> l;

 

以另一個容器元素為初值完成初始化

std::list<int>l;

……

std::vector<float>c(l.begin(),l.end());

 

以陣列元素為初值完成初始化

int array[]={2,4,6,1345}

……

std::set<int>c(arry,arry+sizeof(array)/sizeof(array[0]);

與大小相關的操作

      size()—返回當前容器的元素數量

      empty()—判斷容器是否為空

      max_size()—返回容器能夠容納的最大元素數量

 比較

      ==,!=,<,<=,>=

      比較操作兩端的容器必須屬於同一型別

      如果兩個容器內的所有元素按序相等,那麼這兩個容器相等

賦值和交換

    swap用於提高賦值操作效率

     與迭代器相關操作

begin()—返回一個迭代器,指向第一個元素

end()—返回一個迭代器,指向最後一個元素之後

rbegin()-返回一個逆向迭代器,指向逆向遍歷的第一個元素

rend()-返回一個逆向迭代器,指向逆向遍歷後的最後一個元素之後

元素操作

    insert(pos,e)-將元素e的拷貝安插於迭代器pos所指的位置

    erase(beg,end)-移除[beg,end]區間內的所有元素

    clear()—移除所有元素

5.STL的迭代器

1):迭代器類似於指標型別,它也提供了對物件的間接訪問。 
指標是c語言中就有的東西,迭代器是c++中才有的,指標用起來靈活高效,迭代器功能更豐富些。 
迭代器提供一個對容器物件或者string物件的訪問的方法,並且定義了容器範圍。

(2):迭代器的使用

*iter      返回迭代器iter所指元素的引用

iter->men  解引用iter並獲得該元素的名為men的成員,相當於(*iter.men

++iter      iter指示容器的下一個元素

 --iter      iter指示容器的上一個元素

iter1==iter2  如果兩個迭代器指示的是同一個元素或者它指向同一個容器的尾後迭代器,則相等.

(3)迭代器的型別

那些擁有迭代器的標準庫型別都是使用:iterator和const_iterator來表示迭代器的型別:

 vector <int> ::iteratorit;        //it能讀寫vector<int>的元素

 vector <int>::const_iteratorit;  //it只能讀vector<int>的元素,不可以修改vector<int>中的元素

string::iterator s;               //s可以讀寫string物件中的元素

string::const_iterators;          //s只可以讀string物件中的元素,不可以修改string物件中的元素

vector動態增長的限制: 
(1):不能再範圍for迴圈中向vector物件新增元素。 
(2):任何一種可能改變vector容量的操作,比如push_back,都會使該vector物件的迭代器失效。

迭代器的運算

iter+n           //迭代器加上一個整數值仍得到一個迭代器,迭代器指示的新位置向前移動了,指示可能是容器的一個元素或者是尾部的下一個位置
iter-n          //相反,迭代器指示的位置向後移動了,指示可能是容器的一個元素或者是尾部的下一個位置
iter1+=n   //等價於iter1+n
iter1-=n    //等價於iter2-n
iter1-iter2  //兩個迭代器的距離,
><,>=,<= //位置離begin近的較小
(5).動態陣列Vector

vector模擬動態陣列,vector的元素可以是任意型別T,但必須具備賦值和拷貝能力(具有public拷貝建構函式和過載的賦值操作符)

必須有標頭檔案#include<vector>,支援隨機存取

size返回vector實際元素個數

capacity返回能容納的元素的最大數量

vector的操作

vector<T>c          //產生一個空的vector

vector<T>c1(c2)      //產生同類型的vector,並賦值C2的所有元素

 

vector<T>c(n)        //利用型別T的預設建構函式和拷貝建構函式生成一個大小為n的vector

vector<T>c(beg,end)   //產生一個vector,已區間[beg,end]為元素初值

~vector<T>()         //銷燬所有元素並釋放記憶體

非變動操作

c.size()        //返回元素的個數

c.empty()      //判斷容器是否為空

c.max_size()   //返回元素最大可能數量

c.capacity    //返回重新分配空間前可容納的最大元素數量

c.reserve(n)   //擴大容量為n

vector迭代器相關函式

與常用的相同

vector安插元素

c.insert(pos,e)        在pos位置插入元素e的副本

c.insert(pos,n,e)       在pos位置插入n個元素e的副本

c.insert(pos,beg,end)    在pos位置插入區間[begin,end]內所有元素的副本

c.push_back(e);         在尾部新增一個元素為e的副本

移除操作

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的副本)

 

 

vector的例項:

#include <iostream>

#include <algorithm>

#include <vector>

using namespace std;

 

int main(){

   vector<int> a;        //定義了一個整形動態陣列a

   for (int i = 0; i < 5; ++i){

       a.push_back(5 - i);//在尾部增添資料(5-i)

    }

   cout << "Size: " << a.size() << endl;//返回a的現有元素數量

   a.pop_back();               //移除最後一個元素,但是不返回

   a[0] = 1;            

 

   cout << "Size: " << a.size() << endl;

   for (int i = 0; i < (int)a.size(); ++i){

       cout << a[i] << ", " << endl;

    }

   cout << endl;

 

   sort(a.begin(), a.end());          //對於a中進行sort排序

   cout << "Size: " << a.size() << endl;

   for (int i = 0; i < (int)a.size(); ++i){

       cout << a[i] << ", " << endl;

    }

   cout << endl;

 

   a.clear();                   //把a的元素全部刪除

   cout << "Size: " << a.size() << endl;

   return 0;

}

(6)map/muitimap的使用

使用平衡二叉樹管理元素,可以對元素進行一個快速的定位

元素包括兩部分(key,value),key和value可以是任意型別

map中不允許key相同的元素,multimap允許key相同

map的操作和vector差不多,但是map中多了一個排序,

例如下操作

map c(beg,end,op) 以op為排序準則,以區間[beg,end]內的元素產生一個map

map c(op) 以op為排序準則產生一個空的map

這裡的排序準則可以按照如下的方式:

struct cmp1

{string str;

cmp1(string s){str=s;}

bool operator()(Record &a)

{ return(a.getStr().find(str)!=string::npos);

}

};

應用示例如下:

#include <iostream>

#include <map>

#include <algorithm>

using namespace std;

 

struct T1{

   int v;

   bool operator<(const T1 &a)const{

       return (v < a.v);//過載小於號運算子,返回其排序方式

    }

};

 

struct T2{

   int v;

};

struct cmp{

   const bool operator()(const T2 &a, const T2 &b){

       return (a.v < b.v);

    }

};

 

int main(){

   map<T1, int>mt1; //example for user-defined class//建立mit1

   map<T2, int, cmp>mt2; //example for user-defined class(functor)

//以cmp的排序方式建立mt2

   map<string, int> m2;

   map<string, int>::iterator m2i, p1, p2;//這個地方是建立迭代器

   //map<string, int, greater<string> >m2;

   //map<string, int, greater<string> >::iterator m2i, p1, p2;

   m2["abd"] = 2;

   m2["abc"] = 1;

   m2["cba"] = 2;

   m2.insert(make_pair("aaa", 9));//插入操作std::pair的主要作用是將兩個資料組合成一個數據,兩個資料可以是同一型別或者不同型別

   m2["abf"] = 4;

   m2["abe"] = 2;

   cout << m2["abc"] << endl;

 

   m2i = m2.find("cba");

   if(m2i != m2.end()){

       cout << m2i->first << ": " <<m2i->second << endl;//利用迭代器分別指向第一個元素和第二個元素

   }else{

       cout << "find nothing" << endl;

    }

 

   cout << "Iterate" << endl;

   for(m2i = m2.begin(); m2i != m2.end(); m2i++){

       cout << m2i->first << ": " <<m2i->second << endl;

    }

 

   return 0;

}

關於multimap的操作例項

#include <iostream>

#include <map>

#include <algorithm>

using namespace std;

 

int main(){

   multimap<string, int> mm1;

   multimap<string, int>::iterator mm1i, p1, p2;

   mm1.insert(make_pair("b", 3));

   mm1.insert(make_pair("a", 0));

   mm1.insert(make_pair("b", 5));

   mm1.insert(make_pair("c", 4));

   mm1.insert(make_pair("b", 2));

   cout << "Size: " << mm1.size() << endl;

   for(mm1i = mm1.begin(); mm1i != mm1.end(); mm1i++){

       cout << mm1i->first << ": " <<mm1i->second << endl;

    }

 

   cout << "COUNT: " << mm1.count("b")<< endl;

   cout << "Bound: " << endl;

   p1 = mm1.lower_bound("b");//這個地方是特殊搜尋操作,lower_bound(key)返回的是鍵值大於等於key的第一個元素

   p2 = mm1.upper_bound("b");//返回鍵值大於key的第一個元素

   for(mm1i = p1; mm1i != p2; mm1i++){

       cout << mm1i->first << ": " <<mm1i->second << endl;

    }

 

   return 0;

}

(6)find函式用法

個人覺得這個函式比較好用

例如在vector中的利用

#include <iostream>

#include <algorithm>

#include <vector>

using namespace std;

 

int main()

{

   vector<string> m;

   m.push_back("hello");

   m.push_back("hello2");

   m.push_back("hello3");

    if (find(m.begin(), m.end(), "hello") == m.end())

        cout << "no" << endl;

    else

       cout << "yes"<< endl;

}

這段程式的意思就是在m中找尋hello

 

 演算法中的查詢演算法
(1)find
template<class InIt,class T>
InIt find(InIt first,InIt last,const T&val)
返回區間[first,last)中的迭代器i,使得*i==val
(2)find_if
template<class InIt,class Perd>
InIt find_if(InIt first,InIt last,Perd pr);
返回區間[first,last)中的迭代器i,使得
pr(*i)==true
(3) binary_search折半查詢,要求容器已經有序且支援隨機訪問迭代器,返回是否找到
template<class Fwdlt,class T>
bool  binary_search(FwdIt first,FwdIt last,const T&val);
上面這個版本,比較兩個元素x,y大小時,看x<y



心得總結:
關於stl這一部分,我覺得是增加了寫程式時的方便性,利用vector可以建立陣列,這時就不須考慮陣列容量的大小,利用map時查詢更為方便,如果vector中的內容發生了改變,map的物件也隨之改變,增加或刪除都要記得map中也進行相應的操作,還有一點很重要的是不要忘記迭代器,建立相應的指標,便於操作,還有一點就是演算法的部分,find,sort的使用,注意建立相應的排序準則。