C++ 關(guān)鍵字 Const Define Static
作者:AceTan,轉(zhuǎn)載請(qǐng)標(biāo)明出處橘霎!
今天來(lái)討論一下在C++中很常見(jiàn)的三個(gè)關(guān)鍵字 Const Define Static.
0x00: const
const 限定符:有時(shí)候我們需要定義這樣一種變量仅孩,它的值是不可改變的塔粒。這時(shí)候上鞠,我們就需要用到const這個(gè)關(guān)鍵字了妓羊。
const 關(guān)鍵字在各大考試和C++筆試中經(jīng)常遇到驾讲。比如下面這一道面試題:
說(shuō)出const關(guān)鍵字在下列語(yǔ)句中的作用可婶。
- const int a;
- int const a;
- const int *a;
- int const *a;
- int *const a沿癞;
- const int * const a;
- const char* func1();
- int GetX() const;
看完是不是一臉懵逼?看完下面的解讀矛渴,上面的問(wèn)題將迎刃而解椎扬。
首先需要知道的是const修飾符所修飾的變量值不可改變惫搏。
另外需要注意的是,在用const定義關(guān)鍵字時(shí)蚕涤, const int 和 int const 這兩種寫(xiě)法是等價(jià)的(沒(méi)有*)筐赔,它表示所定義的整型變量的值是不可改變的。
現(xiàn)在我們的關(guān)鍵問(wèn)題是揖铜,const修飾符到底是修飾的誰(shuí)茴丰。是這個(gè)整型變量,還是指針天吓,還是指針?biāo)赶虻闹怠?/p>
默認(rèn)狀態(tài)下,const 對(duì)象僅在文件內(nèi)有效贿肩。 如果想讓它在其他文件內(nèi)也有效,需要添加extern關(guān)鍵字龄寞。
引用和const
const 的引用 可以把引用綁定到const對(duì)象上汰规,就像綁定到其他對(duì)象上一樣,我們稱之為對(duì)常量的引用(reference to const)萄焦。 與普通引用不同的是控轿,對(duì)常量的引用不能被用作修改它所綁定的對(duì)象。
舉個(gè)栗子:
int v = 123;
const int& r1 = v; // 允許將const int& 綁定到一個(gè)普通的int對(duì)象上
const int& r2 = 123; // 正確:r2是一個(gè) 常量引用
const int& r3 = r1 * 10;// 正確:r3是一個(gè)常量引用
int& r4 = r1 * 5; // 錯(cuò)誤:r4是一個(gè)普通的非常量引用
int& r5 = v; // r5綁定對(duì)象v
r5 = 1; // 正確,r5并非一個(gè)常量
r1 = 1; // 錯(cuò)誤:r1是一個(gè)常量引用拂封,值不可更改
在實(shí)際編程中茬射,如果寫(xiě)的語(yǔ)法有錯(cuò)誤,IDE一般都是會(huì)提示的冒签,根據(jù)提示修改就好在抛。 但我們應(yīng)該記住這些常見(jiàn)的特性。
指針和const
指針和const鬼混在一塊萧恕,那才是噩夢(mèng)的開(kāi)始刚梭。
指針是對(duì)象,而引用不是票唆。因此朴读,可以像其他對(duì)象類型一樣,允許指針本身定為常量走趋。常量指針(const pointer)必須要初始化衅金。
int a = 0;
int* const p = &a; // p是一個(gè)常量指針,指向整型數(shù)a
const double pi = 3.14159;
const double* const pPi = π // pPi是一個(gè)指向常量對(duì)象pi的常量指針
是不是看著有點(diǎn)頭暈簿煌,理清這些關(guān)系的最好方法是從右往左讀氮唯。
拿上面的第四個(gè)來(lái)解釋一下。離pPi最近的是const姨伟,說(shuō)明pPi是一個(gè)常量對(duì)象惩琉。對(duì)象的類型是什么呢,繼續(xù)往左讀夺荒,對(duì)象的類型由聲明符的其余部分所決定的瞒渠,聲明符的下一個(gè)是*, 意思是pPi是一個(gè)常量指針良蒸。該聲明語(yǔ)句的基本數(shù)據(jù)類型(double)確定了常量對(duì)象(pPi)指向了一個(gè)double型對(duì)象。再往左讀還是個(gè)const伍玖,說(shuō)明這個(gè)double型對(duì)象也是個(gè)常量诚啃。
C++ 中有兩個(gè)術(shù)語(yǔ)對(duì)指針是否是常量,以及指針指向的對(duì)象是否是常量加以區(qū)分私沮。
頂層const(top-level const) 表示指針本身是個(gè)常量始赎。
底層const(low-level const) 表示指針?biāo)傅膶?duì)象是一個(gè)常量。
這兩個(gè)問(wèn)題是獨(dú)立的仔燕。
const的常見(jiàn)用法
- const可以用來(lái)定義常量造垛。但它更大的魅力是它可以修飾函數(shù)的參數(shù)、返回值晰搀,甚至函數(shù)的定義體五辽。這也是實(shí)際應(yīng)用中用的比較多的。
use const whenever you need
- const修飾函數(shù)參數(shù)外恕。const修飾參數(shù)杆逗,是防止傳入的參數(shù)被意外的修改。前面講到過(guò)鳞疲,可以用引用或者指針作為參數(shù)來(lái)作為輸出罪郊。 這樣是不需要加const的。其他一些情況尚洽,如果傳入的參數(shù)不需要被改變悔橄,則需要加const修飾∠俸粒基本類型一般是不需要const進(jìn)行修飾的癣疟,因?yàn)樗扇≈祵_f的方式。如果是用戶自定義類型潮酒,我們一般采用引用傳遞睛挚,像 Function(A& a),其中A是用戶自定義的類型。 Function(A a)的效率不如Function(A& a),因?yàn)楹瘮?shù)體內(nèi)將產(chǎn)生A類型的臨時(shí)對(duì)象用于復(fù)制參數(shù)a急黎,而臨時(shí)對(duì)象的構(gòu)造扎狱、復(fù)制、析構(gòu)過(guò)程都將消耗時(shí)間叁熔。但這樣聲明有一個(gè)缺點(diǎn)委乌,它會(huì)使調(diào)用者誤認(rèn)為出入的引用時(shí)可修改的床牧,解決的方法就是加const進(jìn)行修飾荣回。
如果傳入的參數(shù)不需要更改,那么加const修飾在實(shí)際編程中幾乎是必須的戈咳。
- const修飾函數(shù)的返回值心软。const一般用來(lái)修飾以"指針傳遞"方式的函數(shù)返回值壕吹,它表示函數(shù)的返回值(即指針)的內(nèi)容是不可修改的。該返回值只能被賦給加const修飾的同類型指針删铃。修飾以"值傳遞"方式的函數(shù)返回值是沒(méi)有意義的耳贬。
const char* GetStr();
const char* someStr = GetStr(); // 正確
char* str2 = GetStr(); // 錯(cuò)誤
- const修飾成員函數(shù)。 任何不會(huì)修改數(shù)據(jù)成員的函數(shù)都應(yīng)該聲明為const類型猎唁。確切的說(shuō)咒劲,const是修飾this指向的對(duì)象的。
class A
{
private:
int num; // 成員變量
public:
void Func(int x); // 其實(shí)原型是兩個(gè)參數(shù) Func(A* const this, int x)
void Func2(int y) const;// 原型是Func2(const A* const this, int y)
};
void A::Func(int x)
{
num = num * x; // 正確:this所指向的對(duì)象沒(méi)有const修飾诫隅,可以更改
}
void A::Func2(int y) const
{
num = num * y; // 錯(cuò)誤:this所指向的對(duì)象被const修飾腐魂,無(wú)法更改這個(gè)對(duì)象的數(shù)據(jù)。
}
至于const為什么放在后面逐纬,想必各位看官已經(jīng)猜到了蛔屹。原型的第一個(gè)參數(shù)是被省略了,無(wú)法修飾豁生,大概也只能放到函數(shù)的后面了兔毒,雖然看起來(lái)怪怪的。
0x01: define
準(zhǔn)確的說(shuō)應(yīng)該是#define甸箱。 它繼承自C語(yǔ)言育叁,是一個(gè)預(yù)處理指令。所謂的預(yù)處理芍殖,就是在編譯之前執(zhí)行一段程序擂红,可以部分的改變我們寫(xiě)的程序。 之前我們見(jiàn)過(guò)的#include就是一條預(yù)處理命令围小,它將所包含的文件替換到所用指令的地方昵骤。#define指令是用來(lái)把一個(gè)名字定義預(yù)處理變量的。預(yù)處理變量有兩種狀態(tài):已定義與未定義肯适。#define是用來(lái)定義它的变秦。與之對(duì)應(yīng)的是另外兩個(gè)指令分別檢查預(yù)處理變量是否已經(jīng)定義。#ifdef 當(dāng)且僅當(dāng)變量已定義時(shí)為真框舔,#ifndef當(dāng)且僅當(dāng)變量未定義時(shí)為真蹦玫。一旦檢查為真,則執(zhí)行后續(xù)的操作直到遇到#endif 指令為止刘绣。
預(yù)處理變量無(wú)視C++語(yǔ)言中關(guān)于作用域的規(guī)則
#define 的常見(jiàn)用法
- 頭文件保護(hù)(header guard)樱溉。在定義一個(gè)頭文件時(shí),建議習(xí)慣性地加上頭文件保護(hù)纬凤,沒(méi)必要太在乎你的程序到底需不需要福贞。這是一個(gè)比較好的編程習(xí)慣。 一般預(yù)處理變量名我們用所要定義的類名的大寫(xiě)來(lái)命名停士。示例如下:
#ifndef _FORM_RIDE_H_
#define _FORM_RIDE_H_
// 你的代碼
#endif // _FORM_RIDE_H_
加了頭文件保護(hù)挖帘,別人就不用擔(dān)心是否重復(fù)引入你的文件了完丽。
- 改變程序執(zhí)行流程。 平臺(tái)的差異性拇舀,客服端和服務(wù)器的差異性逻族,有時(shí)候?yàn)榱俗尨a具有更好的兼容性,通常定義這些宏來(lái)執(zhí)行不同的邏輯代碼骄崩。這時(shí)候只需要一個(gè)總的控制文件聘鳞,就能讓代碼在不同的情形下執(zhí)行不同的邏輯。
#ifdef _CLIENT_
if (pEntity == NULL)
{
return false;
}
string pathname = string(pEntity->GetCore()->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_STUB_)
string pathname = string(pKernel->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_MEMBER_) || defined(_SERVER_ROOM_)
string pathname = string(pKernel->GetResourcePath()) + "ini/rule/ridestrength/ride_strength_info.xml";
#endif
- 充當(dāng)函數(shù)的作用要拂。說(shuō)是充當(dāng)函數(shù)搁痛,其實(shí)和函數(shù)還是有挺大差別。只是在用到的地方進(jìn)行替換宇弛,使程序看起來(lái)更簡(jiǎn)潔鸡典。下面的代碼是用來(lái)檢查循環(huán)的,可以避免死循環(huán)枪芒。預(yù)設(shè)一個(gè)循環(huán)最大值彻况,然后調(diào)用這個(gè)宏讓它檢查。
#include <iostream>
using namespace std;
int g_nMaxCirculateCount = 100;
void SetMaxCirculateCount(int count)
{
if (g_nMaxCirculateCount != count)
{
g_nMaxCirculateCount = count;
}
}
#define LoopBeginCheck(name) \
int nCheck##name##Count = 0;
#define LoopDoCheck(name) \
nCheck##name##Count++;\
if((g_nMaxCirculateCount > 0) && (nCheck##name##Count > g_nMaxCirculateCount))\
{\
cout << "文件路徑:" << __FILE__ << endl \
<< "函數(shù):" << __FUNCTION__<< endl \
<< "所在行:" << __LINE__ << endl \
<< "循環(huán)次數(shù):" << nCheck##name##Count << endl; \
break;\
}
int main()
{
int n = 77 * 88; // 實(shí)際需要執(zhí)行的循環(huán)次數(shù)
SetMaxCirculateCount(10); // 設(shè)置的最大循環(huán)次數(shù)
LoopBeginCheck(var);
for (int i = 1; i < n; i++)
{
LoopDoCheck(var);
cout << "循環(huán)結(jié)束" << endl;
}
return 0;
}
這里的 \ 起鏈接換行的作用舅踪,##name## 是拼接參數(shù)纽甘。 __FILE__等則是一些內(nèi)置宏。
0x02: static
static定義的變量存儲(chǔ)在靜態(tài)數(shù)據(jù)區(qū),在靜態(tài)數(shù)據(jù)區(qū)抽碌,內(nèi)存中所有的字節(jié)默認(rèn)值都是0x00悍赢。
靜態(tài)變量作用范圍在一個(gè)文件內(nèi),程序開(kāi)始時(shí)分配空間货徙,結(jié)束時(shí)釋放空間左权,默認(rèn)初始化為0,使用時(shí)可以改變其值痴颊。與全局變量不同的是赏迟,靜態(tài)變量或靜態(tài)函數(shù)只有本文件內(nèi)的代碼才能訪問(wèn)它,它的名字在其它文件中不可見(jiàn)蠢棱。
static的主要有以下三個(gè)特性:
表示代碼退出一個(gè)塊后锌杀,仍然能夠存在的局部變量。因?yàn)閿?shù)據(jù)會(huì)存儲(chǔ)在靜態(tài)數(shù)據(jù)區(qū)泻仙。
用來(lái)表示不能被其它文件訪問(wèn)的全局變量和函數(shù)糕再。
表示屬于一個(gè)類而不是屬于此類的任何特定對(duì)象的變量和函數(shù)。這個(gè)和Java中static關(guān)鍵字的意義相同玉转。
有了static關(guān)鍵字突想,變量就變的有些復(fù)雜了。是時(shí)候理清一下各類型變量的作用域范圍了。
常見(jiàn)的變量分為如下幾種:全局變量蒿柳、靜態(tài)全局變量、靜態(tài)局部變量和局部變量漩蟆。
全局變量垒探。存儲(chǔ):靜態(tài)存儲(chǔ)區(qū)域。 作用域:在整個(gè)工程文件內(nèi)都有效怠李。
靜態(tài)全局變量圾叼。存儲(chǔ):靜態(tài)存儲(chǔ)區(qū)域。 作用域:在定義它的文件內(nèi)有效捺癞。
靜態(tài)局部變量 存儲(chǔ):靜態(tài)存儲(chǔ)區(qū)域夷蚊。 作用域:只在定義它的函數(shù)內(nèi)有效。程序僅分配一次內(nèi)存,函數(shù)返回后髓介,改變量不會(huì)消失惕鼓。
局部變量 存儲(chǔ):內(nèi)存棧中。 作用域:只在定義它的函數(shù)內(nèi)有效唐础。程序返回后局部變量被回收箱歧。
static函數(shù)在內(nèi)存中只有一份,普通函數(shù)在每個(gè)被調(diào)用中維持一份拷貝一膨。
#include <iostream>
using namespace std;
int n = 100; // 全局變量
static int m; // 靜態(tài)全局變量
class MyClass
{
public:
void ChangeX(int x); // 普通成員函數(shù)
static void ChangeY(int y); // 靜態(tài)成員函數(shù)
int GetY(); // 獲取全局變量的值
private:
int m_x; // 普通成員變量
static int m_y; // 靜態(tài)成員變量
};
void MyClass::ChangeX(int x)
{
++m_x;
static int times = 0;
++times;
cout << "第" << times << "次調(diào)用該函數(shù)" << endl;
}
void MyClass::ChangeY(int y)
{
//m_x = y; // 報(bào)錯(cuò):靜態(tài)成員函數(shù)只能引用靜態(tài)成員變量
m_y = y;
}
int MyClass::GetY()
{
cout << "靜態(tài)成員變量y的值為:" << m_y << endl;
return m_y;
}
int MyClass::m_y = 0;//定義并初始化靜態(tài)數(shù)據(jù)成員
int main()
{
n = 88;
cout << "全局變量改為" << n << endl;
cout << "靜態(tài)全局變量的初始值為" << m << endl;
MyClass cls1; // 創(chuàng)建第一個(gè)類
cls1.ChangeY(111); // 改變了靜態(tài)成員的值
MyClass cls2; // 創(chuàng)建第一個(gè)類
cls2.ChangeX(1); // 連續(xù)調(diào)用3次
cls2.ChangeX(2);
cls2.ChangeX(3);
cls2.GetY(); // 直接輸出靜態(tài)成員變量看看
return 0;
}
輸出結(jié)果:
全局變量改為88
靜態(tài)全局變量的初始值為0
第1次調(diào)用該函數(shù)
第2次調(diào)用該函數(shù)
第3次調(diào)用該函數(shù)
靜態(tài)成員變量y的值為:111
輸出結(jié)果印證了上面所討論的內(nèi)容呀邢。
0x03: 結(jié)束語(yǔ)
const define static在實(shí)際編程中應(yīng)用的非常多,各位看官應(yīng)多加理解豹绪,靈活應(yīng)用价淌。