[轉]靜態成員數據和靜態成員函數
在沒有講述本章內容之前如果我們想要在一個范圍內共享某一個數據,那么我們會設立全局對象,但面向對象的程序是由對象構成的,我們如何才能在類范圍內共享數據呢?
這個問題便是本章的重點:
聲明為static的類成員或者成員函數便能在類的范圍內共同享,我們把這樣的成員稱做靜態成員和靜態成員函數。
下面我們用幾個實例來說明這個問題,類的成員需要保護,通常情況下為了不違背類的封裝特性,我們是把類成員設置為protected(保護狀態)的,但是我們為了簡化代碼,使要說明的問題更為直觀,更容易理解,我們在此處都設置為public。
以下程序我們來做一個模擬訪問的例子,在程序中,每建立一個對象我們設置的類靜態成員變自動加一,代碼如下:
#include <iostream>
using namespace std;
class Internet
{
public:
??? Internet(char *name,char *address)
??? {
??????? strcpy(Internet::name,name);
??????? strcpy(Internet::address,address);
??????? count++;
??? }
??? static void Internet::Sc()//靜態成員函數
??? {
??????? cout<<count<<endl;
??? }
??? Internet &Rq();
public:
??? char name[20];
??? char address[20];
??? static int count;//這里如果寫成static int count=0;就是錯誤的
};
Internet& Internet::Rq()//返回引用的成員函數
{
??? return *this;
}
int Internet::count = 0;//靜態成員的初始化
void vist()
{
??? Internet a1("中國軟件開發實驗室","
www.cndev-lab.com
");
??? Internet a2("中國軟件開發實驗室","
www.cndev-lab.com
");
}
void fn(Internet &s)
{
??? cout<<s.Rq().count;
}
void main()
{
??? cout<<Internet::count<<endl;//靜態成員值的輸出
??? vist();
??? Internet::Sc();//靜態成員函數的調用
??? Internet b("中國軟件","
www.cnsoft.com
");
??? Internet::Sc();
??? fn(b);
??? cin.get();
}
上面代碼我們用了幾種常用的方式建立對象,當建立新對象并調用其構造函數的時候,靜態成員cout便運行加1操作,靜態成員的初始化應該在主函數調用之前,并且不能在類的聲明中出現,通過運行過程的觀察我們發現,靜態成員count的狀態并不會隨著一個新的對象的新建而重新定義,盡而我們了解到類的靜態成員是屬于類的而不是屬于哪一個對象的,所以靜態成員的使用應該是類名稱加域區分符加成員名稱的,在上面的代碼中就是Internet::count,雖然我們仍然可以使用對象名加點操作符號加成員名稱的方式使用,但是不推薦的,靜態態類成員的特性就是屬于類而不專屬于某一個對象。
靜態成員函數的特性類似于靜態成員的使用,同樣與對象無關,調用方法為類名稱加域區分符加成員函數名稱,在上面的代碼中就是Internet::Sc();,靜態成員函數由于與對象無關系,所以在其中是不能對類的普通成員進行直接操作的。
如果上面的 static void Internet::Sc()修改成為:
static void Internet::Sc()//靜態成員函數
{
??? cout<<name<<endl;//錯誤
??? cout<<count<<endl;
}
靜態成員函數與普通成員函數的差別就在于缺少this指針,沒有這個this指針自然也就無從知道name是哪一個對象的成員了。
根據類靜態成員的特性我們可以簡單歸納出幾點,靜態成員的使用范圍:
1.用來保存對象的個數。
2.作為一個標記,標記一些動作是否發生,比如:文件的打開狀態,打印機的使用狀態,等等。
3.存儲鏈表的第一個或者最后一個成員的內存地址。
為了做一些必要的練習,深入的掌握靜態對象的存在的意義,我們以前面的結構體的教程為基礎,用類的方式描述一個線性鏈表,用于存儲若干學生的姓名,代碼如下:
#include <iostream>
using namespace std;
class Student
{
public:
??? Student (char *name);
??? ~Student();
public:
??? char name[30];
??? Student *next;
??? static Student *point;
};
Student::Student (char *name)
{
??? strcpy(Student::name,name);
??? this->next=point;
??? point=this;
}
Student::~Student ()//析構過程就是節點的脫離過程
{
??? cout<<"析構:"<<name<<endl;
??? if(point==this)
??? {
??????? point=this->next;
??????? cin.get();
??????? return;
??? }
??? for(Student *ps=point;ps;ps=ps->next)
??? {
??????? if(ps->next==this)
??????? {
??????? cout<<ps->next<<"|"<<this->next<<endl;
??????? ps->next=next;//=next也可以寫成this->next;
??????? cin.get();
??????? return;
??????? }
??? }
??? cin.get();
}
Student* Student::point=NULL;
void main()
{
??? Student *c = new Student("marry");
??? Student a("colin");
??? Student b("jamesji");
??? delete c;
??? Student *fp=Student::point;
??? while(fp!=NULL)
??? {
??????? cout<<fp->name<<endl;
??????? fp=fp->next;
??? }
??? cin.get();
}
從上面的代碼來看,原來單純結構化編程需要的一個鏈表進入全局指針在這里被類的靜態成員指針所替代(類的靜態成員完全可以替代全局變量),這個例子的理解重點主要是要注意觀察類成員的析構順序,通過對析構順序的理解,使用析構函數來進行節點的脫鏈操作。
靜態成員的提出是為了解決數據共享的問題。實現共享有許多方法,如:設置全局性的變量或對象是一種方法。但是,全局變量或對象是有局限性的。這一章里,我們主要講述類的靜態成員來實現數據的共享。
靜態數據成員
在類中,靜態成員可以實現多個對象之間的數據共享,并且使用靜態數據成員還不會破壞隱藏的原則,即保證了安全性。因此,靜態成員是類的所有對象中共享的成員,而不是某個對象的成員。
使用靜態數據成員可以節省內存,因為它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新后的相同的值,這樣可以提高時間效率。
靜態數據成員的使用方法和注意事項如下:
1、靜態數據成員在定義或說明時前面加關鍵字static。
2、靜態成員初始化與一般數據成員初始化不同。靜態數據成員初始化的格式如下:
<數據類型><類名>::<靜態數據成員名>=<值>
這表明:
????? (1) 初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆。
(2) 初始化時不加該成員的訪問權限控制符private,public等。
(3) 初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。
3、靜態數據成員是靜態存儲的,它是靜態生存期,必須對它進行初始化。
4、引用靜態數據成員時,采用如下格式:
<類名>::<靜態成員名>
如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式來引用靜態數據成員。
下面舉一例子,說明靜態數據成員的應用:
#include
class Myclass
{
public:
Myclass(int a, int b, int c);
void GetNumber();
void GetSum();
private:
int A, B, C;
static int Sum;
};
int Myclass::Sum = 0;
Myclass::Myclass(int a, int b, int c)
{
A = a;
B = b;
C = c;
Sum += A+B+C;
}
void Myclass::GetNumber()
{
cout<<"Number="< }
void Myclass::GetSum()
{
cout<<"Sum="< }
void main()
{
Myclass M(3, 7, 10),N(14, 9, 11);
M.GetNumber();
N.GetNumber();
M.GetSum();
N.GetSum();
}
從輸出結果可以看到Sum的值對M對象和對N對象都是相等的。這是因為在初始化M對象時,將M對象的三個int型數據成員的值求和后賦給了Sum,于是Sum保存了該值。在初始化N對象時,對將N對象的三個int型數據成員的值求和后又加到Sum已有的值上,于是Sum將保存另后的值。所以,不論是通過對象M還是通過對象N來引用的值都是一樣的,即為54。
靜態成員函數
靜態成員函數和靜態數據成員一樣,它們都屬于類的靜態成員,它們都不是對象成員。因此,對靜態成員的引用不需要用對象名。
在靜態成員函數的實現中不能直接引用類中說明的非靜態成員,可以引用類中說明的靜態成員。如果靜態成員函數中要引用非靜態成員時,可通過對象來引用。下面通過例子來說明這一點。
#include
class M
{
public:
M(int a) { A=a; B+=a;}
static void f1(M m);
private:
int A;
static int B;
};
void M::f1(M m)
{
cout<<"A="< cout<<"B="< }
int M::B=0;
void main()
{
M P(5),Q(10);
M::f1(P); file://調用時不用對象名
M::f1(Q);
}
讀者可以自行分析其結果。從中可看出,調用靜態成員函數使用如下格式:
<類名>::<靜態成員函數名>(<參數表>);
posted @
2006-07-31 02:54 Jerry Cat 閱讀(289) |
評論 (0) |
編輯 收藏
為MFC應用程序添全屏幕顯示功能
在CMainFrame類中添加下列成員變量和成員函數(使用ClassWizard),下面是這些變量和函數的功能
說明:
成員變量:
BOOL m_bFullScreen; //全屏幕顯示標志
CRect m_FullScreenWindowRect; //全屏幕顯示窗口Rect
WINDOWPLACEMENT m_wpPrev; //用于保存正常視圖時的窗口位置信息
CToolBar * m_wndFullScreenBar; //全屏幕顯示時的浮動工具條
成員函數:
void OnMenuFullscreen(); //全屏幕顯示的處理函數
void OnGetMinMaxInfo(); //捕獲WM_GETMINMAXINFO消息以便允許你增加窗口大小
void OnUpdateViewFullScreen(); //更新“全屏幕顯示”菜單的狀態
源碼
? void CMainFrame::OnMenuFullscreen()
{//全屏幕顯示的處理函數
RECT rectDesktop;
WINDOWPLACEMENT wpNew;
if (m_bFullScreen)
{//全屏幕顯示模式
//隱藏工具條和狀態條
m_wndStatusBar.ShowWindow(SW_HIDE);
m_wndToolBar.ShowWindow(SW_HIDE);
//保存正常視圖時的窗口位置信息以便恢復原來狀態
GetWindowPlacement (&m_wpPrev);
m_wpPrev.length = sizeof m_wpPrev;
//調整RECT為新的窗口尺寸
::GetWindowRect ( ::GetDesktopWindow(), &rectDesktop );
::AdjustWindowRectEx(&rectDesktop, GetStyle(), TRUE, GetExStyle());
//保存RECT以便OnGetMinMaxInfo()使用
m_FullScreenWindowRect = rectDesktop;
wpNew = m_wpPrev;
wpNew.showCmd = SW_SHOWNORMAL;
wpNew.rcNormalPosition = rectDesktop;
//生成新的工具條
m_wndFullScreenBar=new CToolBar;
if(!m_wndFullScreenBar->Create(this, CBRS_SIZE_DYNAMIC|CBRS_FLOATING)
|| !m_wndFullScreenBar->LoadToolBar(IDR_FULLSCREEN))
{
TRACE0("Failed to create toolbar\n");
return; // fail to create
}
}
posted @
2006-07-31 02:51 Jerry Cat 閱讀(356) |
評論 (0) |
編輯 收藏
MFC socket程序開發
Socket編程在大多數的編程語言中都是一件比較有趣的事情。它是比較常用的編寫通過網絡通信的服務器和客戶端方法。在windows平臺Socket通信大多是基于MS Winsock設計的。Windows支持基于TCP和UDP的socket通信。Windows APIs在socket編程中是非常有用的,但是有些人發現在用它們工作的時候有困難。
所以在這里我介紹一種最簡單用MFC socket類進行socket編程的方法。這不僅可以使你的工作變得簡單而且能減少你在網絡程序上的開發時間。你可以定制一個socket類,然后你可以在你的其他的網絡應用程序中重用。
在socket編程中,MFC提供了兩個基本的類,分別是CAsyncSocket和Csocket。Csocket是從CAsyncSocket繼承來的。我們可以建立定制的socket類,也是從CasyncSocket繼承而來的,當然也是為了我們程序特殊的需要。
初始化socket
首先需要調用AfxSocketInit()函數來初始化我們的socket環境。
為了初始化sockets,我們需要調用AfxSocketInit()函數。它通常是在MFC中的InitInstance()函數中被調用的。如果我們用程序向導來創建socket程序的話,查看“use Windows Sockets”這個選項,然后選中它。它將會自動的為我們創建這個步驟了。(如果我們沒有選中這個選項的話,我們也可以手動添加這些代碼的。)這個函數的返回值顯示這個函數的調用成功或失敗。
BOOL CServerApp::InitInstance()
{....
if( AfxSocketInit() == FALSE)
? {
? AfxMessageBox("Sockets Could Not Be Initialized");
? return FALSE;
? }
? ...
}
創建Server Sockets
為了創建一個Server Socket,我們需要聲明一個CAyncSocket的變量或者我們自己定制的一個從AyncSocket或是Cscket繼承來的類的類型的變量。然后調用Create()函數,同時指定監聽的端口。這個函數的返回值顯示這個函數的調用成功或失敗。
UpdateData(TRUE);
m_sListener.Create(m_port);
if(m_sListener.Listen()==FALSE)
{
AfxMessageBox("Unable to Listen on that port,please try another port");
m_sListener.Close();
return;
}
創建Client Sockets
為了創建Client socket類,我們需要聲明一個CAyncSocket的變量或者我們自己定制的一個從AyncSocket或是Cscket繼承來的類的類型的變量。然后調用Create()函數,同時指定監聽的端口。這個函數的返回值顯示這個函數的調用成功或失敗。
m_sConnected.Create();
m_sConnected.Connect("server ip",port);
監聽客戶端的連接
創建了server socket以后,我們要進行監聽。調用Listen()函數。這個函數的返回值顯示這個函數的調用成功或失敗。
if( m_sListener.Listen()== FALSE)
{
AfxMessageBox("Unable to Listen on that port,please try another port");
m_sListener.Close();
return;
}
接受連接
連接請求要被接受accept,是用另外的socket,不是正在監聽的socket。請參看代碼。
void CXXXDlg::OnAccept()
{
?CString strIP;
?UINT port;
?if(m_sListener.Accept(m_sConnected))
?{
? m_sConnected.GetSockName(strIP,port); //應該是GetPeerName,獲取對方的IP和port
? m_status="Client Connected,IP :"+ strIP;
? m_sConnected.Send("Connected To Server",strlen("Connected To? Server"));?????
UpdateData(FALSE);
?}
?else
?{
?AfxMessageBox("Cannoot Accept Connection");
?}
}
發送數據
數據放在一個buffer中或是結構體中,調用send()函數發送。
m_sConnected.Send(pBuf,iLen);
接受數據
調用receive()接受數據。
void CXXXrDlg::OnReceive()
{
char *pBuf =new char [1025];
CString strData;
int iLen;
iLen=m_sConnected.Receive(pBuf,1024);???
if(iLen == SOCKET_ERROR)?????
? {
? AfxMessageBox("Could not Recieve");?????
? }?????
else????
? {
? pBuf[iLen]=NULL;
? strData=pBuf;
? m_recieveddata.Insert(m_recieveddata.GetLength(),strData);
?//display in server?????????????
?UpdateData(FALSE);
?m_sConnected.Send(pBuf,iLen);? //send the data back to the Client????
?delete pBuf;?????????
? }
}
關閉連接
m_sConnected.ShutDown(0);??? 停止發送數據
m_sConnected.ShutDown(1);??? 停止接受數據
m_sConnected.ShutDown(2);??? 停止發送接受數據
m_sConnected.Close();
編寫自己的socket類
在class view中選擇添加一個新類,設置它的基類為CAsyncSocket,在類向導的幫助下添加如下的一些函數。
class MySocket : public CAsyncSocket
{ // Attributes
public:
?// Operations
public:
MySocket();
virtual ~MySocket();
// Overrides
public:
void SetParentDlg(CDialog *pDlg);// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(MySocket)
public:
virtual void OnAccept(int nErrorCode);
virtual void OnClose(int nErrorCode);
virtual void OnConnect(int nErrorCode);
virtual void OnOutOfBandData(int nErrorCode);
virtual void OnReceive(int nErrorCode);
virtual void OnSend(int nErrorCode);
//}}AFX_VIRTUAL // Generated message map functions
//{{AFX_MSG(MySocket)
// NOTE - the ClassWizard will add and remove member functions here. //}}AFX_MSG
protected:
private:
CDialog * m_pDlg;
};
設置“Parent Dialog”
調用這個socket類的SetParentDlg函數,保證當socket事件發生的時候這個窗體能接收到。
m_sListener.SetParentDlg(this);
m_sConnected.SetParentDlg(this);
建立Socket 事件和窗體成員函數之間的聯系
在這個窗體類中添加一些函數,比如void OnReceive(); void OnClose(); void OnAccept(); void OnConnect()等,它們會在我們編寫的的socket類中調用到。
void MySocket::OnAccept(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
if(nErrorCode==0)
{
((CServerDlg*)m_pDlg)->OnAccept();
}
CAsyncSocket::OnAccept(nErrorCode);
}
這里只寫了一個OnAccept()函數,其他的幾個中也有類似的調用。詳細的請參考代碼。
posted @
2006-07-28 01:07 Jerry Cat 閱讀(767) |
評論 (0) |
編輯 收藏
構造函數、析構函數與賦值函數是每個類最基本的函數。每個類只有一個析構函數,但可以有多個構造函數(包含一個拷貝構造函數,其它的稱為普通構造函數)和多個賦值函數(除了同類的賦值以外,還有其他的賦值方法)。對于任意一個類A,如果不想編寫上述函數,C++編譯器將自動為A產生四個缺省的函數,如
A(void);??????????????????? // 缺省的無參數構造函數
A(const A &a);????????????? // 缺省的拷貝構造函數
~A(void);?????????????????? // 缺省的析構函數
A & operate =(const A &a);? // 缺省的賦值函數
有幾個需要注意的內容:
@ 構造函數與析構函數的另一個特別之處是沒有返回值類型
@ 構造從類層次的最頂層的基類開始,在每一層中,首先調用基類的構造函數,然后調用成員對象的構造函數。析構則嚴格按照與構造相反的次序執行,在析構的時候,最低層的派生類的析構函數最開始被調用,然后調用每個基類的析構函數。
@ “缺省的拷貝構造函數”和“缺省的賦值函數”均采用“位拷貝”而非“值拷貝”的方式來實現,倘若類中含有指針變量,這兩個函數注定將出錯
下面通過例子進一步說明,
1.構造函數的初始化表
設存在兩個類:
class
?A
{
????…
????A(
void
);????????????????
//
?無參數構造函數
????A(
const
?A?
&
other);??????
//
?拷貝構造函數
????A?
&
?operate?
=
(?
const
?A?
&
other);??
//
?賦值函數
????
virtual
?
~
A(
void
);????????
//
析構函數
};
class
?B
{
public
:
????B(
const
?A?
&
a);????
//
?B的構造函數
private
:???
????A??m_a;????????????
//
?成員對象
};
下面面是B的構造函數的2個實現,其中第一個的類B的構造函數在其初始化表里調用了類A的拷貝構造函數,從而將成員對象m_a初始化;而第二個的B的構造函數在函數體內用賦值的方式將成員對象m_a初始化。我們看到的只是一條賦值語句,但實際上B的構造函數干了兩件事:先暗地里創建m_a對象(調用了A的無參數構造函數),再調用類A的賦值函數,將參數a賦給m_a。
B::B(
const
?A?
&
a)
?:?m_a(a)
{
???…
}
B::B(
const
?A?
&
a)
{
????m_a?
=
?a;
????…
}
2.拷貝函數和構造函數的區別
拷貝構造函數是在對象被創建時調用的,而賦值函數只能被已經存在了的對象調用。
String? a(“hello”);
String? b(“world”);
String? c = a;? // 調用了拷貝構造函數,最好寫成 c(a);
c = b; ?? ??? ??? ?// 調用了賦值函數
本例中第三個語句的風格較差,宜改寫成String c(a) 以區別于第四個語句。
如果我們實在不想編寫拷貝構造函數和賦值函數,又不允許別人使用編譯器生成的缺省函數,可以將拷貝構造函數和賦值函數聲明為私有函數,不用編寫代碼。
3.析構函數與虛析構函數
基類的構造函數、析構函數、賦值函數都不能被派生類繼承。如果類之間存在繼承關系,在編寫上述基本函數時應注意以下事項:
@ 派生類的構造函數應在其初始化表里調用基類的構造函數
@ 基類與派生類的析構函數應該為虛(即加virtual關鍵字)
#include?
<
iostream
>
class
?Base
{
public
:
????
virtual
?
~
Base()?{?cout
<<
?
"
~Base
"
?
<<
?endl?;?}
};
class
?Derived?:?
public
?Base
{
public
:
????
virtual
?
~
Derived()?{?cout
<<
?
"
~Derived
"
?
<<
?endl?;?}
};
void
?main(
void
)
{
????Base?
*
?pB?
=
?
new
?Derived;??
//
?upcast
???delete?pB;
}
輸出結果為:
?????? ~Derived
?????? ~Base
如果析構函數不為虛,那么輸出結果為
?????? ~Base
posted @
2006-07-28 00:50 Jerry Cat 閱讀(211) |
評論 (0) |
編輯 收藏
什么是對象的句柄?它是指針嗎?它是引用嗎?它是指向指針的指針?它是什么?
句柄術語一般用來指獲取另一個對象的方法——一個廣義的假指針。這個術語是(故意的)含糊不清的。
含糊不清在實際中的某些情況下是有用的。例如,在早期設計時,你可能不準備用句柄來表示。你可能不確定是否將一個簡單的指針或者引用或者指向指針的指針或者指向引用的指針或者整型標識符放在一個數組或者字符串(或其它鍵)以便能夠以哈希表(hash-table)(或其他數據結構)或數據庫鍵或者一些其它的技巧來查詢。如果你只知道你會需要一些唯一標識的東西來獲取對象,那么這些東西就被稱為句柄。
因此,如果你的最終目標是要讓代碼唯一的標識/查詢一個Fred類的指定的對象的話,你需要傳遞一個Fred句柄這些代碼。句柄可以是一個能被作為眾所周知的查詢表中的鍵(key)來使用的字符串(比如,在std::map<std::string,Fred> 或 std::map<std::string,Fred*>中的鍵),或者它可以是一個作為數組中的索引的整數(比如,Fred* array = new Fred[maxNumFreds]),或者它可以是一個簡單的 Fred*,或者它可以是其它的一些東西。
初學者常??紤]指針,但實際上使用未初始化的指針有底層的風險。例如,如果Fred對象需要移動怎么辦?當Fred對象可以被安全刪除時我們如何獲知?如果Fred對象需要(臨時的)連續的從磁盤獲得怎么辦?等等。這些時候的大多數,我們增加一個間接層來管理位置。例如,句柄可以是Fred**,指向Fred*的指針可以保證不會被移動。當Fred對象需要移動時,你只要更新指向Fred*的指針就可以了?;蛘咦層靡粋€整數作為句柄,然后在表或數組或其他地方查詢Fred的對象(或者指向Fred對象的指針)。
重點是當我們不知道要做的事情的細節時,使用句柄。
使用句柄的另一個時機是想要將已經完成的東西含糊化的時候(有時用術語magic cookie也一樣,就像這樣,“軟件傳遞一個magic cookie來唯一標識并定位適當的Fred對象”)。將已經完成的東西含糊化的原因是使得句柄的特殊細節或表示物改變時所產生的連鎖反應最小化。舉例來說,當將一個句柄從用來在表中查詢的字符串變為在數組中查詢的整數時,我們可不想更新大量的代碼。
當句柄的細節或表示物改變時,維護工作更為簡單(或者說閱讀和書寫代碼更容易),因此常常將句柄封裝到類中。這樣的類常重載operator-> 和 operator*算符(既然句柄的效果象指針,那么它可能看起來也象指針)。
posted @
2006-07-28 00:48 Jerry Cat 閱讀(506) |
評論 (0) |
編輯 收藏
[轉]重載函數和重載運算符
重載函數有如下約束
@ 該組重載函數中任何兩個都必須有不同的參量表。
@ 具有相同類型參量表、僅在返回值類型上不同的重載函數會引起錯誤。
@ 成員函數的重載不能僅基于一個說明為靜態的,另一個說明為非靜態的。
@ typedef說明并未定義新的類型,它們僅為已存在的類型引入了一個同義詞。它們不能影響重載機制。
@ 枚舉類型是一些可區分的類型,故可以區分重載函數。
@ 從區分重載函數的意義上說,類型“數組”和“指針”是相同的。對于一維數組來說是正確的。
運算符重載有如下的約束
@ 運算符要遵守它們同內部類型一起使用所指定的優先原則、分組及操作數的個數。
@ 單目運算符說明為成員函數不帶參量;如果說明為全局函數,要帶一個參量。雙目運算符說明為成員函數只帶一個參量;如果說明為全局函數,要帶兩個參量。
@ 所有的重載運算符除了賦值(operator=)外均可被派生類繼承。
@ 重載運算符的成員函數的第一個參量總是激活該運算符的對象的類類型參量(運算符被定義的類,或者定義了運算符的類的派生類)。對于第一個參量也不支持轉換。
具體內容:
單目運算符函數
ret-type operator op()?? ??? ??? ?//成員,使用類型的內部成員
ret-type operator op(arg)?? ??? ?//全局,參數為對其操作的類型的變量
雙目運算符函數
ret-type oprator op(arg)?? ??? ?//arg可以為任意類型的變量
ret-type operator op(arg1, arg2)
?? ??? ?//全局,arg1和arg2是參量。至少其中之一必須是操作類類型。
注意:對于雙目運算符的返回類型沒有限制;然而大多數用戶自定義型雙目運算符返回類類型或類類型的引用。
參考:
C++運算符重載轉換運算符
C++運算符重載賦值運算符
posted @
2006-07-28 00:43 Jerry Cat 閱讀(408) |
評論 (0) |
編輯 收藏
[轉]智能指針與微妙的隱式轉換
??? C++
雖然是強類型語言,但是卻還不如
Java
、
C#
那么足夠的強類型,原因是允許的隱式轉換太多
-
從
C
語言繼承下來的基本類型之間的隱式轉換
-
T*
指針到
void*
的隱式轉換
-
non-explicit constructor
接受一個參數的隱式轉換
-
從子類到基類的隱式轉換
(
安全)
-
從
const
到
non-const
的同類型的隱式轉換
(
安全
)
除開上面的五種隱式轉換外,
C++
的編譯器還非常聰明,當沒法直接隱式轉換的時候,它會嘗試間接的方式隱式轉換,這使得有時候的隱式轉換非常的微妙,一個誤用會被編譯器接受而會出現意想不到的結果。例如假設類
A
有一個
non-explicit constructor
,唯一的參數是類
B
,而類
B
也有一個
non-explicit constructor
接受類型
C
,那么當試圖用類型
C
的實例初始化類
A
的時候,編譯器發現沒有直接從類型
C
構造的過程,但是呢,由于類
B
可以被接受,而類型
C
又可以向類型
B
隱式轉換,因此從
C->B->A
的路就通暢了。這樣的隱式轉換多數時候沒什么大礙,但是不是我們想要的,因為它可能造成一些微妙的
bug
而難以捕捉。
?
為了在培訓的時候展示棧上析構函數的特點和自動資源管理,準備下面的一個例子,結果測試的時候由于誤用而發現一些問題。
(
測試的
IDE
是
Visual Studio 2005)
class A
{
public:
A(){ a = 100; }
int a;
void f();
};
?
A * pa = new A();
std::auto_ptr<A>? p = pa;? //
無意這樣使用的,本意是
std::auto_ptr<A> p(pa)
p->f();
?
這個寫法是拷貝構造函數的形式,顯然從
T*
是不能直接拷貝構造的
auto_ptr
的,但是編譯器會嘗試其他的路徑來轉換成
auto_ptr
來拷貝構造,因此如果存在一個中間的
,這個類能接受從
T*
的構造,而
同時auto_ptr也能接受從類X
的構造,那編譯器就會很高興的生成這樣的代碼。
這段代碼在
VC6
上是通不過的,因為
VC6
的
auto_ptr
實現就只有一個接受
T*
指針的
explicit constructor
.
但是
C++ Standard
的修正規范中,要求
auto_ptr
還應該有個接受
auto_ptr_ref
的
constructor
。那么這個
auto_ptr_ref
是什么呢?按照
C++ Standard
的解釋
:
Template auto_ptr_ref holds a reference to an auto_ptr. It is used by the auto_ptr
conversions to allow auto_ptr
objects to be passed to and returned from functions.
有興趣可以參考
Scott Meyers
的
" auto_ptr update page "? (
http://www.awprofessional.com/content/images/020163371X/autoptrupdate%5Cauto_ptr_update.html
?)講訴auto_ptr的歷史.
?
再回到前面的代碼,本來應該是通不過的編譯,但是
VC2005
的編譯器卻沒有任何怨言的通過
(
即使把警告等級設置到
4)
。結果運行的時候卻崩潰了,出錯在
auto_ptr
的析構函數
,delete
的指針所指向地址是
100
,而如果在
p->f()
后面加上一句
cout << pa->a << endl;
發現輸出結果為
0
。
為什么會這樣,原因就是前面所訴的間接的隱式轉換,這與
VC 2006
的
auto_ptr
和
auto_ptr_ref
實現有關,看看
P.J.Plauger
是怎么實現的
:
// auto_ptr_ref
template<class _Ty>
struct auto_ptr_ref
{
// proxy reference for auto_ptr copying
auto_ptr_ref(void *_Right)
: _Ref(_Right)
{?? // construct from generic pointer to auto_ptr ptr
}
void *_Ref;// generic pointer to auto_ptr ptr
};
?
// construct auto_ptr from an auto_ptr_ref object
auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
{
// construct by assuming pointer from _Right auto_ptr_ref
_Ty **_Pptr = (_Ty **)_Right._Ref;
_Ty *_Ptr = *_Pptr;
*_Pptr = 0;
// release old
_Myptr = _Ptr;
// reset this
}
?
這樣代碼通過編譯的原因也就清楚了,
A* -> void * -> auto_ptr_ref -> auto_ptr -> copy constructor -> accept.
好長的隱式轉換鏈
, -_-, C++
編譯器太聰明了。
那么為什么最后會出現指針被破壞的結果呢,原因在
auto_ptr
的實現,因為按照
C++ Standard
要求,
auto_ptr_ref
應該是包含一個
auto_ptr
的引用,因此
auto_ptr
的構造函數也就假設了
auto_ptr_ref
的成員
_Ref
是一個指向
auto_ptr
的指針。
而
auto_ptr
中只有一個成員就是
A*
的指針,因此指向
auto_ptr
對象的指針相當于就是個
A**
指針,因此上面
auto_ptr
從
auto_ptr_ref
構造的代碼是合理的。
但是由于罪惡的
void*
造成了一條非常寬敞的隱式轉換的道路,
A*
指針也能夠被接受,因此把
A*
當作
A**
來使用,結果可想而知,
A*
指向地址的前
4
個字節
(
因為
32
位
OS)
被拷貝出來,而這四個字節被賦值為
0( *_Pptr=0 )
。
所以出現了最后的結果是
_Myptr
值為
100
,而
pa->a
為
0
。
如果要正確執行結果,只要保證是個
A**
指針就行了,有兩個方法
第一,
auto_ptr_ref
所包含的引用是指向的
auto_ptr
對象
A * p = new A();
std::auto_ptr<A> pt( new A() );
std::auto_ptr_ref<A> ra( pt );
std::auto_ptr<A> pb
=
ra
;
pb->f();
?
第二,直接用二級指針
A * p = new A();
std::auto_ptr<A> pb = &p;? //
這句話后
, p
將等于
0
pb->f();
?
當然第二種是利用了
VC2005
的實現而造出來的,看著很別扭
,:)
。
我不明白
P.J.Plauger
為什么用
void *
,而不是用
auto_ptr<T>&
,因為任何指針都能隱式轉換為
void *
,這樣的危險性大多了。并且如果用了
auto_ptr<T>&
,從
auto_ptr_ref
構造也容易寫得更簡單清楚,看看以前的實現方式吧,仍然是
P.J.Plauger
的,但是版本低了點:
template<class _Ty>
struct auto_ptr_ref
{
// proxy reference for auto_ptr copying
?
auto_ptr_ref(auto_ptr<_Ty>& _Right)
: _Ref(_Right)
{
// construct from compatible auto_ptr
}
auto_ptr<_Ty>& _Ref;
// reference to constructor argument
};
auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
: _Myptr(_Right._Ref.release())
{
// construct by assuming pointer from _Right auto_ptr_ref
}
?
這樣的實現方法,顯然不能接受任何指針的隱式轉換,也就防止一開始的那種錯誤寫法,并且也是符合
C++ Standard
的要求的。
而
SGI STL
的
auto_ptr_ref
的實現則是包含了一個
T*
的指針,構造
auto_ptr
時候直接從
auto_ptr_ref
中拷貝這個指針,因此這樣的實現可以上代碼編譯通過,運行也正確,不過不符合
C++ Standard
。
?
總結一下,危險的潛伏bug的隱式轉換應該被杜絕的,特別是
void *
的隱式轉換和構造函數的隱式轉換,因此建議是
:
-
慎用
void *
,因為
void *
必須要求你知道轉換前的實現,因此更適合用在底層的、性能相關的內部實現。
-
單一參數的構造函數應該注意是否允許隱式轉換,如果不需要,加上
explicit
。例如
STL
容器中
vector
接受從
int
的構造函數,用于預先申請空間,這樣的構造函數顯然不需要隱式轉換,因此加上了
explicit
。
-
重載函數中,如果可能,就用更有明確意義的名字替代重載,因為隱式轉換也許會帶來一些意想不到的麻煩。
-
避免隱式轉換不等于是多用顯示轉換。
Meyers
在
Effective C++
中提到,即使
C++
風格的顯示轉換也應該盡量少用,最好是改進設計。
posted @
2006-07-25 00:37 Jerry Cat 閱讀(769) |
評論 (0) |
編輯 收藏
[轉]Linux下使用和生成庫
基本概念
庫有動態與靜態兩種,動態通常用.so為后綴,靜態用.a為后綴。例如:libhello.so libhello.a
為了在同一系統中使用不同版本的庫,可以在庫文件名后加上版本號為后綴,例如: libhello.so.1.0,由于程序連接默認以.so為文件后綴名。所以為了使用這些庫,通常使用建立符號連接的方式。
ln -s libhello.so.1.0 libhello.so.1
ln -s libhello.so.1 libhello.so
使用庫
當要使用靜態的程序庫時,連接器會找出程序所需的函數,然后將它們拷貝到執行文件,由于這種拷貝是完整的,所以一旦連接成功,靜態程序庫也就不再需要了。然而,對動態庫而言,就不是這樣。動態庫會在執行程序內留下一個標記‘指明當程序執行時,首先必須載入這個庫。由于動態庫節省空間,linux下進行連接的缺省操作是首先連接動態庫,也就是說,如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相連接。
現在假設有一個叫hello的程序開發包,它提供一個靜態庫libhello.a 一個動態庫libhello.so,一個頭文件hello.h,頭文件中提供sayhello()這個函數
/* hello.h */
void sayhello();
另外還有一些說明文檔。這一個典型的程序開發包結構
1.與動態庫連接
linux默認的就是與動態庫連接,下面這段程序testlib.c使用hello庫中的sayhello()函數
/*testlib.c*/
#include <hello.h>
#include <stdio.h>
int main()
{
sayhello();
return 0;
}
使用如下命令進行編譯
$gcc -c testlib.c -o testlib.o
用如下命令連接:
$gcc testlib.o -lhello -o testlib
在連接時要注意,假設libhello.o 和libhello.a都在缺省的庫搜索路徑下/usr/lib下,如果在其它位置要加上-L參數
與與靜態庫連接麻煩一些,主要是參數問題。還是上面的例子:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello
注:這個特別的"-WI,-Bstatic"參數,實際上是傳給了連接器ld.
指示它與靜態庫連接,如果系統中只有靜態庫當然就不需要這個參數了。
如果要和多個庫相連接,而每個庫的連接方式不一樣,比如上面的程序既要和libhello進行靜態連接,又要和libbye進行動態連接,其命令應為:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye
3.動態庫的路徑問題
為了讓執行程序順利找到動態庫,有三種方法:
(1)把庫拷貝到/usr/lib和/lib目錄下。
(2)在LD_LIBRARY_PATH環境變量中加上庫所在路徑。例如動態庫libhello.so在/home/ting/lib目錄下,以bash為例,使用命令:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
(3) 修改/etc/ld.so.conf文件,把庫所在的路徑加到文件末尾,并執行ldconfig刷新。這樣,加入的目錄下的所有庫文件都可見、
4.查看庫中的符號
有時候可能需要查看一個庫中到底有哪些函數,nm命令可以打印出庫中的涉及到的所有符號。庫既可以是靜態的也可以是動態的。nm列出的符號有很多,常見的有三種,一種是在庫中被調用,但并沒有在庫中定義(表明需要其他庫支持),用U表示;一種是庫中定義的函數,用T表示,這是最常見的;另外一種是所謂的“弱態”符號,它們雖然在庫中被定義,但是可能被其他庫中的同名符號覆蓋,用W表示。例如,假設開發者希望知道上央提到的hello庫中是否定義了printf():
$nm libhello.so |grep printf
U printf
U表示符號printf被引用,但是并沒有在函數內定義,由此可以推斷,要正常使用hello庫,必須有其它庫支持,再使用ldd命令查看hello依賴于哪些庫:
$ldd hello
libc.so.6=>/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
從上面的結果可以繼續查看printf最終在哪里被定義,有興趣可以go on
生成庫
第一步要把源代碼編繹成目標代碼。以下面的代碼為例,生成上面用到的hello庫:
/* hello.c */
#include <stdio.h>
void sayhello()
{
printf("hello,world\n");
}
用gcc編繹該文件,在編繹時可以使用任何全法的編繹參數,例如-g加入調試代碼等:
gcc -c hello.c -o hello.o
1.連接成靜態庫
連接成靜態庫使用ar命令,其實ar是archive的意思
$ar cqs libhello.a hello.o
2.連接成動態庫
生成動態庫用gcc來完成,由于可能存在多個版本,因此通常指定版本號:
$gcc -shared -Wl,-soname,libhello.so.1 -o libhello.so.1.0 hello.o
另外再建立兩個符號連接:
$ln -s libhello.so.1.0 libhello.so.1
$ln -s libhello.so.1 libhello.so
這樣一個libhello的動態連接庫就生成了。最重要的是傳gcc -shared 參數使其生成是動態庫而不是普通執行程序。
-Wl 表示后面的參數也就是-soname,libhello.so.1直接傳給連接器ld進行處理。實際上,每一個庫都有一個soname,當連接器發現它正在查找的程序庫中有這樣一個名稱,連接器便會將soname嵌入連結中的二進制文件內,而不是它正在運行的實際文件名,在程序執行期間,程序會查找擁有soname名字的文件,而不是庫的文件名,換句話說,soname是庫的區分標志。
這樣做的目的主要是允許系統中多個版本的庫文件共存,習慣上在命名庫文件的時候通常與soname相同
libxxxx.so.major.minor
其中,xxxx是庫的名字,major是主版本號,minor 是次版本號
posted @
2006-07-25 00:33 Jerry Cat 閱讀(537) |
評論 (0) |
編輯 收藏
[轉]如何讓你的程序安全通過windows防火墻
http://m.shnenglu.com/davyy/archive/2006/07/24/10410.html
大家開發網絡程序,經常要連接其他主機,如果在xp上運行,一定會提示你,只有選擇解除阻止才能
實現正常的網絡連接.那么有沒有辦法在防火墻的例外列表里面通過編程的方式加入自己的程序呢?
當然有了,不然就不要介紹了:)
xp的系統目錄下面有個hnetcfg.dll就是這個編程接口,頭文件是netfw.h,初始化代碼如下:
INetFwProfile* m_pFireWallProfile=NULL;
??? HRESULT hr? =? S_FALSE;
??? INetFwMgr *? fwMgr? =? NULL;
??? INetFwPolicy *? fwPolicy? =? NULL;
??? FW_ERROR_CODE ret? =? FW_NOERROR;
???? try
?????? {
???????? if ( m_pFireWallProfile )
???????????? throw? FW_ERR_INITIALIZED;
???????? //? Create an instance of the firewall settings manager.
???????? hr? =? CoCreateInstance( __uuidof(NetFwMgr), NULL, CLSCTX_INPROC_SERVER, __uuidof( INetFwMgr), ( void ** ) & fwMgr );
???????? if ( FAILED( hr ))
???????????? throw? FW_ERR_CREATE_SETTING_MANAGER;
???????? //? Retrieve the local firewall policy.
???????? hr? =? fwMgr -> get_LocalPolicy(? & fwPolicy );
???????? if ( FAILED( hr ))
???????????? throw? FW_ERR_LOCAL_POLICY;
???????? //? Retrieve the firewall profile currently in effect
???????? hr? =? fwPolicy -> get_CurrentProfile(? & m_pFireWallProfile );
???????? if ( FAILED( hr ))
???????????? throw? FW_ERR_PROFILE;
??? }
???? catch ( FW_ERROR_CODE nError)
????? {
??????? ret? =? nError;
??? }
???? if ( fwPolicy )
??????? fwPolicy -> Release();
???? if ( fwMgr )
??????? fwMgr -> Release();
???? return? ret; 將程序名稱加入例外列表:
WinXPSP2FireWall::AddApplication( const wchar_t* lpszProcessImageFileName, const wchar_t* lpszRegisterName )
{
??? FW_ERROR_CODE ret = FW_NOERROR;
??? HRESULT hr;
??? BOOL bAppEnable;
??? BSTR bstrProcessImageFileName = NULL;
??? BSTR bstrRegisterName = NULL;
??? INetFwAuthorizedApplication* pFWApp = NULL;
??? INetFwAuthorizedApplications* pFWApps = NULL;
??? try
??? {
??????? if( m_pFireWallProfile == NULL )
??????????? throw FW_ERR_INITIALIZED;
??????? if( lpszProcessImageFileName == NULL || lpszRegisterName? == NULL )
??????????? throw FW_ERR_INVALID_ARG;
??????? // First of all, check the application is already authorized;
??????? FW_ERROR_CODE? nError = this->IsAppEnabled( lpszProcessImageFileName, bAppEnable );
??????? if( nError != FW_NOERROR )
??????????? throw nError;
??????? // Only add the application if it isn't authorized
??????? if( bAppEnable == FALSE )
??????? {
??????????? // Retrieve the authorized application collection
??????????? hr = m_pFireWallProfile->get_AuthorizedApplications( &pFWApps );
??????????? if( FAILED( hr ))
??????????????? throw FW_ERR_AUTH_APPLICATIONS;
??????????? // Create an instance of an authorized application
??????????? hr = CoCreateInstance( __uuidof(NetFwAuthorizedApplication), NULL, CLSCTX_INPROC_SERVER, __uuidof(INetFwAuthorizedApplication), (void**)&pFWApp);
??????????? if( FAILED( hr ))
??????????????? throw FW_ERR_CREATE_APP_INSTANCE;
??????????? // Allocate a BSTR for the Process Image FileName
??????????? bstrProcessImageFileName = SysAllocString( lpszProcessImageFileName );
??????????? if( SysStringLen( bstrProcessImageFileName ) == 0)
??????????????? throw FW_ERR_SYS_ALLOC_STRING;
??????????? // Set the process image file name
??????????? hr = pFWApp->put_ProcessImageFileName( bstrProcessImageFileName );
??????????? if( FAILED( hr ) )
??????????????? throw FW_ERR_PUT_PROCESS_IMAGE_NAME;
??????????? // Allocate a BSTR for register name
??????????? bstrRegisterName = SysAllocString( lpszRegisterName );
??????????? if( SysStringLen( bstrRegisterName ) == 0)
??????????????? throw FW_ERR_SYS_ALLOC_STRING;
??????????? // Set a registered name of the process
??????????? hr = pFWApp->put_Name( bstrRegisterName );
??????????? if( FAILED( hr ))
??????????????? throw FW_ERR_PUT_REGISTER_NAME;
??????????? // Add the application to the collection
??????????? hr = pFWApps->Add( pFWApp );
??????????? if( FAILED( hr ))
??????????????? throw FW_ERR_ADD_TO_COLLECTION;
??????? }
??? }
??? catch( FW_ERROR_CODE nError )
??? {
??????? ret = nError;
??? }
??? SysFreeString( bstrProcessImageFileName );
??? SysFreeString( bstrRegisterName );
??? if( pFWApp )
??????? pFWApp->Release();
??? if( pFWApps )
??????? pFWApps->Release();
??? return ret;
}
posted @
2006-07-25 00:22 Jerry Cat 閱讀(509) |
評論 (0) |
編輯 收藏
/********************************************\
|????歡迎轉載, 但請保留作者姓名和原文鏈接, 祝您進步并共勉!???? |
\********************************************/
讀VC++內幕之體悟 - 09
作者: Jerry Cat
時間: 2006/07/24
鏈接:
http://m.shnenglu.com/jerysun0818/archive/2006/07/24/10428.html
9.?? 所有的ActiveX控件屬性,包括設計時的屬性,在運行時都是可以訪問的。
posted @
2006-07-24 23:58 Jerry Cat 閱讀(585) |
評論 (0) |
編輯 收藏