4、【C++】靜態成員變數/靜態成員函式/行內函數/友元函式/友元類/友元成員函式
一、靜態成員
我們可以使用 static 關鍵字來把類成員定義為靜態的。當我們宣告類的成員為靜態時,這意味著無論建立多少個類的物件,靜態成員都只有一個副本。
靜態成員在類的所有物件中是共享的。如果不存在其他的初始化語句,在建立第一個物件時,所有的靜態資料都會被初始化為零。我們不能把靜態成員的初始化放置在類的定義中,但是可以在類的外部通過使用範圍解析運算子 :: 來重新宣告靜態變數從而對它進行初始化,如下面的例項所示。
下面的例項有助於更好地理解靜態成員資料的概念:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;//宣告類的靜態成員
// 建構函式定義
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次建立物件時增加 1
objectCount++ ;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
// 初始化類 Box 的靜態成員,其實是定義並初始化的過程
int Box::objectCount = 0;
//也可以只定義,卻不初始化
//int Box::objectCount;
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 宣告 box1
Box Box2(8.5, 6.0, 2.0); // 宣告 box2
// 輸出物件的總數
cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
靜態成員變數在類中僅僅是宣告,沒有定義,所以要在類的外面定義,實際上是給靜態成員變數分配記憶體。如果不加定義就會報錯,初始化是賦一個初始值,而定義是分配記憶體。
可以使用靜態成員變數清楚的瞭解構造和解構函式的呼叫情況。
#include <iostream>
using namespace std;
class Cpoint{
public:
static int value;//統計建構函式的執行次數
static int num;//統計解構函式的執行次數
Cpoint(int x,int y){
xp=x;yp=y;
value++;
cout << "呼叫Cpiont構造:" << value << endl;
}
~Cpoint(){num++; cout << "呼叫Cpiont析構:" << num << endl;}
private:
int xp,yp;
};
int Cpoint::value=0;
int Cpoint::num=0;
class CRect{
public:
CRect(int x1,int x2):mpt1(x1,x2),mpt2(x1,x2) {cout << "呼叫CRect構造\n";}
~CRect(){cout << "呼叫CRect析構\n";}
private:
Cpoint mpt1,mpt2;
};
int main()
{
CRect p(10,20);
cout << "Hello, world!" << endl;
return 0;
}
編譯執行結果:
呼叫Cpiont構造:1
呼叫Cpiont構造:2
呼叫CRect構造
Hello, world!
呼叫CRect析構
呼叫Cpiont析構:1
呼叫Cpiont析構:2
二、靜態成員函式
如果把函式成員宣告為靜態的,就可以把函式與類的任何特定物件獨立開來。靜態成員函式即使在類物件不存在的情況下也能被呼叫,靜態函式只要使用類名加範圍解析運算子 :: 就可以訪問。
靜態成員函式只能訪問靜態成員資料、其他靜態成員函式和類外部的其他函式。
靜態成員函式有一個類範圍,他們不能訪問類的 this 指標。您可以使用靜態成員函式來判斷類的某些物件是否已被建立。
靜態成員函式與普通成員函式的區別:
(1)靜態成員函式沒有 this 指標,只能訪問靜態成員(包括靜態成員變數和靜態成員函式)。
(2)普通成員函式有 this 指標,可以訪問類中的任意成員;而靜態成員函式沒有 this 指標。
下面的例項有助於更好地理解靜態成員函式的概念:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;//宣告類的靜態成員
// 建構函式定義
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次建立物件時增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
// 初始化類 Box 的靜態成員
int Box::objectCount = 0;
int main(void)
{
// 在建立物件之前輸出物件的總數
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 宣告 box1
Box Box2(8.5, 6.0, 2.0); // 宣告 box2
// 在建立物件之後輸出物件的總數
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
三、行內函數
C++ 行內函數是通常與類一起使用。如果一個函式是內聯的,那麼在編譯時,編譯器會把該函式的程式碼副本放置在每個呼叫該函式的地方。
對行內函數進行任何修改,都需要重新編譯函式的所有客戶端,因為編譯器需要重新更換一次所有的程式碼,否則將會繼續使用舊的函式。
如果想把一個函式定義為行內函數,則需要在函式名前面放置關鍵字 inline,在呼叫函式之前需要對函式進行定義。如果已定義的函式多於一行,編譯器會忽略 inline 限定符。
在類定義中定義的函式都是行內函數,即使沒有使用 inline 說明符。
下面是一個例項,使用行內函數來返回兩個數中的最大值:
#include <iostream>
using namespace std;
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
// 程式的主函式
int main( )
{
cout << "Max (20,10): " << Max(20,10) << endl;
cout << "Max (0,200): " << Max(0,200) << endl;
cout << "Max (100,1010): " << Max(100,1010) << endl;
return 0;
}
行內函數inline:引入行內函數的目的是為了解決程式中函式呼叫的效率問題,這麼說吧,程式在編譯器編譯的時候,編譯器將程式中出現的行內函數的呼叫表示式用行內函數的函式體進行替換,而對於其他的函式,都是在執行時候才被替代。這其實就是個空間代價換時間的過程。所以行內函數一般都是1-5行的小函式。在使用行內函數時要留神:
(1)在行內函數內不允許使用迴圈語句和開關語句;
(2)行內函數的定義必須出現在行內函數第一次呼叫之前;
(3)類結構中所在的類說明內部定義的函式是行內函數。
行內函數的使用:
建議:只有當函式只有10行甚至更少時才將其定義為行內函數。
定義:當函式被宣告為行內函數之後,編譯器會將其內聯展開,而不是按通常的函式呼叫機制進行呼叫。
優點:當函式體比較小的時候,內聯該函式可以令目的碼更加高效。對於存取函式以及其它函式體比較短,效能關鍵的函式,鼓勵使用內聯。
缺點: 濫用內聯將導致程式變慢。內聯可能使目的碼量或增或減,這取決於行內函數的大小。內聯非常短小的存取函式通常會減少程式碼大小,但內聯一個相當大的函式將戲劇性的增加程式碼大小。現代處理器由於更好的利用了指令快取,小巧的程式碼往往執行更快。
結論: 一個較為合理的經驗準則是,不要內聯超過10行的函式。謹慎對待解構函式,解構函式往往比其表面看起來要更長,因為有隱含的成員和基類解構函式被呼叫!
另一個實用的經驗準則:內聯那些包含迴圈或 switch 語句的函式常常是得不償失(除非在大多數情況下,這些迴圈或 switch 語句從不被執行)。
有些函式即使宣告為內聯的也不一定會被編譯器內聯,這點很重要;比如虛擬函式和遞迴函式就不會被正常內聯。
通常,遞迴函式不應該宣告成行內函數。(遞迴呼叫堆疊的展開並不像迴圈那麼簡單,比如遞迴層數在編譯時可能是未知的,大多數編譯器都不支援內聯遞迴函式)。虛擬函式內聯的主要原因則是想把它的函式體放在類定義內,為了圖個方便,抑或是當作文件描述其行為,比如精短的存取函式。
巨集和行內函數
(1)巨集有什麼優缺點
優點:
1、提高了程式可毒性,同時也方便進行修改;
2、提高了程式的執行效率,使用帶參的巨集定義既可以完成函式呼叫的功能,又能避免函數出棧與入棧操作,減少系統開銷,提高執行效率;
缺點:
1、由於是直接嵌入,程式碼相對多一點
2、引數每次用於巨集定義時它們都將重新求值,由於多次求值,帶有副作用的引數可能會產生不可預料的結果
3、對帶參的巨集來說,由於是直接替換,不會檢查引數是否合法,存在安全隱患
(2)巨集函式和行內函數的區別
巨集和行內函數都採用了空間換時間的方法,在其呼叫處進行展開
1、在預編譯時期巨集定義在呼叫處執行替換。在編譯時期,行內函數在呼叫處展開,同時進行引數的型別檢查
2、行內函數是函式,可以像呼叫普通函式一樣呼叫行內函數。巨集定義需要新增很多括號防止歧義,編寫複雜
3、行內函數可以作為類的成員函式,成為類的保護成員或私有成員,當一個表示式涉及到類的保護成員或私有成員時,巨集就不能實現了(無法將this指標放在合適的位置)
四、友元函式
類的友元函式是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。儘管友元函式的原型有在類的定義中出現過,但是友元函式並不是成員函式。
友元可以是一個函式,該函式被稱為友元函式;友元也可以是一個類,該類被稱為友元類,在這種情況下,整個類及其所有成員都是友元。
如果要宣告函式為一個類的友元,需要在類定義中該函式原型前使用關鍵字 friend,如下所示:
#include <iostream>
using namespace std;
class A
{
public:
friend void set_show(int x, A &a); //該函式是友元函式的宣告
private:
int data;
};
void set_show(int x, A &a) //友元函式定義,為了訪問類A中的成員
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
set_show(1, a);
return 0;
}
五、友元類
友元類的所有成員函式都是另一個類的友元函式,都可以訪問另一個類中的隱藏資訊(包括私有成員和保護成員)。當希望一個類可以存取另一個類的私有成員時,可以將該類宣告為另一類的友元類。
關於友元類的注意事項:
(1) 友元關係不能被繼承。
(2) 友元關係是單向的,不具有交換性。若類B是類A的友元,類A不一定是類B的友元,要看在類中是否有相應的宣告。
(3) 友元關係不具有傳遞性。若類B是類A的友元,類C是B的友元,類C不一定是類A的友元,同樣要看類中是否有相應的申明。
#include <iostream>
using namespace std;
class A
{
public:
friend class C; //這是友元類的宣告
private:
int data;
};
class C //友元類定義,為了訪問類A中的成員
{
public:
void set_show(int x, A &a) { a.data = x; cout<<a.data<<endl;}
};
int main(void)
{
class A a;
class C c;
c.set_show(1, a);
return 0;
}
六、友元成員函式
使類B中的成員函式成為類A的友元函式,這樣類B的該成員函式就可以訪問類A的所有成員了。
當用到友元成員函式時,需注意友元宣告和友元定義之間的相互依賴,在下面例子中,類B必須先定義,否則類A就不能將一個B的函式指定為友元。然而,只有在定義了類A之後,才能定義類B的該成員函式。更一般的講,必須先定義包含成員函式的類,才能將成員函式設為友元。另一方面,不必預先宣告類和非成員函式來將它們設為友元。
#include <iostream>
using namespace std;
class A; //當用到友元成員函式時,需注意友元宣告與友元定義之間的互相依賴。這是類A的宣告
class B
{
public:
void set_show(int x, A &a); //該函式是類A的友元函式
};
class A
{
public:
friend void B::set_show(int x, A &a); //該函式是友元成員函式的宣告
private:
int data;
void show() { cout << data << endl; }
};
void B::set_show(int x, A &a) //只有在定義類A後才能定義該函式,畢竟,它被設為友元是為了訪問類A的成員
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
class B b;
b.set_show(1, a);
return 0;
}