C++中友元類和嵌套類使用詳解
前言
友元這個(gè)詞,在學(xué)習(xí)類的時(shí)候肯定接觸過,但是當(dāng)時(shí)我們只用了很多友元函數(shù)。
友元有三種:
- 友元函數(shù)
- 友元類
- 友元類方法
類并非只能擁有友元函數(shù),也可以將類作為友元。在這種情況下,友元類的所以方法都能訪問原始類的私有成員和保護(hù)成員。另外,也可以做更嚴(yán)格的限制,只將特定的成員函數(shù)指定為另一個(gè)類的友元。
1. 友元類
假如我們有兩個(gè)類:Tv
電視機(jī)類,Remote
遙控器類。那么這兩個(gè)類是什么關(guān)系呢?既不是has-a關(guān)系,也不是 is-a關(guān)系,但是我們知道遙控器可以控制電視機(jī),那么遙控器必須能夠訪問電視機(jī)的私有或保護(hù)數(shù)據(jù)。所以,遙控器就是電視機(jī)的友元。
類似于友元函數(shù)的聲明,友元類的聲明:
friend class Remote;
友元聲明可以位于公有、私有或保護(hù)部分,其所在的位置無關(guān)緊要。
下面是代碼實(shí)現(xiàn):
//友元類1.h #ifndef TV_H_ #define TV_H_ class Tv { private: int state;//On or Off int volume;//音量 int maxchannel;//頻道數(shù) int channel;//頻道 int mode;//有線還是天線,Antenna or Cable int input;//TV or DVD public: friend class Remote; enum{Off,On}; enum{MinVal,MaxVal=20}; enum{Antenna,Cable}; enum{TV,DVD}; Tv(int s=Off,int mc=125):state(s),volume(5),maxchannel(mc), channel(2),mode(Cable),input(TV){} void onoff(){state=(state==On)?Off:On;} bool ison() const{return state==On;} bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode(){mode=(mode==Antenna)?Cable:Antenna;} void set_input(){input=(input==TV)?DVD:TV;} void settings() const;//display all settings }; class Remote { private: int mode;//控制TV or DVD public: Remote(int m=Tv::TV):mode(m){}; bool volup(Tv & t){return t.volup();} bool voldown(Tv & t){return t.voldown();} void onoff(Tv &t){t.onoff();} void chanup(Tv &t){t.chanup();} void chandown(Tv &t){t.chandown();} void set_chan(Tv &t,int c){t.channel=c;} void set_mode(Tv &t){t.set_mode();} void set_input(Tv &t){t.set_input();} }; #endif
//友元類1.cpp #include"友元類1.h" #include<iostream> bool Tv::volup() { if(volume<MaxVal) { volume++; return true; } else return false; } bool Tv::voldown() { if(volume>MinVal) { volume--; return true; } else return false; } void Tv::chanup() { if(channel<maxchannel) channel++; else channel=1; } void Tv::chandown() { if(channel>1) channel--; else channel=maxchannel; } void Tv::settings() const { using std::cout; using std::endl; cout<<"Tv is "<<(state==Off? "Off":"On")<<endl; if(state==On) { cout<<"Volume setting = "<<volume<<endl; cout<<"Channel setting = "<<channel<<endl; cout<<"Mode = " <<(mode==Antenna?"antenna":"cable")<<endl; cout<<"Input = " <<(input==TV?"TV":"DVD")<<endl; } }
//友元類1main.cpp #include<iostream> #include"友元類1.h" int main() { using std::cout; Tv s42; cout<<"Initial setting for 42\" TV:\n"; s42.settings(); s42.onoff(); s42.chanup(); cout<<"\nAdjusted settings for 42\" TV:\n"; s42.settings(); Remote grey; grey.set_chan(s42,10); grey.volup(s42); grey.volup(s42); cout<<"\n42\" settings after using remote:\n"; s42.settings(); Tv s58(Tv::On); s58.set_mode(); grey.set_chan(s58,28); cout<<"\n58\" settings:\n"; s58.settings(); return 0; }
PS D:\study\c++\path_to_c++> g++ -I .\include\ -o 友元類1 .\友元類1.cpp .\友元類1main.cpp
PS D:\study\c++\path_to_c++> .\友元類1.exe
Initial setting for 42" TV:
Tv is OffAdjusted settings for 42" TV:
Tv is On
Volume setting = 5
Channel setting = 3
Mode = cable
Input = TV42" settings after using remote:
Tv is On
Volume setting = 7
Channel setting = 10
Mode = cable
Input = TV58" settings:
Tv is On
Volume setting = 5
Channel setting = 28
Mode = antenna
Input = TV
總之,友元類和友元函數(shù)很類似,不需要過多說明了。
2. 友元成員函數(shù)
在上面那個(gè)例子中,我們知道大部分Remote
方法都是用Tv
類的公有接口實(shí)現(xiàn)的。這意味著這些方法不是真正需要作為友元。事實(shí)上,只有一個(gè)直接訪問Tv
的私有數(shù)據(jù)的Remote
方法即Remote::chan()
,因此它才是唯一作為友元的方法。我們可以選擇僅讓特定的類成員成為另一個(gè)類的友元,而不必讓整個(gè)類成為友元,但這樣做會(huì)有一些麻煩。
讓Remote::chan()
成為Tv
類的友元的方法是,在Tv
類聲明中將其聲明為友元:
class Tv { friend void Remote::set_chan(Tv & t,int c); ... }
但是,編譯器能處理這條語句,它必須知道Remote
的定義。否則,它就不知道Remote::set_chan
是什么東西。所以我們必須把Remote
的聲明放到Tv
聲明的前面。但是Remote
聲明中同樣提到了TV
類,那么我們必須把TV
聲明放到Remote
聲明的前面。這就發(fā)生了循環(huán)依賴。我們得使用 前向聲明(forward declaration) 來解決這一問題。
class Tv; class Remote{...}; class Tv{...};
但是,還有一點(diǎn)麻煩需要解決:Remote
的類聲明中不能直接給出成員函數(shù)的定義了,因?yàn)檫@些函數(shù)會(huì)訪問Tv
類成員,而Tv
類的成員的聲明是Remote
類的后面的。那么我們必須在Remote
的類聲明外給出方法定義。
代碼實(shí)現(xiàn):
//友元成員函數(shù)1.h #ifndef TVFM_H_ #define TVFM_H_ class Tv; class Remote { public: enum{Off,On}; enum{MinVal,MaxVal=20}; enum{Antenna,Cable}; enum{TV,DVD}; private: int mode;//控制TV or DVD public: Remote(int m=TV):mode(m){}; bool volup(Tv & t); bool voldown(Tv & t); void onoff(Tv &t); void chanup(Tv &t); void chandown(Tv &t); void set_chan(Tv &t,int c); void set_mode(Tv &t); void set_input(Tv &t); }; class Tv { private: int state;//On or Off int volume;//音量 int maxchannel;//頻道數(shù) int channel;//頻道 int mode;//有線還是天線,Antenna or Cable int input;//TV or DVD public: friend void Remote::set_chan(Tv &t,int c); enum{Off,On}; enum{MinVal,MaxVal=20}; enum{Antenna,Cable}; enum{TV,DVD}; Tv(int s=Off,int mc=125):state(s),volume(5),maxchannel(mc), channel(2),mode(Cable),input(TV){} void onoff(){state=(state==On)?Off:On;} bool ison() const{return state==On;} bool volup(); bool voldown(); void chanup(); void chandown(); void set_mode(){mode=(mode==Antenna)?Cable:Antenna;} void set_input(){input=(input==TV)?DVD:TV;} void settings() const;//display all settings }; inline bool Remote::volup(Tv & t){return t.volup();} inline bool Remote::voldown(Tv & t){return t.voldown();} inline void Remote::onoff(Tv &t){t.onoff();} inline void Remote::chanup(Tv &t){t.chanup();} inline void Remote::chandown(Tv &t){t.chandown();} inline void Remote::set_chan(Tv &t,int c){t.channel=c;} inline void Remote::set_mode(Tv &t){t.set_mode();} inline void Remote::set_input(Tv &t){t.set_input();} #endif
之前我們說過,內(nèi)聯(lián)函數(shù)的鏈接性是內(nèi)部的,這就意味著函數(shù)定義必須在使用函數(shù)的文件中。在上面的代碼中,內(nèi)聯(lián)函數(shù)的定義位于頭文件中。當(dāng)然你也可以將定義放在實(shí)現(xiàn)文件中,但必須刪除關(guān)鍵字inline
,這樣函數(shù)的鏈接性將是外部的。
還有就是,我們直接讓整個(gè)Remote
類成為友元并不需要前向聲明,因?yàn)橛言Z句已經(jīng)指出Remote
是一個(gè)類:
friend class Remote;
。
總之,不推薦使用友元成員函數(shù),使用友元類完全可以達(dá)到相同的目的。
3. 其他友元關(guān)系
3.1 成為彼此的友元類
還是電視機(jī)和遙控器的例子,我們知道遙控器能控制電視機(jī),但是我告訴你,現(xiàn)代電視機(jī)也是可以控制遙控器的。例如,我們現(xiàn)在可以在電視上玩角色扮演游戲,當(dāng)你控制的角色從高處落入水中時(shí),遙控器(手柄)會(huì)發(fā)出振動(dòng)模擬落水感。那么,遙控器是電視機(jī)的友元,電視機(jī)也是遙控器的友元,那么它們互為友元。
class Tv { friend class Remote; public: void buzz(Remote & r); ... }; class Remote { friend class Tv; public: void bool volup(Tv &t){t.volup();} ... }; inline void Tv::buzz(Remote &r) { ... }
這里buzz
函數(shù)的定義必須放到Remote
類聲明的后面,因?yàn)?code>buzz的定義中會(huì)使用到Remote
的成員。
3.2 共同的友元
使用友元的另一種情況是,函數(shù)需要訪問兩個(gè)類的私有數(shù)據(jù),那么必須這樣做:函數(shù)既是一個(gè)類的友元也是另一個(gè)類的友元.
例如,有兩個(gè)類Analyzer
和Probe
,我們需要同步它們的時(shí)間成員:
class Analyzer; class Probe { friend void sync(Analyzer & a,const Probe &p); friend void sync(Probe &p,const Analyzer &a); }; class Probe { friend void sync(Analyzer & a,const Probe &p); friend void sync(Probe &p,const Analyzer &a); }; inline void sync(Analyzer & a,const Probe &p) { ... } inline void sync(Probe &p,const Analyzer &a) { ... }
4. 嵌套類
在C++中我們可以將類聲明放在另一個(gè)類中。在另一個(gè)類中聲明的類被稱為嵌套類。
實(shí)際上,嵌套類很簡(jiǎn)單,它的原理和類中聲明結(jié)構(gòu)體、常量、枚舉、typedef
、名稱空間是一樣的,這些技術(shù)我們一直都在使用。
對(duì)類進(jìn)行嵌套和包含是不一樣的。包含意味著將類對(duì)象作為另一個(gè)類的成員,而對(duì)類進(jìn)行嵌套不創(chuàng)建類成員,而是定義了一種類型,該類型僅在包含嵌套類的類中有效。
一般來說我們使用嵌套類是為了幫助實(shí)現(xiàn)另一個(gè)類,并避免名稱沖突
嵌套類的作用域和訪問控制
作用域
如果嵌套類是在另一個(gè)類的私有部分聲明的,那么只能在后者的類作用域中使用它,派生類以及外部世界無法使用它。
如果嵌套類是在另一個(gè)類的保護(hù)部分聲明的,那么只能在后者、后者的派生類的類作用域中使用該嵌套類,外部世界無法使用它。
如果嵌套類是在另一個(gè)類的公有部分聲明的,那么能在后者、后者的派生類和外部世界中使用它。
class Team { public: class Coach{...} ... };
上面的Coach
就是一個(gè)公有部分的嵌套類,那么我們可以這樣:
Team::Coach forhire;
總之,嵌套類的作用域和類中聲明結(jié)構(gòu)體、常量、枚舉、typedef
、名稱空間是一樣。但是對(duì)于枚舉量來說,我們一般把它放在類的公有部分,例如ios_base
類中的各種格式常量:ios_base::showpoint
等。
訪問控制
嵌套類的訪問控制和常規(guī)類是一模一樣的,嵌套類也有public
,private
,protected
,只有公有部分對(duì)外部世界開放。
例如:
class A { class B { private: int num; public void foo(); }; };
則在A的類作用域中,可以創(chuàng)建B對(duì)象,并使用B.foo()
方法。
看看一個(gè)類模板中使用嵌套類的例子:
#ifndef QUEUETP_H_ #define QUEUETP_H_ template<typename Item> class QueueTP { private: enum{Q_SIZE=10}; class Node { public: Item item; Node *next; Node(const Item & i):item(i),next(0){} }; Node *front; Node *rear; int items; const int qsize; QueueTP(const QueueTP &q):qsize(0){}//搶占定義,賦值構(gòu)造函數(shù) QueueTP & operator=(const QueueTP &q){return *this;}//搶占定義 public: QueueTP(int qs=Q_SIZE):qsize(qs) { front = rear =0; items=0; } ~QueueTP() { Node* temp; while (front !=0) { temp=front; front=front->next; delete temp; } } bool isempty() const { return items==0; } bool isfull() const { return items==qsize; } int queuecount() const { return items; } bool enqueue(const Item & item) { if(isfull()) return false; Node * add = new Node(item); items++; if(front==0) front=add; else rear->next=add; rear=add; return true; } bool dequeue(Item &item) { if(front==0) return 0; item=front->item; items--; Node * temp=front; front=front->next; delete temp; if(items==0) rear=0; return true; } }; #endif
到此這篇關(guān)于C++中友元類和嵌套類使用詳解的文章就介紹到這了,更多相關(guān)C++友元類和嵌套類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入解析設(shè)計(jì)模式中的適配器模式在C++中的運(yùn)用
這篇文章主要介紹了設(shè)計(jì)模式中的適配器模式在C++中的運(yùn)用,通常適配器模式可以細(xì)分為類適配器和對(duì)象適配器兩種情況,需要的朋友可以參考下2016-03-03如何用C寫一個(gè)web服務(wù)器之基礎(chǔ)功能
C語言是一門很基礎(chǔ)的語言,程序員們對(duì)它推崇備至,本文將帶著大家來看一下,如何用C寫一個(gè)web服務(wù)器。2021-05-05VS中scanf函數(shù)報(bào)錯(cuò)問題的幾種解決方法
本文主要介紹了VS中scanf函數(shù)報(bào)錯(cuò)問題的幾種解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07結(jié)合C++11新特性來學(xué)習(xí)C++中l(wèi)ambda表達(dá)式的用法
這篇文章主要介紹了C++中l(wèi)ambda表達(dá)式的用法,lambda表達(dá)式的引入可謂是C++11中的一大亮點(diǎn),同時(shí)文中也涉及到了C++14標(biāo)準(zhǔn)中關(guān)于lambda的一些內(nèi)容,需要的朋友可以參考下2016-01-01C++?string如何獲取文件路徑文件名、文件路徑、文件后綴(兩種方式)
這篇文章主要介紹了C++?string如何獲取文件路徑文件名、文件路徑、文件后綴(兩種方式),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2023-06-06