嵌入式開發之C++基礎學習筆記5--靜態成員,友元,運算符重載,模板,文件流

一. 靜態成員  

 在一個類中還能夠定義靜態成員,但靜態成員是全部對象公有的。靜態成員分爲靜態數據成員和靜態成員函數。
 1.靜態數據成員
 在類中定義靜態數據成員的方法就是在該成員的前面加上關鍵字static.
 定義靜態數據成員的語句格式以下:
 class 類名
 {
   ……
   static 類型說明符 成員名;
   ……
 };
 靜態數據成員是類的全部對象共享的成員。靜態數據成員所佔的空間不會隨着對象的產生而分配,也不會隨着對象的消失而回收。對靜態數據成員的操做和類中通常數據成員的操做是不同的,定義爲私有的靜態數據成員不能被外界所訪問。靜態數據成員可由任意訪問權限許可的函數所訪問。
 因爲靜態數據成員是類的全部對象共享的,而不從屬於任何一個具體對象,因此必須對類的靜態數據成員進行初始化,但對它的初始化不能在類的構造函數中進行,其初始化語句應當寫在程序的全局區域中,而且必須指明其數據類型與所屬的類名,其初始化格式以下:
 類型 類名::變量名=值;
 對於在類的public部分說明的靜態數據成員,在類的外部能夠不使用成員函數而直接訪問,但在使用時必須用類名指明所屬的類,其訪問格式爲:
 類名::靜態數據成員名
 對於在類的非public部分說明的靜態數據成員,則只能由類的成員函數訪問,其訪問方法與訪問類中普通數據成員的訪問方法徹底同樣,但在類的外部不能訪問。

 2.靜態成員函數
 靜態成員函數的定義與通常成員函數的定義相同,只是在其前面冠以static關鍵字,其定義格式以下:
 class 類名
 {
   …
   static 類型 函數名(形參)
 {   函數體   }
   …
 };
說明:
   (1)類的靜態成員函數只能訪問類的靜態數據成員,而不能訪問類中的普通函數成員(非靜態數據成員),由於普通數據成員只有類的對象存在時纔有意義。
   (2)靜態成員函數與類相聯繫,而不與類的對象相聯繫,因此,在類的外部調用類中的公有靜態成員函數,必須在其左面加上「類名::」,而不是經過對象名調用公有靜態成員函數。在類的外部不能調用類中的私有靜態成員函數。
 
 示例:
#include <iostream>
using namespace std;
class A {
public:
int c;// ISO C++ 不容許成員‘c’的初始化
static int a;// ISO C++ 不容許在類內初始化很是量靜態成員‘a’
static string b;
static void hello(){
cout << "HelloWorld" << endl;
}
};
int A::a = 20;
string A::b = "jintao";
int main() {
cout << A::a  << "\n" << A::b << endl;
A :: hello();
}
 運行結果:
20
jintao
HelloWorld


二. 友元函數

1.1爲何要使用友元函數
在實現類之間數據共享時,減小系統開銷,提升效率。若是類A中的函數要訪問類B中的成員(例如:智能指針類的實現),那麼類A中該函數要是類B的友元函數。具體來講:爲了使其餘類的成員函數直接訪問該類的私有變量。即:容許外面的類或函數去訪問類的私有變量和保護變量,從而使兩個類共享同一函數。
實際上具體大概有下面兩種狀況須要使用友元函數:(1)運算符重載的某些場合須要使用友元。(2)兩個類要共享數據的時候。

1.2使用友元函數的優缺點
1.2.1優勢:可以提升效率,表達簡單、清晰。
1.2.2缺點:友元函數破環了封裝機制,儘可能不使用成員函數,除非不得已的狀況下才使用友元函數。

2.友元函數的使用
2.1友元函數的參數:
由於友元函數沒有this指針,則參數要有三種狀況:
2.1.1 要訪問非static成員時,須要對象作參數;
2.1.2 要訪問static成員或全局變量時,則不須要對象作參數;
2.1.3 若是作參數的對象是全局對象,則不須要對象作參數;

