【概念】:聲明為static的類成員稱為類的靜態(tài)成員,用static修飾的成員變量,稱之為靜態(tài)成員變量;用static修飾的成員函數(shù),稱之為靜態(tài)成員函數(shù)。靜態(tài)成員變量一定要在類外進(jìn)行初始化
一、面試題引入
??面試題:實(shí)現(xiàn)一個(gè)類,計(jì)算程序中創(chuàng)建出了多少個(gè)類對(duì)象
- 上面這個(gè)是曾經(jīng)一家公司的面試題,要你用一個(gè)類去計(jì)算創(chuàng)建出多少個(gè)對(duì)象。分析一下可以知道我們?nèi)?shí)例化出一個(gè)對(duì)象的時(shí)候,無非是調(diào)用構(gòu)造或者是拷貝構(gòu)造,或者是通過一些傳參返回的方式去構(gòu)造對(duì)象
- 那第一時(shí)間就會(huì)想到在全局定義一個(gè)
count
,然后在可能產(chǎn)生構(gòu)造的地方進(jìn)行累加
int count = 0;
class A {
A()
{
count++;
}
A(const A& a)
{
count++;
}
};
void func(A a)
{
count++;
}
int main(void)
{
A aa1;
A aa2(aa1);
func(aa1);
return 0;
}
但是編譯后可以發(fā)現(xiàn),count++
的地方都出現(xiàn)了報(bào)錯(cuò),說是不明確的問題
- 但是你仔細(xì)去看輸出窗口的話可以發(fā)現(xiàn)其實(shí)這個(gè)
count
是和【std庫】中的count發(fā)生了沖突
那這個(gè)時(shí)候該怎么辦呢?
??有同學(xué)說:這還不簡(jiǎn)單,不要寫成count不就可以了,改成Count
都可以
- 確實(shí),這不乏是一種直截了當(dāng)?shù)姆椒?。但是同學(xué),我們現(xiàn)在學(xué)習(xí)的是C++而不是C語言,改成大寫的這種方式是C語言思路
- 還記得我們?cè)诩?xì)談namespace命名空間說到的部分展開命名空間嗎?和std庫里發(fā)生沖突就是因?yàn)?code>using namespace std;展開了std的命名空間,在這里我們只需要部分展開即可
using std::cout;
using std::endl;
此時(shí)通過調(diào)試再去觀察就可以看出創(chuàng)建了多少個(gè)對(duì)象
- 但這個(gè)時(shí)候呢,我手比較欠缺??,對(duì)這個(gè)
count
又去++了幾下,此時(shí)最后的結(jié)果就出現(xiàn)問題了
二、static特性細(xì)述
可以看到對(duì)于上面這種問題在C語言中是無法避免的,因?yàn)閏ount是一個(gè)全局變量,那么它的生命周期就是從定義開始到main函數(shù)結(jié)束的時(shí)候銷毀,這任何地方都是可以訪問到的,并且它還不具有常性可以做任意修改,這其實(shí)也就缺乏了一定的安全性
- 于是呢,在C++中就引入了這樣一個(gè)東西,把count作為類的成員函數(shù)
class A {
public:
A(int a = 0)
{
count++;
}
A(const A& a)
{
count++;
}
private:
int count = 0;
};
- 但是對(duì)于這個(gè)count而言還是屬于某個(gè)對(duì)象的,但我們?nèi)粢ソy(tǒng)計(jì)的話它一定不是專屬于某個(gè)對(duì)象的 ,而是要屬于所有對(duì)象。此時(shí)我們就可以用
static
來修飾一下這個(gè)成員變量
static int count = 0;
- 此時(shí)這個(gè)count便被包裹在了這個(gè)類中,我們知道類也是一個(gè)作用域,可以起到隔絕外界的作用,那么此時(shí)我們的count就不會(huì)和庫里面的count沖突了,直接展開std命名空間也不會(huì)存在問題
- static其實(shí)我們?cè)贑語言中也有學(xué)到過,若一個(gè)變量被
static
修飾的話,它就不會(huì)放到在【棧區(qū)】了,而是在【靜態(tài)區(qū)中】
此時(shí)就引出了static的第一條特性如下??
- 靜態(tài)成員為所有類對(duì)象所共享,不屬于某個(gè)具體的對(duì)象,存放在靜態(tài)區(qū)
但此刻我為其設(shè)定缺省值的時(shí)候卻報(bào)出了這樣的錯(cuò)誤,似乎不能在這里為它初始化呢?
- 之前學(xué)習(xí)了構(gòu)造函數(shù)的【初始化列表】,在類內(nèi)直接定義是因?yàn)槿笔≈凳墙o到構(gòu)造函數(shù)的初始化列表用的,初始化列表初始化的是非靜態(tài)成員,是屬于當(dāng)前對(duì)象的;而靜態(tài)成員是屬于所有對(duì)象的,是共有的
- 所以我們考慮把它放到全局去進(jìn)行一個(gè)定義,但是出了當(dāng)前作用域又無法訪問,此時(shí)就可以使用我們學(xué)習(xí)過的域作用限定符
::
int A::count = 0;
那么就引出了static
的第二條特性??
- 靜態(tài)成員變量必須在類外定義,定義時(shí)不添加static關(guān)鍵字,類中只是聲明
那此時(shí)我若是要在外界去訪問一下這個(gè)靜態(tài)成員呢?能不能做到
- 可以看到,直接打印訪問是不可以的,因?yàn)樾枰蜃饔孟薅ǚ?code>::
- 不過呢,加上域作用限定符
::
又說它是私有的無法訪問
那么就引出了static
的第三條特性??
- 靜態(tài)成員也是類的成員,受public、protected、private 訪問限定符的限制
那要怎么去訪問呢?這里有兩種方式
- 將count改為公有的之后就可以通過下面這兩種方式去訪問類中的靜態(tài)成員變量
cout << A::count << endl;
cout << aa1.count << endl;
cout << aa2.count << endl;
【拓展一下】
- 對(duì)于這種方式也是可以的,如果你有認(rèn)真看過詳解類封裝中的this指針就可以知道下面這種做法就是為當(dāng)前的this指針傳遞了一個(gè)空的地址,雖然我們看到了
->
,但其實(shí)并沒有去產(chǎn)生一個(gè)解引用,因?yàn)閏ount是一個(gè)靜態(tài)成員變量,雖然形式上放在類內(nèi),但上面說過了它是存放在內(nèi)存中的靜態(tài)區(qū),所以無法用this指針訪問到這個(gè)count
A* aa3 = nullptr;
aa3->count;
那么就引出了static
的第四條特性??
- 類靜態(tài)成員即可用
[類名::靜態(tài)成員]
或者[對(duì)象.靜態(tài)成員]
來訪問
上面這樣都可以訪問是因?yàn)槲覍㈧o態(tài)變量count設(shè)置成了公有,若一旦設(shè)置為私有的話,上面這些訪問形式就都是非法的了
- 此時(shí)我們可以在類中寫一個(gè)對(duì)外公開的接口,那么外界就可以通過這個(gè)接口訪問到類內(nèi)的私有成員變量了,在Java里面很喜歡這么去寫,不過在C++中一般很少這樣,C++一般會(huì)使用友元
int GetCount()
{
return count;
}
可以看到成員函數(shù)都是用對(duì)象去調(diào)的,那我現(xiàn)在假設(shè)我沒有對(duì)象呢【博主確實(shí)沒有??】。此時(shí)還有辦法獲取到類內(nèi)的靜態(tài)成員變量嗎?
- 此時(shí)就要使用到一個(gè)靜態(tài)成員變量的雙胞胎兄弟 —— 【靜態(tài)成員函數(shù)】
-
只需要在GetCount()成員函數(shù)前加一個(gè)
static
作為修飾即可
static int GetCount()
{
return count;
}
- 在外界還是使用域作用限定符
::
便可以訪問到,可以說這個(gè)【靜態(tài)成員函數(shù)】是專門為靜態(tài)成員變量而生的
看來這個(gè)靜態(tài)成員函數(shù)還挺厲害的,若是我現(xiàn)在類中又新增了一個(gè)普通成員變量,可以在里面進(jìn)行訪問嗎?
通過運(yùn)行可以看出似乎是不可以
那么就引出了static
的第五條特性??
- 靜態(tài)成員函數(shù)沒有隱藏的this指針,不能訪問任何非靜態(tài)成員
- 這其實(shí)很清楚,因?yàn)殪o態(tài)成員函數(shù)在靜態(tài)區(qū)沒有this指針,但普通的成員變量都是屬于當(dāng)前對(duì)象的,需要通過this指針來訪問
三、疑難解惑
學(xué)習(xí)完上了上面這五條特性之后,來回答一下下面這三個(gè)問題吧
??靜態(tài)成員函數(shù)可以調(diào)用非靜態(tài)成員函數(shù)嗎?
- 這個(gè)當(dāng)然也是不可以的,記住一點(diǎn)!在靜態(tài)成員函數(shù)內(nèi)部只能調(diào)用靜態(tài)的成員變量或者函數(shù)
可以看到靜態(tài)成員函數(shù)也是可以調(diào)用靜態(tài)成員函數(shù)的
??非靜態(tài)成員函數(shù)可以調(diào)用類的靜態(tài)成員函數(shù)嗎?
- 這個(gè)是可以的,因?yàn)殪o態(tài)成員函數(shù)是公共的,公共的大家當(dāng)然都可以用。包括像count也是可以使用的,這里不做演示
??請(qǐng)問靜態(tài)成員函數(shù)存放在哪里,靜態(tài)區(qū)還是公共代碼區(qū)?
- 答案揭曉,靜態(tài)成員函數(shù)是存放在公共代碼段的,我們?cè)陬惡蛯?duì)象的封裝思想中有談到過,在一個(gè)類中對(duì)于成員變量而言和對(duì)象一起存放在【棧區(qū)】,但對(duì)于類內(nèi)的成員函數(shù)而言,則不屬于類,而是存放在【公共代碼段】,那對(duì)于靜態(tài)成員函數(shù)來說也是屬于成員函數(shù)的一種,在編譯過后都會(huì)被解析成指令,這些指令都是存放在公共代碼段的
- 而對(duì)于靜態(tài)成員變量來說是存放在靜態(tài)區(qū)的,若是你去計(jì)算一下類的大小其實(shí)就一目了然了
四、在線OJ實(shí)訓(xùn)
在學(xué)習(xí)了C++中的靜態(tài)成員相關(guān)知識(shí)后,我們通過一道在線OJ來練練手
鏈接:??蚃Z64 求1+2+3+…+n
1. 題目描述
2. 思路分析
- 來分析一下這道題該怎么去做,題目本身要求的意思很簡(jiǎn)單,就是求解1 ~ n的階乘,但是限制條件有很多,例如:不能用分支判斷、循環(huán)、條件判斷、乘除法等等,這就令很多同學(xué)抓耳撓腮了,這要怎么去寫呀?
??有同學(xué)說:這還不簡(jiǎn)單,用個(gè)遞歸唄
- 遞歸是我們求解階乘以及前n項(xiàng)和最常見的,不過既然是遞歸,那一定存在遞歸出口,那你肯定得去用if條件判斷一下是否到底了遞歸出口吧
在同學(xué)們冥思苦想后,有一位同學(xué)提到了我們剛學(xué)習(xí)的static成員,那我們就用它來試試??
- 首先要考慮清楚,此處是有兩個(gè)變量在一直遞增的,一個(gè)是1 ~ n這些數(shù),一個(gè)則是累加和,不過它們都是上一輪的基礎(chǔ)上去進(jìn)行一個(gè)遞增,我們知道對(duì)于靜態(tài)成員變量只會(huì)在定義的時(shí)候初始化一次,后面每一次的值都是在上一次的基礎(chǔ)上變化的
static int sum;
static int i;
- 所以此刻便可以將這個(gè)遞增的變量設(shè)置為
i
,將累加和設(shè)置為sum
,它們均為靜態(tài)變量,根據(jù)我們上面所學(xué)知識(shí)要將其在類的外部進(jìn)行定義初始化
int Count::sum = 0;
int Count::i = 1;
- 可以把這個(gè)累加的邏輯放在類的構(gòu)造函數(shù)中,然后考慮去實(shí)例化具有n個(gè)大小的對(duì)象數(shù)組,那么就會(huì)調(diào)用n次構(gòu)造函數(shù)去進(jìn)行一個(gè)累加
Count()
{
sum += i;
i++;
}
Count c[n];
- 最后在類的內(nèi)部提供的一個(gè)靜態(tài)成員函數(shù),外界便可以通過它來獲取到靜態(tài)成員變量
sum
static int GetSum()
{
return sum;
}
return Count::GetSum();
3. 代碼展示
最后展示一下整體代碼和運(yùn)行結(jié)果
class Count{
public:
Count()
{
sum += i;
i++;
}
static int GetSum()
{
return sum;
}
private:
static int sum;
static int i;
};
int Count::sum = 0;
int Count::i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Count c[n];
return Count::GetSum();
}
};
五、一道巧妙的筆試題
在學(xué)習(xí)了上面有關(guān)類中static成員的相關(guān)知識(shí)后,接下去我們通過一道很巧妙的筆試題來加深大家的理解
?? 設(shè)計(jì)一個(gè)類,在類外面只能在棧/堆上創(chuàng)建對(duì)象
- 首先我們理解一下題目所要表述的含義,即我們需要去設(shè)計(jì)一個(gè)類,這個(gè)類呢可以指定在【?!炕蛘摺径选可先ラ_辟空間創(chuàng)建對(duì)象
- 看到下面我們?cè)陬愅庾孕腥ァ緱^(qū)】、【堆區(qū)】、【靜態(tài)區(qū)】創(chuàng)建了對(duì)象,這也是大家能想到的常規(guī)的做法
A a1; // 棧區(qū)
static A a2; // 靜態(tài)區(qū)
A* a3 = new A(); // 堆區(qū)
不過呢這其實(shí)并不符合我們的題意,題意是讓類內(nèi)去提供一個(gè)接口,可以使得外界只能在【?!炕颉径选恐袆?chuàng)建對(duì)象,接下去就來看看我是怎么做的吧
- 首先我做了一件 “很絕”的事,那就是將當(dāng)前類A的構(gòu)造函數(shù)私有化,那么在類外就無法輕易地去實(shí)例化出對(duì)象了,這和我們?cè)贘ava中講工具類的時(shí)候?qū)⑵錁?gòu)造器私有化是一個(gè)意思
class A {
public:
private:
// 構(gòu)造函數(shù)私有化
A(){}
int _a;
};
?? 那有同學(xué)就會(huì)問了,這類外都實(shí)例化不出對(duì)象了,還談何在【?!炕颉径选可祥_辟空間存儲(chǔ)對(duì)象呢?
- 這就需要思考了,既然類外無法實(shí)例化了,那還有類內(nèi)呢,我們可以在類內(nèi)的成員函數(shù)中來實(shí)例化出當(dāng)前類的對(duì)象。于是我便寫了這么兩個(gè)成員函數(shù),在函數(shù)內(nèi)部實(shí)例化出對(duì)象供類外去進(jìn)行調(diào)用
A GetStackObj()
{
A a;
return a;
}
A* GetHeapObj()
{
return new A;
}
- 但是呢這卻有一個(gè)問題,我們知道成員函數(shù)都是需要對(duì)象去進(jìn)行調(diào)用的,可是呢在類外我們都實(shí)例化不出對(duì)象,此時(shí)要如何去調(diào)用這兩個(gè)函數(shù)呢?
- 還記得我們本文學(xué)習(xí)了什么嗎?沒錯(cuò),就是
static
成員,除了static成員變量外還有【static成員函數(shù)】,此時(shí)我們就可以將這兩個(gè)成員函數(shù)定義為【靜態(tài)成員函數(shù)】,除了可以使用對(duì)象.函數(shù)名()
的形式來調(diào)用外,還可以使用類名::函數(shù)名()
來進(jìn)行調(diào)用。那此時(shí)我們?cè)陬愅饩涂梢韵裣旅孢@樣去進(jìn)行調(diào)用
A::GetStackObj(); // 棧區(qū)
A::GetHeapObj(); // 堆區(qū)
所以上面的這道筆試題就很好得運(yùn)用了本節(jié)所學(xué)習(xí)的知識(shí),希望讀者在學(xué)習(xí)完之后也可以做到靈活運(yùn)用
六、有關(guān)static修飾變量的一些注意要點(diǎn)
說完static修飾成員變量和成員函數(shù),這里再來補(bǔ)充一點(diǎn)有關(guān)static修飾變量的注意點(diǎn),我們主要通過題目來進(jìn)行講解
- 有一個(gè)類A,其數(shù)據(jù)成員如下: 則構(gòu)造函數(shù)中,成員變量一定要通過初始化列表來初始化的是:( )
?class A {
private:
int a;
public:
const int b;
float* &c;
static const char* d;
static double* e;
};
A.a b c
B.b c
C.b c d e
D.b c d
E.b
F.c
【答案】:B
【解析】:
對(duì)初始化列表不了解的可以先看看C++ | 談?wù)剺?gòu)造函數(shù)的初始化列表
- 對(duì)于【const成員變量】、【引用成員變量】、【無默認(rèn)構(gòu)造函數(shù)的自定義類型】都是必須通過初始化列表進(jìn)行初始化的,因此
b
、c
很明確要選上,對(duì)于d
而言,雖然它有const修飾,但前面又有[static]
作為修飾,所以是一個(gè)靜態(tài)成員變量,不屬于類,存放在靜態(tài)區(qū)中,當(dāng)程序開始執(zhí)行的時(shí)候就被初始化了,對(duì)于e
而言也是同理,所以答案選擇【B】
- 設(shè)已經(jīng)有A,B,C,D4個(gè)類的定義,程序中A,B,C,D析構(gòu)函數(shù)調(diào)用順序?yàn)???)
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
A.D B A C
B.B A D C
C.C D B A
D.A B D C
【答案】:B
【解析】:
這題的話通過調(diào)試來看一下就很清楚了,主要是觀察static
的修飾對(duì)局部變量的作用域和生命周期的更改
- 可以觀察到,因?yàn)閷?duì)象d有static修飾,它的生命周期發(fā)生了變化,本來應(yīng)該是最早析構(gòu)的,卻等到了b和a析構(gòu)完了之后它才去析構(gòu),所以生命周期發(fā)生了延長,但還是比最先定義出來的對(duì)象d先析構(gòu),因?yàn)閐后于c被實(shí)例化出來
- 雖然它的生命周期發(fā)生了變化,但是作用域卻沒有發(fā)生改變,從下圖可以看出在fun()函數(shù)中訪問不到main函數(shù)中定義的對(duì)象d
-
在一個(gè)cpp文件里面,定義了一個(gè)static類型的全局變量,下面一個(gè)正確的描述是:( )
A. 只能在該cpp所在的編譯模塊中使用該變量
B. 該變量的值是不可改變的
C. 該變量不能在類的成員函數(shù)中引用
D. 這種變量只能是基本類型(如int,char)不能是C++類類型
【答案】:A
【分析】:
- 首先那來看一下本題的代碼,定義了一個(gè)靜態(tài)的全局變量c,還有兩個(gè)類,class A 和 class B
static int c = 1;
class A {
public:
A()
{
cout << "A的構(gòu)造函數(shù)" << endl;
}
void func()
{
cout << "A的成員函數(shù)" << endl;
cout << c << endl;
}
~A()
{
cout << "A析構(gòu)函數(shù)" << endl;
}
private:
int _a = 1;
};
class B {
public:
B()
{
cout << "B的構(gòu)造函數(shù)" << endl;
}
void func()
{
cout << "B的成員函數(shù)" << endl;
}
~B()
{
cout << "B析構(gòu)函數(shù)" << endl;
}
private:
int _b = 1;
};
- 然后通過一些測(cè)試看到,靜態(tài)全局變量是可以更改的,而且可以在類的成員函數(shù)中進(jìn)行調(diào)用,并且看大這個(gè)
static B b
就可以知道其也可以為自定義類型即C++的類類型
- 但是對(duì)于A選項(xiàng)確實(shí)就是這樣,涉及到一些鏈接屬性的解析,可以看看這篇文章
【總結(jié)一下】:文章來源:http://www.zghlxwxcb.cn/news/detail-424045.html
-
做了上面的三題,我們來總結(jié)一下:
- 對(duì)于
static
修飾的成員變量,存放在靜態(tài)區(qū),而不在棧區(qū),是不屬于當(dāng)前類的,因此需要在類外初始化 - 對(duì)于
static
修飾的局部變量,其生命周期會(huì)延長,但作用域不會(huì)發(fā)生變化 - 對(duì)于
static
修飾的全局變量,只在當(dāng)前編譯模塊中(即當(dāng)前文件內(nèi))生效,其他文件不可訪問。因此其作用域發(fā)生了變化,但是生命周期沒有變化(從定義到結(jié)束都不會(huì)被釋放)
- 對(duì)于
文章來源地址http://www.zghlxwxcb.cn/news/detail-424045.html
到了這里,關(guān)于C++ | 說說類中的static成員的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!