2.2友元函數的位置
由於友元函數是類外的函數,因此它的聲明能夠放在類的私有段或公有段且沒有區別。

2.3友元函數的調用
能夠直接調用友元函數,不須要經過對象或指針

2.4友元函數的分類:
根據這個函數的來源不一樣,能夠分爲三種方法:

2.4.1普通函數友元函數
2.4.1.1 目的:使普通函數可以訪問類的友元

2.4.1.2 語法:
聲明: friend + 普通函數聲明
實現位置:能夠在類外或類中
實現代碼:與普通函數相同
調用:相似普通函數,直接調用

2.4.1.3代碼:
#include <iostream>
using namespace std;
class student {
public:
void get();
friend void mm(student &t);
private:
int num;
};


void student::get() {
cin >> num;
}


void mm(student &t) {
cout << t.num << endl;
}

int main() {
student st1;
st1.get();
mm(st1);
return 0;
}

2.4.2類Y的全部成員函數都爲類X友元函數—友元類
2.4.2.1目的:使用單個聲明使Y類的全部函數成爲類X的友元,它提供一種類之間合做的一種方式,使類Y的對象能夠具備類X和類Y的功能。
2.4.2.2語法:
聲明位置:公有私有都可,常寫爲私有(把類當作一個變量)
聲明: friend + 類名(不是對象哦)
2.4.2.3代碼:
#include <iostream>
using namespace std;
class CApple {
friend class COrange;   //聲明友元類爲COrange
public:
CApple() {
m_x = 0;
m_y = 0;
}  //默認構造函數
CApple(int x, int y);
void disp();
private:
int m_x;      //數據成員
int m_y;
};
CApple::CApple(int x, int y)   //構造函數
{
m_x = x;
m_y = y;
}
void CApple::disp() {
cout << m_x << ";" << m_y << endl;
}

class COrange      //友元類COrange的聲明
{
public:
COrange(int x, int y);   //構造函數
void disp();
private:
CApple m_apple;    //聲明數據成員爲CApple類的對象
};

COrange::COrange(int x, int y) {
m_apple.m_x = x;    //訪問對象m_ apple的私有成員m_x
m_apple.m_y = y;    //訪問對象m_ apple的私有成員m_y
}
void COrange::disp() {
cout << m_apple.m_x << ";" << m_apple.m_y << endl;
}

int main() {
CApple apple(55, 88);   //建立CApple類對象apple
apple.disp();
COrange orange(10, 100);  //建立COrange類對象orange
orange.disp();
}
運行結果:
55;88
10;100

2.4.3類Y的一個成員函數爲類X的友元函數
2.4.3.1目的:使類Y的一個成員函數成爲類X的友元,具體而言:在類Y的這個成員函數中,藉助參數X,能夠直接以X的私有變量
2.4.3.2語法:
聲明位置:聲明在公有中 (自己爲函數)
聲明:friend + 成員函數的聲明
調用:先定義Y的對象y---使用y調用本身的成員函數---本身的成員函數中使用了友元機制

2.4.3.3代碼:
實現代碼和2.4.2.3中的實現及其類似只是設置友元的時候變爲friend void boy::disp(girl &);本身解決嘍……
小結:其實一些操做符的重載實現也是要在類外實現的,那麼一般這樣的話,聲明爲類的友元是必須滴。

4.友元函數和類的成員函數的區別
4.1 成員函數有this指針,而友元函數沒有this指針。
4.2 友元函數是不能被繼承的,就像父親的朋友未必是兒子的朋友。

三 .運算符重載 

     C++語言中預約義的運算符的操做對象只能是基本數據類型,可是,在實際應用中,對於不少用戶自定義數據類型(如類)也須要相似的功能,這就須要對已有的運算符賦予多種含義,使同一個運算符具備做用於不一樣類性的數據致使不一樣類型的行爲,這就是運算符重載。所以,運算符重載的目的是設置C++語言中的某一運算符,讓它們之間並不衝突,C++語言會根據運算符的位置辨別應使用哪種功能進行運算。可見,運算符重載的優勢是容許改變使用於系統內部的運算符的操做方式,以適應用戶新定義類型的相似運算。 
   運算符重載的實質是函數重載。事實上,C++語言中的每個運算符對應着一個運算符函數,在實現過程當中,把指定的運算表達式中的運算符轉化爲對運算符函數的調用,而表達式中的運算對象轉化爲運算符函數的實參,這個過程是在編譯階段完成的。例如:
   int a=1,b=2;
   a+b;
   表達式「a+b」在編譯前,將被解釋爲函數調用形式:operator+(a,b)。
   其中,operator是一個關鍵字,它與後面的「+」共同組成了該運算符函數的函數名。
   運算符重載是一種特殊的函數重載。在類中能夠採用下述兩種方法對運算符函數進行重載。
   1.重載爲類的成員函數
   將運算符函數重載爲類的成員函數是指在類中定義一個同名的運算符函數,其語句格式爲:
   TYPE X::operator@(形參表)
   {
   //函數體
   //從新定義運算符@在指定類X中的功能
   }
   其中,operator是關鍵字,@是須要被重載的運算符,X是須要重載該運算符的類名,TYPE是該運算符函數的返回值類型。關鍵字operator與後面的運算符@共同組成了該運算符函數的函數名。


   2.重載爲類的友元函數
   能夠定義一個與某一運算符函數同名的全局函數,而後再將該全局函數聲明爲類的友元函數,從而實現運算符的重載。其語法格式爲:
   friend TYPE operator@(形參表);
   下面介紹運算符重載的幾個問題:
   以上兩種重載形式都可訪問類的私有成員;
   幾乎全部的運算符均可以被重載,但下列運算符不容許重載:
   「。」、「。*」、「::」、「?:」
   運算符重載後,既不會改變原運算符的優先級和結合特性也不會改變使用運算符的語法和參數個數;
   「=」、「()」、「[]」和「->」等運算符不能重載爲友元函數;
   當運算符重載爲類的成員函數時,函數的參數個數比原來的運算對象少一個(右++和右——除外);當重載爲類的友元函數時,參數個數與原運算符的運算個數相同;單目運算符最好重載爲類的成員函數,而雙目運算符則最好重載爲類的友元函數。


 示例:
#include <iostream>
using namespace std;
class CComplex //定義複數類
{
float real, imag; //複數的實部和虛部
public:
CComplex(float r, float i) {
real = r;
imag = i;
} //帶參數的構造函數
CComplex() {
real = 0;
imag = 0;
} //不帶參數的構造函數
void Print(); //複數的輸出函數
friend CComplex operator+(CComplex a, CComplex b);
//用友元函數重載複數相加運算符
friend CComplex operator-(CComplex a, CComplex b);
//重載複數相減運算符
friend CComplex operator*(CComplex a, CComplex b);
//重載複數相乘運算符
friend CComplex operator/(CComplex a, CComplex b);
//重載複數相除運算符
};


void CComplex::Print() //定義複數的輸出函數
{
cout << real;
if (imag > 0)
cout << "+";
if (imag != 0)
cout << imag << "i\n";
}


CComplex operator +(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real + b.real;
temp.imag = a.imag + b.imag;
return temp;
}


CComplex operator -(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real - b.real;
temp.imag = a.imag - b.imag;
return temp;
}


CComplex operator *(CComplex a, CComplex b) {
CComplex temp;
temp.real = a.real * b.real - a.imag * b.imag;
temp.imag = a.real * b.imag + a.imag * b.real;
return temp;
}


CComplex operator /(CComplex a, CComplex b) {
CComplex temp;
float tt;
tt = 1 / (b.real * b.real + b.imag * b.imag);
temp.real = (a.real * b.real + a.imag * b.imag) * tt;
temp.imag = (b.real * a.imag - b.imag * a.real) * tt;
return temp;
}


int main() {
CComplex c1(2.3, 4.6), c2(3.6, 2.8), c3; //定義三個複數對象
c1.Print(); //輸出複數c1和c2
c2.Print();
c3 = c1 + c2; //複數c1,c2相加,結果給c3
c3.Print(); //輸出相加結果
c3 = c2 - c1; //複數c2,c1相減,結果給c3
c3.Print(); //輸出相減結果
c3 = c2 * c1; //複數c1,c2相乘,結果給c3
c3.Print(); //輸出相乘結果
c3 = c1 / c2; //複數c1,c2相除,結果給c3
c3.Print(); //輸出相除結果
}
 運行結果:
2.3+4.6i
3.6+2.8i
5.9+7.4i
1.3-1.8i
-4.6+23i
1.01731+0.486538i

四,模板

    模板(template)利用一種徹底通用的方法來設計函數或類而沒必要預先說明將被使用的每一個對象的類型,利用模板功能能夠構造相關的函數或類的系列,所以模板也可稱爲參數化的類型。在C++語言中,模板可分爲類模板(class template)和函數模板(function template)。
    在程序中說明了一個函數模板後,編譯系統發現有一個相應的函數調用時,將根據實參中的類型來確認是否匹配函數模板中對應的形參,而後生成一個重載函數。該重載函數的定義體與函數模板的函數定義體相同,稱之爲模板函數(template function)。
    函數模板與模板函數的區別是:函數模板是模板的定義,定義中用到通用類型參數。模版函數是實實在在的函數定義,它由編譯系統在遇到具體函數調用時所產生,具備程序代碼。
    一樣,在說明了一個類模板以後,能夠建立類模板的實例,即生成模板類。
    類模板與摸板類的區別是:類模板是模板的定義,不是一個實實在在的類,定義中用到通用類型參數;模板類是實實在在的類定義,是類模板的實例。


    1.函數模板
    經過前面知識的學習可知,在所定義的函數中,函數形參的類型是固定的,當調用函數時,實參的類型要與被調函數的形參類型保持一致,不然會出現類型不一致的錯誤。所以,對於功能相同而只是參數的類型不一樣的狀況,也必須定義不一樣的函數來分別完成相應的功能,這顯然是很不靈活的。
    C++語言中提供的函數模板功能就是爲解決以上問題而提出的。C++語言提供的函數模板能夠定義一個對任何類型變量均可進行操做的函數,從而大大加強了函數設計的通用性。由於普通函數只能傳遞變量參數,而函數模板卻提供了傳遞類型的機制。
    在C++語言中,使用函數模板的方法是先說明函數模板,而後實例化成相應的模板函數進行調用執行。
    函數模板的通常說明形式以下:


    template <類型形參表>
    返回值類型 函數名(形參表)
    {
    //函數定義體
    }
    在上面的定義形式中,<參數形參表>能夠有一到若干個形參,各形參前必須加上class關鍵字,表示傳遞類型,當有多個形參時,各形參間用逗號分隔。從中能夠看出,<類型形參表>中的每一個形參就表示了一種數據類型。「形參表」中至少有一個形參的類型必須用<類型形參表>中的形參來定義。
    函數模板只是說明,不能直接執行,須要實例化爲模板函數後才能執行。當編譯系統發現有一個函數調用:函數名(實參表);時,將根據「實參表」中的實參的類型和已定義的函數模板生成一個重載函數即模板函數。該模板函數的定義體與函數模板的定義體相同,而「形參表」中的類型則以「實參表」中的實際類型爲依據。


    2.類模板
    類模板實際上就是函數模板的推廣。
    說明類模板的通常格式爲:
    template <類型形參表>
    class 類模板名
    {
    private:
    私有成員定義
    protected:
    保護成員定義
    public:
    公有成員定義
    };


    (1)<類型形參表>中能夠包括一到若干個形參,這些形參既能夠是「類型形參」,也能夠是「表達式形參」。每一個類型形參前必須加class關鍵字,表示對類模板進行實例化時表明某種數據類型,也就是說,類型形參是在類模板實例化時傳遞數據類型用的;表達式形參的類型是某種具體的數據類型,當對類模板進行實例化時,給這些參數提供的是具體的數據,也就是說,表達式形參是用來傳遞具體數據的。當<類型形參表>中的參數有多個時,需用逗號隔開。如:
    template <class arg1,int arg2,class arg3>
    class myclass
    {
    //類的定義體
    };
    此處定義的類模板名是myclass,它有三個參數arg一、arg2和arg3,其中arg1和arg3是類型形參,在類模板實例化時用於傳遞數據類型,arg2是表達式形參,用於在類模板實例化時傳遞具體數據。


    (2)類模板中成員函數能夠放在類模板的定義體中(此時與類中的成員函數的定義方法一致)定義,也能夠放在類模板的外部來定義,此時成員函數的定義格式以下:
    template <類型形參表>
    函數值的返回來性 類模板名<類型名錶>::成員函數(形參)
    { 函數體   }
    其中:類模板名便是類模板中定義的名稱;
    類型名錶便是類模板定義中的<類型形參表>中的形參名。


    (3)類模板定義只是對類的描述,它自己還不是一個實實在在的類,是類模板。


    (4)類模板不能直接使用,必須先實例化爲相應的模板類,定義模板類的對象(即實例)後,纔可以使用。能夠用如下方式建立類模板的實例。
    類模板名<類型實參表> 對象名錶;
    此處的<類型實參表>要與該模板中的<類型形參表>匹配,也就是說,實例化中所用的實參必須和類模板中定義的形參具備一樣的順序和類型,不然會產生錯誤。

五,流和文件流:

  在程序設計中,數據輸入/輸出(I/O)操做是必不可少的,C++語言的數據輸入/輸出操做是經過I/O流庫來實現的。C++中把數據之間的傳輸操做稱爲流,流既能夠表示數據從內存傳送到某個載體或設備中,即輸出流,也能夠表示數據從某個載
 在程序設計中,數據輸入/輸出(I/O)操做是必不可少的,C++語言的數據輸入/輸出操做是經過I/O流庫來實現的。C++中把數據之間的傳輸操做稱爲流,流既能夠表示數據從內存傳送到某個載體或設備中,即輸出流,也能夠表示數據從某個載體或設備傳送到內存緩衝區變量中,即輸入流。在進行I/O操做時,首先是打開操做,使流和文件發生聯繫,創建聯繫後的文件才容許數據流入和流出,輸入或輸出結束後,使用關閉操做使文件與流斷開聯繫。
 標準輸入輸出流(cout、cin)在使用過程當中,只要在程序的開頭嵌入相應的頭文件「iostream.h」便可。
 文件的打開和關閉是經過使用fstream類的成員函數open和close來實現的,fstream類用來對文件流進行操做,fstream類的頭文件是fstream.h.
 1.數據的輸出cout
 cout是標準輸出設備即顯示器(默認設備)鏈接的預約義輸出流。C++語言的插入運算符「<<」向輸出流發送字符,cout是數據的目的地,插入運算符「<<」把數據傳送到cout.
   輸出流對象cout輸出數據的語句格式爲:
   cout<<數據1<<數據2<<……<<數據n;
   其中,「<<」是輸出操做符,用於向cout輸出流中插入數據。
   在cout中還可使用流控制符控制數據的輸出格式,但要注意使用這些流控制符時,要在程序的開始部分嵌入頭文件「iomanip.h」。
   經常使用的流控制符及其功能以下表所示:
   表1  I/O流的經常使用控制符



 2.數據的輸入cin
 cin是與標準輸入設備即鍵盤(默認設備)鏈接的預約義輸入流。它從輸入流中取出數據,數據從輸入提起運算符「>>」處流進程序。
 輸入流對象cin輸入數據的語句格式爲:
 cin>>變量名1>>變量名2>>……>>變量名n;
 其中,「>>」是輸入操做符,用於從cin輸入流中取得數據,並將取得的數據傳送給其後的變量,從而完成輸入數據的功能。
 注意:「>>」操做符後除了變量名外不得有其餘數字、字符串,不然系統會報錯。


  3.文件流及其有關的類
 文件是存儲在磁盤、磁帶等外部設備上的數據的集合,每個文件都必須有一個惟一的文件名稱。在使用文件前必須首先打開文件,使用完畢後必須關閉文件。對文件的操做是由文件流類完成的。文件流類在流與文件之間創建鏈接。下圖是與文件操做相關的類及其繼承關係。
 圖1 幾個與文件處理相關的類及其繼承關係結構圖


 下表是經常使用的I/O流類庫的說明,以及在編程中須要包含到程序中的頭文件。
 表2 經常使用I/O流類庫說明



 4.文件的打開與關閉
 對文件的操做是由文件流類完成的。文件流類在流與文件間創建鏈接。因爲文件流分爲三種:文件輸入流、文件輸出流、文件輸入/輸出流,因此相應的必須將文件流說明爲ifstream、ofstream和fstream類的對象,而後利用文件流的對象對文件進行操做。
 對文件的操做過程可按照一下四步進行:即定義文件流類的對象、打開文件、堆文件進行讀寫操做、關閉文件,下面分別進行介紹。
(1)定義文件流對象
 利用文件流類能夠定義文件流類對象,方法是:
 文件流類 對象名;
   如:
   ifstream ifile; //定義一個文件輸入流對象
   ofstream ofile; //定義一個文件輸出流對象
   fstream iofile; //定義一個文件輸出/輸入流對象

(2)打開文件
   定義了文件流對象後,就能夠利用其成員函數open()打開須要操做的文件,該成員函數的函數原型爲:
   void open(const unsigned char *filename,int mode,int access=filebuf:openprot);
   其中:filename是一個字符型指針,指定了要打開的文件名;mode指定了文件的打開方式,其值以下表所示;access指定了文件的系統屬性,其取值爲:
   0   通常文件
   1   只讀文件
   2   隱藏文件
   3   系統文件

   表3 在ios類中定義的文件打開方式


  說明:
   1)在實際使用過程當中,能夠根據須要將以上打開文件的方式用「|」組合起來。如:
   ios::in|ios::out                表示以讀/寫方式打開文件
   ios::in|ios:: binary             表示以二進制讀方式打開文件
在程序設計中,數據輸入/輸出(I/O)操做是必不可少的,C++語言的數據輸入/輸出操做是經過I/O流庫來實現的。C++中把數據之間的傳輸操做稱爲流,流既能夠表示數據從內存傳送到某個載體或設備中,即輸出流,也能夠表示數據從某個載
   ios::out|ios:: binary            表示以二進制寫方式打開文件
   ios::in|ios::out|ios::binary       表示以二進制讀/寫方式打開文件
   2)若是未指明以二進制方式打開文件,則默認是以文本方式打開文件。
   3)對於ifstream流,mode參數的默認值爲ios::in,對於ofstream流,mode的默認值爲ios::out.

(3)文件的關閉
   在文件操做結束(即讀、寫完畢)時應及時調用成員函數close()來關閉文件。該函數比較簡單,沒有參數和返回值。
   利用對象和相應的成員函數對文件進行讀寫操做,咱們將單獨介紹。

   5.文件的讀寫操做
   在打開文件後就能夠對文件進行讀寫操做了。從一個文件中讀出數據,可使用文件流類的get、getline、read成員函數以及運算符「>>」;而向一個文件寫入數據,可使用其put、write函數以及插入符「<<」,以下表所示:
   表4 文件流類的文件操做成員函數
  順序文件操做:這種操做方式只能從文件的開始處依次順序讀寫文件內容,而不能任意讀寫文件內容。   從一個文件中讀出數據,可使用get、getline、read成員函數以及運算符「>>」;而向一個文件寫入數據,可使用put、write成員函數以及插入符「<<」。
相關文章
相關標籤/搜索